From 5dfcf000b1a23ea0f48ed873fb8576d5fef17949 Mon Sep 17 00:00:00 2001 From: subrahmanyaman Date: Thu, 20 Jan 2022 04:13:27 +0000 Subject: [PATCH 1/5] Initial Commit --- .../javacard/kmapplet/KMAndroidSEApplet.java | 325 +++ .../javacard/keymaster/KMAndroidSEApplet.java | 556 ---- .../android/javacard/seprovider/KMAESKey.java | 0 .../seprovider/KMAndroidSEProvider.java | 114 +- .../seprovider/KMAttestationCert.java | 0 .../javacard/seprovider/KMAttestationKey.java | 0 .../seprovider/KMComputedHmacKey.java | 0 .../KMConfigurations.java | 2 +- .../seprovider/KMDeviceUniqueKey.java | 0 .../seprovider/KMECDeviceUniqueKey.java | 0 .../javacard/seprovider/KMECPrivateKey.java | 0 .../KMEcdsa256NoDigestSignature.java | 0 .../android/javacard/seprovider/KMError.java | 0 .../javacard/seprovider/KMException.java | 0 .../javacard/seprovider/KMHmacKey.java | 0 .../seprovider/KMKeymasterProvision.java | 314 +++ .../seprovider/KMKeymintProvision.java | 107 + .../javacard/seprovider/KMMasterKey.java | 0 .../javacard/seprovider/KMOperation.java | 0 .../javacard/seprovider/KMOperationImpl.java | 0 .../javacard/seprovider/KMPoolManager.java | 0 .../javacard/seprovider/KMPreSharedKey.java | 0 .../KMRsa2048NoDigestSignature.java | 0 .../seprovider/KMRsaOAEPEncoding.java | 0 .../javacard/seprovider/KMSEProvider.java | 0 .../android/javacard/seprovider/KMType.java | 0 .../javacard/seprovider/KMUpgradable.java | 0 .../keymaster/KMAttestationCertImpl.java | 1083 -------- .../javacard/keymaster/KMJCardSimApplet.java | 550 ---- .../android/javacard/keymaster/KMUtils.java | 445 --- .../JCardSimProviderLib/lib/gpapi-upgrade.jar | Bin 12638 -> 0 bytes .../lib/hamcrest-core-1.3.jar | Bin 45024 -> 0 bytes .../lib/jcardsim-3.0.5-SNAPSHOT.jar | Bin 492079 -> 0 bytes Applet/JCardSimProviderLib/lib/junit-4.13.jar | Bin 381765 -> 0 bytes .../android/javacard/seprovider/KMAESKey.java | 39 - .../seprovider/KMAttestationCert.java | 196 -- .../javacard/seprovider/KMAttestationKey.java | 25 - .../android/javacard/seprovider/KMCipher.java | 45 - .../javacard/seprovider/KMCipherImpl.java | 239 -- .../seprovider/KMComputedHmacKey.java | 5 - .../seprovider/KMDeviceUniqueKey.java | 21 - .../seprovider/KMECDeviceUniqueKey.java | 53 - .../javacard/seprovider/KMECPrivateKey.java | 38 - .../KMEcdsa256NoDigestSignature.java | 191 -- .../android/javacard/seprovider/KMError.java | 134 - .../javacard/seprovider/KMException.java | 55 - .../javacard/seprovider/KMHmacKey.java | 43 - .../javacard/seprovider/KMJCardSimulator.java | 1860 ------------- .../javacard/seprovider/KMMasterKey.java | 25 - .../javacard/seprovider/KMOperation.java | 54 - .../javacard/seprovider/KMOperationImpl.java | 100 - .../javacard/seprovider/KMPreSharedKey.java | 25 - .../KMRsa2048NoDigestSignature.java | 166 -- .../javacard/seprovider/KMSEProvider.java | 779 ------ .../android/javacard/seprovider/KMType.java | 348 --- .../javacard/seprovider/KMUpgradable.java | 30 - .../keymaster/KMKeymasterSpecification.java | 301 -- .../keymaster/KMKeymintSpecification.java | 328 --- .../javacard/keymaster/KMSpecification.java | 65 - .../{keymaster => kmdevice}/KMArray.java | 53 +- .../kmdevice}/KMAttestationCertImpl.java | 373 +-- .../{keymaster => kmdevice}/KMBignumTag.java | 32 +- .../{keymaster => kmdevice}/KMBoolTag.java | 57 +- .../{keymaster => kmdevice}/KMByteBlob.java | 57 +- .../{keymaster => kmdevice}/KMByteTag.java | 74 +- .../javacard/kmdevice}/KMConfigurations.java | 2 +- .../{keymaster => kmdevice}/KMCose.java | 212 +- .../KMCoseCertPayload.java | 19 +- .../KMCoseHeaders.java | 49 +- .../{keymaster => kmdevice}/KMCoseKey.java | 73 +- .../{keymaster => kmdevice}/KMCoseMap.java | 28 +- .../KMCosePairByteBlobTag.java | 12 +- .../KMCosePairCoseKeyTag.java | 16 +- .../KMCosePairIntegerTag.java | 6 +- .../KMCosePairNegIntegerTag.java | 6 +- .../KMCosePairSimpleValueTag.java | 6 +- .../KMCosePairTagType.java | 32 +- .../KMCosePairTextStringTag.java | 18 +- .../{keymaster => kmdevice}/KMDecoder.java | 95 +- .../{keymaster => kmdevice}/KMEncoder.java | 124 +- .../{keymaster => kmdevice}/KMEnum.java | 56 +- .../KMEnumArrayTag.java | 53 +- .../{keymaster => kmdevice}/KMEnumTag.java | 29 +- .../{keymaster => kmdevice}/KMError.java | 3 +- .../KMHardwareAuthToken.java | 127 +- .../KMHmacSharingParameters.java | 49 +- .../{keymaster => kmdevice}/KMInteger.java | 89 +- .../KMIntegerArrayTag.java | 50 +- .../{keymaster => kmdevice}/KMIntegerTag.java | 95 +- .../KMKeyCharacteristics.java | 89 +- .../KMKeyParameters.java | 338 ++- .../KMKeymasterDevice.java} | 2410 ++++++++--------- .../javacard/kmdevice/KMKeymintDevice.java | 276 ++ .../{keymaster => kmdevice}/KMMap.java | 30 +- .../{keymaster => kmdevice}/KMNInteger.java | 4 +- .../KMOperationState.java | 16 +- .../KMPKCS8Decoder.java | 78 +- .../{keymaster => kmdevice}/KMRepository.java | 55 +- .../KMSimpleValue.java | 8 +- .../{keymaster => kmdevice}/KMTag.java | 20 +- .../{keymaster => kmdevice}/KMTextString.java | 39 +- .../{keymaster => kmdevice}/KMType.java | 8 +- .../android/javacard/kmdevice}/KMUtils.java | 89 +- .../KMVerificationToken.java | 93 +- .../RemotelyProvisionedComponentDevice.java | 536 ++-- 105 files changed, 4135 insertions(+), 10510 deletions(-) create mode 100644 Applet/AndroidSEApplet/src/com/android/javacard/kmapplet/KMAndroidSEApplet.java delete mode 100644 Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java rename Applet/{AndroidSEProviderLib => AndroidSEProvider}/src/com/android/javacard/seprovider/KMAESKey.java (100%) rename Applet/{AndroidSEProviderLib => AndroidSEProvider}/src/com/android/javacard/seprovider/KMAndroidSEProvider.java (95%) rename Applet/{AndroidSEProviderLib => AndroidSEProvider}/src/com/android/javacard/seprovider/KMAttestationCert.java (100%) rename Applet/{AndroidSEProviderLib => AndroidSEProvider}/src/com/android/javacard/seprovider/KMAttestationKey.java (100%) rename Applet/{AndroidSEProviderLib => AndroidSEProvider}/src/com/android/javacard/seprovider/KMComputedHmacKey.java (100%) rename Applet/AndroidSEProvider/src/com/android/javacard/{keymaster => seprovider}/KMConfigurations.java (96%) rename Applet/{AndroidSEProviderLib => AndroidSEProvider}/src/com/android/javacard/seprovider/KMDeviceUniqueKey.java (100%) rename Applet/{AndroidSEProviderLib => AndroidSEProvider}/src/com/android/javacard/seprovider/KMECDeviceUniqueKey.java (100%) rename Applet/{AndroidSEProviderLib => AndroidSEProvider}/src/com/android/javacard/seprovider/KMECPrivateKey.java (100%) rename Applet/{AndroidSEProviderLib => AndroidSEProvider}/src/com/android/javacard/seprovider/KMEcdsa256NoDigestSignature.java (100%) rename Applet/{AndroidSEProviderLib => AndroidSEProvider}/src/com/android/javacard/seprovider/KMError.java (100%) rename Applet/{AndroidSEProviderLib => AndroidSEProvider}/src/com/android/javacard/seprovider/KMException.java (100%) rename Applet/{AndroidSEProviderLib => AndroidSEProvider}/src/com/android/javacard/seprovider/KMHmacKey.java (100%) create mode 100644 Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMKeymasterProvision.java create mode 100644 Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMKeymintProvision.java rename Applet/{AndroidSEProviderLib => AndroidSEProvider}/src/com/android/javacard/seprovider/KMMasterKey.java (100%) rename Applet/{AndroidSEProviderLib => AndroidSEProvider}/src/com/android/javacard/seprovider/KMOperation.java (100%) rename Applet/{AndroidSEProviderLib => AndroidSEProvider}/src/com/android/javacard/seprovider/KMOperationImpl.java (100%) rename Applet/{AndroidSEProviderLib => AndroidSEProvider}/src/com/android/javacard/seprovider/KMPoolManager.java (100%) rename Applet/{AndroidSEProviderLib => AndroidSEProvider}/src/com/android/javacard/seprovider/KMPreSharedKey.java (100%) rename Applet/{AndroidSEProviderLib => AndroidSEProvider}/src/com/android/javacard/seprovider/KMRsa2048NoDigestSignature.java (100%) rename Applet/{AndroidSEProviderLib => AndroidSEProvider}/src/com/android/javacard/seprovider/KMRsaOAEPEncoding.java (100%) rename Applet/{AndroidSEProviderLib => AndroidSEProvider}/src/com/android/javacard/seprovider/KMSEProvider.java (100%) rename Applet/{AndroidSEProviderLib => AndroidSEProvider}/src/com/android/javacard/seprovider/KMType.java (100%) rename Applet/{AndroidSEProviderLib => AndroidSEProvider}/src/com/android/javacard/seprovider/KMUpgradable.java (100%) delete mode 100644 Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java delete mode 100644 Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMJCardSimApplet.java delete mode 100644 Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMUtils.java delete mode 100644 Applet/JCardSimProviderLib/lib/gpapi-upgrade.jar delete mode 100644 Applet/JCardSimProviderLib/lib/hamcrest-core-1.3.jar delete mode 100644 Applet/JCardSimProviderLib/lib/jcardsim-3.0.5-SNAPSHOT.jar delete mode 100644 Applet/JCardSimProviderLib/lib/junit-4.13.jar delete mode 100644 Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMAESKey.java delete mode 100644 Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMAttestationCert.java delete mode 100644 Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMAttestationKey.java delete mode 100644 Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMCipher.java delete mode 100644 Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMCipherImpl.java delete mode 100644 Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMComputedHmacKey.java delete mode 100644 Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMDeviceUniqueKey.java delete mode 100644 Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMECDeviceUniqueKey.java delete mode 100644 Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMECPrivateKey.java delete mode 100644 Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMEcdsa256NoDigestSignature.java delete mode 100644 Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMError.java delete mode 100644 Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMException.java delete mode 100644 Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMHmacKey.java delete mode 100644 Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMJCardSimulator.java delete mode 100644 Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMMasterKey.java delete mode 100644 Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMOperation.java delete mode 100644 Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMOperationImpl.java delete mode 100644 Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMPreSharedKey.java delete mode 100644 Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMRsa2048NoDigestSignature.java delete mode 100644 Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMSEProvider.java delete mode 100644 Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMType.java delete mode 100644 Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMUpgradable.java delete mode 100644 Applet/src/com/android/javacard/keymaster/KMKeymasterSpecification.java delete mode 100644 Applet/src/com/android/javacard/keymaster/KMKeymintSpecification.java delete mode 100644 Applet/src/com/android/javacard/keymaster/KMSpecification.java rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMArray.java (80%) rename Applet/{AndroidSEProvider/src/com/android/javacard/keymaster => src/com/android/javacard/kmdevice}/KMAttestationCertImpl.java (76%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMBignumTag.java (84%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMBoolTag.java (76%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMByteBlob.java (74%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMByteTag.java (74%) rename Applet/{JCardSimProvider/src/com/android/javacard/keymaster => src/com/android/javacard/kmdevice}/KMConfigurations.java (96%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMCose.java (72%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMCoseCertPayload.java (84%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMCoseHeaders.java (74%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMCoseKey.java (69%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMCoseMap.java (86%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMCosePairByteBlobTag.java (93%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMCosePairCoseKeyTag.java (90%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMCosePairIntegerTag.java (95%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMCosePairNegIntegerTag.java (95%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMCosePairSimpleValueTag.java (94%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMCosePairTagType.java (90%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMCosePairTextStringTag.java (90%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMDecoder.java (90%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMEncoder.java (87%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMEnum.java (85%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMEnumArrayTag.java (85%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMEnumTag.java (87%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMError.java (98%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMHardwareAuthToken.java (52%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMHmacSharingParameters.java (71%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMInteger.java (72%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMIntegerArrayTag.java (79%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMIntegerTag.java (76%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMKeyCharacteristics.java (57%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMKeyParameters.java (56%) rename Applet/src/com/android/javacard/{keymaster/KMKeymasterApplet.java => kmdevice/KMKeymasterDevice.java} (63%) create mode 100644 Applet/src/com/android/javacard/kmdevice/KMKeymintDevice.java rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMMap.java (87%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMNInteger.java (97%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMOperationState.java (95%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMPKCS8Decoder.java (76%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMRepository.java (90%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMSimpleValue.java (89%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMTag.java (83%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMTextString.java (74%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMType.java (98%) rename Applet/{AndroidSEProvider/src/com/android/javacard/keymaster => src/com/android/javacard/kmdevice}/KMUtils.java (85%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/KMVerificationToken.java (59%) rename Applet/src/com/android/javacard/{keymaster => kmdevice}/RemotelyProvisionedComponentDevice.java (73%) diff --git a/Applet/AndroidSEApplet/src/com/android/javacard/kmapplet/KMAndroidSEApplet.java b/Applet/AndroidSEApplet/src/com/android/javacard/kmapplet/KMAndroidSEApplet.java new file mode 100644 index 00000000..b9625035 --- /dev/null +++ b/Applet/AndroidSEApplet/src/com/android/javacard/kmapplet/KMAndroidSEApplet.java @@ -0,0 +1,325 @@ +/* + * 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.kmapplet; + +import org.globalplatform.upgrade.Element; +import org.globalplatform.upgrade.OnUpgradeListener; +import org.globalplatform.upgrade.UpgradeManager; + +import com.android.javacard.kmdevice.KMArray; +import com.android.javacard.kmdevice.KMByteBlob; +import com.android.javacard.kmdevice.KMByteTag; +import com.android.javacard.kmdevice.KMCose; +import com.android.javacard.kmdevice.KMCoseHeaders; +import com.android.javacard.kmdevice.KMCoseKey; +import com.android.javacard.kmdevice.KMDecoder; +import com.android.javacard.kmdevice.KMEnum; +import com.android.javacard.kmdevice.KMEnumArrayTag; +import com.android.javacard.kmdevice.KMEnumTag; +import com.android.javacard.kmdevice.KMInteger; +import com.android.javacard.kmdevice.KMKeyParameters; +import com.android.javacard.kmdevice.KMKeymasterDevice; +import com.android.javacard.kmdevice.KMKeymintDevice; +import com.android.javacard.kmdevice.KMMap; +import com.android.javacard.kmdevice.KMRepository; +import com.android.javacard.kmdevice.KMTag; +import com.android.javacard.kmdevice.KMTextString; +import com.android.javacard.seprovider.KMAndroidSEProvider; +import com.android.javacard.seprovider.KMDeviceUniqueKey; +import com.android.javacard.seprovider.KMError; +import com.android.javacard.seprovider.KMException; +import com.android.javacard.seprovider.KMKeymasterProvision; +import com.android.javacard.seprovider.KMKeymintProvision; +import com.android.javacard.seprovider.KMSEProvider; +import com.android.javacard.seprovider.KMType; + +import javacard.framework.APDU; +import javacard.framework.Applet; +import javacard.framework.AppletEvent; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; +import javacard.security.CryptoException; +import javacardx.apdu.ExtendedLength; + +public class KMAndroidSEApplet extends Applet implements AppletEvent, OnUpgradeListener, ExtendedLength { + + private static final byte KM_BEGIN_STATE = 0x00; + private static final byte ILLEGAL_STATE = KM_BEGIN_STATE + 1; + + + // Provider specific Commands + private static final byte INS_KEYMINT_PROVIDER_APDU_START = 0x00; + private static final byte INS_PROVISION_ATTESTATION_KEY_CMD = INS_KEYMINT_PROVIDER_APDU_START + 1; //0x01 + private static final byte INS_PROVISION_ATTESTATION_CERT_DATA_CMD = INS_KEYMINT_PROVIDER_APDU_START + 2; //0x02 + private static final byte INS_PROVISION_ATTEST_IDS_CMD = INS_KEYMINT_PROVIDER_APDU_START + 3; + private static final byte INS_PROVISION_PRESHARED_SECRET_CMD = + INS_KEYMINT_PROVIDER_APDU_START + 4; + private static final byte INS_SET_BOOT_PARAMS_CMD = INS_KEYMINT_PROVIDER_APDU_START + 5; + private static final byte INS_LOCK_PROVISIONING_CMD = INS_KEYMINT_PROVIDER_APDU_START + 6; + private static final byte INS_GET_PROVISION_STATUS_CMD = INS_KEYMINT_PROVIDER_APDU_START + 7; + private static final byte INS_SET_VERSION_PATCHLEVEL_CMD = + INS_KEYMINT_PROVIDER_APDU_START + 8; //0x08 + private static final byte INS_SET_BOOT_ENDED_CMD = INS_KEYMINT_PROVIDER_APDU_START + 9; //0x09 + private static final byte INS_PROVISION_DEVICE_UNIQUE_KEY_CMD = + INS_KEYMINT_PROVIDER_APDU_START + 10; + private static final byte INS_PROVISION_ADDITIONAL_CERT_CHAIN_CMD = + INS_KEYMINT_PROVIDER_APDU_START + 11; + + // public static final byte BOOT_KEY_MAX_SIZE = 32; + // public static final byte BOOT_HASH_MAX_SIZE = 32; + + // Provision reporting status + + + + public static final byte KM_40 = 0x00; + public static final byte KM_41 = 0x01; + public static final byte KM_100 = 0x03; + + private static byte keymasterState = ILLEGAL_STATE; + //private static byte provisionStatus = NOT_PROVISIONED; + private static byte kmDevice; + private static KMSEProvider seProvider; + private static KMKeymasterProvision seProvisionInst; + private static KMDecoder decoderInst; + private static KMRepository repositoryInst; + private static KMKeymasterDevice kmDeviceInst; + + KMAndroidSEApplet() { + seProvider = new KMAndroidSEProvider(); + repositoryInst = new KMRepository(seProvider.isUpgrading()); + decoderInst = new KMDecoder(); + if(kmDevice == KM_40 || kmDevice == KM_41) { + kmDeviceInst = new KMKeymasterDevice(seProvider, repositoryInst, decoderInst); + seProvisionInst = new KMKeymasterProvision(kmDeviceInst, seProvider, decoderInst, repositoryInst); + } else { + kmDeviceInst = new KMKeymintDevice(seProvider, repositoryInst, decoderInst); + seProvisionInst = new KMKeymintProvision(kmDeviceInst, seProvider, decoderInst, repositoryInst); + } + } + + /** + * 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) { + // TODO Get the specification correctly. + byte Li = bArray[bOffset]; // Length of AID + byte Lc = bArray[(short) (bOffset + Li + 1)]; // Length of ControlInfo + byte La = bArray[(short) (bOffset + Li + Lc + 2)]; // Length of application data + if (La != 1) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + kmDevice = bArray[(short) (bOffset + Li + Lc + 3)]; + new KMAndroidSEApplet().register(bArray, (short) (bOffset + 1), bArray[bOffset]); + } + + @Override + public void process(APDU apdu) { + try { + // If this is select applet apdu which is selecting this applet then return + if (apdu.isISOInterindustryCLA()) { + if (selectingApplet()) { + return; + } + } + short apduIns = validateApdu(apdu); + if (((KMAndroidSEProvider) seProvider).isPowerReset(false)) { + kmDeviceInst.powerReset(); + } + + if (((KMAndroidSEProvider) seProvider).isProvisionLocked()) { + switch (apduIns) { + case INS_SET_BOOT_PARAMS_CMD: + seProvisionInst.processSetBootParamsCmd(apdu); + break; + + case INS_SET_BOOT_ENDED_CMD: + //set the flag to mark boot ended + repositoryInst.setBootEndedStatus(true); + kmDeviceInst.sendError(apdu, KMError.OK); + break; + + default: + kmDeviceInst.process(apdu); + break; + } + return; + } + + if (apduIns == KMType.INVALID_VALUE) { + return; + } + switch (apduIns) { + case INS_PROVISION_ATTESTATION_KEY_CMD: // only keymaster + seProvisionInst.processProvisionAttestationKey(apdu); + break; + case INS_PROVISION_ATTESTATION_CERT_DATA_CMD: // only keymaster + seProvisionInst.processProvisionAttestationCertDataCmd(apdu); + break; + case INS_PROVISION_ATTEST_IDS_CMD: + seProvisionInst.processProvisionAttestIdsCmd(apdu); + break; + + case INS_PROVISION_PRESHARED_SECRET_CMD: + seProvisionInst.processProvisionPreSharedSecretCmd(apdu); + break; + + case INS_GET_PROVISION_STATUS_CMD: + seProvisionInst.processGetProvisionStatusCmd(apdu); + break; + + case INS_LOCK_PROVISIONING_CMD: + seProvisionInst.processLockProvisioningCmd(apdu); + break; + + case INS_SET_BOOT_PARAMS_CMD: + seProvisionInst.processSetBootParamsCmd(apdu); + break; + + case INS_PROVISION_DEVICE_UNIQUE_KEY_CMD: //only keymint + seProvisionInst.processProvisionDeviceUniqueKey(apdu); + break; + + case INS_PROVISION_ADDITIONAL_CERT_CHAIN_CMD:// only keymint + seProvisionInst.processProvisionAdditionalCertChain(apdu); + break; + + default: + kmDeviceInst.process(apdu); + break; + } + } catch (KMException exception) { + kmDeviceInst.sendError(apdu, KMException.reason()); + } catch (ISOException exp) { + kmDeviceInst.sendError(apdu, kmDeviceInst.mapISOErrorToKMError(exp.getReason())); + } catch (CryptoException e) { + kmDeviceInst.sendError(apdu, kmDeviceInst.mapCryptoErrorToKMError(e.getReason())); + } catch (Exception e) { + kmDeviceInst.sendError(apdu, KMError.GENERIC_UNKNOWN_ERROR); + } finally { + kmDeviceInst.clean(); + } + } + + @Override + public void onCleanup() { + } + + @Override + public void onConsolidate() { + } + + @Override + public void onRestore(Element element) { + element.initRead(); + keymasterState = element.readByte(); + repositoryInst.onRestore(element); + seProvider.onRestore(element); + seProvisionInst.onSave(element); + } + + @Override + public Element onSave() { + // SEProvider count + short primitiveCount = seProvider.getBackupPrimitiveByteCount(); + short objectCount = seProvider.getBackupObjectCount(); + + //Provision count + primitiveCount += seProvisionInst.getBackupPrimitiveByteCount(); + objectCount += seProvisionInst.getBackupObjectCount(); + + //Repository count + primitiveCount += repositoryInst.getBackupPrimitiveByteCount(); + objectCount += repositoryInst.getBackupObjectCount(); + //KMKeymasterApplet count + primitiveCount += computePrimitveDataSize(); + objectCount += computeObjectCount(); + + // Create element. + Element element = UpgradeManager.createElement(Element.TYPE_SIMPLE, + primitiveCount, objectCount); + element.write(keymasterState); + repositoryInst.onSave(element); + seProvider.onSave(element); + seProvisionInst.onSave(element); + return element; + } + + private short computePrimitveDataSize() { + // provisionStatus + keymasterState + return (short) 2; + } + + private short computeObjectCount() { + return (short) 0; + } + + private short validateApdu(APDU apdu) { + // Read the apdu header and buffer. + byte[] apduBuffer = apdu.getBuffer(); + byte apduClass = apduBuffer[ISO7816.OFFSET_CLA]; + short P1P2 = Util.getShort(apduBuffer, ISO7816.OFFSET_P1); + + // Validate APDU Header. + if ((apduClass != kmDeviceInst.CLA_ISO7816_NO_SM_NO_CHAN)) { + kmDeviceInst.sendError(apdu, KMError.UNSUPPORTED_CLA); + return KMType.INVALID_VALUE; + } + + if (kmDevice == KMKeymasterDevice.KEYMASTER_SPECIFICATION && + P1P2 != KMKeymasterDevice.KEYMASTER_HAL_VERSION) { + kmDeviceInst.sendError(apdu, KMError.INVALID_P1P2); + return KMType.INVALID_VALUE; + } + // Validate P1P2. + if (kmDevice == KMKeymasterDevice.KEYMINT_SPECIFICATION && + P1P2 != KMKeymasterDevice.KM_HAL_VERSION) { + kmDeviceInst.sendError(apdu, KMError.INVALID_P1P2); + return KMType.INVALID_VALUE; + } + return apduBuffer[ISO7816.OFFSET_INS]; + } + + @Override + public void uninstall() { + kmDeviceInst.onUninstall(); + } + + /** + * Selects this applet. + * + * @return Returns true if the keymaster is in correct state + */ + @Override + public boolean select() { + return kmDeviceInst.onSelect(); + + } + + /** + * De-selects this applet. + */ + @Override + public void deselect() { + kmDeviceInst.onDeselect(); + } +} + diff --git a/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java b/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java deleted file mode 100644 index a210c4c6..00000000 --- a/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java +++ /dev/null @@ -1,556 +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.keymaster; - -import org.globalplatform.upgrade.Element; -import org.globalplatform.upgrade.OnUpgradeListener; -import org.globalplatform.upgrade.UpgradeManager; - -import com.android.javacard.seprovider.KMAndroidSEProvider; -import com.android.javacard.seprovider.KMDeviceUniqueKey; -import com.android.javacard.seprovider.KMError; -import com.android.javacard.seprovider.KMException; -import com.android.javacard.seprovider.KMType; - -import javacard.framework.APDU; -import javacard.framework.ISO7816; -import javacard.framework.ISOException; -import javacard.framework.Util; - -public class KMAndroidSEApplet extends KMKeymasterApplet implements OnUpgradeListener { - - 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_ATTESTATION_KEY_CMD = INS_KEYMINT_PROVIDER_APDU_START + 1; //0x01 - private static final byte INS_PROVISION_ATTESTATION_CERT_DATA_CMD = INS_KEYMINT_PROVIDER_APDU_START + 2; //0x02 - private static final byte INS_PROVISION_ATTEST_IDS_CMD = INS_KEYMINT_PROVIDER_APDU_START + 3; - private static final byte INS_PROVISION_PRESHARED_SECRET_CMD = - INS_KEYMINT_PROVIDER_APDU_START + 4; - private static final byte INS_SET_BOOT_PARAMS_CMD = INS_KEYMINT_PROVIDER_APDU_START + 5; - private static final byte INS_LOCK_PROVISIONING_CMD = INS_KEYMINT_PROVIDER_APDU_START + 6; - private static final byte INS_GET_PROVISION_STATUS_CMD = INS_KEYMINT_PROVIDER_APDU_START + 7; - private static final byte INS_SET_VERSION_PATCHLEVEL_CMD = - INS_KEYMINT_PROVIDER_APDU_START + 8; //0x08 - private static final byte INS_SET_BOOT_ENDED_CMD = INS_KEYMINT_PROVIDER_APDU_START + 9; //0x09 - private static final byte INS_PROVISION_DEVICE_UNIQUE_KEY_CMD = - INS_KEYMINT_PROVIDER_APDU_START + 10; - private static final byte INS_PROVISION_ADDITIONAL_CERT_CHAIN_CMD = - INS_KEYMINT_PROVIDER_APDU_START + 11; - - 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; - - // Provision reporting status -//Provision reporting status - private static final byte NOT_PROVISIONED = 0x00; - private static final byte PROVISION_STATUS_ATTESTATION_KEY = 0x01; - private static final byte PROVISION_STATUS_ATTESTATION_CERT_CHAIN = 0x02; - private static final byte PROVISION_STATUS_ATTESTATION_CERT_PARAMS = 0x04; - private static final byte PROVISION_STATUS_ATTEST_IDS = 0x08; - private static final byte PROVISION_STATUS_PRESHARED_SECRET = 0x10; - private static final byte PROVISION_STATUS_PROVISIONING_LOCKED = 0x20; - private static final byte PROVISION_STATUS_DEVICE_UNIQUE_KEY = 0x40; - private static final byte PROVISION_STATUS_ADDITIONAL_CERT_CHAIN = (byte) 0x80; - - public static final short SHARED_SECRET_KEY_SIZE = 32; - - private static byte keymasterState = ILLEGAL_STATE; - private static byte provisionStatus = NOT_PROVISIONED; - private static byte kmSpecification; - - KMAndroidSEApplet() { - super(new KMAndroidSEProvider(), kmSpecification); - } - - /** - * 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) { - // TODO Get the specification correctly. - byte Li = bArray[bOffset]; // Length of AID - byte Lc = bArray[(short) (bOffset + Li + 1)]; // Length of ControlInfo - byte La = bArray[(short) (bOffset + Li + Lc + 2)]; // Length of application data - if (La != 1) { - ISOException.throwIt(ISO7816.SW_DATA_INVALID); - } - kmSpecification = bArray[(short) (bOffset + Li + Lc + 3)]; - new KMAndroidSEApplet().register(bArray, (short) (bOffset + 1), bArray[bOffset]); - } - - @Override - public void process(APDU apdu) { - try { - // If this is select applet apdu which is selecting this applet then return - if (apdu.isISOInterindustryCLA()) { - if (selectingApplet()) { - return; - } - } - short apduIns = validateApdu(apdu); - if (((KMAndroidSEProvider) seProvider).isPowerReset()) { - super.powerReset(); - } - - if (((KMAndroidSEProvider) seProvider).isProvisionLocked()) { - switch (apduIns) { - case INS_SET_BOOT_PARAMS_CMD: - processSetBootParamsCmd(apdu); - break; - - case INS_SET_BOOT_ENDED_CMD: - //set the flag to mark boot ended - repository.setBootEndedStatus(true); - sendError(apdu, KMError.OK); - break; - - default: - super.process(apdu); - break; - } - return; - } - - if (apduIns == KMType.INVALID_VALUE) { - return; - } - switch (apduIns) { - case INS_PROVISION_ATTESTATION_KEY_CMD: - processProvisionAttestationKey(apdu); - provisionStatus |= PROVISION_STATUS_ATTESTATION_KEY; - sendError(apdu, KMError.OK); - break; - case INS_PROVISION_ATTESTATION_CERT_DATA_CMD: - processProvisionAttestationCertDataCmd(apdu); - provisionStatus |= (PROVISION_STATUS_ATTESTATION_CERT_CHAIN | - PROVISION_STATUS_ATTESTATION_CERT_PARAMS); - sendError(apdu, KMError.OK); - break; - case INS_PROVISION_ATTEST_IDS_CMD: - processProvisionAttestIdsCmd(apdu); - provisionStatus |= PROVISION_STATUS_ATTEST_IDS; - sendError(apdu, KMError.OK); - break; - - case INS_PROVISION_PRESHARED_SECRET_CMD: - processProvisionPreSharedSecretCmd(apdu); - provisionStatus |= PROVISION_STATUS_PRESHARED_SECRET; - sendError(apdu, KMError.OK); - break; - - case INS_GET_PROVISION_STATUS_CMD: - processGetProvisionStatusCmd(apdu); - break; - - case INS_LOCK_PROVISIONING_CMD: - processLockProvisioningCmd(apdu); - break; - - case INS_SET_BOOT_PARAMS_CMD: - - processSetBootParamsCmd(apdu); - break; - - case INS_PROVISION_DEVICE_UNIQUE_KEY_CMD: - processProvisionDeviceUniqueKey(apdu); - break; - - case INS_PROVISION_ADDITIONAL_CERT_CHAIN_CMD: - processProvisionAdditionalCertChain(apdu); - break; - - default: - super.process(apdu); - break; - } - } finally { - repository.clean(); - } - } - - private static void processProvisionDeviceUniqueKey(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. - seProvider.createDeviceUniqueKey(false, scratchPad, (short) 0, pubKeyLen, scratchPad, - pubKeyLen, privKeyLen); - short bcc = generateBcc(false, scratchPad); - short len = KMKeymasterApplet.encodeToApduBuffer(bcc, scratchPad, (short) 0, - MAX_COSE_BUF_SIZE); - ((KMAndroidSEProvider) seProvider).persistBootCertificateChain(scratchPad, (short) 0, len); - sendError(apdu, KMError.OK); - } - - private static void processProvisionAdditionalCertChain(APDU apdu) { - // Prepare the expression to decode - 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 coseSignArr = KMArray.exp(arrInst); - short map = KMMap.instance((short) 1); - KMMap.cast(map).add((short) 0, KMTextString.exp(), coseSignArr); - // receive incoming data and decode it. - 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); - } - // decode - map = decoder.decode(map, buffer, bufferStartOffset, bufferLength); - arrInst = KMMap.cast(map).getKeyValue((short) 0); - // Validate Additional certificate chain. - short leafCoseKey = - validateCertChain(false, KMCose.COSE_ALG_ES256, KMCose.COSE_ALG_ES256, arrInst, - srcBuffer, null); - // Compare the DK_Pub. - short pubKeyLen = KMCoseKey.cast(leafCoseKey).getEcdsa256PublicKey(srcBuffer, (short) 0); - KMDeviceUniqueKey uniqueKey = seProvider.getDeviceUniqueKey(false); - if (uniqueKey == null) { - KMException.throwIt(KMError.STATUS_FAILED); - } - short uniqueKeyLen = uniqueKey.getPublicKey(srcBuffer, pubKeyLen); - if ((pubKeyLen != uniqueKeyLen) || - (0 != Util.arrayCompare(srcBuffer, (short) 0, srcBuffer, pubKeyLen, pubKeyLen))) { - KMException.throwIt(KMError.STATUS_FAILED); - } - seProvider.persistAdditionalCertChain(buffer, bufferStartOffset, bufferLength); - //reclaim memory - repository.reclaimMemory(bufferLength); - sendError(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(); - ((KMAndroidSEProvider) seProvider).setAttestationId(key, KMByteBlob.cast(obj).getBuffer(), - KMByteBlob.cast(obj).getStartOff(), KMByteBlob.cast(obj).length()); - index++; - } - } - - private void processProvisionAttestationCertDataCmd(APDU apdu) { - // TODO Handle this function properly - processAttestationCertDataCmd(apdu); - } - - private void processProvisionAttestationKey(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); - // Re-purpose the apdu buffer as scratch pad. - byte[] scratchPad = apdu.getBuffer(); - - // key params should have os patch, os version and verified root of trust - short keyParams = KMArray.cast(args).get((short) 0); - keyFormatPtr = KMArray.cast(args).get((short) 1); - short rawBlob = KMArray.cast(args).get((short) 2); - // Key format must be RAW format - short keyFormat = KMEnum.cast(keyFormatPtr).getVal(); - if (keyFormat != KMType.RAW) { - KMException.throwIt(KMError.UNIMPLEMENTED); - } - //byte origin = KMType.IMPORTED; - - // get algorithm - only EC keys expected - KMTag.assertPresence(keyParams, KMType.ENUM_TAG, KMType.ALGORITHM, KMError.INVALID_ARGUMENT); - short alg = KMEnumTag.getValue(KMType.ALGORITHM, keyParams); - if (alg != KMType.EC) { - KMException.throwIt(KMError.INVALID_ARGUMENT); - } - // get digest - only SHA256 supported - KMTag.assertPresence(keyParams, KMType.ENUM_ARRAY_TAG, KMType.DIGEST, KMError.INVALID_ARGUMENT); - short len = KMEnumArrayTag.getValues(KMType.DIGEST, keyParams, scratchPad, (short) 0); - if (len != 1) { - KMException.throwIt(KMError.INVALID_ARGUMENT); - } - if (scratchPad[0] != KMType.SHA2_256) { - KMException.throwIt(KMError.INCOMPATIBLE_DIGEST); - } - // Purpose should be ATTEST_KEY - KMTag.assertPresence(keyParams, KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, - KMError.INVALID_ARGUMENT); - len = KMEnumArrayTag.getValues(KMType.PURPOSE, keyParams, scratchPad, (short) 0); - if (len != 1) { - KMException.throwIt(KMError.INVALID_ARGUMENT); - } - if (scratchPad[0] != KMType.ATTEST_KEY) { - KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); - } - // validate Curve - KMTag.assertPresence(keyParams, KMType.ENUM_TAG, KMType.ECCURVE, KMError.INVALID_ARGUMENT); - short curve = KMEnumTag.getValue(KMType.ECCURVE, keyParams); - if (curve != KMType.P_256) { - KMException.throwIt(KMError.UNSUPPORTED_EC_CURVE); - } - // Decode EC Key - short arrPtr = decodeRawECKey(rawBlob); - short secret = KMArray.cast(arrPtr).get((short) 0); - short pubKey = KMArray.cast(arrPtr).get((short) 1); - // Check whether key can be created - seProvider.importAsymmetricKey( - KMType.EC, - KMByteBlob.cast(secret).getBuffer(), - KMByteBlob.cast(secret).getStartOff(), - KMByteBlob.cast(secret).length(), - KMByteBlob.cast(pubKey).getBuffer(), - KMByteBlob.cast(pubKey).getStartOff(), - KMByteBlob.cast(pubKey).length()); - - // persist key - seProvider.createAttestationKey( - KMByteBlob.cast(secret).getBuffer(), - KMByteBlob.cast(secret).getStartOff(), - KMByteBlob.cast(secret).length()); - } - - 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. - ((KMAndroidSEProvider) seProvider).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) { - short resp = KMArray.instance((short) 2); - KMArray.cast(resp).add((short) 0, buildErrorStatus(KMError.OK)); - KMArray.cast(resp).add((short) 1, KMInteger.uint_16(provisionStatus)); - sendOutgoing(apdu, resp); - } - - private void processSetBootParamsCmd(APDU apdu) { - short argsProto = KMArray.instance((short) 5); - - byte[] scratchPad = apdu.getBuffer(); - // Array of 4 expected arguments - // Argument 0 Boot Patch level - KMArray.cast(argsProto).add((short) 0, KMInteger.exp()); - // Argument 1 Verified Boot Key - KMArray.cast(argsProto).add((short) 1, KMByteBlob.exp()); - // Argument 2 Verified Boot Hash - KMArray.cast(argsProto).add((short) 2, KMByteBlob.exp()); - // Argument 3 Verified Boot State - KMArray.cast(argsProto).add((short) 3, KMEnum.instance(KMType.VERIFIED_BOOT_STATE)); - // Argument 4 Device Locked - KMArray.cast(argsProto).add((short) 4, KMEnum.instance(KMType.DEVICE_LOCKED)); - - short args = receiveIncoming(apdu, argsProto); - - short bootParam = KMArray.cast(args).get((short) 0); - - ((KMAndroidSEProvider) seProvider).setBootPatchLevel(KMInteger.cast(bootParam).getBuffer(), - KMInteger.cast(bootParam).getStartOff(), - KMInteger.cast(bootParam).length()); - - bootParam = KMArray.cast(args).get((short) 1); - if (KMByteBlob.cast(bootParam).length() > BOOT_KEY_MAX_SIZE) { - KMException.throwIt(KMError.INVALID_ARGUMENT); - } - ((KMAndroidSEProvider) seProvider).setBootKey(KMByteBlob.cast(bootParam).getBuffer(), - KMByteBlob.cast(bootParam).getStartOff(), - KMByteBlob.cast(bootParam).length()); - - bootParam = KMArray.cast(args).get((short) 2); - if (KMByteBlob.cast(bootParam).length() > BOOT_HASH_MAX_SIZE) { - KMException.throwIt(KMError.INVALID_ARGUMENT); - } - ((KMAndroidSEProvider) seProvider).setVerifiedBootHash(KMByteBlob.cast(bootParam).getBuffer(), - KMByteBlob.cast(bootParam).getStartOff(), - KMByteBlob.cast(bootParam).length()); - - bootParam = KMArray.cast(args).get((short) 3); - byte enumVal = KMEnum.cast(bootParam).getVal(); - ((KMAndroidSEProvider) seProvider).setBootState(enumVal); - - bootParam = KMArray.cast(args).get((short) 4); - enumVal = KMEnum.cast(bootParam).getVal(); - ((KMAndroidSEProvider) seProvider).setDeviceLocked(enumVal == KMType.DEVICE_LOCKED_TRUE); - - - // Clear the Computed SharedHmac and Hmac nonce from persistent memory. - Util.arrayFillNonAtomic(scratchPad, (short) 0, KMRepository.COMPUTED_HMAC_KEY_SIZE, (byte) 0); - seProvider.createComputedHmacKey(scratchPad, (short) 0, KMRepository.COMPUTED_HMAC_KEY_SIZE); - - super.reboot(); - sendError(apdu, KMError.OK); - } - - private void processLockProvisioningCmd(APDU apdu) { - ((KMAndroidSEProvider) seProvider).setProvisionLocked(true); - sendError(apdu, KMError.OK); - } - - @Override - public void onCleanup() { - } - - @Override - public void onConsolidate() { - } - - @Override - public void onRestore(Element element) { - element.initRead(); - provisionStatus = element.readByte(); - keymasterState = element.readByte(); - repository.onRestore(element); - seProvider.onRestore(element); - } - - @Override - public Element onSave() { - // SEProvider count - short primitiveCount = seProvider.getBackupPrimitiveByteCount(); - short objectCount = seProvider.getBackupObjectCount(); - //Repository count - primitiveCount += repository.getBackupPrimitiveByteCount(); - objectCount += repository.getBackupObjectCount(); - //KMKeymasterApplet count - primitiveCount += computePrimitveDataSize(); - objectCount += computeObjectCount(); - - // Create element. - Element element = UpgradeManager.createElement(Element.TYPE_SIMPLE, - primitiveCount, objectCount); - element.write(provisionStatus); - element.write(keymasterState); - repository.onSave(element); - seProvider.onSave(element); - return element; - } - - private short computePrimitveDataSize() { - // provisionStatus + keymasterState - return (short) 2; - } - - private short computeObjectCount() { - return (short) 0; - } - - private short validateApdu(APDU apdu) { - // Read the apdu header and buffer. - byte[] apduBuffer = apdu.getBuffer(); - byte apduClass = apduBuffer[ISO7816.OFFSET_CLA]; - - // Validate APDU Header. - if ((apduClass != CLA_ISO7816_NO_SM_NO_CHAN)) { - sendError(apdu, KMError.UNSUPPORTED_CLA); - return KMType.INVALID_VALUE; - } - - short err = specification.validateApduHeader(apdu); - if (err != KMError.OK) { - sendError(apdu, err); - return KMType.INVALID_VALUE; - } - return apduBuffer[ISO7816.OFFSET_INS]; - } -} - diff --git a/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAESKey.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMAESKey.java similarity index 100% rename from Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAESKey.java rename to Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMAESKey.java diff --git a/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAndroidSEProvider.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMAndroidSEProvider.java similarity index 95% rename from Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAndroidSEProvider.java rename to Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMAndroidSEProvider.java index 9b767b76..488222f9 100644 --- a/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAndroidSEProvider.java +++ b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMAndroidSEProvider.java @@ -140,12 +140,12 @@ public KMAndroidSEProvider() { // Re-usable AES,DES and HMAC keys in persisted memory. aesKeys = new AESKey[2]; aesKeys[KEYSIZE_128_OFFSET] = (AESKey) KeyBuilder.buildKey( - KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_128, false); + KeyBuilder.TYPE_AES_TRANSIENT_RESET, KeyBuilder.LENGTH_AES_128, false); aesKeys[KEYSIZE_256_OFFSET] = (AESKey) KeyBuilder.buildKey( - KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_256, false); - triDesKey = (DESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_DES, + 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, (short) 512, + 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); @@ -167,8 +167,6 @@ public KMAndroidSEProvider() { //Allocate buffer for certificate chain. if (!isUpgrading()) { certificateChain = new byte[CERT_CHAIN_MAX_SIZE]; - additionalCertChain = new byte[ADDITIONAL_CERT_CHAIN_MAX_SIZE]; - //Allocate buffer for certificate chain. //Allocate buffer for certificate chain and cert parameters. // First 2 bytes is reserved for length for all the 3 buffers. short totalLen = (short) (6 + KMConfigurations.CERT_CHAIN_MAX_SIZE + @@ -186,9 +184,10 @@ public KMAndroidSEProvider() { createComputedHmacKey(tmpArray, (short)0, (short) 32); } androidSEProvider = this; - resetFlag = JCSystem.makeTransientByteArray((short) 1, + resetFlag = JCSystem.makeTransientByteArray((short) 2, JCSystem.CLEAR_ON_DESELECT); resetFlag[0] = (byte) POWER_RESET_FALSE; + resetFlag[1] = (byte) POWER_RESET_FALSE; } public static void initStatics() { @@ -908,9 +907,7 @@ public short cmacKDF(KMPreSharedKey pSharedKey, byte[] label, } public void clearCertificateChain() { - JCSystem.beginTransaction(); - Util.arrayFillNonAtomic(certificateChain, (short) 0, CERT_CHAIN_MAX_SIZE, (byte) 0); - JCSystem.commitTransaction(); + Util.arrayFill(certificateChain, (short) 0, CERT_CHAIN_MAX_SIZE, (byte) 0); } //This function supports multi-part request data. @@ -931,9 +928,9 @@ public void persistPartialCertificateChain(byte[] buf, short offset, short len, } JCSystem.beginTransaction(); Util.setShort(certificateChain, (short) 0, (short) (len + persistedLen)); - Util.arrayCopyNonAtomic(buf, offset, certificateChain, - (short) (persistedLen + 2), len); JCSystem.commitTransaction(); + Util.arrayCopy(buf, offset, certificateChain, + (short) (persistedLen + 2), len); } public short readCertificateChain(byte[] buf, short offset) { @@ -949,6 +946,9 @@ public short getCertificateChainLength() { @Override public void onSave(Element element) { element.write(certificateChain); + element.write(provisionData); + element.write(additionalCertChain); + element.write(bcc); KMAESKey.onSave(element, masterKey); KMECPrivateKey.onSave(element, attestationKey); KMHmacKey.onSave(element, preSharedKey); @@ -957,6 +957,9 @@ public void onSave(Element element) { @Override public void onRestore(Element element) { certificateChain = (byte[]) element.readObject(); + provisionData = (byte[]) element.readObject(); + additionalCertChain = (byte[]) element.readObject(); + bcc = (byte[]) element.readObject(); masterKey = KMAESKey.onRestore(element); attestationKey = KMECPrivateKey.onRestore(element); preSharedKey = KMHmacKey.onRestore(element); @@ -1183,59 +1186,43 @@ 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(); + Util.arrayCopy(buffer, (short) start, attIdBrand, (short) 0, length); 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(); + Util.arrayCopy(buffer, (short) start, attIdDevice, (short) 0, length); 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(); + Util.arrayCopy(buffer, (short) start, attIdProduct, (short) 0, length); 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(); + Util.arrayCopy(buffer, (short) start, attIdSerial, (short) 0, length); 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(); + Util.arrayCopy(buffer, (short) start, attIdImei, (short) 0, length); 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(); + Util.arrayCopy(buffer, (short) start, attIdMeId, (short) 0, length); 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(); + Util.arrayCopy(buffer, (short) start, attIdManufacturer, (short) 0, length); 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(); + Util.arrayCopy(buffer, (short) start, attIdModel, (short) 0, length); break; } } @@ -1252,15 +1239,22 @@ public void deleteAttestationIds() { attIdModel = null; } - public boolean isPowerReset() { + public boolean isPowerReset(boolean isForStatusUpdate) { boolean flag = false; - if (resetFlag[0] == POWER_RESET_TRUE) { - resetFlag[0] = POWER_RESET_FALSE; - flag = true; - if (poolMgr != null) { - poolMgr.powerReset(); + if(isForStatusUpdate == false) { + if (resetFlag[0] == POWER_RESET_TRUE) { + resetFlag[0] = POWER_RESET_FALSE; + flag = true; + } + } else { + if (resetFlag[1] == POWER_RESET_TRUE) { + resetFlag[1] = POWER_RESET_FALSE; + flag = true; } } + if ((flag ==true) && (poolMgr != null)) { + poolMgr.powerReset(); + } return flag; } @@ -1328,7 +1322,7 @@ public void setBootPatchLevel(byte[] buffer, short start, short length) { if (length > 4 || length < 0) { KMException.throwIt(KMError.UNKNOWN_ERROR); } - Util.arrayCopyNonAtomic(buffer, start, bootPatchLevel, (short) 0, (short) 4); + Util.arrayCopyNonAtomic(buffer, start, bootPatchLevel, (short) 0, length); } @Override @@ -1481,14 +1475,15 @@ public void persistAdditionalCertChain(byte[] buf, short offset, short len) { // // self-signed cert, leaf contains DK_pu b // ] // Certificate = COSE_Sign1 of a public key - if ((short) (len + 2) >= ADDITIONAL_CERT_CHAIN_MAX_SIZE) { + 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(); + Util.arrayCopy(buf, offset, additionalCertChain, + (short) 2, len); + } @@ -1514,9 +1509,10 @@ public void persistBootCertificateChain(byte[] buf, short offset, short len) { } JCSystem.beginTransaction(); Util.setShort(bcc, (short) 0, (short) len); - Util.arrayCopyNonAtomic(buf, offset, bcc, - (short) 2, len); JCSystem.commitTransaction(); + Util.arrayCopy(buf, offset, bcc, + (short) 2, len); + } public void setProvisionLocked(boolean locked) { @@ -1550,20 +1546,6 @@ public short messageDigest256(byte[] inBuff, short inOffset, public KMComputedHmacKey getComputedHmacKey() { return computedHmacKey; } - - 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 short getProvisionDataBufferOffset(byte dataType) { switch(dataType) { @@ -1583,9 +1565,7 @@ private void persistProvisionData(byte[] buf, short off, short len, short maxSiz if (len > maxSize) { KMException.throwIt(KMError.INVALID_INPUT_LENGTH); } - JCSystem.beginTransaction(); - Util.arrayCopyNonAtomic(buf, off, provisionData, Util.setShort(provisionData, copyToOff, len), len); - JCSystem.commitTransaction(); + Util.arrayCopy(buf, off, provisionData, Util.setShort(provisionData, copyToOff, len), len); } private void persistCertificateChain(byte[] certChain, short certChainOff, short certChainLen) { @@ -1616,9 +1596,7 @@ public void persistProvisionData(byte[] buffer, short certChainOff, short certCh // Next single byte holds the byte string header. // Next 3 bytes holds the total length of the certificate chain. // clear buffer. - JCSystem.beginTransaction(); - Util.arrayFillNonAtomic(provisionData, (short) 0, (short) provisionData.length, (byte) 0); - JCSystem.commitTransaction(); + Util.arrayFill(provisionData, (short) 0, (short) provisionData.length, (byte) 0); // Persist data. persistCertificateChain(buffer, certChainOff, certChainLen); persistCertficateIssuer(buffer, certIssuerOff, certIssuerLen); diff --git a/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAttestationCert.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMAttestationCert.java similarity index 100% rename from Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAttestationCert.java rename to Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMAttestationCert.java diff --git a/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAttestationKey.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMAttestationKey.java similarity index 100% rename from Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAttestationKey.java rename to Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMAttestationKey.java diff --git a/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMComputedHmacKey.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMComputedHmacKey.java similarity index 100% rename from Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMComputedHmacKey.java rename to Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMComputedHmacKey.java diff --git a/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMConfigurations.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMConfigurations.java similarity index 96% rename from Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMConfigurations.java rename to Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMConfigurations.java index 6e5090a1..8373c87f 100644 --- a/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMConfigurations.java +++ b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMConfigurations.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.seprovider; public class KMConfigurations { // Machine types diff --git a/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMDeviceUniqueKey.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMDeviceUniqueKey.java similarity index 100% rename from Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMDeviceUniqueKey.java rename to Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMDeviceUniqueKey.java diff --git a/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMECDeviceUniqueKey.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMECDeviceUniqueKey.java similarity index 100% rename from Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMECDeviceUniqueKey.java rename to Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMECDeviceUniqueKey.java diff --git a/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMECPrivateKey.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMECPrivateKey.java similarity index 100% rename from Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMECPrivateKey.java rename to Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMECPrivateKey.java diff --git a/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMEcdsa256NoDigestSignature.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMEcdsa256NoDigestSignature.java similarity index 100% rename from Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMEcdsa256NoDigestSignature.java rename to Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMEcdsa256NoDigestSignature.java diff --git a/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMError.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMError.java similarity index 100% rename from Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMError.java rename to Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMError.java diff --git a/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMException.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMException.java similarity index 100% rename from Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMException.java rename to Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMException.java diff --git a/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMHmacKey.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMHmacKey.java similarity index 100% rename from Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMHmacKey.java rename to Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMHmacKey.java diff --git a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMKeymasterProvision.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMKeymasterProvision.java new file mode 100644 index 00000000..318bc952 --- /dev/null +++ b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMKeymasterProvision.java @@ -0,0 +1,314 @@ +package com.android.javacard.seprovider; + +import org.globalplatform.upgrade.Element; + +import com.android.javacard.kmdevice.KMArray; +import com.android.javacard.kmdevice.KMInteger; +import com.android.javacard.kmdevice.KMKeyParameters; +import com.android.javacard.kmdevice.KMKeymasterDevice; +import com.android.javacard.kmdevice.KMRepository; +import com.android.javacard.kmdevice.KMTag; +import com.android.javacard.kmdevice.KMByteBlob; +import com.android.javacard.kmdevice.KMByteTag; +import com.android.javacard.kmdevice.KMDecoder; +import com.android.javacard.kmdevice.KMEnum; +import com.android.javacard.kmdevice.KMEnumArrayTag; +import com.android.javacard.kmdevice.KMEnumTag; + +import javacard.framework.APDU; +import javacard.framework.Util; + +public class KMKeymasterProvision { + +//Provision reporting status + private static final byte NOT_PROVISIONED = 0x00; + private static final byte PROVISION_STATUS_ATTESTATION_KEY = 0x01; + private static final byte PROVISION_STATUS_ATTESTATION_CERT_CHAIN = 0x02; + private static final byte PROVISION_STATUS_ATTESTATION_CERT_PARAMS = 0x04; + private static final byte PROVISION_STATUS_ATTEST_IDS = 0x08; + private static final byte PROVISION_STATUS_PRESHARED_SECRET = 0x10; + private static final byte PROVISION_STATUS_PROVISIONING_LOCKED = 0x20; + private static final byte PROVISION_STATUS_DEVICE_UNIQUE_KEY = 0x40; + private static final byte PROVISION_STATUS_ADDITIONAL_CERT_CHAIN = (byte) 0x80; + private static final short POWER_RESET_MASK_FLAG = (short) 0x4000; + + public static final byte BOOT_KEY_MAX_SIZE = 32; + public static final byte BOOT_HASH_MAX_SIZE = 32; + public static final short SHARED_SECRET_KEY_SIZE = 32; + protected static byte provisionStatus = NOT_PROVISIONED; + + protected KMKeymasterDevice kmDeviceInst; + protected KMSEProvider seProvider; + protected KMDecoder kmDecoder; + protected KMRepository kmRepositroyInst; + + public short getBackupPrimitiveByteCount() { + // provisionStatus + return (short) 1; + } + + public short getBackupObjectCount() { + return (short) 0; + } + + public void onSave(Element ele) { + ele.write(provisionStatus); + } + + public void onRestore(Element ele) { + provisionStatus = ele.readByte(); + } + + public KMKeymasterProvision(KMKeymasterDevice deviceInst, KMSEProvider provider, KMDecoder decoder, KMRepository repoInst){ + kmDeviceInst = deviceInst; + seProvider = provider; + kmDecoder = decoder; + kmRepositroyInst = repoInst; + } + + public void processSetBootParamsCmd(APDU apdu) { + short argsProto = KMArray.instance((short) 5); + + byte[] scratchPad = apdu.getBuffer(); + // Array of 4 expected arguments + // Argument 0 Boot Patch level + KMArray.add(argsProto, (short) 0, KMInteger.exp()); + // Argument 1 Verified Boot Key + KMArray.add(argsProto, (short) 1, KMByteBlob.exp()); + // Argument 2 Verified Boot Hash + KMArray.add(argsProto, (short) 2, KMByteBlob.exp()); + // Argument 3 Verified Boot State + KMArray.add(argsProto, (short) 3, KMEnum.instance(KMType.VERIFIED_BOOT_STATE)); + // Argument 4 Device Locked + KMArray.add(argsProto, (short) 4, KMEnum.instance(KMType.DEVICE_LOCKED)); + + short args = kmDeviceInst.receiveIncoming(apdu, argsProto); + + short bootParam = KMArray.get(args, (short) 0); + + ((KMAndroidSEProvider) seProvider).setBootPatchLevel(KMInteger.getBuffer(bootParam), + KMInteger.getStartOff(bootParam), + KMInteger.length(bootParam)); + + bootParam = KMArray.get(args, (short) 1); + if (KMByteBlob.length(bootParam) > BOOT_KEY_MAX_SIZE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + ((KMAndroidSEProvider) seProvider).setBootKey(KMByteBlob.getBuffer(bootParam), + KMByteBlob.getStartOff(bootParam), + KMByteBlob.length(bootParam)); + + bootParam = KMArray.get(args, (short) 2); + if (KMByteBlob.length(bootParam) > BOOT_HASH_MAX_SIZE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + ((KMAndroidSEProvider) seProvider).setVerifiedBootHash(KMByteBlob.getBuffer(bootParam), + KMByteBlob.getStartOff(bootParam), + KMByteBlob.length(bootParam)); + + bootParam = KMArray.get(args, (short) 3); + byte enumVal = KMEnum.getVal(bootParam); + ((KMAndroidSEProvider) seProvider).setBootState(enumVal); + + bootParam = KMArray.get(args, (short) 4); + enumVal = KMEnum.getVal(bootParam); + ((KMAndroidSEProvider) seProvider).setDeviceLocked(enumVal == KMType.DEVICE_LOCKED_TRUE); + + + // Clear the Computed SharedHmac and Hmac nonce from persistent memory. + Util.arrayFillNonAtomic(scratchPad, (short) 0, KMRepository.COMPUTED_HMAC_KEY_SIZE, (byte) 0); + seProvider.createComputedHmacKey(scratchPad, (short) 0, KMRepository.COMPUTED_HMAC_KEY_SIZE); + + kmDeviceInst.reboot(); + kmDeviceInst.sendError(apdu, KMError.OK); + } + + public void processProvisionAttestationKey(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.add(argsProto, (short) 0, keyparams); + KMArray.add(argsProto, (short) 1, keyFormatPtr); + KMArray.add(argsProto, (short) 2, blob); + + short args = kmDeviceInst.receiveIncoming(apdu, argsProto); + // Re-purpose the apdu buffer as scratch pad. + byte[] scratchPad = apdu.getBuffer(); + + // key params should have os patch, os version and verified root of trust + short keyParams = KMArray.get(args, (short) 0); + keyFormatPtr = KMArray.get(args, (short) 1); + short rawBlob = KMArray.get(args, (short) 2); + // Key format must be RAW format + short keyFormat = KMEnum.getVal(keyFormatPtr); + if (keyFormat != KMType.RAW) { + KMException.throwIt(KMError.UNIMPLEMENTED); + } + //byte origin = KMType.IMPORTED; + + // get algorithm - only EC keys expected + KMTag.assertPresence(keyParams, KMType.ENUM_TAG, KMType.ALGORITHM, KMError.INVALID_ARGUMENT); + short alg = KMEnumTag.getValue(KMType.ALGORITHM, keyParams); + if (alg != KMType.EC) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // get digest - only SHA256 supported + KMTag.assertPresence(keyParams, KMType.ENUM_ARRAY_TAG, KMType.DIGEST, KMError.INVALID_ARGUMENT); + short len = KMEnumArrayTag.getValues(KMType.DIGEST, keyParams, scratchPad, (short) 0); + if (len != 1) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + if (scratchPad[0] != KMType.SHA2_256) { + KMException.throwIt(KMError.INCOMPATIBLE_DIGEST); + } + // Purpose should be ATTEST_KEY + KMTag.assertPresence(keyParams, KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, + KMError.INVALID_ARGUMENT); + len = KMEnumArrayTag.getValues(KMType.PURPOSE, keyParams, scratchPad, (short) 0); + if (len != 1) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + if (scratchPad[0] != KMType.ATTEST_KEY) { + KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); + } + // validate Curve + KMTag.assertPresence(keyParams, KMType.ENUM_TAG, KMType.ECCURVE, KMError.INVALID_ARGUMENT); + short curve = KMEnumTag.getValue(KMType.ECCURVE, keyParams); + if (curve != KMType.P_256) { + KMException.throwIt(KMError.UNSUPPORTED_EC_CURVE); + } + // Decode EC Key + short arrPtr = kmDeviceInst.decodeRawECKey(rawBlob); + short secret = KMArray.get(arrPtr, (short) 0); + short pubKey = KMArray.get(arrPtr, (short) 1); + // Check whether key can be created + seProvider.importAsymmetricKey( + KMType.EC, + KMByteBlob.getBuffer(secret), + KMByteBlob.getStartOff(secret), + KMByteBlob.length(secret), + KMByteBlob.getBuffer(pubKey), + KMByteBlob.getStartOff(pubKey), + KMByteBlob.length(pubKey)); + + // persist key + seProvider.createAttestationKey( + KMByteBlob.getBuffer(secret), + KMByteBlob.getStartOff(secret), + KMByteBlob.length(secret)); + + provisionStatus |= PROVISION_STATUS_ATTESTATION_KEY; + kmDeviceInst.sendError(apdu, KMError.OK); + } + + public void processProvisionAttestationCertDataCmd(APDU apdu) { + // TODO Handle this function properly + kmDeviceInst.processAttestationCertDataCmd(apdu); + provisionStatus |= (PROVISION_STATUS_ATTESTATION_CERT_CHAIN | + PROVISION_STATUS_ATTESTATION_CERT_PARAMS); + kmDeviceInst.sendError(apdu, KMError.OK); + } + + public void processProvisionAttestIdsCmd(APDU apdu) { + short keyparams = KMKeyParameters.exp(); + short cmd = KMArray.instance((short) 1); + KMArray.add(cmd, (short) 0, keyparams); + short args = kmDeviceInst.receiveIncoming(apdu, cmd); + + short attData = KMArray.get(args, (short) 0); + // persist attestation Ids - if any is missing then exception occurs + setAttestationIds(attData); + provisionStatus |= PROVISION_STATUS_ATTEST_IDS; + kmDeviceInst.sendError(apdu, KMError.OK); + } + + public void processProvisionPreSharedSecretCmd(APDU apdu) { + short blob = KMByteBlob.exp(); + short argsProto = KMArray.instance((short) 1); + KMArray.add(argsProto, (short) 0, blob); + short args = kmDeviceInst.receiveIncoming(apdu, argsProto); + + short val = KMArray.get(args, (short) 0); + + if (val != KMType.INVALID_VALUE + && KMByteBlob.length(val) != SHARED_SECRET_KEY_SIZE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // Persist shared Hmac. + ((KMAndroidSEProvider) seProvider).createPresharedKey( + KMByteBlob.getBuffer(val), + KMByteBlob.getStartOff(val), + KMByteBlob.length(val)); + provisionStatus |= PROVISION_STATUS_PRESHARED_SECRET; + kmDeviceInst.sendError(apdu, KMError.OK); + + } + + public void processGetProvisionStatusCmd(APDU apdu) { + short resp = KMArray.instance((short) 2); + KMArray.add(resp, (short) 0, buildErrorStatus(KMError.OK)); + KMArray.add(resp, (short) 1, KMInteger.uint_16(provisionStatus)); + kmDeviceInst.sendOutgoing(apdu, resp); + } + + public void processLockProvisioningCmd(APDU apdu) { + ((KMAndroidSEProvider) seProvider).setProvisionLocked(true); + kmDeviceInst.sendError(apdu, KMError.OK); + } + + public void processProvisionDeviceUniqueKey(APDU apdu) { + kmDeviceInst.sendError(apdu, KMError.CMD_NOT_ALLOWED); + } + + public void processProvisionAdditionalCertChain(APDU apdu) { + kmDeviceInst.sendError(apdu, KMError.CMD_NOT_ALLOWED); + } + + protected void setAttestationIds(short attIdVals) { + short vals = KMKeyParameters.getVals(attIdVals); + short index = 0; + short length = KMArray.length(vals); + short key; + short type; + short obj; + while (index < length) { + obj = KMArray.get(vals, index); + key = KMTag.getKMTagKey(obj); + type = KMTag.getKMTagType(obj); + + if (KMType.BYTES_TAG != type) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + obj = KMByteTag.getValue(obj); + ((KMAndroidSEProvider) seProvider).setAttestationId(key, KMByteBlob.getBuffer(obj), + KMByteBlob.getStartOff(obj),KMByteBlob.length(obj)); + index++; + } + } + +//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. + public short buildErrorStatus(short err) { + short int32Ptr = KMInteger.instance((short) 4); + short powerResetStatus = 0; + if (((KMAndroidSEProvider) seProvider).isPowerReset(true)) { + powerResetStatus = POWER_RESET_MASK_FLAG; + } + + Util.setShort(KMInteger.getBuffer(int32Ptr), + KMInteger.getStartOff(int32Ptr), + powerResetStatus); + + Util.setShort(KMInteger.getBuffer(int32Ptr), + (short) (KMInteger.getStartOff(int32Ptr) + 2), + err); + // reset power reset status flag to its default value. + //repository.restorePowerResetStatus(); //TODO + return int32Ptr; + } + +} diff --git a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMKeymintProvision.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMKeymintProvision.java new file mode 100644 index 00000000..da4f670c --- /dev/null +++ b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMKeymintProvision.java @@ -0,0 +1,107 @@ +package com.android.javacard.seprovider; + +import com.android.javacard.kmdevice.KMArray; +import com.android.javacard.kmdevice.KMByteBlob; +import com.android.javacard.kmdevice.KMCose; +import com.android.javacard.kmdevice.KMCoseHeaders; +import com.android.javacard.kmdevice.KMCoseKey; +import com.android.javacard.kmdevice.KMDecoder; +import com.android.javacard.kmdevice.KMInteger; +import com.android.javacard.kmdevice.KMKeymasterDevice; +import com.android.javacard.kmdevice.KMMap; +import com.android.javacard.kmdevice.KMRepository; +import com.android.javacard.kmdevice.KMTextString; + +import javacard.framework.APDU; +import javacard.framework.Util; + +public class KMKeymintProvision extends KMKeymasterProvision{ + + public KMKeymintProvision(KMKeymasterDevice deviceInst, KMSEProvider provider, KMDecoder decoder, KMRepository repoInst){ + super(deviceInst, provider, decoder, repoInst); + } + + @Override + public void processProvisionAttestationKey(APDU apdu) { + kmDeviceInst.sendError(apdu, KMError.CMD_NOT_ALLOWED); + } + + @Override + public void processProvisionAttestationCertDataCmd(APDU apdu) { + kmDeviceInst.sendError(apdu, KMError.CMD_NOT_ALLOWED); + } + + @Override + public void processProvisionDeviceUniqueKey(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.add(arr, (short) 0, coseKeyExp); //[ CoseKey ] + arr = kmDeviceInst.receiveIncoming(apdu, arr); + // Get cose key. + short coseKey = KMArray.get(arr, (short) 0); + short pubKeyLen = KMCoseKey.cast(coseKey).getEcdsa256PublicKey(scratchPad, (short) 0); + short privKeyLen = KMCoseKey.cast(coseKey).getPrivateKey(scratchPad, pubKeyLen); + //Store the Device unique Key. + seProvider.createDeviceUniqueKey(false, scratchPad, (short) 0, pubKeyLen, scratchPad, + pubKeyLen, privKeyLen); + short bcc = kmDeviceInst.generateBcc(false, scratchPad); + short len = KMKeymasterDevice.encodeToApduBuffer(bcc, scratchPad, (short) 0, + kmDeviceInst.MAX_COSE_BUF_SIZE); + ((KMAndroidSEProvider) seProvider).persistBootCertificateChain(scratchPad, (short) 0, len); + kmDeviceInst.sendError(apdu, KMError.OK); + } + + @Override + public void processProvisionAdditionalCertChain(APDU apdu) { + // Prepare the expression to decode + short headers = KMCoseHeaders.exp(); + short arrInst = KMArray.instance((short) 4); + KMArray.add(arrInst, (short) 0, KMByteBlob.exp()); + KMArray.add(arrInst, (short) 1, headers); + KMArray.add(arrInst, (short) 2, KMByteBlob.exp()); + KMArray.add(arrInst, (short) 3, KMByteBlob.exp()); + short coseSignArr = KMArray.exp(arrInst); + short map = KMMap.instance((short) 1); + KMMap.add(map, (short) 0, KMTextString.exp(), coseSignArr); + // receive incoming data and decode it. + byte[] srcBuffer = apdu.getBuffer(); + short bufferLength = apdu.getIncomingLength(); + short bufferStartOffset = kmRepositroyInst.allocReclaimableMemory(bufferLength); + byte[] buffer = kmRepositroyInst.getHeap(); + map = kmDeviceInst.receiveIncoming(apdu, map, buffer, bufferLength, bufferStartOffset); + arrInst = KMMap.getKeyValue(map, (short) 0); + // Validate Additional certificate chain. + short leafCoseKey = + kmDeviceInst.validateCertChain(false, KMCose.COSE_ALG_ES256, KMCose.COSE_ALG_ES256, arrInst, + srcBuffer, null); + // Compare the DK_Pub. + short pubKeyLen = KMCoseKey.cast(leafCoseKey).getEcdsa256PublicKey(srcBuffer, (short) 0); + KMDeviceUniqueKey uniqueKey = seProvider.getDeviceUniqueKey(false); + if (uniqueKey == null) { + KMException.throwIt(KMError.STATUS_FAILED); + } + short uniqueKeyLen = uniqueKey.getPublicKey(srcBuffer, pubKeyLen); + if ((pubKeyLen != uniqueKeyLen) || + (0 != Util.arrayCompare(srcBuffer, (short) 0, srcBuffer, pubKeyLen, pubKeyLen))) { + KMException.throwIt(KMError.STATUS_FAILED); + } + seProvider.persistAdditionalCertChain(buffer, bufferStartOffset, bufferLength); + //reclaim memory + kmRepositroyInst.reclaimMemory(bufferLength); + kmDeviceInst.sendError(apdu, KMError.OK); + } + + @Override + public short buildErrorStatus(short err) { + short int32Ptr = KMInteger.instance((short) 2); + + Util.setShort(KMInteger.getBuffer(int32Ptr), + (short) (KMInteger.getStartOff(int32Ptr)), + err); + + return int32Ptr; + } + +} diff --git a/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMMasterKey.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMMasterKey.java similarity index 100% rename from Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMMasterKey.java rename to Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMMasterKey.java diff --git a/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperation.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMOperation.java similarity index 100% rename from Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperation.java rename to Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMOperation.java diff --git a/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperationImpl.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMOperationImpl.java similarity index 100% rename from Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperationImpl.java rename to Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMOperationImpl.java diff --git a/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMPoolManager.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMPoolManager.java similarity index 100% rename from Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMPoolManager.java rename to Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMPoolManager.java diff --git a/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMPreSharedKey.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMPreSharedKey.java similarity index 100% rename from Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMPreSharedKey.java rename to Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMPreSharedKey.java diff --git a/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsa2048NoDigestSignature.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMRsa2048NoDigestSignature.java similarity index 100% rename from Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsa2048NoDigestSignature.java rename to Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMRsa2048NoDigestSignature.java diff --git a/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsaOAEPEncoding.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMRsaOAEPEncoding.java similarity index 100% rename from Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsaOAEPEncoding.java rename to Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMRsaOAEPEncoding.java diff --git a/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMSEProvider.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMSEProvider.java similarity index 100% rename from Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMSEProvider.java rename to Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMSEProvider.java diff --git a/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMType.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMType.java similarity index 100% rename from Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMType.java rename to Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMType.java diff --git a/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMUpgradable.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMUpgradable.java similarity index 100% rename from Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMUpgradable.java rename to Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMUpgradable.java diff --git a/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java b/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java deleted file mode 100644 index 7dbef4d6..00000000 --- a/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java +++ /dev/null @@ -1,1083 +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.keymaster; - -import com.android.javacard.seprovider.KMAESKey; -import com.android.javacard.seprovider.KMAttestationCert; -import com.android.javacard.seprovider.KMAttestationKey; -import com.android.javacard.seprovider.KMException; -import com.android.javacard.seprovider.KMJCardSimulator; -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 short 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 - }; - // Validity is not fixed field - // Subject is a fixed field with only CN= Android Keystore Key - same for all the keys - private static final byte[] X509Subject = { - 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 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 byte KEYMASTER_VERSION = 100; - private static final byte ATTESTATION_VERSION = 100; - private static final byte[] pubExponent = {0x01, 0x00, 0x01}; - private static final byte SERIAL_NUM = (byte) 0x01; - private static final byte X509_VERSION = (byte) 0x02; - - private static short certStart; - private static short certLength; - private static short tbsStart; - private static short tbsLength; - private static byte[] stack; - private static short stackPtr; - private static short bufStart; - private static short bufLength; - private static KMSEProvider seProvider; - - private static short uniqueId; - private static short attChallenge; - private static short notBefore; - - private static short notAfter; - private static short pubKey; - private static short[] swParams; - private static short swParamsIndex; - private static short[] hwParams; - private static short hwParamsIndex; - private static byte keyUsage; - private static byte unusedBits; - private static KMAttestationCert inst; - private static boolean rsaCert; - private static byte deviceLocked; - private static short verifiedBootKey; - private static byte verifiedState; - private static short verifiedHash; - private static short issuer; - private static short subjectName; - private static short signPriv; - private static short serialNum; - - private static byte certMode; - private static short certAttestKeySecret; - private static short certAttestKeyRsaPubModulus; - private static KMAttestationKey factoryAttestKey; - private static boolean certRsaSign; - private static final byte SERIAL_NUM_MAX_LEN = 20; - private static final byte SUBJECT_NAME_MAX_LEN = 32; - - private KMAttestationCertImpl() { - } - - public static KMAttestationCert instance(boolean rsaCert, KMSEProvider provider) { - if (inst == null) { - inst = new KMAttestationCertImpl(); - seProvider = provider; - } - init(); - KMAttestationCertImpl.rsaCert = rsaCert; - return inst; - } - - private static void init() { - stack = null; - stackPtr = 0; - certStart = 0; - certLength = 0; - bufStart = 0; - bufLength = 0; - tbsLength = 0; - if (swParams == null) { - swParams = JCSystem.makeTransientShortArray((short) MAX_PARAMS, JCSystem.CLEAR_ON_RESET); - } - if (hwParams == null) { - hwParams = JCSystem.makeTransientShortArray((short) MAX_PARAMS, JCSystem.CLEAR_ON_RESET); - } - - swParamsIndex = 0; - hwParamsIndex = 0; - keyUsage = 0; - unusedBits = 8; - attChallenge = 0; - notBefore = 0; - notAfter = 0; - pubKey = 0; - uniqueId = 0; - verifiedBootKey = 0; - verifiedHash = 0; - verifiedState = 0; - rsaCert = true; - deviceLocked = 0; - signPriv = 0; - certMode = KMType.NO_CERT; - certAttestKeySecret = KMType.INVALID_VALUE; - certRsaSign = true; - issuer = KMType.INVALID_VALUE; - subjectName = KMType.INVALID_VALUE; - serialNum = KMType.INVALID_VALUE; - factoryAttestKey = null; - } - - @Override - public KMAttestationCert verifiedBootHash(short obj) { - verifiedHash = obj; - return this; - } - - @Override - public KMAttestationCert verifiedBootKey(short obj) { - verifiedBootKey = obj; - return this; - } - - @Override - public KMAttestationCert verifiedBootState(byte val) { - verifiedState = val; - return this; - } - - private KMAttestationCert uniqueId(short obj) { - uniqueId = obj; - return this; - } - - @Override - public KMAttestationCert notBefore(short obj, boolean derEncoded, byte[] scratchpad) { - if(!derEncoded) { - // convert milliseconds to UTC date - notBefore = KMUtils.convertToDate(obj, scratchpad, true); - }else{ - notBefore = 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 2051 then use generalized - // time format else use utc time format. - short tmpVar = KMInteger.uint_64(KMUtils.firstJan2051, (short) 0); - if (KMInteger.compare(usageExpiryTimeObj, tmpVar) >= 0) { - usageExpiryTimeObj = KMUtils.convertToDate(usageExpiryTimeObj, scratchPad, - false); - } else { - usageExpiryTimeObj = KMUtils - .convertToDate(usageExpiryTimeObj, scratchPad, true); - } - notAfter = usageExpiryTimeObj; - } else { - //notAfter = certExpirtyTimeObj; - } - } else { - // notAfter = KMByteBlob.instance(KMByteBlob.cast(usageExpiryTimeObj).getBuffer(), - // KMByteBlob.cast(usageExpiryTimeObj).getStartOff(), - // KMByteBlob.cast(usageExpiryTimeObj).length()); - notAfter = usageExpiryTimeObj; - } - return this; - } - - @Override - public KMAttestationCert deviceLocked(boolean val) { - if (val) { - deviceLocked = (byte) 0xFF; - } else { - deviceLocked = 0; - } - return this; - } - - @Override - public KMAttestationCert publicKey(short obj) { - pubKey = obj; - return this; - } - - @Override - public KMAttestationCert attestationChallenge(short obj) { - attChallenge = obj; - return this; - } - - @Override - public KMAttestationCert extensionTag(short tag, boolean hwEnforced) { - if (hwEnforced) { - hwParams[hwParamsIndex] = tag; - hwParamsIndex++; - } else { - swParams[swParamsIndex] = tag; - swParamsIndex++; - } - if (KMTag.getKey(tag) == KMType.PURPOSE) { - createKeyUsage(tag); - } - return this; - } - - @Override - public KMAttestationCert issuer(short obj) { - 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) { - keyUsage = (byte) (keyUsage | keyUsageSign); - } else if (KMEnumArrayTag.cast(tag).get(index) == KMType.WRAP_KEY) { - keyUsage = (byte) (keyUsage | keyUsageKeyEncipher); - } else if (KMEnumArrayTag.cast(tag).get(index) == KMType.DECRYPT) { - keyUsage = (byte) (keyUsage | keyUsageDataEncipher); - } else if (KMEnumArrayTag.cast(tag).get(index) == KMType.AGREE_KEY){ - keyUsage = (byte) (keyUsage | keyUsageKeyAgreement); - }else if (KMEnumArrayTag.cast(tag).get(index) == KMType.ATTEST_KEY){ - keyUsage = (byte) (keyUsage | keyUsageCertSign); - } - index++; - } - index = keyUsage; - while (index != 0) { - index = (byte) (index << 1); - unusedBits--; - } - } - -//TODO Serial number, X509Version needa to be passed as parameter - private static void pushTbsCert(boolean rsaCert, boolean rsa) { - short last = stackPtr; - if(certMode == KMType.ATTESTATION_CERT || certMode == KMType.FACTORY_PROVISIONED_ATTEST_CERT) { - pushExtensions(); - } - // subject public key info - if (rsaCert) { - pushRsaSubjectKeyInfo(); - } else { - pushEccSubjectKeyInfo(); - } - // subject - pushBytes(KMByteBlob.cast(subjectName).getBuffer(), KMByteBlob.cast(subjectName).getStartOff(), - KMByteBlob.cast(subjectName).length()); - pushValidity(); - // issuer - der encoded - pushBytes( - KMByteBlob.cast(issuer).getBuffer(), - KMByteBlob.cast(issuer).getStartOff(), - KMByteBlob.cast(issuer).length()); - // Algorithm Id - if(rsa) { - pushAlgorithmId(X509RsaSignAlgIdentifier); - }else{ - pushAlgorithmId(X509EcdsaSignAlgIdentifier); - } - // Serial Number - pushBytes(KMByteBlob.cast(serialNum).getBuffer(), KMByteBlob.cast(serialNum).getStartOff(), - KMByteBlob.cast(serialNum).length()); - pushIntegerHeader(KMByteBlob.cast(serialNum).length()); - // Version - pushByte(X509_VERSION); - pushIntegerHeader((short) 1); - pushByte((byte) 0x03); - pushByte((byte) 0xA0); - // Finally sequence header. - pushSequenceHeader((short) (last - stackPtr)); - } - - private static void pushExtensions() { - short last = stackPtr; - if (keyUsage != 0) { - pushKeyUsage(keyUsage, unusedBits); - } - pushKeyDescription(); - pushSequenceHeader((short) (last - stackPtr)); - // Extensions have explicit tag of [3] - pushLength((short) (last - stackPtr)); - pushByte((byte) 0xA3); - } - - // Time SEQUENCE{UTCTime, UTC or Generalized Time) - private static void pushValidity() { - short last = stackPtr; - if (notAfter != 0) { - pushBytes( - KMByteBlob.cast(notAfter).getBuffer(), - KMByteBlob.cast(notAfter).getStartOff(), - KMByteBlob.cast(notAfter).length()); - } else { - KMException.throwIt(KMError.INVALID_DATA); - } - pushTimeHeader(KMByteBlob.cast(notAfter).length()); - pushBytes( - KMByteBlob.cast(notBefore).getBuffer(), - KMByteBlob.cast(notBefore).getStartOff(), - KMByteBlob.cast(notBefore).length()); - pushTimeHeader(KMByteBlob.cast(notBefore).length()); - pushSequenceHeader((short) (last - stackPtr)); - } - - 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 = stackPtr; - pushBytes(pubExponent, (short) 0, (short) pubExponent.length); - pushIntegerHeader((short) pubExponent.length); - pushBytes( - KMByteBlob.cast(pubKey).getBuffer(), - KMByteBlob.cast(pubKey).getStartOff(), - KMByteBlob.cast(pubKey).length()); - - // encode modulus as positive if the MSB is 1. - if (KMByteBlob.cast(pubKey).get((short) 0) < 0) { - pushByte((byte) 0x00); - pushIntegerHeader((short) (KMByteBlob.cast(pubKey).length() + 1)); - } else { - pushIntegerHeader(KMByteBlob.cast(pubKey).length()); - } - pushSequenceHeader((short) (last - stackPtr)); - pushBitStringHeader((byte) 0x00, (short) (last - stackPtr)); - pushRsaEncryption(); - pushSequenceHeader((short) (last - stackPtr)); - } - - // SEQUENCE{SEQUENCE{ecPubKey, prime256v1}, bitString{pubKey}} - private static void pushEccSubjectKeyInfo() { - short last = stackPtr; - pushBytes( - KMByteBlob.cast(pubKey).getBuffer(), - KMByteBlob.cast(pubKey).getStartOff(), - KMByteBlob.cast(pubKey).length()); - pushBitStringHeader((byte) 0x00, KMByteBlob.cast(pubKey).length()); - pushEcDsa(); - pushSequenceHeader((short) (last - stackPtr)); - } - - private static void pushEcDsa() { - short last = stackPtr; - pushBytes(prime256v1, (short) 0, (short) prime256v1.length); - pushBytes(eccPubKey, (short) 0, (short) eccPubKey.length); - pushSequenceHeader((short) (last - stackPtr)); - } - - private static void pushRsaEncryption() { - short last = stackPtr; - pushNullHeader(); - pushBytes(rsaEncryption, (short) 0, (short) rsaEncryption.length); - pushSequenceHeader((short) (last - stackPtr)); - } - - // KeyDescription ::= SEQUENCE { - // attestationVersion INTEGER, # Value 3 - // attestationSecurityLevel SecurityLevel, # See below - // keymasterVersion INTEGER, # Value 4 - // 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 = stackPtr; - pushHWParams(); - pushSWParams(); - if (uniqueId != 0) { - pushOctetString( - KMByteBlob.cast(uniqueId).getBuffer(), - KMByteBlob.cast(uniqueId).getStartOff(), - KMByteBlob.cast(uniqueId).length()); - } else { - pushOctetStringHeader((short) 0); - } - pushOctetString( - KMByteBlob.cast(attChallenge).getBuffer(), - KMByteBlob.cast(attChallenge).getStartOff(), - KMByteBlob.cast(attChallenge).length()); - pushEnumerated(KMType.STRONGBOX); - pushByte(KEYMASTER_VERSION); - pushIntegerHeader((short) 1); - pushEnumerated(KMType.STRONGBOX); - pushByte(ATTESTATION_VERSION); - pushIntegerHeader((short) 1); - pushSequenceHeader((short) (last - stackPtr)); - pushOctetStringHeader((short) (last - stackPtr)); - pushBytes(androidExtn, (short) 0, (short) androidExtn.length); - pushSequenceHeader((short) (last - stackPtr)); - } - - private static void pushSWParams() { - short last = stackPtr; - // Below are the allowed softwareEnforced Authorization tags inside the attestation certificate's extension. - short[] tagIds = { - KMType.ATTESTATION_APPLICATION_ID, KMType.CREATION_DATETIME, - KMType.USAGE_EXPIRE_DATETIME, KMType.ORIGINATION_EXPIRE_DATETIME, - KMType.ACTIVE_DATETIME, KMType.UNLOCKED_DEVICE_REQUIRED}; - byte index = 0; - do { - pushParams(swParams, swParamsIndex, tagIds[index]); - } while (++index < tagIds.length); - pushSequenceHeader((short) (last - stackPtr)); - } - - private static void pushHWParams() { - short last = stackPtr; - // Below are the allowed hardwareEnforced Authorization tags inside the attestation certificate's extension. - short[] tagIds = { - 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.AUTH_TIMEOUT, KMType.USER_AUTH_TYPE, - KMType.NO_AUTH_REQUIRED, KMType.USER_SECURE_ID, - KMType.RSA_PUBLIC_EXPONENT, KMType.ECCURVE, KMType.MIN_MAC_LENGTH, - KMType.CALLER_NONCE, KMType.PADDING, KMType.DIGEST, KMType.BLOCK_MODE, - KMType.KEYSIZE, KMType.ALGORITHM, KMType.PURPOSE}; - - byte index = 0; - do { - if (tagIds[index] == KMType.ROOT_OF_TRUST) { - pushRoT(); - continue; - } - if (pushParams(hwParams, hwParamsIndex, tagIds[index])) { - continue; - } - } while (++index < tagIds.length); - pushSequenceHeader((short) (last - stackPtr)); - } - - 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 keymaster 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 = stackPtr; - // verified boot hash - pushOctetString( - KMByteBlob.cast(verifiedHash).getBuffer(), - KMByteBlob.cast(verifiedHash).getStartOff(), - KMByteBlob.cast(verifiedHash).length()); - - pushEnumerated(verifiedState); - - pushBoolean(deviceLocked); - // verified boot Key - pushOctetString( - KMByteBlob.cast(verifiedBootKey).getBuffer(), - KMByteBlob.cast(verifiedBootKey).getStartOff(), - KMByteBlob.cast(verifiedBootKey).length()); - - // Finally sequence header - pushSequenceHeader((short) (last - stackPtr)); - // ... and tag Id - pushTagIdHeader(KMType.ROOT_OF_TRUST, (short) (last - stackPtr)); - } - - 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 = stackPtr; - short index = 0; - while (index < len) { - pushByte(buf[(short) (start + index)]); - pushIntegerHeader((short) 1); - index++; - } - pushSetHeader((short) (last - stackPtr)); - pushTagIdHeader(tagId, (short) (last - stackPtr)); - } - - // 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 = stackPtr; - 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 - stackPtr)); - pushTagIdHeader(tagId, (short) (last - stackPtr)); - } - - private static void pushSetHeader(short len) { - pushLength(len); - pushByte((byte) 0x31); - } - - private static void pushEnumerated(byte val) { - short last = stackPtr; - pushByte(val); - pushEnumeratedHeader((short) (last - stackPtr)); - } - - private static void pushEnumeratedHeader(short len) { - pushLength(len); - pushByte((byte) 0x0A); - } - - private static void pushBoolTag(short tagId) { - short last = stackPtr; - pushNullHeader(); - pushTagIdHeader(tagId, (short) (last - stackPtr)); - } - - private static void pushNullHeader() { - pushByte((byte) 0); - pushByte((byte) 0x05); - } - - private static void pushEnumTag(short tagId, byte val) { - short last = stackPtr; - pushByte(val); - pushIntegerHeader((short) (last - stackPtr)); - pushTagIdHeader(tagId, (short) (last - stackPtr)); - } - - private static void pushIntegerTag(short tagId, byte[] buf, short start, short len) { - short last = stackPtr; - pushInteger(buf, start, len); - // pushIntegerHeader((short) (last - stackPtr)); - pushTagIdHeader(tagId, (short) (last - stackPtr)); - } - - // 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 = stackPtr; - 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 - stackPtr)); - } - - // 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 = stackPtr; - pushBytes(buf, start, len); - pushOctetStringHeader((short) (last - stackPtr)); - pushTagIdHeader(tagId, (short) (last - stackPtr)); - } - - // 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 = stackPtr; - pushByte(keyUsage); - pushBitStringHeader(unusedBits, (short) (last - stackPtr)); - pushOctetStringHeader((short) (last - stackPtr)); - pushBytes(keyUsageExtn, (short) 0, (short) keyUsageExtn.length); - pushSequenceHeader((short) (last - stackPtr)); - } - - 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, stackPtr, val); - } - - private static void pushByte(byte val) { - decrementStackPtr((short) 1); - stack[stackPtr] = val; - } - - private static void pushBytes(byte[] buf, short start, short len) { - decrementStackPtr(len); - if (buf != null) { - Util.arrayCopyNonAtomic(buf, start, stack, stackPtr, len); - } - } - - private static void decrementStackPtr(short cnt) { - stackPtr = (short) (stackPtr - cnt); - if (bufStart > stackPtr) { - KMException.throwIt(KMError.UNKNOWN_ERROR); - } - } - - @Override - public KMAttestationCert buffer(byte[] buf, short start, short maxLen) { - stack = buf; - bufStart = start; - bufLength = maxLen; - stackPtr = (short) (bufStart + bufLength); - return this; - } - - @Override - public short getCertStart() { - return certStart; - } - - @Override - public short getCertLength() { - return certLength; - } - - - public void build(KMAttestationKey factoryAttestKey, short attSecret, short attMod, - boolean rsaSign, boolean fakeCert) { - stackPtr = (short)(bufStart + bufLength); - short last = stackPtr; - 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 = stackPtr; - pushBitStringHeader((byte) 0, (short) (last - stackPtr)); - if (rsaSign) { - pushAlgorithmId(X509RsaSignAlgIdentifier); - } else { - pushAlgorithmId(X509EcdsaSignAlgIdentifier); - } - tbsLength = stackPtr; - pushTbsCert(rsaCert, rsaSign); - tbsStart = stackPtr; - tbsLength = (short) (tbsLength - tbsStart); - KMJCardSimulator provider = KMJCardSimulator.getInstance(); - if(attSecret != KMType.INVALID_VALUE || factoryAttestKey != null) { - // Sign with the attestation key - // The pubKey is the modulus. - if (rsaSign) { - sigLen = provider - .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, - tbsStart, - tbsLength, - stack, - signatureOffset); - if(sigLen > RSA_SIG_LEN) KMException.throwIt(KMError.UNKNOWN_ERROR); - } else if (factoryAttestKey != null) { - sigLen = provider - .ecSign256( - factoryAttestKey, - stack, - tbsStart, - tbsLength, - stack, - signatureOffset); - if (sigLen > ECDSA_MAX_SIG_LEN) KMException.throwIt(KMError.UNKNOWN_ERROR); - } else { - sigLen = provider - .ecSign256( - KMByteBlob.cast(attSecret).getBuffer(), - KMByteBlob.cast(attSecret).getStartOff(), - KMByteBlob.cast(attSecret).length(), - stack, - tbsStart, - tbsLength, - stack, - signatureOffset); - if (sigLen > ECDSA_MAX_SIG_LEN) KMException.throwIt(KMError.UNKNOWN_ERROR); - } - // Adjust signature length - stackPtr = 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 - stackPtr = tbsStart; - pushSequenceHeader((short) (last - stackPtr)); - certStart = stackPtr; - certLength = (short)(last - certStart); - print(stack, getCertStart(), getCertLength()); - } - - - @Override - public void build() { - if (certMode == KMType.FAKE_CERT) { - build(null, KMType.INVALID_VALUE, KMType.INVALID_VALUE, true, true); - } else if (certMode == KMType.FACTORY_PROVISIONED_ATTEST_CERT) { - build(factoryAttestKey, KMType.INVALID_VALUE, KMType.INVALID_VALUE, false, false); - } else { - build(null, certAttestKeySecret, certAttestKeyRsaPubModulus, certRsaSign, 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.getKeySizeBits() / 8)); - aesKey.getKey( - KMByteBlob.cast(mKeyData).getBuffer(), /* Key */ - KMByteBlob.cast(mKeyData).getStartOff()); /* Key start*/ - timeOffset = KMByteBlob.instance((short) 32); - appIdOff = KMJCardSimulator.getInstance().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){ - short length = KMByteBlob.cast(number).length(); - if(length > SERIAL_NUM_MAX_LEN){ - return false; - } - byte msb = KMByteBlob.cast(number).get((short)0); - if(msb < 0 && length > (SERIAL_NUM_MAX_LEN -1)){ - return false; - } - serialNum = number; - return true; - } - - @Override - public boolean subjectName(short sub){ - /* - short length = KMByteBlob.cast(sub).length(); - if(length > SUBJECT_NAME_MAX_LEN){ - return false; - } - Util.arrayCopyNonAtomic(KMByteBlob.cast(sub).getBuffer(), KMByteBlob.cast(sub).getStartOff(), - subjectName,(short)0,length); - subjectLen = length; - */ - if(sub == KMType.INVALID_VALUE || KMByteBlob.cast(sub).length() == 0) return false; - subjectName = sub; - return true; - } - - @Override - public KMAttestationCert ecAttestKey(short attestKey, byte mode){ - certMode = mode; - certAttestKeySecret = attestKey; - certAttestKeyRsaPubModulus = KMType.INVALID_VALUE; - certRsaSign = false; - return this; - } - - @Override - public KMAttestationCert rsaAttestKey(short attestPrivExp, short attestMod, byte mode){ - certMode = mode; - certAttestKeySecret = attestPrivExp; - certAttestKeyRsaPubModulus = attestMod; - certRsaSign = true; - return this; - } - - public KMAttestationCert factoryAttestKey(KMAttestationKey key, byte mode) { - certMode = mode; - factoryAttestKey = key; - return this; - } - - private void print(byte[] buf, short start, short length){ - StringBuilder sb = new StringBuilder(length * 2); - for(short i = start; i < (start+length); i ++){ - sb.append(String.format("%02x", buf[i])); - } - System.out.println( sb.toString()); - } -} diff --git a/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMJCardSimApplet.java b/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMJCardSimApplet.java deleted file mode 100644 index f6af7ab2..00000000 --- a/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMJCardSimApplet.java +++ /dev/null @@ -1,550 +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.keymaster; - -import com.android.javacard.seprovider.KMDeviceUniqueKey; -import com.android.javacard.seprovider.KMException; -import com.android.javacard.seprovider.KMJCardSimulator; -import javacard.framework.APDU; -import javacard.framework.ISO7816; -import javacard.framework.Util; - -public class KMJCardSimApplet extends KMKeymasterApplet { - - // Provider specific Commands - private static final byte INS_KEYMINT_PROVIDER_APDU_START = 0x00; - private static final byte INS_PROVISION_ATTESTATION_KEY_CMD = - INS_KEYMINT_PROVIDER_APDU_START + 1; //0x01 - private static final byte INS_PROVISION_ATTESTATION_CERT_DATA_CMD = - INS_KEYMINT_PROVIDER_APDU_START + 2; //0x02 - private static final byte INS_PROVISION_ATTEST_IDS_CMD = - INS_KEYMINT_PROVIDER_APDU_START + 3; //0x03 - private static final byte INS_PROVISION_PRESHARED_SECRET_CMD = - INS_KEYMINT_PROVIDER_APDU_START + 4; //0x04 - private static final byte INS_SET_BOOT_PARAMS_CMD = INS_KEYMINT_PROVIDER_APDU_START + 5; //0x05 - private static final byte INS_LOCK_PROVISIONING_CMD = INS_KEYMINT_PROVIDER_APDU_START + 6; //0x06 - private static final byte INS_GET_PROVISION_STATUS_CMD = - INS_KEYMINT_PROVIDER_APDU_START + 7; //0x07 - private static final byte INS_SET_VERSION_PATCHLEVEL_CMD = - INS_KEYMINT_PROVIDER_APDU_START + 8; //0x08 - private static final byte INS_SET_BOOT_ENDED_CMD = INS_KEYMINT_PROVIDER_APDU_START + 9; //0x09 - - // private static final byte INS_PROVISION_ATTEST_IDS_CMD = INS_KEYMINT_PROVIDER_APDU_START + 1; - // private static final byte INS_PROVISION_PRESHARED_SECRET_CMD = - // INS_KEYMINT_PROVIDER_APDU_START + 2; - // private static final byte INS_LOCK_PROVISIONING_CMD = INS_KEYMINT_PROVIDER_APDU_START + 3; - // private static final byte INS_GET_PROVISION_STATUS_CMD = INS_KEYMINT_PROVIDER_APDU_START + 4; - // private static final byte INS_SET_BOOT_PARAMS_CMD = INS_KEYMINT_PROVIDER_APDU_START + 5; - private static final byte INS_PROVISION_DEVICE_UNIQUE_KEY_CMD = - INS_KEYMINT_PROVIDER_APDU_START + 10; - private static final byte INS_PROVISION_ADDITIONAL_CERT_CHAIN_CMD = - INS_KEYMINT_PROVIDER_APDU_START + 11; - private static final byte INS_KEYMINT_PROVIDER_APDU_END = 0x1F; - - //Provision reporting status - private static final byte NOT_PROVISIONED = 0x00; - private static final byte PROVISION_STATUS_ATTESTATION_KEY = 0x01; - private static final byte PROVISION_STATUS_ATTESTATION_CERT_CHAIN = 0x02; - private static final byte PROVISION_STATUS_ATTESTATION_CERT_PARAMS = 0x04; - private static final byte PROVISION_STATUS_ATTEST_IDS = 0x08; - private static final byte PROVISION_STATUS_PRESHARED_SECRET = 0x10; - private static final byte PROVISION_STATUS_PROVISIONING_LOCKED = 0x20; - private static final byte PROVISION_STATUS_DEVICE_UNIQUE_KEY = 0x40; - private static final byte PROVISION_STATUS_ADDITIONAL_CERT_CHAIN = (byte) 0x80; - private static final short POWER_RESET_MASK_FLAG = (short) 0x4000; - public static final short SHARED_SECRET_KEY_SIZE = 32; - public static final byte BOOT_KEY_MAX_SIZE = 32; - public static final byte BOOT_HASH_MAX_SIZE = 32; - - private static byte provisionStatus = NOT_PROVISIONED; - private static byte kmSpecification; - - KMJCardSimApplet() { - super(new KMJCardSimulator(), kmSpecification); - } - - /** - * 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) { - kmSpecification = bArray[(short) (bOffset + 1)]; - new KMJCardSimApplet().register(); - } - - @Override - public void process(APDU apdu) { - try { - // If this is select applet apdu which is selecting this applet then return - if (apdu.isISOInterindustryCLA()) { - if (selectingApplet()) { - return; - } - } - short apduIns = validateApdu(apdu); - if (((KMJCardSimulator) seProvider).isPowerReset()) { - super.powerReset(); - } - if (((KMJCardSimulator) seProvider).isProvisionLocked()) { - switch (apduIns) { - case INS_SET_BOOT_PARAMS_CMD: - processSetBootParamsCmd(apdu); - break; - default: - super.process(apdu); - break; - } - return; - } - if (apduIns == KMType.INVALID_VALUE) { - return; - } - switch (apduIns) { - case INS_PROVISION_ATTESTATION_KEY_CMD: - processProvisionAttestationKey(apdu); - provisionStatus |= PROVISION_STATUS_ATTESTATION_KEY; - sendError(apdu, KMError.OK); - break; - case INS_PROVISION_ATTESTATION_CERT_DATA_CMD: - processProvisionAttestationCertDataCmd(apdu); - provisionStatus |= (PROVISION_STATUS_ATTESTATION_CERT_CHAIN | - PROVISION_STATUS_ATTESTATION_CERT_PARAMS); - sendError(apdu, KMError.OK); - break; - case INS_PROVISION_ATTEST_IDS_CMD: - processProvisionAttestIdsCmd(apdu); - provisionStatus |= PROVISION_STATUS_ATTEST_IDS; - sendError(apdu, KMError.OK); - break; - case INS_PROVISION_PRESHARED_SECRET_CMD: - processProvisionPreSharedSecretCmd(apdu); - provisionStatus |= PROVISION_STATUS_PRESHARED_SECRET; - sendError(apdu, KMError.OK); - break; - case INS_GET_PROVISION_STATUS_CMD: - processGetProvisionStatusCmd(apdu); - break; - case INS_LOCK_PROVISIONING_CMD: - processLockProvisioningCmd(apdu); - break; - case INS_SET_BOOT_PARAMS_CMD: - processSetBootParamsCmd(apdu); - break; - case INS_PROVISION_DEVICE_UNIQUE_KEY_CMD: - processProvisionDeviceUniqueKey(apdu); - provisionStatus |= PROVISION_STATUS_DEVICE_UNIQUE_KEY; - break; - case INS_PROVISION_ADDITIONAL_CERT_CHAIN_CMD: - processProvisionAdditionalCertChain(apdu); - provisionStatus |= PROVISION_STATUS_ADDITIONAL_CERT_CHAIN; - break; - default: - super.process(apdu); - break; - } - } finally { - repository.clean(); - } - } - - private void processProvisionAttestationCertDataCmd(APDU apdu) { - // TODO Handle this function properly - processAttestationCertDataCmd(apdu); - } - - private void processProvisionAttestationKey(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); - // Re-purpose the apdu buffer as scratch pad. - byte[] scratchPad = apdu.getBuffer(); - - // key params should have os patch, os version and verified root of trust - short keyParams = KMArray.cast(args).get((short) 0); - keyFormatPtr = KMArray.cast(args).get((short) 1); - short rawBlob = KMArray.cast(args).get((short) 2); - // Key format must be RAW format - short keyFormat = KMEnum.cast(keyFormatPtr).getVal(); - if (keyFormat != KMType.RAW) { - KMException.throwIt(KMError.UNIMPLEMENTED); - } - //byte origin = KMType.IMPORTED; - - // get algorithm - only EC keys expected - KMTag.assertPresence(keyParams, KMType.ENUM_TAG, KMType.ALGORITHM, KMError.INVALID_ARGUMENT); - short alg = KMEnumTag.getValue(KMType.ALGORITHM, keyParams); - if (alg != KMType.EC) { - KMException.throwIt(KMError.INVALID_ARGUMENT); - } - // get digest - only SHA256 supported - KMTag.assertPresence(keyParams, KMType.ENUM_ARRAY_TAG, KMType.DIGEST, KMError.INVALID_ARGUMENT); - short len = KMEnumArrayTag.getValues(KMType.DIGEST, keyParams, scratchPad, (short) 0); - if (len != 1) { - KMException.throwIt(KMError.INVALID_ARGUMENT); - } - if (scratchPad[0] != KMType.SHA2_256) { - KMException.throwIt(KMError.INCOMPATIBLE_DIGEST); - } - // Purpose should be ATTEST_KEY - KMTag.assertPresence(keyParams, KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, - KMError.INVALID_ARGUMENT); - len = KMEnumArrayTag.getValues(KMType.PURPOSE, keyParams, scratchPad, (short) 0); - if (len != 1) { - KMException.throwIt(KMError.INVALID_ARGUMENT); - } - if (scratchPad[0] != KMType.ATTEST_KEY) { - KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); - } - // validate Curve - KMTag.assertPresence(keyParams, KMType.ENUM_TAG, KMType.ECCURVE, KMError.INVALID_ARGUMENT); - short curve = KMEnumTag.getValue(KMType.ECCURVE, keyParams); - if (curve != KMType.P_256) { - KMException.throwIt(KMError.UNSUPPORTED_EC_CURVE); - } - // Decode EC Key - short arrPtr = decodeRawECKey(rawBlob); - short secret = KMArray.cast(arrPtr).get((short) 0); - short pubKey = KMArray.cast(arrPtr).get((short) 1); - // Check whether key can be created - seProvider.importAsymmetricKey( - KMType.EC, - KMByteBlob.cast(secret).getBuffer(), - KMByteBlob.cast(secret).getStartOff(), - KMByteBlob.cast(secret).length(), - KMByteBlob.cast(pubKey).getBuffer(), - KMByteBlob.cast(pubKey).getStartOff(), - KMByteBlob.cast(pubKey).length()); - - // persist key - seProvider.createAttestationKey( - KMByteBlob.cast(secret).getBuffer(), - KMByteBlob.cast(secret).getStartOff(), - KMByteBlob.cast(secret).length()); - } - - 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(); - ((KMJCardSimulator) seProvider).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. - ((KMJCardSimulator) seProvider).createPresharedKey( - KMByteBlob.cast(val).getBuffer(), - KMByteBlob.cast(val).getStartOff(), - KMByteBlob.cast(val).length()); - } - - private void processGetProvisionStatusCmd(APDU apdu) { - short resp = KMArray.instance((short) 2); - KMArray.cast(resp).add((short) 0, buildErrorStatus(KMError.OK)); - KMArray.cast(resp).add((short) 1, KMInteger.uint_16(provisionStatus)); - sendOutgoing(apdu, resp); - - } - - private void processSetBootParamsCmd(APDU apdu) { - short argsProto = KMArray.instance((short) 5); - // Array of 4 expected arguments - // Argument 0 Boot Patch level - KMArray.cast(argsProto).add((short) 0, KMInteger.exp()); - // Argument 1 Verified Boot Key - KMArray.cast(argsProto).add((short) 1, KMByteBlob.exp()); - // Argument 2 Verified Boot Hash - KMArray.cast(argsProto).add((short) 2, KMByteBlob.exp()); - // Argument 3 Verified Boot State - KMArray.cast(argsProto).add((short) 3, KMEnum.instance(KMType.VERIFIED_BOOT_STATE)); - // Argument 4 Device Locked - KMArray.cast(argsProto).add((short) 4, KMEnum.instance(KMType.DEVICE_LOCKED)); - - short args = receiveIncoming(apdu, argsProto); - short bootParam = KMArray.cast(args).get((short) 0); - - ((KMJCardSimulator) seProvider).setBootPatchLevel(KMInteger.cast(bootParam).getBuffer(), - KMInteger.cast(bootParam).getStartOff(), - KMInteger.cast(bootParam).length()); - - bootParam = KMArray.cast(args).get((short) 1); - if (KMByteBlob.cast(bootParam).length() > BOOT_KEY_MAX_SIZE) { - KMException.throwIt(KMError.INVALID_ARGUMENT); - } - ((KMJCardSimulator) seProvider).setBootKey(KMByteBlob.cast(bootParam).getBuffer(), - KMByteBlob.cast(bootParam).getStartOff(), - KMByteBlob.cast(bootParam).length()); - - bootParam = KMArray.cast(args).get((short) 2); - if (KMByteBlob.cast(bootParam).length() > BOOT_HASH_MAX_SIZE) { - KMException.throwIt(KMError.INVALID_ARGUMENT); - } - ((KMJCardSimulator) seProvider).setVerifiedBootHash(KMByteBlob.cast(bootParam).getBuffer(), - KMByteBlob.cast(bootParam).getStartOff(), - KMByteBlob.cast(bootParam).length()); - - bootParam = KMArray.cast(args).get((short) 3); - byte enumVal = KMEnum.cast(bootParam).getVal(); - ((KMJCardSimulator) seProvider).setBootState(enumVal); - - bootParam = KMArray.cast(args).get((short) 4); - enumVal = KMEnum.cast(bootParam).getVal(); - ((KMJCardSimulator) seProvider).setDeviceLocked(enumVal == KMType.DEVICE_LOCKED_TRUE); - - super.reboot(); - sendError(apdu, KMError.OK); - } - - private void processLockProvisioningCmd(APDU apdu) { - ((KMJCardSimulator) seProvider).setProvisionLocked(true); - sendError(apdu, KMError.OK); - } - - private short validateApdu(APDU apdu) { - // Read the apdu header and buffer. - byte[] apduBuffer = apdu.getBuffer(); - byte apduClass = apduBuffer[ISO7816.OFFSET_CLA]; - - // Validate APDU Header. - if ((apduClass != CLA_ISO7816_NO_SM_NO_CHAN)) { - sendError(apdu, KMError.UNSUPPORTED_CLA); - return KMType.INVALID_VALUE; - } - - short err = specification.validateApduHeader(apdu); - if (err != KMError.OK) { - sendError(apdu, err); - return KMType.INVALID_VALUE; - } - return apduBuffer[ISO7816.OFFSET_INS]; - } - - private void setDummyBootParams() { - short osVersion = KMInteger.uint_16(((short) 0)); - short osPatchLevel = KMInteger.uint_16((short) 0); - short vendorPatchLevel = KMInteger.uint_16((short) 0); - short bootPatchLevel = KMInteger.uint_16((short) 0); - - super.setOsVersion(osVersion); - super.setOsPatchLevel(osPatchLevel); - super.setVendorPatchLevel(vendorPatchLevel); - - byte[] bootBlob = new byte[32]; - short bootKey = KMByteBlob.instance(bootBlob, (short) 0, - (short) bootBlob.length); - short verifiedHash = KMByteBlob.instance(bootBlob, (short) 0, - (short) bootBlob.length); - short bootState = KMType.UNVERIFIED_BOOT; - - ((KMJCardSimulator) seProvider).setBootPatchLevel( - KMInteger.cast(bootPatchLevel).getBuffer(), - KMInteger.cast(bootPatchLevel).getStartOff(), - KMInteger.cast(bootPatchLevel).length()); - - ((KMJCardSimulator) seProvider).setBootKey( - KMByteBlob.cast(bootKey).getBuffer(), - KMByteBlob.cast(bootKey).getStartOff(), - KMByteBlob.cast(bootKey).length()); - - ((KMJCardSimulator) seProvider).setVerifiedBootHash( - KMByteBlob.cast(verifiedHash).getBuffer(), - KMByteBlob.cast(verifiedHash).getStartOff(), - KMByteBlob.cast(verifiedHash).length()); - - ((KMJCardSimulator) seProvider).setBootState((byte) bootState); - ((KMJCardSimulator) seProvider).setDeviceLocked(true); - super.reboot(); - } - - private void setDummyPresharedKey() { - final byte[] presharedKey = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - ((KMJCardSimulator) seProvider) - .createPresharedKey(presharedKey, (short) 0, (short) presharedKey.length); - } - - private void setDummyAttestationIds() { - final byte[] brand = {'g', 'e', 'n', 'e', 'r', 'i', 'c'}; - final byte[] device = {'v', 's', 'o', 'c', '_', 'x', '8', '6', '_', '6', '4'};//vsoc_x86_64 - final byte[] product = //aosp_cf_x86_64_phone - {'a', 'o', 's', 'p', '_', 'c', 'f', '_', 'x', '8', '6', '_', '6', '4', '_', 'p', 'h', 'o', - 'n', 'e'}; - final byte[] serial = {}; - final byte[] imei = {'0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0'}; - final byte[] meid = {'0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0'}; - final byte[] manufacturer = {'G', 'o', 'o', 'g', 'l', 'e'}; - final byte[] model = //"Cuttlefish x86_64 phone" - {'C', 'u', 't', 't', 'l', 'e', 'f', 'i', 's', 'h', ' ', 'x', '8', '6', '_', '6', '4', - ' ', 'p', 'h', 'o', 'n', 'e'}; - ((KMJCardSimulator) seProvider) - .setAttestationId(KMType.ATTESTATION_ID_BRAND, brand, (short) 0, (short) brand.length); - ((KMJCardSimulator) seProvider) - .setAttestationId(KMType.ATTESTATION_ID_IMEI, imei, (short) 0, (short) imei.length); - ((KMJCardSimulator) seProvider) - .setAttestationId(KMType.ATTESTATION_ID_DEVICE, device, (short) 0, (short) device.length); - ((KMJCardSimulator) seProvider) - .setAttestationId(KMType.ATTESTATION_ID_MEID, meid, (short) 0, (short) meid.length); - ((KMJCardSimulator) seProvider) - .setAttestationId(KMType.ATTESTATION_ID_MODEL, model, (short) 0, (short) model.length); - ((KMJCardSimulator) seProvider) - .setAttestationId(KMType.ATTESTATION_ID_MANUFACTURER, manufacturer, (short) 0, - (short) manufacturer.length); - ((KMJCardSimulator) seProvider) - .setAttestationId(KMType.ATTESTATION_ID_PRODUCT, product, (short) 0, - (short) product.length); - ((KMJCardSimulator) seProvider) - .setAttestationId(KMType.ATTESTATION_ID_SERIAL, serial, (short) 0, (short) serial.length); - } - - private static void processProvisionDeviceUniqueKey(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. - seProvider.createDeviceUniqueKey(false, scratchPad, (short) 0, pubKeyLen, scratchPad, - pubKeyLen, privKeyLen); - // Newly added code 30/07/2021 - short bcc = generateBcc(false, scratchPad); - short len = KMKeymasterApplet.encodeToApduBuffer(bcc, scratchPad, (short) 0, - MAX_COSE_BUF_SIZE); - ((KMJCardSimulator) seProvider).persistBootCertificateChain(scratchPad, (short) 0, len); - sendError(apdu, KMError.OK); - } - - private static void processProvisionAdditionalCertChain(APDU apdu) { - // Prepare the expression to decode - 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 coseSignArr = KMArray.exp(arrInst); - short map = KMMap.instance((short) 1); - KMMap.cast(map).add((short) 0, KMTextString.exp(), coseSignArr); - // TODO duplicate code. - // receive incoming data and decode it. - 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); - } - // decode - map = decoder.decode(map, buffer, bufferStartOffset, bufferLength); - arrInst = KMMap.cast(map).getKeyValue((short) 0); - // Validate Additional certificate chain. - short leafCoseKey = - validateCertChain(false, KMCose.COSE_ALG_ES256, KMCose.COSE_ALG_ES256, arrInst, - srcBuffer, null); - // Compare the DK_Pub. - short pubKeyLen = KMCoseKey.cast(leafCoseKey).getEcdsa256PublicKey(srcBuffer, (short) 0); - KMDeviceUniqueKey uniqueKey = seProvider.getDeviceUniqueKey(false); - if (uniqueKey == null) { - KMException.throwIt(KMError.STATUS_FAILED); - } - short uniqueKeyLen = uniqueKey.getPublicKey(srcBuffer, pubKeyLen); - if ((pubKeyLen != uniqueKeyLen) || - (0 != Util.arrayCompare(srcBuffer, (short) 0, srcBuffer, pubKeyLen, pubKeyLen))) { - KMException.throwIt(KMError.STATUS_FAILED); - } - seProvider.persistAdditionalCertChain(buffer, bufferStartOffset, bufferLength); - //reclaim memory - repository.reclaimMemory(bufferLength); - sendError(apdu, KMError.OK); - } - - private static short buildErrorStatus(short err) { - short int32Ptr = KMInteger.instance((short) 4); - short powerResetStatus = 0; - if (((KMJCardSimulator) 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; - } - -} diff --git a/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMUtils.java b/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMUtils.java deleted file mode 100644 index 3e9002e2..00000000 --- a/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMUtils.java +++ /dev/null @@ -1,445 +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.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[] firstJan2051 = { - 0, 0, 0x02, 0x53, 0x26, (byte) 0x0E, (byte) 0x1C, 0x00}; // 2556144000000 - // 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, firstJan2051, - (short) 0, (short) 8) >= 0) { - KMException.throwIt(KMError.INVALID_ARGUMENT); - } - - if (KMInteger.unsignedByteArrayCompare(scratchPad, (short) 0, firstJan2051, (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(firstJan2051, (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; - while (index >= 0) { - tmp = (short) (buf[(short) (op1 + index)] + buf[(short) (op2 + index)] + 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(oneMonthMsec, (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/Applet/JCardSimProviderLib/lib/gpapi-upgrade.jar b/Applet/JCardSimProviderLib/lib/gpapi-upgrade.jar deleted file mode 100644 index e4814bde4b6b21982103c55f9b1a3a4a58f0b807..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12638 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 I@K+`Of52({3;+NC diff --git a/Applet/JCardSimProviderLib/lib/hamcrest-core-1.3.jar b/Applet/JCardSimProviderLib/lib/hamcrest-core-1.3.jar deleted file mode 100644 index 9d5fe16e3dd37ebe79a36f61f5d0e1a69a653a8a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 45024 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 UgpPtD2z>DYYp}SJr5?)v0beu;=l}o! diff --git a/Applet/JCardSimProviderLib/lib/jcardsim-3.0.5-SNAPSHOT.jar b/Applet/JCardSimProviderLib/lib/jcardsim-3.0.5-SNAPSHOT.jar deleted file mode 100644 index d756d67b5252cfb5e2d688e340e5d200f15e372a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 492079 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�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%0DWi^Ss!AyH_)8PVX^!IHYj7 ze>O}=>u}vCh}O=Kul?flo!f8L+kqn|9#X_Mx_^GewVOk(A2sxlC^zCPOc}=x^v}-YK16;(R-2I7t*Wq zTX9DXLMepQ)=U&F+VR~ZAIs$6j5nOn!*<&7Y+QxIHx$%YL-$EojV5E z(Ajne_=&>2x~b#T=EU{n5{OhhXnT6TU416KwqmmIQPDGg1Ig94$(ZGCV+7mI1NL1UXj!mOYv^sn%e!4Q z%XPvnu0E8g*L*6;XFt3c+fVT?ONiz$F7W}d8v@lt-$ZcSqvO^v4%GqCrLY~p$=P~v zAlthpj-9e4a)^zq?jQQTJ+2JRa{yhAP!nC(TmpDaj@UOt7=NBlM*-Ae>sn*xFGB(+(g)UN-ewD(%w+Vw-B7k9=3u_?3+0y+i7&K4?4ngfHp(nDJv7@pH<2 znEd_Uh6P;+4#So2N@DE4Ruccf;Op#UZuO50zW-p{`oGtf(>JvIj}y&1ZrbL1PSa4g zbpj{we6^&x^hF_m`N9IfLy=gf87%)dax_420SXP9EP}O_)j~T->@Y`(xY`?z-Hvdt z=hwAd(y9>*tJ;8qiwM)*#=jbH;vu?ZPpzi>wQHH z8&d7d>5UB@5Ma*^ud@=_rZ1HhZJKqdlS)Y#AlE_U3JbLsm-dpUk{j&v@{CM&}NrhSx;^JarA+c6-~3n$i0i2r=VZU zs*DjCPdHHj2A4$GJno#)9r|^^n%zWD;~jDG9(wUHBU%U30^rLG#()l3RDokhp@?yQg)3AWVOmbnG!C(8kn;WCczw70 zw9(aX=Xu0`b)J*_Zzx;V`c7v5=tPqL?=EU=_>b1*{0Hj_3x3avwsrV!g0=pizKAL% zam8;?%BKb+L=bsE6y|a(T)&={yy=FXP_%&mp+ZrMR547nI{k_K&CV#F|L2by4-D!m zhrh1{3#s(ghrM+Z1U2p3x1EP)pZ715Tt7nXf_ML*g}UH#(BwLk*^%XvL|=n|T{=bKn?`)Mlpg9i9+W7l=eA5U_Q zbzw%T;+J=Yk2!r&PB^%&rD=n6_gBoN(Hji42pRPlRwgSSf>*#lpEmUfZ!`&F4;F}G=_C+DF`MbFavC;D z&9xi)R4A)JTlPjIHsdsPLbf8UyZyCO!^7U!PJ3JdD-l`H-h`c)Y(NBf$40qAF8VF7 zD4wP7cFDk!&@v~!(S1F<=hNv9em^!%67yDhIzK^NfDfTvk}mVW(E`uzY{(PtMF=}Q zL5Jvb4Pjsz(NAO{Lw5O+t9I#~s|gO6W4$E`%(Y!F*u$RgYn;5L{KDJFekZs^~TYtPMdzl_&l5{!tU4pAz-ldjwGmNSeoW`t?DA5#X zD;P)lSS#tIh*BBW=y#w0#(ssBCkUfvq6&OgeAeZDuqwrwCR0+R8x8Usf-rq&k%^@; zj*$Q6UAY(DDL0jpPuf?N@(iOWS{LXji$-c-rKZ%GRyLs1FH(PlPrUuJiZw2&L%UDj zq*>s~c^A7np6d)qo`nCjZZ5*BS zZJhq+)3TQRhF;L4`_wnNRLPetiKC$K!Dgg_2cQiG7UUD64k=xy7~8uz(NwPXCJ0Gy>A{jlGB?aDUi|s0kN4dV69VJ`bv4DT{`)EbeDsl!vp+b z)n_U7{85@}VXQ3%R}(lHDt^GclGa(rmK)*u&V*f!&~AtuV-%2#U%Jh5)Gb^WP#$Vr zsj#AEC*Q{ztXKF(Ae+_A=TeCjV=HPFpe=;IMk5Qvek6}~G zDC`=M&Ho`t9bmz|a4PjAd%E(`w?^hQ{d;n_m_U=a8D{mN&UB<#v~Ha5Ut1z)5f?-K z9h*D9fB)Zp_J6n~&JHfd#3GFU(*-`M`sS->it*JoIa+k$&oAoto2#Kp%p-x?n#dUH zmv(!-@l4+jI+~1qW)7Orqd5|?4d$Ao({kD%;k@tpOJrTHF;SO;+z>8Z;&n9<;n# zQzQu)|GG9W^a2TA4FzE-r^1@IW`57@c;DMb@_b(^x62uG(u-B6J-< zw0o#xUA{|MZ7GxAy?L2OrH@cfbg|n|>c8WB;t|cz&kkhKd=Z_J(A3)B!Z`CZCU{=I z)daHwjqlfOIH>3Q_pt2cXmNj<_EZEW#Q8X?#7bO^Bqx7ouvCTPzd0xu@Bru%g{E+e zoMvCC9f-ZM*a}`4Mh&}AJeX&{o_*&v)f&3+HgA1e)s^~N8$RO3QMFh-qSZHk46NFC zRecO9d#oB{jEYKSr6CJT=h!F{%SD2Hm&#q`)F{8E*SH?-;-kuRyy%j8b!(+bUO8+U zNB;*LY0Lc(oe|Gjnp%fSrX;G=sF_;SHH6daT^;}5Zci|mA0qP8v1<&{-|m;BcW1+J z_v)B9UPR0@NbBvX^K}rLf=QTo!z^B}KX6D;aP3}heo3*&P=B%f5mvBt2o0Q(@A}3Y zrA6p)4+H=O`6?in+FiM!*Yv|FHbv+ub>nLnsO&h^g2k(I%{0qlhqvmZSl9i5Jd8^Q zI2oU%e%$34q`N*Je@vE$-Irf+AFLo=OYx+0F>NH!SQcj{E(z?+!Z#LqamvYxia{yc zQ$N21d5nV1!VJCR1d?Xwf}@b~X?6p7#&10Pqt9AvGTH7kn|BcT_?ZZjbL1L4NSUXg z320uUqgF?jQfqOHp?MJZyI5Tai4h$N*03X-wtEQ%6v>TDv^EI5{qKsuF?Avj`6KAwSO;-+^=z`F)Cx}-+uB-EqblwO~K}7WR$#ky7)V%DCy>OwvaPuMpHmQlVLFlRBP@{g@Y8VmW79+_k)+ zrsX#p$;K_38_gb9jOTcX_qm>FO8}lmm)ZWj7<{#ACBPd>>mSDRFdj`p+cn}~x2Iuf ze8ur{u8Iu05*Iv-K4EAdZa?u~pYeN|uvhc(Arhj+j7Bol`$r`>OmdcP|IkJO#R@HoDu(6g8U82l3vYiBjvX zYBe}FBpdjLoUwQM`QkmQjV+cvPJU>w3dpE7s90Z|HtMIsAfev)f#8z%)B`X}Zr!U^ zGL$7rs)D#iy~96^_uXY{#5Gv1HRpmVtXHfAJ%rQg^#nag|HSDOlNP#C^g6>I=u&xq z$+W`|uS(k$)z=LkMu3nIm>_t7ybn9Mt~t>N9h0<<2cQ+`X8L5st9!k7Ox{93uTc}+ zW;3-9EmR7Pz>-MhEU9xJ$??U=YAobO*IyB|>fDQJg*T9X^3{H_ui1NkeS=NcJVORY zj8B-sc4qYMuBqo#lPE>rrgyA4r?qZjgbM#qxx5fM5U=tCqDV}zM3RN?WFjV3E{c;n zo6@}`%)O7{#0Ml$k*8$>7Yg#XPyvprP86!-Ci0lYv>N{8{-KuMX$BvO8WwvYZT5l{ z1v6z>eLtb(6?LiBa7NHAG)B?YkIk1E07NSi8NOlPtKP6+b*A190>B`$T0?Mvo3;8? zfMS(y&MVxyBCcfjA74J0FG^}-9zUQSbxxp(QUQ8*&Xf)!CtkrpA@*dJ~B4pYbajy4W>HAkqHxBdnbZ|QSSllBP6K67FD2r;}m zeY~adSW+IeE!l~0>-=du9%%;UVSynQPc*NVO*UkOmXM%M!%!X38Pl@xY;C(uX!`+N zvw-@@4RIDrZ8wL*LkF5qeGj=fzF6p4Mt+0Ab?=!FcP|7+C|<4p1TPkplM3d*vH@f%D<p~wkneIJ^&i6bmyWXw_>Uh#DF3avVgG0K=6{MCyZ`7GlggWl8%oGu+N^O_VpPJVR!tEp zrwN-bmfh4dDj<2)0UvXe3$Y>R#t%K%`sExjv}QjW+-GFk=1iGtWJb18 zRU0`qKVc}W6e-sBlh^`xXNGXBhZBJw6%Wi5`sa6-nV{L`7`#A}a8}5}qSu07uHafZfKW8Tl>FgRcJ`_m2%XO8*wZu?9nwE#cQ>7?trBn*Mt5=yHPDce|LGht zRZ8vuV7^V_t~8XYgxxzH6z`U#dm{BVcY|Po-bb}Z49C>CiDtcZ=Po|PK$x@JHq@bB zEeHJJBQ#8A+Ij+aund0)GQq8*ctfdEvXwfe_y}}h7|>xGHNo(KDhYA2oABMU(1kVm)Pt~wsnOx5G=T2C{#$w z*i+FyaT^2$Je9FMA9{vL_j3UHV$v(mzbE50hD9lGZgb`J3h^5yCX~7@if^+>X{>kJ z)aWW!=7pK>4ieJxjx_h4E+m=px^w2ox794Gi_~rwiGEMkC zdVi3AWjO1|_S!|2J%Mu);^%18@iE8=>bq=-j-hGbeIVZEmZSpJP4S^1A|W20})tPA2nz$&yf{chqJ|yGsgYoEwQ}pNngL_z7b-{XMuF+3spt>sJ zT>4Veo91Mg1}o6$mGB&wYtLTb8*6f6awN;_9e6Y_Ie}+}i6mD4GmTf1*h9ggfj@wb zE?<$D9gFzIfhZKFPg_0D2<|W1Y2*n<+0u@5*)tnYj zE3NQ?K~=nEDj6B(VfuY>Uhu4MGJH@B5~a-3;Dr!ep}gFYilLoiEfM`KNN#X$ESvUqiR;#HG@Xyxh2o+Ta0mBImob=VZBv5hHQto)^?Pm zGcud@_ot`G5mVm0}m3eHd@dSNpv@90?SGAa%AH7Kx-buv7+S;tahn{f{ zRp-SZ;&yHyp!3^frGE5*>amV)r}nr|xH`-mwdao5;%pZPB_s>?dj*RS$V8DLN4 zl}rjdVH5DmNy8X|53v6d8C1_&+WrNip8c-^@b7=L?IPyJRz|{B#@5C*PXCYAkfmbj zh-Hf6qvA@1HEdtdSWlFXiFA;xMF#6!s~kEs1d(5#NlLO4B)L?vJhxVn+|bS#4n+f# z+PFb?h$xFO0WH{d@M}WPJ%ql|>yYmH*E3oVho^i}u7JVhdA95H1IIDXvG>E1@5jqG zm(D=5FKWq33cZaqDx5{Ssl0jXkD7V2)J011S_}axX1QVx{Mta@ySp~}B8VV!&`DHw zj@1Bx_@A{LZ)|-CeFuRnsALWZ1q!LS5&h?;NXHK7+1vaU;zb`xE44mS$;H_W(vw7E zv+tRqq%Y3tYHTD@DLKfpqfS%mDIOJKAu&NKl5J&NrX)rVB(KTK3(|}8S@V{KR2F@d zG1>%jf9t3i72H_E!Lt+Xfp2(&=!tg^=0jrk{{-b}U8#}KO^7Cp?6gMlbNB0Kr`pdz zJ!TiNC9J);M!dXT^mLn$e1)}&R1Xf`EC z;kZ(UL(GK0Y);sei%1%=EHYn0-F-Al*!kQv>3ipz|$A5`&D81`sRjclq)7>(f3)0hT z8zvXe3x?=sl*r^#uJ=OO0rVpaWr~st;q?{O)s`CZcKV~p{%#LnZeIr~#z`9VRO8urx6jz(I2#wI?&f1*=%0a!yc5KZ17o*q~jlq%?1 zBEtbBkt zu=~1Z^L_GFCNJoUuxim+C(VeD@m6R|tmROWeL%wu(im({MF zA-t*D6j4nk%f{Ujr$?Z>__Z>y@~KruN3H2**eK$>10xylYqMyZq(p7Qs+qpH5MhPj$&wA_-j)?9JRW+fA7@kTTs z`0Y!3E%}M%2kiai?A`GGd$~mlZZrI{ZM!7Yi*skbpSMVJp-6P&s^Ea=u*+9i*A>2~ zwdZUMcW&&u=jJTDN*kIAE>2xWG?`D|4Q^FC%BYzkF2ZWx4{>u4z>_&rNZ38t3&vi@ z{byb9^JAjsqsIPI!JsfXrrG|LC+8!79CvFF<8XzxIC+9LLV~hBa+NQ@7y7gk?v$M0 zAu=Bt4=QwYcRX7rjBI0>g6JH`_l04{ypL^;X=SSya-scq0}S~N^T2r2193=7Kl znq_J~pvHb=863`s6h6gn3=Q`&SgV8SN!uQeH&WH>26gvnL+a2Ld{dZF)@Z$EHea&< z4qjtozCr;XZ2$gTFDYfdF>{MQJw;dp)pbU8TwP=8bErkvz|$ZG(UDoNYit5vk`vk+ z+q4V=eYi07@R;xSP^`od(JpH>&u_Cd$6V9&Y(k!fU>+|Dv6pci-M~gBf_w&&?V*R? z)h-9BA8IJDwIe?v4#3}R3N$c|h0p0m59viUG-B*(i;Szc&lRv9bl8X5?EqQq(}<9t zXI_4K%&5r%Sd+iaf1PzHba(R+ki=~QMs!GWGe-jEzD13@x1Q9izLq8XFIk5i;@8+v z7P`A!cf-fU!2mRe3iu%~DCNns6fugkTFOd#L{a{);q5;WXbLy1T`Y+sB}irH5B!{a z46j4;-n9E$-QSS!W2Zu@6I<5=UJ1><2N~X;tixW{$Lq}}@2snb%(U6JuIINe%R=J! z+!8lV_^;ZMR`f961x0SGuoJVV!H{jm<-4&fgy@Z@BNogqy!+d<0IvLFcDG)fSqfMs zJ5lB1m+F@#LDhB__p0fw7LIuZfyZfq7?Z!Nb;9hk_~Ib2EOq^it7`?s@sWEv6#uHRwh^`ZRt^x z@0stFO=5a;^M?T-H&~zTDf=$dbILK>v47sz=L4t5-fMJ5vs7iSSboREqds>zqFG)% zR`pCpzdCn4W^^89lcL0(fMR-fIkut^rztzCzt!y-~

Lj>xRnB_wN- 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 HkNo+6GuF>m diff --git a/Applet/JCardSimProviderLib/lib/junit-4.13.jar b/Applet/JCardSimProviderLib/lib/junit-4.13.jar deleted file mode 100644 index acc3c4320b580776193b875cd6e131d460ce2b7a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 381765 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%0A#7=ht}u^WobG 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 z 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 0) { - //verify if padding is corrupted. - byte paddingByte = scratchPad[i + 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 (scratchPad[i + len - j] != paddingByte) { - KMException.throwIt(KMError.INVALID_ARGUMENT); - } - } - len = (short) (len - (short) paddingByte);// remove the padding bytes - } - } - return len; - } - return KMType.INVALID_VALUE; - } - - @Override - public short getCipherAlgorithm() { - return cipherAlg; - } - - @Override - public void setCipherAlgorithm(short alg) { - cipherAlg = alg; - } - - @Override - public short update(byte[] buffer, short startOff, short length, byte[] scratchPad, short i) { - if (cipherAlg == KMType.AES && (blockMode == KMType.GCM || blockMode == KMType.CTR)) { - try { - return (short) sunCipher.update(buffer, startOff, length, scratchPad, i); - } catch (ShortBufferException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } catch (IllegalStateException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } - } else { - return cipher.update(buffer, startOff, length, scratchPad, i); - } - return KMType.INVALID_VALUE; - } - - @Override - public void updateAAD(byte[] buffer, short startOff, short length) { - try { - sunCipher.updateAAD(buffer, startOff, length); - } catch (IllegalArgumentException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } catch (IllegalStateException e) { - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } catch (UnsupportedOperationException e) { - CryptoException.throwIt(CryptoException.ILLEGAL_USE); - } - } - - @Override - public short getPaddingAlgorithm() { - return padding; - } - - @Override - public void setPaddingAlgorithm(short alg) { - padding = alg; - } - - @Override - public void setBlockMode(short mode) { - blockMode = mode; - } - - @Override - public short getBlockMode() { - return blockMode; - } - - public short getMode() { - return mode; - } - - public void setMode(short mode) { - this.mode = mode; - } - - @Override - public short getCipherProvider() { - return KMCipher.SUN_JCE; - } - - @Override - public short getAesGcmOutputSize(short len, short macLength) { - if (sunCipher != null) { - return (short) sunCipher.getOutputSize(len); - } else { - if (mode == KMType.ENCRYPT) { - return (short) (len + macLength); - } else { - return (short) (len - macLength); - } - } - } - -} diff --git a/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMComputedHmacKey.java b/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMComputedHmacKey.java deleted file mode 100644 index 3b0aa405..00000000 --- a/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMComputedHmacKey.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.android.javacard.seprovider; - - -public interface KMComputedHmacKey { -} diff --git a/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMDeviceUniqueKey.java b/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMDeviceUniqueKey.java deleted file mode 100644 index 08e60a3f..00000000 --- a/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMDeviceUniqueKey.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 KMDeviceUniqueKey { - - short getPublicKey(byte[] buf, short offset); -} diff --git a/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMECDeviceUniqueKey.java b/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMECDeviceUniqueKey.java deleted file mode 100644 index 0f68de61..00000000 --- a/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMECDeviceUniqueKey.java +++ /dev/null @@ -1,53 +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.ECPrivateKey; -import javacard.security.ECPublicKey; -import javacard.security.KeyPair; - -public class KMECDeviceUniqueKey implements KMDeviceUniqueKey { - - private KeyPair ecKeyPair; - - @Override - public short getPublicKey(byte[] buf, short offset) { - ECPublicKey publicKey = getPublicKey(); - return publicKey.getW(buf, offset); - } - - public KMECDeviceUniqueKey(KeyPair ecPair) { - ecKeyPair = ecPair; - } - - public void setS(byte[] buffer, short offset, short length) { - ECPrivateKey ecPriv = (ECPrivateKey) ecKeyPair.getPrivate(); - ecPriv.setS(buffer, offset, length); - } - - public void setW(byte[] buffer, short offset, short length) { - ECPublicKey ecPublicKey = (ECPublicKey) ecKeyPair.getPublic(); - ecPublicKey.setW(buffer, offset, length); - } - - public ECPrivateKey getPrivateKey() { - return (ECPrivateKey) ecKeyPair.getPrivate(); - } - - public ECPublicKey getPublicKey() { - return (ECPublicKey) ecKeyPair.getPublic(); - } - -} diff --git a/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMECPrivateKey.java b/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMECPrivateKey.java deleted file mode 100644 index f45c55ba..00000000 --- a/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMECPrivateKey.java +++ /dev/null @@ -1,38 +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.ECPrivateKey; -import javacard.security.KeyPair; - -public class KMECPrivateKey implements KMAttestationKey { - - private KeyPair ecKeyPair; - - public KMECPrivateKey(KeyPair ecPair) { - ecKeyPair = ecPair; - } - - public void setS(byte[] buffer, short offset, short length) { - ECPrivateKey ecPriv = (ECPrivateKey) ecKeyPair.getPrivate(); - ecPriv.setS(buffer, offset, length); - } - - public ECPrivateKey getPrivateKey() { - return (ECPrivateKey) ecKeyPair.getPrivate(); - } - -} diff --git a/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMEcdsa256NoDigestSignature.java b/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMEcdsa256NoDigestSignature.java deleted file mode 100644 index e53a8963..00000000 --- a/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMEcdsa256NoDigestSignature.java +++ /dev/null @@ -1,191 +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 java.math.BigInteger; -import java.security.AlgorithmParameters; -import java.security.InvalidKeyException; -import java.security.KeyFactory; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.SignatureException; -import java.security.interfaces.ECPrivateKey; -import java.security.spec.ECGenParameterSpec; -import java.security.spec.ECParameterSpec; -import java.security.spec.ECPrivateKeySpec; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.InvalidParameterSpecException; -import java.security.interfaces.ECPrivateKey; -import java.security.interfaces.ECPublicKey; -import java.security.spec.ECGenParameterSpec; -import java.security.spec.ECParameterSpec; -import java.security.spec.ECPoint; -import java.security.spec.ECPrivateKeySpec; -import java.security.spec.ECPublicKeySpec; -import javacard.framework.Util; -import javacard.security.CryptoException; -import javacard.security.Key; -import javacard.security.Signature; - - -public class KMEcdsa256NoDigestSignature extends Signature { - - private java.security.Signature sunSigner; - - public KMEcdsa256NoDigestSignature(byte mode, byte[] key, short keyStart, short keyLength) { - KeyFactory kf; - try { - sunSigner = java.security.Signature.getInstance("NONEwithECDSA", "SunEC"); - kf = KeyFactory.getInstance("EC"); - AlgorithmParameters parameters = AlgorithmParameters.getInstance("EC", "SunEC"); - //Supported curve secp256r1 - parameters.init(new ECGenParameterSpec("secp256r1")); - ECParameterSpec ecParameters = parameters.getParameterSpec(ECParameterSpec.class); - if (mode == Signature.MODE_SIGN) { - byte[] privKey = new byte[keyLength]; - for (short i = 0; i < keyLength; i++) { - privKey[i] = key[keyStart + i]; - } - BigInteger bI = new BigInteger(privKey); - ECPrivateKeySpec prikeyspec = new ECPrivateKeySpec(bI, ecParameters); - ECPrivateKey privkey = (ECPrivateKey) kf.generatePrivate(prikeyspec); - sunSigner.initSign(privkey); - } else { - //Check if the first byte is 04 and remove it. - if (key[keyStart] == 0x04) { - //uncompressed format. - keyStart++; - keyLength--; - } - short i = 0; - byte[] pubx = new byte[keyLength / 2]; - for (; i < keyLength / 2; i++) { - pubx[i] = key[keyStart + i]; - } - byte[] puby = new byte[keyLength / 2]; - for (i = 0; i < keyLength / 2; i++) { - puby[i] = key[keyStart + keyLength / 2 + i]; - } - BigInteger bIX = new BigInteger(pubx); - BigInteger bIY = new BigInteger(puby); - ECPoint point = new ECPoint(bIX, bIY); - ECPublicKeySpec pubkeyspec = new ECPublicKeySpec(point, ecParameters); - ECPublicKey pubkey = (ECPublicKey) kf.generatePublic(pubkeyspec); - sunSigner.initVerify(pubkey); - } - } catch (NoSuchAlgorithmException e) { - CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); - } catch (NoSuchProviderException e) { - CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); - } catch (InvalidParameterSpecException e) { - CryptoException.throwIt(CryptoException.INVALID_INIT); - } catch (InvalidKeySpecException e) { - CryptoException.throwIt(CryptoException.INVALID_INIT); - } catch (InvalidKeyException e) { - CryptoException.throwIt(CryptoException.INVALID_INIT); - } - } - - @Override - public void init(Key key, byte b) throws CryptoException { - - } - - @Override - public void init(Key key, byte b, byte[] bytes, short i, short i1) throws CryptoException { - - } - - @Override - public void setInitialDigest(byte[] bytes, short i, short i1, byte[] bytes1, short i2, short i3) - throws CryptoException { - - } - - @Override - public byte getAlgorithm() { - return 0; - } - - @Override - public byte getMessageDigestAlgorithm() { - return 0; - } - - @Override - public byte getCipherAlgorithm() { - return 0; - } - - @Override - public byte getPaddingAlgorithm() { - return 0; - } - - @Override - public short getLength() throws CryptoException { - return 0; - } - - @Override - public void update(byte[] message, short msgStart, short messageLength) throws CryptoException { - byte[] msgBytes = new byte[messageLength]; - for (int i = 0; i < messageLength; i++) { - msgBytes[i] = message[msgStart + i]; - } - try { - if (messageLength > 0) { - sunSigner.update(msgBytes); - } - } catch (SignatureException e) { - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } - } - - @Override - public short sign(byte[] bytes, short i, short i1, byte[] bytes1, short i2) - throws CryptoException { - short len = 0; - try { - update(bytes, i, i1); - byte[] sig = sunSigner.sign(); - Util.arrayCopyNonAtomic(sig, (short) 0, bytes1, i2, (short) sig.length); - return (short) sig.length; - } catch (SignatureException e) { - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } - return len; - } - - @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 { - // Public key operations not handled here. - return false; - } - - @Override - public boolean verifyPreComputedHash(byte[] bytes, short i, short i1, byte[] bytes1, short i2, - short i3) throws CryptoException { - return false; - } -} diff --git a/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMError.java b/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMError.java deleted file mode 100644 index 5754abe9..00000000 --- a/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMError.java +++ /dev/null @@ -1,134 +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" 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 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 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 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 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 UNKNOWN_ERROR: - return UNKNOWN_ERROR; - case CRYPTO_NO_SUCH_ALGORITHM: - return UNSUPPORTED_ALGORITHM; - case UNSUPPORTED_INSTRUCTION: - case CMD_NOT_ALLOWED: - case SW_WRONG_LENGTH: - return UNIMPLEMENTED; - } - return err; - } -} diff --git a/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMException.java b/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMException.java deleted file mode 100644 index c0b2431f..00000000 --- a/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMException.java +++ /dev/null @@ -1,55 +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" 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; - } -/* - public static KMException instance() { - if (exception == null) { - exception = new KMException(); - } - return exception; - } -*/ -} - - diff --git a/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMHmacKey.java b/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMHmacKey.java deleted file mode 100644 index eaa54962..00000000 --- a/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMHmacKey.java +++ /dev/null @@ -1,43 +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.HMACKey; - -public class KMHmacKey implements KMPreSharedKey, KMComputedHmacKey { - - private HMACKey hmacKey; - - public KMHmacKey(HMACKey key) { - hmacKey = key; - } - - public void setKey(byte[] keyData, short kOff, short length) { - hmacKey.setKey(keyData, kOff, length); - } - - public byte getKey(byte[] keyData, short kOff) { - return hmacKey.getKey(keyData, kOff); - } - - public short getKeySizeBits() { - return hmacKey.getSize(); - } - - public HMACKey getKey() { - return hmacKey; - } -} diff --git a/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMJCardSimulator.java b/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMJCardSimulator.java deleted file mode 100644 index f01e4637..00000000 --- a/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMJCardSimulator.java +++ /dev/null @@ -1,1860 +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" 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 com.android.javacard.keymaster.KMConfigurations; -import java.math.BigInteger; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.KeyFactory; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.MGF1ParameterSpec; -import java.security.spec.RSAPrivateKeySpec; -import java.security.spec.RSAPublicKeySpec; -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.KeyBuilder; -import javacard.security.KeyPair; -import javacard.security.MessageDigest; -import javacard.security.RSAPrivateKey; -import javacard.security.KeyAgreement; -import javacard.security.RandomData; -import javacard.security.Signature; -import javacardx.crypto.AEADCipher; -import javacardx.crypto.Cipher; -import javax.crypto.AEADBadTagException; -import javax.crypto.BadPaddingException; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.ShortBufferException; -import javax.crypto.spec.GCMParameterSpec; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.OAEPParameterSpec; -import javax.crypto.spec.PSource; -import javax.crypto.spec.SecretKeySpec; -import org.globalplatform.upgrade.Element; - -/** - * Simulator only supports 512 bit RSA key pair, 128 AES Key, 128 bit 3Des key, less then 256 bit EC - * Key, and upto 512 bit HMAC key. Also simulator does not support TRNG, so this implementation just - * creates its own RNG using PRNG. - */ -public class KMJCardSimulator implements KMSEProvider { - public static final short AES_GCM_TAG_LENGTH = 16; - public static final short AES_GCM_NONCE_LENGTH = 12; - public static final short MAX_RND_NUM_SIZE = 64; - public static final short ENTROPY_POOL_SIZE = 16; // simulator does not support 256 bit aes keys - public static final byte[] aesICV = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - private static final short CERT_CHAIN_MAX_SIZE = 2500;//First 2 bytes for length. - private static final short ADDITIONAL_CERT_CHAIN_MAX_SIZE = 1024;//First 2 bytes for length. - private static final short BCC_MAX_SIZE = 512; - private static final short RSA_KEY_SIZE = 256; - public static final byte POWER_RESET_FALSE = (byte)0xAA; - public static final byte POWER_RESET_TRUE = (byte)0x00; - public static final byte AES_BLOCK_SIZE = 16; - private static final short CERT_CHAIN_OFFSET = 0; - private static final short CERT_ISSUER_OFFSET = KMConfigurations.CERT_CHAIN_MAX_SIZE; - private static final short CERT_EXPIRY_OFFSET = - (short) (CERT_ISSUER_OFFSET + KMConfigurations.CERT_ISSUER_MAX_SIZE); - private static final short COMPUTED_HMAC_KEY_SIZE = 32; - - public static byte[] resetFlag; - public static boolean jcardSim = false; - private static Signature kdf; - private static Signature hmacSignature; - private static KeyAgreement keyAgreement; - - private static byte[] rngCounter; - private static AESKey aesRngKey; - private static Cipher aesRngCipher; - private static byte[] entropyPool; - private static byte[] rndNum; - private byte[] additionalCertChain; - private byte[] bcc; - private byte[] provisionData; - private KMAESKey masterKey; - private KMECPrivateKey attestationKey; - private KMECDeviceUniqueKey testKey; - private KMECDeviceUniqueKey deviceUniqueKey; - private KMHmacKey preSharedKey; - private KMHmacKey computedHmacKey; - private boolean deviceReboot; - - // 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; - private boolean isProvisionLocked; - - - private static KMJCardSimulator jCardSimulator = null; - - public static KMJCardSimulator getInstance() { - return jCardSimulator; - } - - // Implements Oracle Simulator based restricted crypto provider - public KMJCardSimulator() { - // Various Keys - kdf = Signature.getInstance(Signature.ALG_AES_CMAC_128, false); - hmacSignature = Signature.getInstance(Signature.ALG_HMAC_SHA_256, false); - keyAgreement = KeyAgreement.getInstance(KeyAgreement.ALG_EC_SVDP_DH_PLAIN, false); - // RNG - rndNum = JCSystem.makeTransientByteArray(MAX_RND_NUM_SIZE, JCSystem.CLEAR_ON_RESET); - entropyPool = JCSystem.makeTransientByteArray(ENTROPY_POOL_SIZE, JCSystem.CLEAR_ON_RESET); - rngCounter = JCSystem.makeTransientByteArray((short) 8, JCSystem.CLEAR_ON_RESET); - initEntropyPool(entropyPool); - try { - aesRngCipher = Cipher.getInstance(Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, false); - } catch (CryptoException exp) { - ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED); - } - aesRngKey = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_128, false); - // various ciphers - //Allocate buffer for certificate chain. - //Allocate buffer for certificate chain and cert parameters. - // First 2 bytes is reserved for length for all the 3 buffers. - short totalLen = (short) (6 + KMConfigurations.CERT_CHAIN_MAX_SIZE + - KMConfigurations.CERT_ISSUER_MAX_SIZE + KMConfigurations.CERT_EXPIRY_MAX_SIZE); - provisionData = new byte[totalLen]; - additionalCertChain = new byte[ADDITIONAL_CERT_CHAIN_MAX_SIZE]; - bcc = new byte[BCC_MAX_SIZE]; - byte[] tmpArray = new byte[32]; - createAttestationKey(tmpArray, (short) 0, (short) 32); - // Pre-shared secret key length is 32 bytes. - createPresharedKey(tmpArray, (short) 0, (short) 32); - // Initialize the Computed Hmac Key object. - createComputedHmacKey(tmpArray, (short)0, (short) 32); - jCardSimulator = this; - resetFlag = new byte[1]; - resetFlag[0] = (byte) POWER_RESET_FALSE; - deviceReboot = false; - } - - - public KeyPair createRsaKeyPair() { - KeyPair rsaKeyPair = new KeyPair(KeyPair.ALG_RSA, KeyBuilder.LENGTH_RSA_2048); - rsaKeyPair.genKeyPair(); - return rsaKeyPair; - } - - - public RSAPrivateKey createRsaKey(byte[] modBuffer, short modOff, short modLength, - byte[] privBuffer, short privOff, short privLength) { - KeyPair rsaKeyPair = new KeyPair(KeyPair.ALG_RSA, KeyBuilder.LENGTH_RSA_2048); - RSAPrivateKey privKey = (RSAPrivateKey) rsaKeyPair.getPrivate(); - privKey.setExponent(privBuffer, privOff, privLength); - privKey.setModulus(modBuffer, modOff, modLength); - return privKey; - - } - - - public KeyPair createECKeyPair() { - KeyPair ecKeyPair = new KeyPair(KeyPair.ALG_EC_FP, KeyBuilder.LENGTH_EC_FP_256); - ecKeyPair.genKeyPair(); - return ecKeyPair; - } - - - public ECPrivateKey createEcKey(byte[] privBuffer, short privOff, short privLength) { - KeyPair ecKeyPair = new KeyPair(KeyPair.ALG_EC_FP, KeyBuilder.LENGTH_EC_FP_256); - ECPrivateKey privKey = (ECPrivateKey) ecKeyPair.getPrivate(); - privKey.setS(privBuffer, privOff, privLength); - return privKey; - } - - - public AESKey createAESKey(short keysize) { - byte[] rndNum = new byte[(short) (keysize / 8)]; - return createAESKey(rndNum, (short) 0, (short) rndNum.length); - } - - public AESKey createAESKey(byte[] buf, short startOff, short length) { - AESKey key = null; - short keysize = (short) (length * 8); - if (keysize == 128) { - key = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_128, false); - key.setKey(buf, (short) startOff); - } else if (keysize == 256) { - key = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_256, false); - key.setKey(buf, (short) startOff); - } - return key; - } - - - public DESKey createTDESKey() { - byte[] rndNum = new byte[24]; - newRandomNumber(rndNum, (short) 0, (short) rndNum.length); - return createTDESKey(rndNum, (short) 0, (short) rndNum.length); - } - - - public DESKey createTDESKey(byte[] secretBuffer, short secretOff, short secretLength) { - DESKey triDesKey = - (DESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_DES, KeyBuilder.LENGTH_DES3_3KEY, false); - triDesKey.setKey(secretBuffer, secretOff); - return triDesKey; - } - - - public HMACKey createHMACKey(short keysize) { - if ((keysize % 8 != 0) || !(keysize >= 64 && keysize <= 512)) { - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } - byte[] rndNum = new byte[(short) (keysize / 8)]; - newRandomNumber(rndNum, (short) 0, (short) (keysize / 8)); - return createHMACKey(rndNum, (short) 0, (short) rndNum.length); - } - - @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. - byte[] exp = new byte[RSA_KEY_SIZE]; - lengths[0] = privKey.getExponent(exp, (short) 0); - if (lengths[0] > privKeyLength) { - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } - Util.arrayFillNonAtomic(privKeyBuf, privKeyStart, privKeyLength, (byte) 0); - Util.arrayCopyNonAtomic(exp, (short) 0, - privKeyBuf, (short) (privKeyStart + privKeyLength - lengths[0]), lengths[0]); - //Copy modulus - byte[] mod = new byte[RSA_KEY_SIZE]; - lengths[1] = privKey.getModulus(mod, (short) 0); - if (lengths[1] > pubModLength) { - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } - Util.arrayFillNonAtomic(pubModBuf, pubModStart, pubModLength, (byte) 0); - Util.arrayCopyNonAtomic(mod, (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: - AESKey aesKey = createAESKey(buf, startOff, length); - break; - case KMType.DES: - DESKey desKey = createTDESKey(buf, startOff, length); - break; - case KMType.HMAC: - HMACKey hmacKey = 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: - RSAPrivateKey rsaKey = createRsaKey(pubModBuf, pubModStart, pubModLength, privKeyBuf, - privKeyStart, privKeyLength); - break; - case KMType.EC: - ECPrivateKey ecPrivKey = createEcKey(privKeyBuf, privKeyStart, privKeyLength); - break; - default: - CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); - break; - } - return true; - } - - - public HMACKey createHMACKey(byte[] secretBuffer, short secretOff, short secretLength) { - HMACKey key = null; - key = (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC, - KeyBuilder.LENGTH_HMAC_SHA_256_BLOCK_64, false); - key.setKey(secretBuffer, secretOff, secretLength); - return key; - } - - @Override - public short aesGCMEncrypt( - byte[] keyBuf, - short keyStart, - short keyLen, - 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) { - //Create the sun jce compliant aes key - if (keyLen != 32 && keyLen != 16) { - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } - java.security.Key aesKey = new SecretKeySpec(keyBuf, keyStart, keyLen, "AES"); - // Create the cipher - javax.crypto.Cipher cipher = null; - try { - cipher = javax.crypto.Cipher.getInstance("AES/GCM/NoPadding", "SunJCE"); - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); - } catch (NoSuchProviderException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.INVALID_INIT); - } catch (NoSuchPaddingException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } - // Copy nonce - if (nonceLen != AES_GCM_NONCE_LENGTH) { - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } - byte[] iv = new byte[AES_GCM_NONCE_LENGTH]; - Util.arrayCopyNonAtomic(nonce, nonceStart, iv, (short) 0, AES_GCM_NONCE_LENGTH); - // Init Cipher - GCMParameterSpec spec = new GCMParameterSpec(AES_GCM_TAG_LENGTH * 8, nonce, nonceStart, - AES_GCM_NONCE_LENGTH); - try { - cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, aesKey, spec); - } catch (InvalidKeyException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.INVALID_INIT); - } catch (InvalidAlgorithmParameterException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); - } - // Create auth data - byte[] aad = new byte[authDataLen]; - Util.arrayCopyNonAtomic(authData, authDataStart, aad, (short) 0, authDataLen); - cipher.updateAAD(aad); - // Encrypt secret - short len = 0; - byte[] outputBuf = new byte[cipher.getOutputSize(secretLen)]; - try { - len = (short) (cipher.doFinal(secret, secretStart, secretLen, outputBuf, (short) 0)); - } catch (ShortBufferException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } catch (IllegalBlockSizeException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } catch (BadPaddingException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } - // Extract Tag appended at the end. - Util.arrayCopyNonAtomic(outputBuf, (short) (len - AES_GCM_TAG_LENGTH), authTag, authTagStart, - AES_GCM_TAG_LENGTH); - //Copy the encrypted data - Util.arrayCopyNonAtomic(outputBuf, (short) 0, encSecret, encSecretStart, - (short) (len - AES_GCM_TAG_LENGTH)); - return (short) (len - AES_GCM_TAG_LENGTH); - } - - public boolean aesGCMDecrypt( - byte[] keyBuf, - short keyStart, - short keyLen, - 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) { - // Create the sun jce compliant aes key - if (keyLen != 32 && keyLen != 16) { - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } - java.security.Key aesKey = new SecretKeySpec(keyBuf, keyStart, keyLen, - "AES"); - // Create the cipher - javax.crypto.Cipher cipher = null; - try { - cipher = javax.crypto.Cipher.getInstance("AES/GCM/NoPadding", "SunJCE"); - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); - } catch (NoSuchProviderException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.INVALID_INIT); - } catch (NoSuchPaddingException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } - // Copy nonce - if (nonceLen != AES_GCM_NONCE_LENGTH) { - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } - byte[] iv = new byte[AES_GCM_NONCE_LENGTH]; - Util.arrayCopyNonAtomic(nonce, nonceStart, iv, (short) 0, - AES_GCM_NONCE_LENGTH); - // Init Cipher - GCMParameterSpec spec = new GCMParameterSpec(authTagLen * 8, nonce, - nonceStart, AES_GCM_NONCE_LENGTH); - try { - cipher.init(javax.crypto.Cipher.DECRYPT_MODE, aesKey, spec); - } catch (InvalidKeyException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.INVALID_INIT); - } catch (InvalidAlgorithmParameterException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); - } - // Create auth data - byte[] aad = new byte[authDataLen]; - Util.arrayCopyNonAtomic(authData, authDataStart, aad, (short) 0, - authDataLen); - cipher.updateAAD(aad); - // Append the auth tag at the end of data - byte[] inputBuf = new byte[(short) (encSecretLen + authTagLen)]; - Util.arrayCopyNonAtomic(encSecret, encSecretStart, inputBuf, (short) 0, - encSecretLen); - Util.arrayCopyNonAtomic(authTag, authTagStart, inputBuf, encSecretLen, - authTagLen); - // Decrypt - short len = 0; - byte[] outputBuf = new byte[cipher.getOutputSize((short) inputBuf.length)]; - try { - len = (short) (cipher.doFinal(inputBuf, (short) 0, - (short) inputBuf.length, outputBuf, (short) 0)); - } catch (AEADBadTagException e) { - e.printStackTrace(); - return false; - } catch (ShortBufferException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } catch (IllegalBlockSizeException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } catch (BadPaddingException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } - // Copy the decrypted data - Util.arrayCopyNonAtomic(outputBuf, (short) 0, secret, secretStart, len); - return true; - } - - @Override - public void getTrueRandomNumber(byte[] buf, short start, short length) { - Util.arrayCopy(entropyPool, (short) 0, buf, start, length); - } - - public HMACKey cmacKdf(byte[] keyMaterial, short keyMaterialStart, short keyMaterialLen, - byte[] label, - short labelStart, short labelLen, byte[] context, short contextStart, short contextLength) { - // This is hardcoded to requirement - 32 byte output with two concatenated 16 bytes K1 and K2. - final byte n = 2; // hardcoded - final byte[] L = {0, 0, 1, - 0}; // [L] 256 bits - hardcoded 32 bits as per reference impl in keymaster. - final byte[] zero = {0}; // byte - byte[] iBuf = new byte[]{0, 0, 0, 0}; // [i] counter - 32 bits - byte[] keyOut = new byte[(short) (n * 16)]; - Signature prf = Signature.getInstance(Signature.ALG_AES_CMAC_128, false); - AESKey key = (AESKey) KeyBuilder - .buildKey(KeyBuilder.TYPE_AES, KeyBuilder.LENGTH_AES_256, false); - key.setKey(keyMaterial, keyMaterialStart); - prf.init(key, Signature.MODE_SIGN); - byte i = 1; - short pos = 0; - while (i <= n) { - iBuf[3] = i; - prf.update(iBuf, (short) 0, (short) 4); // 4 bytes of iBuf with counter in it - prf.update(label, labelStart, labelLen); // label - prf.update(zero, (short) 0, (short) 1); // 1 byte of 0x00 - prf.update(context, contextStart, contextLength); // context - pos = prf.sign(L, (short) 0, (short) 4, keyOut, pos); // 4 bytes of L - signature of 16 bytes - i++; - } - return createHMACKey(keyOut, (short) 0, (short) keyOut.length); - } - - @Override - public short cmacKDF(KMPreSharedKey pSharedKey, byte[] label, - short labelStart, short labelLen, byte[] context, short contextStart, short contextLength, - byte[] keyBuf, short keyStart) { - KMHmacKey key = (KMHmacKey) pSharedKey; - short keyMaterialLen = key.getKeySizeBits(); - keyMaterialLen = (short) (keyMaterialLen / 8); - short keyMaterialStart = 0; - byte[] keyMaterial = new byte[keyMaterialLen]; - key.getKey(keyMaterial, keyMaterialStart); - HMACKey hmacKey = cmacKdf(keyMaterial, keyMaterialStart, keyMaterialLen, label, labelStart, - labelLen, context, contextStart, contextLength); - return hmacKey.getKey(keyBuf, keyStart); - } - - - 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 hmacKDF(KMMasterKey masterkey, byte[] data, short dataStart, - short dataLength, byte[] signature, short signatureStart) { - KMAESKey aesKey = (KMAESKey) masterkey; - short keyLen = (short) (aesKey.getKeySizeBits() / 8); - byte[] keyData = new byte[keyLen]; - aesKey.getKey(keyData, (short) 0); - return hmacSign(keyData, (short) 0, keyLen, data, dataStart, dataLength, - signature, signatureStart); - } - - @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 - byte[] prk = new byte[32]; - hkdfExtract(ikm, ikmOff, ikmLen, salt, saltOff, saltLen, prk, (short) 0); - //HMAC_expand - return hkdfExpand(prk, (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) ((outLen + digestLen - 1) / digestLen); - if (n > 255) { - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } - HMACKey hmacKey = createHMACKey(prk, prkOff, prkLen); - byte[] previousOutput = new byte[32]; // Length of output 32. - byte[] cnt = {(byte) 0}; - short bytesCopied = 0; - short len = 0; - for (short i = 0; i < n; i++) { - cnt[0]++; - hmacSignature.init(hmacKey, Signature.MODE_SIGN); - if (i != 0) - hmacSignature.update(previousOutput, (short) 0, (short) 32); - hmacSignature.update(info, infoOff, infoLen); - len = hmacSignature.sign(cnt, (short) 0, (short) 1, previousOutput, (short) 0); - if ((short) (bytesCopied + len) > outLen) { - len = (short) (outLen - bytesCopied); - } - Util.arrayCopyNonAtomic(previousOutput, (short) 0, 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 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 boolean hmacVerify(KMComputedHmacKey key, byte[] data, short dataStart, - short dataLength, byte[] mac, short macStart, short macLength) { - KMHmacKey hmacKey = (KMHmacKey) key; - hmacSignature.init(hmacKey.getKey(), 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) { - KMCipher cipher = createRsaDecipher( - KMType.RSA_OAEP, KMType.SHA1, secret, secretStart, secretLength, modBuffer, modOff, - modLength); - return cipher.doFinal( - inputDataBuf, inputDataStart, inputDataLength, outputDataBuf, outputDataStart); - } - - @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) { - switch (alg) { - case KMType.AES: - case KMType.DES: - if (blockMode != KMType.GCM) { - KMCipher cipher = createSymmetricCipher(alg, purpose, blockMode, padding, keyBuf, - keyStart, keyLength, - ivBuf, ivStart, ivLength); - return new KMOperationImpl(cipher); - } else { - KMCipher aesGcm = createAesGcmCipher(purpose, macLength, keyBuf, keyStart, keyLength, - ivBuf, ivStart, ivLength); - return new KMOperationImpl(aesGcm); - } - case KMType.HMAC: - Signature signerVerifier = createHmacSignerVerifier(purpose, digest, keyBuf, keyStart, - keyLength); - return new KMOperationImpl(signerVerifier); - default: - CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); - } - return null; - } - - @Override - public KMOperation initTrustedConfirmationSymmetricOperation(KMComputedHmacKey computedHmacKey) { - KMOperationImpl opr = null; - KMHmacKey key = (KMHmacKey) computedHmacKey; - Signature signerVerifier = createHmacSignerVerifier(KMType.VERIFY, KMType.SHA2_256, key.getKey()); - return new KMOperationImpl(signerVerifier); - } - - @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) { - if (alg == KMType.RSA) { - switch (purpose) { - case KMType.SIGN: - Signature signer = - createRsaSigner( - digest, - padding, - privKeyBuf, - privKeyStart, - privKeyLength, - pubModBuf, - pubModStart, - pubModLength); - return new KMOperationImpl(signer); - case KMType.DECRYPT: - KMCipher decipher = - createRsaDecipher( - padding, mgfDigest, privKeyBuf, privKeyStart, privKeyLength, pubModBuf, pubModStart, - pubModLength); - return new KMOperationImpl(decipher); - default: - KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); - } - } else if (alg == KMType.EC) { - switch (purpose) { - case KMType.SIGN: - Signature signer = - createEcSigner(digest, privKeyBuf, privKeyStart, privKeyLength); - return new KMOperationImpl(signer); - case KMType.AGREE_KEY: - KeyAgreement keyAgreement = - createKeyAgreement(privKeyBuf, privKeyStart, privKeyLength); - return new KMOperationImpl(keyAgreement); - default: - KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); - } - } - CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); - return null; - } - - public KMCipher createRsaDecipher(short padding, short digest, byte[] secret, short secretStart, - short secretLength, byte[] modBuffer, short modOff, short modLength) { - byte cipherAlg = mapCipherAlg(KMType.RSA, (byte) padding, (byte) 0); - if (cipherAlg == Cipher.ALG_RSA_PKCS1_OAEP) { - return createRsaOAEP256Cipher(KMType.DECRYPT, (byte) digest, secret, secretStart, - secretLength, modBuffer, modOff, modLength); - } - Cipher rsaCipher = Cipher.getInstance(cipherAlg, false); - RSAPrivateKey key = (RSAPrivateKey) KeyBuilder - .buildKey(KeyBuilder.TYPE_RSA_PRIVATE, KeyBuilder.LENGTH_RSA_2048, false); - key.setExponent(secret, secretStart, secretLength); - key.setModulus(modBuffer, modOff, modLength); - rsaCipher.init(key, Cipher.MODE_DECRYPT); - KMCipherImpl inst = new KMCipherImpl(rsaCipher); - inst.setCipherAlgorithm(KMType.RSA); - inst.setMode(KMType.DECRYPT); - inst.setPaddingAlgorithm(padding); - return inst; - } - - private MGF1ParameterSpec getMGF1ParamSpec(byte mgfDigest) { - switch (mgfDigest) { - case KMType.SHA1: - return MGF1ParameterSpec.SHA1; - case KMType.SHA2_256: - return MGF1ParameterSpec.SHA256; - case KMType.SHA2_224: - return MGF1ParameterSpec.SHA224; - case KMType.SHA2_384: - return MGF1ParameterSpec.SHA384; - case KMType.SHA2_512: - return MGF1ParameterSpec.SHA512; - default: - KMException.throwIt(KMError.UNSUPPORTED_DIGEST); - } - return null; - } - - private KMCipher createRsaOAEP256Cipher(byte mode, byte mgfDigest, - byte[] secret, short secretStart, short secretLen, - byte[] modBuffer, short modOff, short modLength) { - // Convert byte arrays into keys - byte[] exp = null; - byte[] mod = new byte[modLength]; - if (secret != null) { - exp = new byte[secretLen]; - Util.arrayCopyNonAtomic(secret, secretStart, exp, (short) 0, secretLen); - } else { - exp = new byte[]{0x01, 0x00, 0x01}; - } - Util.arrayCopyNonAtomic(modBuffer, modOff, mod, (short) 0, modLength); - String modString = toHexString(mod); - String expString = toHexString(exp); - BigInteger modInt = new BigInteger(modString, 16); - BigInteger expInt = new BigInteger(expString, 16); - javax.crypto.Cipher rsaCipher = null; - try { - KeyFactory kf = KeyFactory.getInstance("RSA"); - // Create cipher with oaep padding - OAEPParameterSpec oaepSpec = new OAEPParameterSpec("SHA-256", "MGF1", - getMGF1ParamSpec(mgfDigest), PSource.PSpecified.DEFAULT); - rsaCipher = javax.crypto.Cipher.getInstance("RSA/ECB/OAEPPadding", "SunJCE"); - if (mode == KMType.ENCRYPT) { - RSAPublicKeySpec pubSpec = new RSAPublicKeySpec(modInt, expInt); - java.security.interfaces.RSAPublicKey pubKey = (java.security.interfaces.RSAPublicKey) kf - .generatePublic(pubSpec); - rsaCipher.init(javax.crypto.Cipher.ENCRYPT_MODE, pubKey, oaepSpec); - } else { - RSAPrivateKeySpec privSpec = new RSAPrivateKeySpec(modInt, expInt); - java.security.interfaces.RSAPrivateKey privKey = (java.security.interfaces.RSAPrivateKey) kf - .generatePrivate(privSpec); - rsaCipher.init(javax.crypto.Cipher.DECRYPT_MODE, privKey, oaepSpec); - } - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); - } catch (InvalidKeySpecException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } catch (InvalidKeyException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.INVALID_INIT); - } catch (InvalidAlgorithmParameterException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } catch (NoSuchPaddingException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } catch (NoSuchProviderException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.INVALID_INIT); - } - KMCipherImpl ret = new KMCipherImpl(rsaCipher); - ret.setCipherAlgorithm(KMType.RSA); - ret.setPaddingAlgorithm(KMType.RSA_OAEP); - ret.setMode(mode); - return ret; - } - - private String toHexString(byte[] num) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < num.length; i++) { - sb.append(String.format("%02X", num[i])); - } - return sb.toString(); - } - - public Signature createRsaSigner(short digest, short padding, byte[] secret, - short secretStart, short secretLength, byte[] modBuffer, - short modOff, short modLength) { - short alg = mapSignature256Alg(KMType.RSA, (byte) padding); - if (padding == KMType.PADDING_NONE || - (padding == KMType.RSA_PKCS1_1_5_SIGN && digest == KMType.DIGEST_NONE)) { - return createNoDigestSigner(padding, secret, secretStart, secretLength, - modBuffer, modOff, modLength); - } - Signature rsaSigner = Signature.getInstance((byte) alg, false); - RSAPrivateKey key = (RSAPrivateKey) KeyBuilder - .buildKey(KeyBuilder.TYPE_RSA_PRIVATE, KeyBuilder.LENGTH_RSA_2048, false); - key.setExponent(secret, secretStart, secretLength); - key.setModulus(modBuffer, modOff, modLength); - rsaSigner.init(key, Signature.MODE_SIGN); - return rsaSigner; - } - - private Signature createNoDigestSigner(short padding, - byte[] secret, short secretStart, short secretLength, - byte[] modBuffer, short modOff, short modLength) { - Cipher rsaCipher = Cipher.getInstance(Cipher.ALG_RSA_NOPAD, false); - RSAPrivateKey key = (RSAPrivateKey) KeyBuilder.buildKey(KeyBuilder.TYPE_RSA_PRIVATE, - KeyBuilder.LENGTH_RSA_2048, false); - key.setExponent(secret, secretStart, secretLength); - key.setModulus(modBuffer, modOff, modLength); - rsaCipher.init(key, Cipher.MODE_DECRYPT); - KMRsa2048NoDigestSignature inst = new KMRsa2048NoDigestSignature(rsaCipher, (byte) padding, - modBuffer, modOff, modLength); - return inst; - } - - public KeyAgreement createKeyAgreement(byte[] secret, short secretStart, - short secretLength) { - ECPrivateKey key = (ECPrivateKey) KeyBuilder - .buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, KeyBuilder.LENGTH_EC_FP_256, false); - key.setS(secret, secretStart, secretLength); - - KeyAgreement keyAgreement = KeyAgreement.getInstance(KeyAgreement.ALG_EC_SVDP_DH_PLAIN, - false); - keyAgreement.init(key); - return keyAgreement; - } - - public Signature createEcSigner(short digest, byte[] secret, short secretStart, - short secretLength) { - short alg = mapSignature256Alg(KMType.EC, (byte) 0); - Signature ecSigner; - if (digest == KMType.DIGEST_NONE) { - ecSigner = new KMEcdsa256NoDigestSignature(Signature.MODE_SIGN, secret, secretStart, - secretLength); - } else { - ECPrivateKey key = (ECPrivateKey) KeyBuilder - .buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, KeyBuilder.LENGTH_EC_FP_256, false); - key.setS(secret, secretStart, secretLength); - ecSigner = Signature.getInstance((byte) alg, false); - ecSigner.init(key, Signature.MODE_SIGN); - } - return ecSigner; - } - - - public KMCipher createSymmetricCipher( - short cipherAlg, short mode, short blockMode, short padding, byte[] secret, short secretStart, - short secretLength) { - return createSymmetricCipher(cipherAlg, mode, blockMode, padding, secret, secretStart, - secretLength, null, (short) 0, (short) 0); - } - - - public KMCipher createSymmetricCipher(short alg, short purpose, short blockMode, short padding, - byte[] secret, - short secretStart, short secretLength, - byte[] ivBuffer, short ivStart, short ivLength) { - Key key = null; - Cipher symmCipher = null; - short len = 0; - switch (secretLength) { - case 32: - len = KeyBuilder.LENGTH_AES_256; - break; - case 16: - len = KeyBuilder.LENGTH_AES_128; - break; - case 24: - len = KeyBuilder.LENGTH_DES3_3KEY; - break; - default: - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - break; - } - short cipherAlg = mapCipherAlg((byte) alg, (byte) padding, (byte) blockMode); - switch (cipherAlg) { - case Cipher.ALG_AES_BLOCK_128_CBC_NOPAD: - key = KeyBuilder.buildKey(KeyBuilder.TYPE_AES, len, false); - ((AESKey) key).setKey(secret, secretStart); - symmCipher = Cipher.getInstance((byte) cipherAlg, false); - symmCipher.init(key, mapPurpose(purpose), ivBuffer, ivStart, ivLength); - break; - case Cipher.ALG_AES_BLOCK_128_ECB_NOPAD: - key = KeyBuilder.buildKey(KeyBuilder.TYPE_AES, len, false); - ((AESKey) key).setKey(secret, secretStart); - symmCipher = Cipher.getInstance((byte) cipherAlg, false); - symmCipher.init(key, mapPurpose(purpose)); - break; - case Cipher.ALG_DES_CBC_NOPAD: - key = KeyBuilder.buildKey(KeyBuilder.TYPE_DES, len, false); - ((DESKey) key).setKey(secret, secretStart); - symmCipher = Cipher.getInstance((byte) cipherAlg, false); - //While sending back the iv send only 8 bytes. - symmCipher.init(key, mapPurpose(purpose), ivBuffer, ivStart, (short) 8); - break; - case Cipher.ALG_DES_ECB_NOPAD: - key = KeyBuilder.buildKey(KeyBuilder.TYPE_DES, len, false); - ((DESKey) key).setKey(secret, secretStart); - symmCipher = Cipher.getInstance((byte) cipherAlg, false); - symmCipher.init(key, mapPurpose(purpose)); - break; - case Cipher.ALG_AES_CTR: // uses SUNJCE - return createAesCtrCipherNoPad(purpose, secret, secretStart, secretLength, ivBuffer, - ivStart, ivLength); - default://This should never happen - CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); - break; - } - KMCipherImpl cipher = new KMCipherImpl(symmCipher); - cipher.setCipherAlgorithm(alg); - cipher.setPaddingAlgorithm(padding); - cipher.setMode(purpose); - cipher.setBlockMode(blockMode); - return cipher; - } - - 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 byte mapSignature256Alg(byte alg, byte padding) { - switch (alg) { - case KMType.RSA: - switch (padding) { - case KMType.RSA_PKCS1_1_5_SIGN: - return Signature.ALG_RSA_SHA_256_PKCS1; - case KMType.RSA_PSS: - return Signature.ALG_RSA_SHA_256_PKCS1_PSS; - } - break; - case KMType.EC: - 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) { - 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: - return Cipher.ALG_RSA_PKCS1_OAEP; - } - break; - } - return -1; - } - - private KMCipher createAesCtrCipherNoPad(short mode, byte[] secret, short secretStart, - short secretLength, byte[] ivBuffer, short ivStart, short ivLength) { - if (secretLength != 16 && secretLength != 32) { - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } - if (ivLength != 16) { - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } - if (mode != KMType.ENCRYPT && mode != KMType.DECRYPT) { - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } - //Create the sun jce compliant aes key - byte[] keyMaterial = new byte[secretLength]; - Util.arrayCopyNonAtomic(secret, secretStart, keyMaterial, (short) 0, secretLength); - java.security.Key aesKey = new SecretKeySpec(keyMaterial, (short) 0, keyMaterial.length, "AES"); - // Create the cipher - javax.crypto.Cipher cipher = null; - try { - cipher = javax.crypto.Cipher.getInstance("AES/CTR/NoPadding", "SunJCE"); - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); - } catch (NoSuchProviderException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.INVALID_INIT); - } catch (NoSuchPaddingException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } - // Copy nonce - byte[] iv = new byte[ivLength]; - Util.arrayCopyNonAtomic(ivBuffer, ivStart, iv, (short) 0, ivLength); - // Init Cipher - IvParameterSpec ivSpec = new IvParameterSpec(iv); - try { - if (mode == KMType.ENCRYPT) { - cipher.init(javax.crypto.Cipher.ENCRYPT_MODE, aesKey, ivSpec); - } else { - cipher.init(javax.crypto.Cipher.DECRYPT_MODE, aesKey, ivSpec); - } - } catch (InvalidKeyException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.INVALID_INIT); - } catch (InvalidAlgorithmParameterException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); - } - KMCipherImpl ret = new KMCipherImpl(cipher); - ret.setCipherAlgorithm(KMType.AES); - ret.setMode(mode); - ret.setPaddingAlgorithm((short) 0); - ret.setBlockMode(KMType.CTR); - return ret; - } - - - public Signature createHmacSignerVerifier(short purpose, short digest, byte[] secret, - short secretStart, short secretLength) { - short alg = Signature.ALG_HMAC_SHA_256; - if (digest != KMType.SHA2_256) { - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } - Signature hmacSignerVerifier = Signature.getInstance((byte) alg, false); - HMACKey key = (HMACKey) KeyBuilder - .buildKey(KeyBuilder.TYPE_HMAC, (short) (secretLength * 8), false); - key.setKey(secret, secretStart, secretLength); - hmacSignerVerifier.init(key, (byte) purpose); - return hmacSignerVerifier; - } - - private Signature createHmacSignerVerifier(short purpose, short digest, HMACKey key) { - byte alg = Signature.ALG_HMAC_SHA_256; - if (digest != KMType.SHA2_256) { - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } - Signature hmacSignerVerifier = Signature.getInstance((byte) alg, false); - hmacSignerVerifier.init(key, (byte) purpose); - return hmacSignerVerifier; - } - - public KMCipher createAesGcmCipher(short mode, short tagLen, byte[] secret, short secretStart, - short secretLength, - byte[] ivBuffer, short ivStart, short ivLength) { - if (secretLength != 16 && secretLength != 32) { - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } - if (ivLength != AES_GCM_NONCE_LENGTH) { - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } - if (mode != KMType.ENCRYPT && mode != KMType.DECRYPT) { - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } - //Create the sun jce compliant aes key - byte[] keyMaterial = new byte[secretLength]; - Util.arrayCopyNonAtomic(secret, secretStart, keyMaterial, (short) 0, secretLength); - java.security.Key aesKey = new SecretKeySpec(keyMaterial, (short) 0, keyMaterial.length, "AES"); - // Create the cipher - javax.crypto.Cipher cipher = null; - try { - cipher = javax.crypto.Cipher.getInstance("AES/GCM/NoPadding", "SunJCE"); - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); - } catch (NoSuchProviderException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.INVALID_INIT); - } catch (NoSuchPaddingException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } - // Copy nonce - byte[] iv = new byte[AES_GCM_NONCE_LENGTH]; - Util.arrayCopyNonAtomic(ivBuffer, ivStart, iv, (short) 0, AES_GCM_NONCE_LENGTH); - // Init Cipher - GCMParameterSpec spec = new GCMParameterSpec(tagLen, iv, (short) 0, AES_GCM_NONCE_LENGTH); - try { - if (mode == KMType.ENCRYPT) { - mode = javax.crypto.Cipher.ENCRYPT_MODE; - } else { - mode = javax.crypto.Cipher.DECRYPT_MODE; - } - cipher.init(mode, aesKey, spec); - } catch (InvalidKeyException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.INVALID_INIT); - } catch (InvalidAlgorithmParameterException e) { - e.printStackTrace(); - CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); - } - KMCipherImpl ret = new KMCipherImpl(cipher); - ret.setCipherAlgorithm(KMType.AES); - ret.setMode(mode); - ret.setPaddingAlgorithm((short) 0); - ret.setBlockMode(KMType.GCM); - return ret; - } - - private void initEntropyPool(byte[] pool) { - byte index = 0; - RandomData trng; - while (index < rngCounter.length) { - rngCounter[index++] = 0; - } - try { - trng = RandomData.getInstance(RandomData.ALG_TRNG); - trng.nextBytes(pool, (short) 0, (short) pool.length); - } catch (CryptoException exp) { - if (exp.getReason() == CryptoException.NO_SUCH_ALGORITHM) { - // simulator does not support TRNG algorithm. So, PRNG algorithm (deprecated) is used. - trng = RandomData.getInstance(RandomData.ALG_PSEUDO_RANDOM); - trng.nextBytes(pool, (short) 0, (short) pool.length); - } else { - ISOException.throwIt(ISO7816.SW_UNKNOWN); - } - } - } - - // Generate a secure random number from existing entropy pool. This uses aes ecb algorithm with - // 8 byte rngCounter and 16 byte block size. - @Override - public void newRandomNumber(byte[] num, short startOff, short length) { - byte[] countBuf = new byte[AES_BLOCK_SIZE]; - byte[] randBuf = new byte[AES_BLOCK_SIZE]; - short len = AES_BLOCK_SIZE; - aesRngKey.setKey(entropyPool, (short) 0); - aesRngCipher.init(aesRngKey, Cipher.MODE_ENCRYPT, aesICV, (short) 0, (short) 16); - while (length > 0) { - if (length < len) { - len = length; - } - // increment rngCounter by one - incrementCounter(); - // copy the 8 byte rngCounter into the 16 byte rngCounter buffer. - Util.arrayCopy(rngCounter, (short) 0, countBuf, (short) 0, (short) rngCounter.length); - // encrypt the rngCounter buffer with existing entropy which forms the aes key. - aesRngCipher.doFinal( - countBuf, (short) 0, AES_BLOCK_SIZE, randBuf, (short) 0); - // copy the encrypted rngCounter block to buffer passed in the argument - Util.arrayCopy(randBuf, (short) 0, num, startOff, len); - length = (short) (length - len); - startOff = (short) (startOff + len); - } - } - - // increment 8 byte rngCounter by one - private void incrementCounter() { - // start with least significant byte - short index = (short) (rngCounter.length - 1); - while (index >= 0) { - // if the msb of current byte is set then it will be negative - if (rngCounter[index] < 0) { - // then increment the rngCounter - rngCounter[index]++; - // is the msb still set? i.e. no carry over - if (rngCounter[index] < 0) { - break; // then break - } else { - index--; // else go to the higher order byte - } - } else { - // if msb is not set then increment the rngCounter - rngCounter[index]++; - break; - } - } - } - - @Override - public void addRngEntropy(byte[] num, short offset, short length) { - // Maximum length can be 256 bytes. But currently we support max 32 bytes seed. - // Get existing entropy pool. - if (length > 32) { - length = 32; - } - // Create new temporary pool. - // Populate the new pool with the entropy which is derived from current entropy pool. - newRandomNumber(rndNum, (short) 0, (short) entropyPool.length); - // Copy the entropy to the current pool - updates the entropy pool. - Util.arrayCopy(rndNum, (short) 0, entropyPool, (short) 0, (short) entropyPool.length); - short index = 0; - short randIndex = 0; - // XOR the seed received from the master in the entropy pool - 16 bytes (entPool.length). - // at a time. - while (index < length) { - entropyPool[randIndex] = (byte) (entropyPool[randIndex] ^ num[(short) (offset + index)]); - randIndex++; - index++; - if (randIndex >= entropyPool.length) { - randIndex = 0; - } - } - } - - @Override - public short getAttestationKeyAlgorithm(){ - return KMType.INVALID_VALUE; - } - - @Override - public short getAdditionalCertChainLength() { - return Util.getShort(additionalCertChain, (short) 0); - } - - @Override - public byte[] getAdditionalCertChain() { - return additionalCertChain; - // short length = Util.getShort(additionalCertChain, (short) 0); - // Util.arrayCopyNonAtomic(additionalCertChain, (short) 2, buffer, start, length); - // return length; - } - - 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(); - } - - @Override - public byte[] getBootCertificateChain() { - return bcc; - } - - @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 signer = createRsaSigner(KMType.SHA2_256, KMType.RSA_PKCS1_1_5_SIGN,secret,secretStart,secretLength,modBuf,modStart,modLength); - return signer.sign(inputDataBuf, inputDataStart, inputDataLength, - outputDataBuf, outputDataStart); - } - - @Override - public short ecSign256(byte[] secret, short secretStart, short secretLength, - byte[] inputDataBuf, short inputDataStart, short inputDataLength, - byte[] outputDataBuf, short outputDataStart){ - Signature signer = createEcSigner(KMType.SHA2_256, secret, secretStart, secretLength); - return signer.sign(inputDataBuf, inputDataStart, inputDataLength, outputDataBuf, outputDataStart); - } - - @Override - public short ecSign256(KMAttestationKey attestationKey, - byte[] inputDataBuf, short inputDataStart, short inputDataLength, - byte[] outputDataBuf, short outputDataStart) { - - ECPrivateKey key = ((KMECPrivateKey) attestationKey).getPrivateKey(); - - Signature signer = Signature - .getInstance(Signature.ALG_ECDSA_SHA_256, false); - signer.init(key, Signature.MODE_SIGN); - return signer.sign(inputDataBuf, inputDataStart, inputDataLength, - outputDataBuf, outputDataStart); - } - - - @Override - public short ecSign256(KMDeviceUniqueKey deviceUniqueKey, byte[] inputDataBuf, short inputDataStart, - short inputDataLength, byte[] outputDataBuf, short outputDataStart) { - ECPrivateKey key = ((KMECDeviceUniqueKey) deviceUniqueKey).getPrivateKey(); - Signature signer = Signature - .getInstance(Signature.ALG_ECDSA_SHA_256, false); - signer.init(key, Signature.MODE_SIGN); - return signer.sign(inputDataBuf, inputDataStart, inputDataLength, - outputDataBuf, outputDataStart); - } - - @Override - public boolean ecVerify256(byte[] pubKey, short pubKeyOffset, short pubKeyLen, byte[] inputDataBuf, - short inputDataStart, short inputDataLength, byte[] signatureDataBuf, - short signatureDataStart, short signatureDataLen) { - - KeyPair ecKeyPair = new KeyPair(KeyPair.ALG_EC_FP, KeyBuilder.LENGTH_EC_FP_256); - ECPublicKey ecPublicKey = (ECPublicKey) ecKeyPair.getPublic(); - ecPublicKey.setW(pubKey, pubKeyOffset, pubKeyLen); - - Signature signer = Signature - .getInstance(Signature.ALG_ECDSA_SHA_256, false); - signer.init(ecPublicKey, Signature.MODE_VERIFY); - return signer.verify(inputDataBuf, inputDataStart, inputDataLength, - signatureDataBuf, signatureDataStart, signatureDataLen); - } - - - @Override - 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 (len > 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(); - } - - @Override - public void onSave(Element ele) { - } - - @Override - public void onRestore(Element ele) { - } - - @Override - public short getBackupPrimitiveByteCount() { - return 0; - } - - @Override - public short getBackupObjectCount() { - return 0; - } - - @Override - public boolean isUpgrading() { - return false; - } - - @Override - public KMComputedHmacKey createComputedHmacKey(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); - } - computedHmacKey.setKey(keyData, offset, length); - return (KMComputedHmacKey) computedHmacKey; - } - - @Override - public KMMasterKey createMasterKey(short keySizeBits) { - if (masterKey == null) { - AESKey key = (AESKey) KeyBuilder.buildKey( - KeyBuilder.TYPE_AES, keySizeBits, false); - masterKey = new KMAESKey(key); - short keyLen = (short) (keySizeBits / 8); - byte[] keyData = new byte[keyLen]; - getTrueRandomNumber(keyData, (short) 0, keyLen); - masterKey.setKey(keyData, (short) 0); - } - return (KMMasterKey) masterKey; - } - - @Override - public boolean isAttestationKeyProvisioned(){ - return false; - } - /** - * This function creates an ECKey and initializes the ECPrivateKey with the provided input key - * data. The initialized Key is maintained by the SEProvider. This function should be called only - * while provisioning the attestation key. - * - * @param keyData buffer containing the ec private key. - * @param offset start of the buffer. - * @param length length of the buffer. - * @return An instance of KMAttestationKey. - */ - @Override - public KMAttestationKey createAttestationKey(byte[] keyData, short offset, short length){ - if (attestationKey == null) { - // Strongbox supports only P-256 curve for EC key. - KeyPair ecKeyPair = new KeyPair(KeyPair.ALG_EC_FP, KeyBuilder.LENGTH_EC_FP_256); - attestationKey = new KMECPrivateKey(ecKeyPair); - } - attestationKey.setS(keyData, offset, length); - return (KMAttestationKey) attestationKey; - } - - private KMDeviceUniqueKey createDeviceUniqueKey(KMECDeviceUniqueKey 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); - key = new KMECDeviceUniqueKey(ecKeyPair); - } - key.setS(privKey, privKeyOff, privKeyLen); - key.setW(pubKey, pubKeyOff, pubKeyLen); - return (KMDeviceUniqueKey) key; - } - - @Override - public KMDeviceUniqueKey createDeviceUniqueKey(boolean testMode, - byte[] pubKey, short pubKeyOff, short pubKeyLen, byte[] privKey, - short privKeyOff, short privKeyLen) { - KMDeviceUniqueKey key; - if (testMode) { - key = createDeviceUniqueKey(testKey, pubKey, pubKeyOff, - pubKeyLen, privKey, privKeyOff, privKeyLen); - if (testKey == null) testKey = (KMECDeviceUniqueKey) key; - } else { - key = createDeviceUniqueKey(deviceUniqueKey, pubKey, pubKeyOff, - pubKeyLen, privKey, privKeyOff, privKeyLen); - if (deviceUniqueKey == null) deviceUniqueKey = (KMECDeviceUniqueKey) key; - } - return key; - } - - @Override - public KMDeviceUniqueKey getDeviceUniqueKey(boolean testMode) { - return ((KMDeviceUniqueKey) (testMode ? testKey : deviceUniqueKey)); - } - - /** - * This function creates an HMACKey and initializes the key with the provided input key data. This - * created key is maintained by the SEProvider. This function should be called only while - * provisioing the pre-shared secret. - * - * @param keyData buffer containing the key data. - * @param offset start of the buffer. - * @param length length of the buffer. - * @return An instance of KMPreSharedKey. - */ - public KMPreSharedKey createPresharedKey(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); - } - preSharedKey.setKey(keyData, offset, length); - return (KMPreSharedKey) preSharedKey; - } - - @Override - public KMMasterKey getMasterKey() { - return (KMMasterKey) masterKey; - } - - /** - * Returns the factory provisioned attestation key if any. - * - * @return Instance of the KMAttestationKey. - */ - @Override - public KMAttestationKey getAttestationKey() { - return (KMAttestationKey) attestationKey; - } - - @Override - public KMPreSharedKey getPresharedKey() { - return (KMPreSharedKey) preSharedKey; - } - - @Override - public short getAttestationId(short tag, byte[] buffer, short start) { - switch(tag){ - // Attestation Id Brand - case KMType.ATTESTATION_ID_BRAND: - Util.arrayCopyNonAtomic(attIdBrand,(short)0,buffer, start,(short)attIdBrand.length); - return (short)attIdBrand.length; - // Attestation Id Device - case KMType.ATTESTATION_ID_DEVICE: - Util.arrayCopyNonAtomic(attIdDevice,(short)0,buffer, start,(short)attIdDevice.length); - return (short)attIdDevice.length; - // Attestation Id Product - case KMType.ATTESTATION_ID_PRODUCT: - Util.arrayCopyNonAtomic(attIdProduct,(short)0,buffer, start,(short)attIdProduct.length); - return (short)attIdProduct.length; - // Attestation Id Serial - case KMType.ATTESTATION_ID_SERIAL: - Util.arrayCopyNonAtomic(attIdSerial,(short)0,buffer, start,(short)attIdSerial.length); - return (short)attIdSerial.length; - // Attestation Id IMEI - case KMType.ATTESTATION_ID_IMEI: - Util.arrayCopyNonAtomic(attIdImei,(short)0,buffer, start,(short)attIdImei.length); - return (short)attIdImei.length; - // Attestation Id MEID - case KMType.ATTESTATION_ID_MEID: - Util.arrayCopyNonAtomic(attIdMeId,(short)0,buffer, start,(short)attIdMeId.length); - return (short)attIdMeId.length; - // Attestation Id Manufacturer - case KMType.ATTESTATION_ID_MANUFACTURER: - Util.arrayCopyNonAtomic(attIdManufacturer,(short)0,buffer, start,(short)attIdManufacturer.length); - return (short)attIdManufacturer.length; - // Attestation Id Model - case KMType.ATTESTATION_ID_MODEL: - Util.arrayCopyNonAtomic(attIdModel,(short)0,buffer, start,(short)attIdModel.length); - return (short)attIdModel.length; - } - return (short)0; - } - - public void setAttestationId(short tag, byte[] buffer, short start, short length) { - switch(tag){ - // Attestation Id Brand - case KMType.ATTESTATION_ID_BRAND: - attIdBrand = new byte[length]; - Util.arrayCopyNonAtomic(buffer, (short)start, attIdBrand,(short)0,length); - break; - // Attestation Id Device - case KMType.ATTESTATION_ID_DEVICE: - attIdDevice = new byte[length]; - Util.arrayCopyNonAtomic(buffer, (short)start, attIdDevice,(short)0,length); - break; - // Attestation Id Product - case KMType.ATTESTATION_ID_PRODUCT: - attIdProduct = new byte[length]; - Util.arrayCopyNonAtomic(buffer, (short)start, attIdProduct,(short)0,length); - break; - // Attestation Id Serial - case KMType.ATTESTATION_ID_SERIAL: - attIdSerial = new byte[length]; - Util.arrayCopyNonAtomic(buffer, (short)start, attIdSerial,(short)0,length); - break; - // Attestation Id IMEI - case KMType.ATTESTATION_ID_IMEI: - attIdImei = new byte[length]; - Util.arrayCopyNonAtomic(buffer, (short)start, attIdImei,(short)0,length); - break; - // Attestation Id MEID - case KMType.ATTESTATION_ID_MEID: - attIdMeId = new byte[length]; - Util.arrayCopyNonAtomic(buffer, (short)start, attIdMeId,(short)0,length); - break; - // Attestation Id Manufacturer - case KMType.ATTESTATION_ID_MANUFACTURER: - attIdManufacturer = new byte[length]; - Util.arrayCopyNonAtomic(buffer, (short)start, attIdManufacturer,(short)0,length); - break; - // Attestation Id Model - case KMType.ATTESTATION_ID_MODEL: - attIdModel = new byte[length]; - Util.arrayCopyNonAtomic(buffer, (short)start, attIdModel,(short)0,length); - break; - } - } - - @Override - public void deleteAttestationIds() { - attIdBrand = null; - attIdDevice = null; - attIdProduct = null; - attIdSerial = null; - attIdImei = null; - attIdMeId = null; - attIdManufacturer = null; - attIdModel = null; - } - - - public boolean isPowerReset(){ - boolean flag = false; - if(resetFlag[0] == POWER_RESET_TRUE){ - resetFlag[0] = POWER_RESET_FALSE; - flag = true; - } - return flag; - } - - @Override - public short getVerifiedBootHash(byte[] buffer, short start) { - Util.arrayCopyNonAtomic(verifiedHash,(short)0,buffer,start,(short)verifiedHash.length); - return (short)verifiedHash.length; - } - - @Override - public short getBootKey(byte[] buffer, short start) { - Util.arrayCopyNonAtomic(bootKey,(short) 0, buffer, start, (short)bootKey.length); - return (short)bootKey.length; - } - - @Override - public short getBootState() { - return bootState; - } - - @Override - public boolean isDeviceBootLocked() { - return deviceBootLocked; - } - - @Override - public short getBootPatchLevel(byte[] buffer, short start) { - Util.arrayCopyNonAtomic(bootPatchLevel, (short)0, buffer, start, (short)bootPatchLevel.length); - return (short)bootPatchLevel.length; - } - - 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.arrayCopyNonAtomic(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.arrayCopyNonAtomic(buffer, start, bootKey, (short)0, (short)32); - } - - public void setBootState(short state) { - bootState = state; - } - - 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.arrayCopyNonAtomic(buffer, start, bootPatchLevel, (short)0, (short) 4); - } - - public void setProvisionLocked(boolean locked) { - isProvisionLocked = locked; - } - - public boolean isProvisionLocked() { - return isProvisionLocked; - } - - @Override - public short messageDigest256(byte[] inBuff, short inOffset, - short inLength, byte[] outBuff, short outOffset) { - MessageDigest mDigest = null; - short len = 0; - try { - mDigest = MessageDigest.getInitializedMessageDigestInstance(MessageDigest.ALG_SHA_256, false); - len = mDigest.doFinal(inBuff, inOffset, inLength, outBuff, outOffset); - } catch (Exception e) { - - } - return len; - } - - private short getProvisionDataBufferOffset(byte dataType) { - switch(dataType) { - case CERTIFICATE_CHAIN: - return CERT_CHAIN_OFFSET; - case CERTIFICATE_ISSUER: - return CERT_ISSUER_OFFSET; - case CERTIFICATE_EXPIRY: - return CERT_EXPIRY_OFFSET; - default: - KMException.throwIt(KMError.INVALID_ARGUMENT); - } - return 0; - } - - private void persistProvisionData(byte[] buf, short off, short len, short maxSize, short copyToOff) { - if (len > maxSize) { - KMException.throwIt(KMError.INVALID_INPUT_LENGTH); - } - JCSystem.beginTransaction(); - Util.setShort(provisionData, copyToOff, len); - Util.arrayCopyNonAtomic(buf, off, provisionData, (short) (copyToOff + 2), len); - JCSystem.commitTransaction(); - } - - private void persistCertificateChain(byte[] certChain, short certChainOff, short certChainLen) { - persistProvisionData(certChain, certChainOff, certChainLen, - KMConfigurations.CERT_CHAIN_MAX_SIZE, CERT_CHAIN_OFFSET); - } - - private void persistCertficateIssuer(byte[] certIssuer, short certIssuerOff, short certIssuerLen) { - persistProvisionData(certIssuer, certIssuerOff, certIssuerLen, - KMConfigurations.CERT_ISSUER_MAX_SIZE, CERT_ISSUER_OFFSET); - } - - private void persistCertificateExpiryTime(byte[] certExpiry, short certExpiryOff, short certExpiryLen) { - persistProvisionData(certExpiry, certExpiryOff, certExpiryLen, - KMConfigurations.CERT_EXPIRY_MAX_SIZE, CERT_EXPIRY_OFFSET); - } - - @Override - public void persistProvisionData(byte[] buffer, short certChainOff, short certChainLen, - short certIssuerOff, short certIssuerLen, short certExpiryOff ,short certExpiryLen) { - // All the buffers uses first two bytes for length. The certificate chain - // is stored as shown below. - // _____________________________________________________ - // | 2 Bytes | 1 Byte | 3 Bytes | Cert1 | Cert2 |... - // |_________|________|_________|_______|________|_______ - // First two bytes holds the length of the total buffer. - // CBOR format: - // Next single byte holds the byte string header. - // Next 3 bytes holds the total length of the certificate chain. - // clear buffer. - JCSystem.beginTransaction(); - Util.arrayFillNonAtomic(provisionData, (short) 0, (short) provisionData.length, (byte) 0); - JCSystem.commitTransaction(); - // Persist data. - persistCertificateChain(buffer, certChainOff, certChainLen); - persistCertficateIssuer(buffer, certIssuerOff, certIssuerLen); - persistCertificateExpiryTime(buffer, certExpiryOff, certExpiryLen); - } - - @Override - public short readProvisionedData(byte dataType, byte[] buf, short offset) { - short provisionBufOffset = getProvisionDataBufferOffset(dataType); - short len = Util.getShort(provisionData, provisionBufOffset); - Util.arrayCopyNonAtomic(provisionData, (short) (2 + provisionBufOffset), buf, offset, len); - return len; - } - - @Override - public KMComputedHmacKey getComputedHmacKey() { - return (KMComputedHmacKey) computedHmacKey; - } - - @Override - public short getProvisionedDataLength(byte dataType) { - short provisionBufOffset = getProvisionDataBufferOffset(dataType); - return Util.getShort(provisionData, provisionBufOffset); - } - -} diff --git a/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMMasterKey.java b/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMMasterKey.java deleted file mode 100644 index 6eab5e56..00000000 --- a/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMMasterKey.java +++ /dev/null @@ -1,25 +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/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMOperation.java b/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMOperation.java deleted file mode 100644 index b73d58d1..00000000 --- a/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMOperation.java +++ /dev/null @@ -1,54 +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" 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); -} diff --git a/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMOperationImpl.java b/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMOperationImpl.java deleted file mode 100644 index 715092af..00000000 --- a/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMOperationImpl.java +++ /dev/null @@ -1,100 +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.KeyAgreement; -import javacard.security.Signature; - -public class KMOperationImpl implements KMOperation { - - private KMCipher cipher; - private Signature signature; - private KeyAgreement keyAgreement; - - public KMOperationImpl(KMCipher cipher) { - this.cipher = cipher; - this.signature = null; - this.keyAgreement = null; - } - - public KMOperationImpl(Signature sign) { - this.cipher = null; - this.signature = sign; - this.keyAgreement = null; - } - - public KMOperationImpl(KeyAgreement keyAgreement) { - this.cipher = null; - this.signature = null; - this.keyAgreement = keyAgreement; - } - - @Override - public short update(byte[] inputDataBuf, short inputDataStart, short inputDataLength, - byte[] outputDataBuf, short outputDataStart) { - return cipher - .update(inputDataBuf, inputDataStart, inputDataLength, outputDataBuf, outputDataStart); - } - - @Override - public short update(byte[] inputDataBuf, short inputDataStart, short inputDataLength) { - signature.update(inputDataBuf, inputDataStart, inputDataLength); - return 0; - } - - @Override - public short finish(byte[] inputDataBuf, short inputDataStart, short inputDataLength, - byte[] outputDataBuf, short outputDataStart) { - if (cipher != null) { - return cipher - .doFinal(inputDataBuf, inputDataStart, inputDataLength, outputDataBuf, outputDataStart); - } else if (keyAgreement != null) { - return keyAgreement.generateSecret(inputDataBuf, inputDataStart, inputDataLength, - outputDataBuf, outputDataStart); - } else { - KMException.throwIt(KMError.UNKNOWN_ERROR); - } - return 0; - } - - @Override - public short sign(byte[] inputDataBuf, short inputDataStart, short inputDataLength, - byte[] signBuf, short signStart) { - return signature.sign(inputDataBuf, inputDataStart, inputDataLength, signBuf, signStart); - } - - @Override - public boolean verify(byte[] inputDataBuf, short inputDataStart, short inputDataLength, - byte[] signBuf, short signStart, short signLength) { - return signature - .verify(inputDataBuf, inputDataStart, inputDataLength, signBuf, signStart, signLength); - } - - @Override - public void abort() { - // do nothing - } - - @Override - public void updateAAD(byte[] dataBuf, short dataStart, short dataLength) { - cipher.updateAAD(dataBuf, dataStart, dataLength); - } - - @Override - public short getAESGCMOutputSize(short dataSize, short macLength) { - return cipher.getAesGcmOutputSize(dataSize, macLength); - } -} diff --git a/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMPreSharedKey.java b/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMPreSharedKey.java deleted file mode 100644 index 86bb1df9..00000000 --- a/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMPreSharedKey.java +++ /dev/null @@ -1,25 +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/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMRsa2048NoDigestSignature.java b/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMRsa2048NoDigestSignature.java deleted file mode 100644 index f9e8875d..00000000 --- a/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMRsa2048NoDigestSignature.java +++ /dev/null @@ -1,166 +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.framework.Util; -import javacard.security.CryptoException; -import javacard.security.Key; -import javacard.security.Signature; -import javacardx.crypto.Cipher; - -public class KMRsa2048NoDigestSignature extends Signature { - - private Cipher inst; // ALG_RSA_NOPAD. - private byte padding; - private byte[] rsaModulus; // to compare with the data value - - public KMRsa2048NoDigestSignature(Cipher ciph, byte padding, byte[] mod, short start, short len) { - inst = ciph; - this.padding = padding; - if (len != 256) { - CryptoException.throwIt(CryptoException.INVALID_INIT); - } - rsaModulus = new byte[256]; - Util.arrayCopyNonAtomic(mod, start, rsaModulus, (short) 0, len); - } - - @Override - public void init(Key key, byte b) throws CryptoException { - - } - - @Override - public void init(Key key, byte b, byte[] bytes, short i, short i1) throws CryptoException { - } - - @Override - public void setInitialDigest(byte[] bytes, short i, short i1, byte[] bytes1, short i2, short i3) - throws CryptoException { - } - - @Override - public byte getAlgorithm() { - return 0; - } - - @Override - public byte getMessageDigestAlgorithm() { - return 0; - } - - @Override - public byte getCipherAlgorithm() { - return 0; - } - - @Override - public byte getPaddingAlgorithm() { - return 0; - } - - @Override - public short getLength() throws CryptoException { - return 0; - } - - @Override - public void update(byte[] bytes, short i, short i1) throws CryptoException { - } - - @Override - public short sign(byte[] bytes, short i, short i1, byte[] bytes1, short i2) - throws CryptoException { - byte[] inputData = padData(bytes, i, i1); - return inst.doFinal(inputData, (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 { - // Public key operations not handled here. - return false; - } - - @Override - public boolean verifyPreComputedHash(byte[] bytes, short i, short i1, byte[] bytes1, short i2, - short i3) throws CryptoException { - return false; - } - - private byte[] padData(byte[] buf, short start, short len) { - byte[] inputData = new byte[256]; - if (!isValidData(buf, start, len)) { - CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); - } - Util.arrayFillNonAtomic(inputData, (short) 0, (short) 256, (byte) 0x00); - if (padding == KMType.PADDING_NONE) { // add zero to right - } else if (padding == KMType.RSA_PKCS1_1_5_SIGN) {// 0x00||0x01||PS||0x00 - inputData[0] = 0x00; - inputData[1] = 0x01; - Util.arrayFillNonAtomic(inputData, (short) 2, (short) (256 - len - 3), (byte) 0xFF); - inputData[(short) (256 - len - 1)] = 0x00; - } else { - CryptoException.throwIt(CryptoException.ILLEGAL_USE); - } - Util.arrayCopyNonAtomic(buf, start, inputData, (short) (256 - len), len); - return inputData; - } - - private boolean isValidData(byte[] buf, short start, short len) { - if (padding == KMType.PADDING_NONE) { - if (len > 256) { - return false; - } else if (len == 256) { - short v = unsignedByteArrayCompare(buf, start, rsaModulus, (short) 0, len); - if (v > 0) { - return false; - } - } - } else {//pkcs1 no digest - if (len > 245) { - KMException.throwIt(KMError.INVALID_INPUT_LENGTH); - return false; - } - } - return true; - } - - private 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; - } -} diff --git a/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMSEProvider.java b/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMSEProvider.java deleted file mode 100644 index 8252ca7f..00000000 --- a/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMSEProvider.java +++ /dev/null @@ -1,779 +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; - -/** - * 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 extends KMUpgradable { - // Provision related constants. - public static final byte CERTIFICATE_CHAIN = 0; - public static final byte CERTIFICATE_EXPIRY = 1; - public static final byte CERTIFICATE_ISSUER = 2; - - /** - * 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. 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 hmacKey is the computed hmac key. - * @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( - KMDeviceUniqueKey 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 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 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(byte[] keyData, short offset, short length); - - /** - * 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 keySizeBits key size in bits. - * @return An instance of KMMasterKey. - */ - KMMasterKey createMasterKey(short keySizeBits); - - /** - * Returns the master key. - * - * @return Instance of the KMMasterKey - */ - KMMasterKey getMasterKey(); - - /** - * 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(); - - /** - * Returns the preshared key. - * - * @return Instance of the KMPreSharedKey. - */ - KMPreSharedKey getPresharedKey(); - - /** - * Returns the value of the attestation id. - * - * @param tag - attestation id tag key as defined KMType. - * @param buffer - memorey buffer in which value of the id must be copied - * @param start - start offset in the buffer - * @return length - length of the returned attestation id value. - */ - short getAttestationId(short tag, byte[] buffer, short start); - - /** - * Delete the attestation ids permanently. - */ - void deleteAttestationIds(); - - /** - * Get Verified Boot hash. Part of RoT. Part of data sent by the aosp bootloader. - */ - short getVerifiedBootHash(byte[] buffer, short start); - - /** - * Get Boot Key. Part of RoT. Part of data sent by the aosp bootloader. - */ - short getBootKey(byte[] buffer, short start); - - /** - * Get Boot state. Part of RoT. Part of data sent by the aosp bootloader. - */ - short getBootState(); - - /** - * Returns true if device bootloader is locked. Part of RoT. Part of data sent by the aosp - * bootloader. - */ - boolean isDeviceBootLocked(); - - /** - * Get Boot patch level. Part of data sent by the aosp bootloader. - */ - short getBootPatchLevel(byte[] buffer, short start); - - /** - * 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. - */ - KMDeviceUniqueKey createDeviceUniqueKey(boolean testMode, - byte[] pubKey, short pubKeyOff, short pubKeyLen, - byte[] privKey, short privKeyOff, short privKeyLen); - - /** - * Returns the instance KMDeviceUnique if it is created. - * - * @param testMode Indicates if current execution is for test or production. - * @return instance of KMDeviceUniqueKey if present; null otherwise. - */ - KMDeviceUniqueKey getDeviceUniqueKey(boolean testMode); - - /** - * Persists the additional certificate chain in persistent memory. - * - * @param buf buffer containing the cbor encoded additional certificate chain. - * @param offset start offset of the buffer. - * @param len length of the buffer. - */ - void persistAdditionalCertChain(byte[] buf, short offset, short len); - - /** - * Returns the additional certificate chain length. - * - * @return length of the encoded additional certificate chain. - */ - short getAdditionalCertChainLength(); - - /** - * Returns the additional certificate chain. - * - * @return additional cert chain. - */ - byte[] getAdditionalCertChain(); - - - /** - * Returns the boot certificate chain. - * - * @return boot certificate chain. - */ - byte[] getBootCertificateChain(); - - /** - * Returns the computed Hmac key. - * - * @return Instance of the computed hmac key. - */ - KMComputedHmacKey getComputedHmacKey(); - - /** - * 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); - - public boolean isProvisionLocked(); - -/** - * This function creates an ECKey and initializes the ECPrivateKey with the provided input key - * data. The initialized Key is maintained by the SEProvider. This function should be called only - * while provisioning the attestation key. - * - * @param keyData buffer containing the ec private key. - * @param offset start of the buffer. - * @param length length of the buffer. - * @return An instance of KMAttestationKey. - */ - KMAttestationKey createAttestationKey(byte[] keyData, short offset, short length); - /** - * This operation persists the provision data in the persistent memory. - * - * @param buf buffer which contains all the provision data. - * @param certChainOff is the start of the cert chain. - * @param certChainLen is the length of the cert chain. - * @param certIssuerOff is the start of the cert issuer. - * @param certIssuerLen is the length of the cert issuer. - * @param certExpiryOff is the start of the cert expiry. - * @param certExpiryLen is the length of the cert expiry. - */ - void persistProvisionData(byte[] buf, short certChainOff, short certChainLen, - short certIssuerOff, short certIssuerLen, short certExpiryOff, short certExpiryLen); - - /** - * The operation reads the provisioned data from persistent memory. - * - * @param dataType type of the provision data to read. - * @param buf is the start of data buffer. - * @param offset is the start of the data. - * @return the length of the data buffer in bytes. - */ - short readProvisionedData(byte dataType, byte[] buf, short offset); - - /** - * This function returns the provisioned data length. - * - * @param dataType type of the provision data to read. - * @return length of the certificate chain. - */ - short getProvisionedDataLength(byte dataType); - /** - * Returns the attestation key. - * - * @return Instance of the KMAttestationKey. - */ - KMAttestationKey getAttestationKey(); - - -} diff --git a/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMType.java b/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMType.java deleted file mode 100644 index acae1362..00000000 --- a/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMType.java +++ /dev/null @@ -1,348 +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" 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; - protected static final byte TLV_HEADER_SIZE = 3; - - // 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; - // 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; - - // 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; - - // Verified Boot Key - public static final short VERIFIED_BOOT_KEY = (short) 0xF004; - - // Verified Boot Hash - public static final short VERIFIED_BOOT_HASH = (short) 0xF005; - - // 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; - // 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; - // 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; - - // 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_NO_DIGEST = 1; - public static final byte BUF_EC_NO_DIGEST = 2; - public static final byte BUF_BLOCK_ALIGN = 3; -} diff --git a/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMUpgradable.java b/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMUpgradable.java deleted file mode 100644 index 9bca1c8c..00000000 --- a/Applet/JCardSimProviderLib/src/com/android/javacard/seprovider/KMUpgradable.java +++ /dev/null @@ -1,30 +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 org.globalplatform.upgrade.Element; - -public interface KMUpgradable { - - void onSave(Element ele); - - void onRestore(Element ele); - - short getBackupPrimitiveByteCount(); - - short getBackupObjectCount(); - -} diff --git a/Applet/src/com/android/javacard/keymaster/KMKeymasterSpecification.java b/Applet/src/com/android/javacard/keymaster/KMKeymasterSpecification.java deleted file mode 100644 index 8001d767..00000000 --- a/Applet/src/com/android/javacard/keymaster/KMKeymasterSpecification.java +++ /dev/null @@ -1,301 +0,0 @@ -package com.android.javacard.keymaster; - -import static com.android.javacard.keymaster.KMKeymasterApplet.seProvider; - -import com.android.javacard.seprovider.KMAttestationCert; -import com.android.javacard.seprovider.KMException; -import com.android.javacard.seprovider.KMSEProvider; -import javacard.framework.APDU; -import javacard.framework.ISO7816; -import javacard.framework.Util; - -public class KMKeymasterSpecification implements KMSpecification { - - // getHardwareInfo constants. - private static final byte[] JAVACARD_KEYMASTER_DEVICE = { - 0x4A, 0x61, 0x76, 0x61, 0x63, 0x61, 0x72, 0x64, 0x4B, 0x65, 0x79, 0x6D, 0x61, 0x73, 0x74, - 0x65, 0x72, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, - }; - private static final byte[] GOOGLE = {0x47, 0x6F, 0x6F, 0x67, 0x6C, 0x65}; - private static final byte[] X509Subject = { - 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 SERIAL_NUM = (byte) 0x01; - - - @Override - public short getHardwareInfo() { - short respPtr = KMArray.instance((short) 4); - KMArray resp = KMArray.cast(respPtr); - resp.add((short) 0, KMInteger.uint_16(KMError.OK)); - resp.add((short) 1, KMEnum.instance(KMType.HARDWARE_TYPE, KMType.STRONGBOX)); - resp.add( - (short) 2, - KMByteBlob.instance( - JAVACARD_KEYMASTER_DEVICE, (short) 0, (short) JAVACARD_KEYMASTER_DEVICE.length)); - resp.add((short) 3, KMByteBlob.instance(GOOGLE, (short) 0, (short) GOOGLE.length)); - return respPtr; - } - - @Override - public short makeKeyCharacteristics(short keyParams, short osVersion, short osPatch, - short vendorPatch, short bootPatch, short origin, byte[] scratchPad) { - short strongboxParams = KMKeyParameters.makeSbEnforced( - keyParams, (byte) origin, osVersion, osPatch, vendorPatch, bootPatch, scratchPad); - short teeParams = KMKeyParameters.makeTeeEnforced(keyParams, scratchPad); - short swParams = KMKeyParameters.makeKeystoreEnforced(keyParams, scratchPad); - short hwParams = KMKeyParameters.makeHwEnforced(strongboxParams, teeParams); - short arr = KMArray.instance((short) 0); - short emptyParams = KMKeyParameters.instance(arr); - short keyCharacteristics = KMKeyCharacteristics.instance(); - KMKeyCharacteristics.cast(keyCharacteristics).setStrongboxEnforced(hwParams); - KMKeyCharacteristics.cast(keyCharacteristics).setKeystoreEnforced(swParams); - KMKeyCharacteristics.cast(keyCharacteristics).setTeeEnforced(emptyParams); - return keyCharacteristics; - } - - @Override - public short makeKeyCharacteristicsForKeyblob(short swParams, short sbParams, short teeParams) { - short keyChars = KMKeyCharacteristics.instance(); - KMKeyCharacteristics.cast(keyChars).setStrongboxEnforced(sbParams); - KMKeyCharacteristics.cast(keyChars).setKeystoreEnforced(swParams); - KMKeyCharacteristics.cast(keyChars).setTeeEnforced(teeParams); - return keyChars; - } - - @Override - public boolean canCreateEarlyBootKeys() { - return false; - } - - @Override - public short getHardwareParamters(short sbParams, short teeParams) { - return sbParams; - } - - @Override - public short concatParamsForAuthData(short keyBlobPtr, short hwParams, short swParams, - short hiddenParams, short pubKey) { - short arrayLen = 3; - if (pubKey != KMType.INVALID_VALUE) { - arrayLen = 4; - } - short params = KMArray.instance((short) arrayLen); - KMArray.cast(params).add((short) 0, KMKeyParameters.cast(hwParams).getVals()); - KMArray.cast(params).add((short) 1, KMKeyParameters.cast(swParams).getVals()); - KMArray.cast(params).add((short) 2, KMKeyParameters.cast(hiddenParams).getVals()); - if (4 == arrayLen) { - KMArray.cast(params).add((short) 3, pubKey); - } - return params; - } - - @Override - public boolean isFactoryAttestationSupported() { - return true; - } - - @Override - public KMAttestationCert makeCommonCert(short swParams, short hwParams, short keyParams, - byte[] scratchPad, KMSEProvider seProvider) { - boolean rsaCert = (KMEnumTag.getValue(KMType.ALGORITHM, hwParams) == KMType.RSA); - KMAttestationCert cert = KMAttestationCertImpl.instance(rsaCert, seProvider); - // notBefore - short notBefore = - KMKeyParameters.findTag(KMType.DATE_TAG, KMType.ACTIVE_DATETIME, swParams); - if (notBefore == KMType.INVALID_VALUE) { - notBefore = - KMKeyParameters.findTag(KMType.DATE_TAG, KMType.CREATION_DATETIME, swParams); - if (notBefore == KMType.INVALID_VALUE) { - KMException.throwIt(KMError.INVALID_KEY_BLOB); - } - } - notBefore = KMIntegerTag.cast(notBefore).getValue(); - cert.notBefore(notBefore, false, scratchPad); - // notAfter - // expiry time - byte blob - boolean derEncoded = false; - short notAfter = - KMKeyParameters.findTag(KMType.DATE_TAG, KMType.USAGE_EXPIRE_DATETIME, swParams); - if (notAfter == KMType.INVALID_VALUE) { - notAfter = getProvisionedCertificateData(seProvider, KMSEProvider.CERTIFICATE_EXPIRY); - derEncoded = true; - } - cert.notAfter(notAfter, derEncoded, scratchPad); - // SubjectName - cert.subjectName(KMByteBlob.instance(X509Subject, (short) 0, (short) X509Subject.length)); - // Serial - short serialNumber = KMByteBlob.instance((short) 1); - KMByteBlob.cast(serialNumber).add((short) 0, SERIAL_NUM); - cert.serialNumber(serialNumber); - // Issuer. - cert.issuer(getProvisionedCertificateData(seProvider, KMSEProvider.CERTIFICATE_ISSUER)); - return cert; - } - - @Override - public boolean isKeyAgreementSupported() { - return false; - } - - private short getProvisionedCertificateData(KMSEProvider kmseProvider, byte dataType) { - short len = seProvider.getProvisionedDataLength(dataType); - if (len == 0) { - KMException.throwIt(KMError.INVALID_DATA); - } - short ptr = KMByteBlob.instance(len); - seProvider.readProvisionedData( - dataType, - KMByteBlob.cast(ptr).getBuffer(), - KMByteBlob.cast(ptr).getStartOff()); - return ptr; - } - - @Override - public short getConfirmationToken(short confToken, short keyParams) { - short cToken = - KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.CONFIRMATION_TOKEN, keyParams); - if (cToken == KMType.INVALID_VALUE) { - KMException.throwIt(KMError.NO_USER_CONFIRMATION); - } - return KMByteTag.cast(cToken).getValue(); - } - - @Override - public short getKMVerificationTokenExp() { - return KMVerificationToken.exp2(); - } - - @Override - public short getMacFromVerificationToken(short verToken) { - return KMVerificationToken.cast(verToken).getMac((short) 0x04); - } - - @Override - public short getMgf1Digest(short keyParams, short hwParams) { - return KMType.SHA1; - } - - @Override - public short generateAttestKeyExp() { - // Arguments - short keyParams = KMKeyParameters.expAny(); - short keyBlob = KMByteBlob.exp(); - short argsProto = KMArray.instance((short) 2); - KMArray.cast(argsProto).add((short) 0, keyBlob); - KMArray.cast(argsProto).add((short) 1, keyParams); - return argsProto; - } - - @Override - public void getAttestKeyInputParameters(short arrPtr, short[] data, byte keyBlobOff, - byte keyParametersOff, - byte attestKeyBlobOff, byte attestKeyParamsOff, byte attestKeyIssuerOff) { - data[keyBlobOff] = KMArray.cast(arrPtr).get((short) 0); - data[keyParametersOff] = KMArray.cast(arrPtr).get((short) 1); - data[attestKeyBlobOff] = KMType.INVALID_VALUE; - data[attestKeyParamsOff] = KMType.INVALID_VALUE; - data[attestKeyIssuerOff] = KMType.INVALID_VALUE; - } - - @Override - public short prepareBeginResp(short paramsPtr, short opHandlePtr, short bufModPtr, - short macLengthPtr) { - short resp = KMArray.instance((short) 3); - KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); - KMArray.cast(resp).add((short) 1, paramsPtr); - KMArray.cast(resp).add((short) 2, opHandlePtr); - return resp; - } - - @Override - public short prepareFinishExp() { - short byteBlob = KMByteBlob.exp(); - short cmd = KMArray.instance((short) 6); - KMArray.cast(cmd).add((short) 0, KMInteger.exp());//op handle - short keyParam = KMKeyParameters.exp(); - KMArray.cast(cmd).add((short) 1, keyParam);// Key Parameters - KMArray.cast(cmd).add((short) 2, byteBlob);// input data - KMArray.cast(cmd).add((short) 3, byteBlob); // signature - short authToken = KMHardwareAuthToken.exp(); - KMArray.cast(cmd).add((short) 4, authToken); // auth token - short verToken = getKMVerificationTokenExp(); - KMArray.cast(cmd).add((short) 5, verToken); // time stamp token - return cmd; - } - - @Override - public short prepareUpdateExp() { - short cmd = KMArray.instance((short) 5); - // Arguments - short keyParams = KMKeyParameters.exp(); - KMArray.cast(cmd).add((short) 0, KMInteger.exp()); - KMArray.cast(cmd).add((short) 1, keyParams); - KMArray.cast(cmd).add((short) 2, KMByteBlob.exp()); - short authToken = KMHardwareAuthToken.exp(); - KMArray.cast(cmd).add((short) 3, authToken); - short verToken = getKMVerificationTokenExp(); - KMArray.cast(cmd).add((short) 4, verToken); - return cmd; - } - - @Override - public void getUpdateInputParameters(short arrPtr, short[] data, byte opHandleOff, - byte keyParametersOff, byte inputDataOff, byte hwTokenOff, - byte verToken) { - data[opHandleOff] = KMArray.cast(arrPtr).get((short) 0); - data[keyParametersOff] = KMArray.cast(arrPtr).get((short) 1); - data[inputDataOff] = KMArray.cast(arrPtr).get((short) 2); - data[hwTokenOff] = KMArray.cast(arrPtr).get((short) 3); - data[verToken] = KMArray.cast(arrPtr).get((short) 4); - } - - @Override - public void getFinishInputParameters(short arrPtr, short[] data, byte opHandleOff, - byte keyParametersOff, byte inputDataOff, byte signDataOff, byte hwTokenOff, byte verToken, - byte confToken) { - data[opHandleOff] = KMArray.cast(arrPtr).get((short) 0); - data[keyParametersOff] = KMArray.cast(arrPtr).get((short) 1); - data[inputDataOff] = KMArray.cast(arrPtr).get((short) 2); - data[signDataOff] = KMArray.cast(arrPtr).get((short) 3); - data[hwTokenOff] = KMArray.cast(arrPtr).get((short) 4); - data[verToken] = KMArray.cast(arrPtr).get((short) 5); - data[confToken] = KMType.INVALID_VALUE; - } - - @Override - public short prepareFinishResp(short outputPtr) { - short keyParam = KMArray.instance((short) 0); - keyParam = KMKeyParameters.instance(keyParam); - short resp = KMArray.instance((short) 3); - KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); - KMArray.cast(resp).add((short) 1, keyParam); - KMArray.cast(resp).add((short) 2, outputPtr); - return resp; - } - - @Override - public short prepareUpdateResp(short outputPtr, short inputConsumedPtr) { - short resp = KMArray.instance((short) 4); - KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); - KMArray.cast(resp).add((short) 1, inputConsumedPtr); - short keyParm = KMKeyParameters.instance(KMArray.instance((short) 0)); - KMArray.cast(resp).add((short) 2, keyParm); - KMArray.cast(resp).add((short) 3, outputPtr); - return resp; - } - - @Override - public short validateApduHeader(APDU apdu) { - return KMError.OK; - } - - @Override - public boolean isAssociatedDataTagSupported() { - return true; - } -} diff --git a/Applet/src/com/android/javacard/keymaster/KMKeymintSpecification.java b/Applet/src/com/android/javacard/keymaster/KMKeymintSpecification.java deleted file mode 100644 index eef71995..00000000 --- a/Applet/src/com/android/javacard/keymaster/KMKeymintSpecification.java +++ /dev/null @@ -1,328 +0,0 @@ -package com.android.javacard.keymaster; - -import com.android.javacard.seprovider.KMAttestationCert; -import com.android.javacard.seprovider.KMException; -import com.android.javacard.seprovider.KMSEProvider; -import javacard.framework.APDU; -import javacard.framework.ISO7816; -import javacard.framework.ISOException; -import javacard.framework.Util; - -public class KMKeymintSpecification implements KMSpecification { - - public static final byte[] JAVACARD_KEYMINT_DEVICE = { - 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 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, - }; - - @Override - public short getHardwareInfo() { - final byte version = 1; - // 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( - JAVACARD_KEYMINT_DEVICE, (short) 0, (short) JAVACARD_KEYMINT_DEVICE.length)); - resp.add((short) 4, KMByteBlob.instance(GOOGLE, (short) 0, (short) GOOGLE.length)); - resp.add((short) 5, KMInteger.uint_8((byte) 1)); - return respPtr; - } - - @Override - public short makeKeyCharacteristics(short keyParams, short osVersion, short osPatch, - short vendorPatch, short bootPatch, short origin, byte[] scratchPad) { - short strongboxParams = KMKeyParameters.makeSbEnforced( - keyParams, (byte) origin, osVersion, osPatch, vendorPatch, bootPatch, scratchPad); - short teeParams = KMKeyParameters.makeTeeEnforced(keyParams, scratchPad); - short swParams = KMKeyParameters.makeKeystoreEnforced(keyParams, scratchPad); - //short emptyParam = KMArray.instance((short) 0); - short keyCharacteristics = KMKeyCharacteristics.instance(); - KMKeyCharacteristics.cast(keyCharacteristics).setStrongboxEnforced(strongboxParams); - KMKeyCharacteristics.cast(keyCharacteristics).setKeystoreEnforced(swParams); - KMKeyCharacteristics.cast(keyCharacteristics).setTeeEnforced(teeParams); - return keyCharacteristics; - } - - @Override - public short makeKeyCharacteristicsForKeyblob(short swParams, short sbParams, short teeParams) { - short keyChars = KMKeyCharacteristics.instance(); - short emptyParam = KMArray.instance((short) 0); - emptyParam = KMKeyParameters.instance(emptyParam); - KMKeyCharacteristics.cast(keyChars).setStrongboxEnforced(sbParams); - KMKeyCharacteristics.cast(keyChars).setKeystoreEnforced(emptyParam); - KMKeyCharacteristics.cast(keyChars).setTeeEnforced(teeParams); - return keyChars; - } - - @Override - public boolean canCreateEarlyBootKeys() { - return true; - } - - @Override - public short getHardwareParamters(short sbParams, short teeParams) { - return KMKeyParameters.makeHwEnforced(sbParams, teeParams); - } - - @Override - public short concatParamsForAuthData(short keyBlobPtr, short hwParams, short swParams, - short hiddenParams, short pubKey) { - short arrayLen = 2; - if (pubKey != KMType.INVALID_VALUE) { - arrayLen = 3; - } - short params = KMArray.instance((short) arrayLen); - KMArray.cast(params).add((short) 0, KMKeyParameters.cast(hwParams).getVals()); - KMArray.cast(params).add((short) 1, KMKeyParameters.cast(hiddenParams).getVals()); - if (3 == arrayLen) { - KMArray.cast(params).add((short) 2, pubKey); - } - return params; - } - - @Override - public boolean isFactoryAttestationSupported() { - return false; - } - - @Override - public KMAttestationCert makeCommonCert(short swParams, short hwParams, short keyParams, - byte[] scratchPad, KMSEProvider seProvider) { - short alg = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, keyParams); - boolean rsaCert = KMEnumTag.cast(alg).getValue() == KMType.RSA; - KMAttestationCert cert = KMAttestationCertImpl.instance(rsaCert, seProvider); - - // Validity period must be specified - short notBefore = KMKeyParameters.findTag(KMType.DATE_TAG, KMType.CERTIFICATE_NOT_BEFORE, - keyParams); - 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, - keyParams); - 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, keyParams); - 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; - } - - @Override - public short getMgf1Digest(short keyParams, short hwParams) { - short mgfDigest = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, - KMType.RSA_OAEP_MGF_DIGEST, keyParams); - 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); - } - if (!KMEnumArrayTag - .contains(KMType.RSA_OAEP_MGF_DIGEST, mgfDigest, hwParams)) { - KMException.throwIt(KMError.INCOMPATIBLE_MGF_DIGEST); - } - if (mgfDigest != KMType.SHA1 && mgfDigest != KMType.SHA2_256) { - KMException.throwIt(KMError.UNSUPPORTED_MGF_DIGEST); - } - } - return mgfDigest; - } - - @Override - public short generateAttestKeyExp() { - short params = KMKeyParameters.expAny(); - short blob = KMByteBlob.exp(); - // Array of expected arguments - short cmd = KMArray.instance((short) 5); - KMArray.cast(cmd).add((short) 0, blob); //key blob - KMArray.cast(cmd).add((short) 1, params); //keyparamters to be attested. - KMArray.cast(cmd).add((short) 2, blob); //attest key blob - KMArray.cast(cmd).add((short) 3, params); //attest key params - KMArray.cast(cmd).add((short) 4, blob); //attest issuer - return cmd; - } - - @Override - public void getAttestKeyInputParameters(short arrPtr, short[] data, byte keyBlobOff, - byte keyParametersOff, - byte attestKeyBlobOff, byte attestKeyParamsOff, byte attestKeyIssuerOff) { - data[keyBlobOff] = KMArray.cast(arrPtr).get((short) 0); - data[keyParametersOff] = KMArray.cast(arrPtr).get((short) 1); - data[attestKeyBlobOff] = KMArray.cast(arrPtr).get((short) 2); - data[attestKeyParamsOff] = KMArray.cast(arrPtr).get((short) 3); - data[attestKeyIssuerOff] = KMArray.cast(arrPtr).get((short) 4); - } - - @Override - public short prepareBeginResp(short paramsPtr, short opHandlePtr, short bufModePtr, - short macLengthPtr) { - short resp = KMArray.instance((short) 5); - KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); - KMArray.cast(resp).add((short) 1, paramsPtr); - KMArray.cast(resp).add((short) 2, opHandlePtr); - KMArray.cast(resp).add((short) 3, bufModePtr); - KMArray.cast(resp).add((short) 4, macLengthPtr); - return resp; - } - - @Override - public short prepareFinishExp() { - short byteBlob = KMByteBlob.exp(); - short cmd = KMArray.instance((short) 6); - KMArray.cast(cmd).add((short) 0, KMInteger.exp());//op handle - KMArray.cast(cmd).add((short) 1, byteBlob);// input data - KMArray.cast(cmd).add((short) 2, byteBlob); // signature - short authToken = KMHardwareAuthToken.exp(); - KMArray.cast(cmd).add((short) 3, authToken); // auth token - short verToken = getKMVerificationTokenExp(); - KMArray.cast(cmd).add((short) 4, verToken); // time stamp token - KMArray.cast(cmd).add((short) 5, byteBlob); // confirmation token - return cmd; - } - - @Override - public short prepareUpdateExp() { - 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 = getKMVerificationTokenExp(); - KMArray.cast(cmd).add((short) 3, verToken); - return cmd; - } - - @Override - public void getUpdateInputParameters(short arrPtr, short[] data, byte opHandleOff, - byte keyParametersOff, byte inputDataOff, byte hwTokenOff, - byte verToken) { - data[opHandleOff] = KMArray.cast(arrPtr).get((short) 0); - data[inputDataOff] = KMArray.cast(arrPtr).get((short) 1); - data[hwTokenOff] = KMArray.cast(arrPtr).get((short) 2); - data[verToken] = KMArray.cast(arrPtr).get((short) 3); - } - - @Override - public void getFinishInputParameters(short arrPtr, short[] data, byte opHandleOff, - byte keyParametersOff, byte inputDataOff, byte signDataOff, byte hwTokenOff, byte verToken, - byte confToken) { - data[opHandleOff] = KMArray.cast(arrPtr).get((short) 0); - data[inputDataOff] = KMArray.cast(arrPtr).get((short) 1); - data[signDataOff] = KMArray.cast(arrPtr).get((short) 2); - data[hwTokenOff] = KMArray.cast(arrPtr).get((short) 3); - data[verToken] = KMArray.cast(arrPtr).get((short) 4); - data[confToken] = KMArray.cast(arrPtr).get((short) 5); - } - - @Override - public short prepareFinishResp(short outputPtr) { - short resp = KMArray.instance((short) 2); - KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); - KMArray.cast(resp).add((short) 1, outputPtr); - return resp; - } - - @Override - public short prepareUpdateResp(short outputPtr, short inputConsumedPtr) { - short resp = KMArray.instance((short) 2); - KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); - KMArray.cast(resp).add((short) 1, outputPtr); - return resp; - } - - - @Override - public short validateApduHeader(APDU apdu) { - byte[] apduBuffer = apdu.getBuffer(); - short P1P2 = Util.getShort(apduBuffer, ISO7816.OFFSET_P1); - byte ins = apduBuffer[ISO7816.OFFSET_INS]; - switch (ins) { - case KMKeymasterApplet.INS_GET_CERT_CHAIN_CMD: - return KMError.UNSUPPORTED_INSTRUCTION; - } - if (P1P2 != KMKeymasterApplet.KM_HAL_VERSION) { - return KMError.INVALID_P1P2; - } - return KMError.OK; - } - - @Override - public boolean isAssociatedDataTagSupported() { - return false; - } - - @Override - public boolean isKeyAgreementSupported() { - return true; - } - - @Override - public short getConfirmationToken(short confToken, short keyParams) { - if (0 == KMByteBlob.cast(confToken).length()) { - KMException.throwIt(KMError.NO_USER_CONFIRMATION); - } - return confToken; - } - - @Override - public short getKMVerificationTokenExp() { - return KMVerificationToken.exp1(); - } - - @Override - public short getMacFromVerificationToken(short verToken) { - return KMVerificationToken.cast(verToken).getMac((short) 0x02); - } - -} diff --git a/Applet/src/com/android/javacard/keymaster/KMSpecification.java b/Applet/src/com/android/javacard/keymaster/KMSpecification.java deleted file mode 100644 index 2300331c..00000000 --- a/Applet/src/com/android/javacard/keymaster/KMSpecification.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.android.javacard.keymaster; - -import com.android.javacard.seprovider.KMAttestationCert; -import com.android.javacard.seprovider.KMSEProvider; -import javacard.framework.APDU; - -public interface KMSpecification { - - short getHardwareInfo(); - - short makeKeyCharacteristics(short keyParams, short osVersion, short osPatch, short vendorPatch, - short bootPatch, short origin, byte[] scratchPad); - - short makeKeyCharacteristicsForKeyblob(short swParams, short sbParams, short teeParams); - - boolean canCreateEarlyBootKeys(); - - short getHardwareParamters(short sbParams, short teeParams); - - short concatParamsForAuthData(short arrPtr, short hwParams, short swParams, short hiddenParams, - short pubKey); - - boolean isFactoryAttestationSupported(); - - KMAttestationCert makeCommonCert(short swParams, short hwParams, short keyParams, - byte[] scratchPad, KMSEProvider seProvider); - - boolean isKeyAgreementSupported(); - - short getConfirmationToken(short confToken, short keyParams); - - short getKMVerificationTokenExp(); - - short getMacFromVerificationToken(short verToken); - - short getMgf1Digest(short keyParams, short hwParams); - - short generateAttestKeyExp(); - - void getAttestKeyInputParameters(short arrPtr, short[] data, byte keyBlobOff, - byte keyParametersOff, - byte attestKeyBlobOff, byte attestKeyParamsOff, byte attestKeyIssuerOff); - - short prepareBeginResp(short paramsPtr, short opHandlePtr, short bufModePtr, short macLengthPtr); - - short prepareFinishExp(); - - short prepareUpdateExp(); - - void getUpdateInputParameters(short arrPtr, short[] data, byte opHandleOff, - byte keyParametersOff, byte inputDataOff, byte hwTokenOff, - byte verToken); - - void getFinishInputParameters(short arrPtr, short[] data, byte opHandleOff, - byte keyParametersOff, byte inputDataOff, byte signDataOff, byte hwTokenOff, - byte verToken, byte confToken); - - short prepareFinishResp(short outputPtr); - - short prepareUpdateResp(short outputPtr, short inputConsumedPtr); - - short validateApduHeader(APDU apdu); - - boolean isAssociatedDataTagSupported(); -} diff --git a/Applet/src/com/android/javacard/keymaster/KMArray.java b/Applet/src/com/android/javacard/kmdevice/KMArray.java similarity index 80% rename from Applet/src/com/android/javacard/keymaster/KMArray.java rename to Applet/src/com/android/javacard/kmdevice/KMArray.java index a468e924..4c6b0c98 100644 --- a/Applet/src/com/android/javacard/keymaster/KMArray.java +++ b/Applet/src/com/android/javacard/kmdevice/KMArray.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import javacard.framework.ISO7816; import javacard.framework.ISOException; @@ -75,14 +75,14 @@ public static short instance(short length, byte type) { return ptr; } - public static KMArray cast(short ptr) { + private 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) { + private void add(short index, short objPtr) { short len = length(); if (index >= len) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); @@ -93,7 +93,7 @@ public void add(short index, short objPtr) { objPtr); } - public short get(short index) { + private short get(short index) { short len = length(); if (index >= len) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); @@ -102,7 +102,7 @@ public short get(short index) { heap, (short) (getStartOff() + (short) (index * 2))); } - public void swap(short index1, short index2) { + private void swap(short index1, short index2) { short len = length(); if (index1 >= len || index2 >= len) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); @@ -125,25 +125,58 @@ public void swap(short index1, short index2) { indexPtr1); } - public short containedType() { + private short containedType() { return Util.getShort(heap, (short) (KMType.instanceTable[KM_ARRAY_OFFSET] + TLV_HEADER_SIZE)); } - public short getStartOff() { + private short getStartOff() { return (short) (KMType.instanceTable[KM_ARRAY_OFFSET] + TLV_HEADER_SIZE + ARRAY_HEADER_SIZE); } - public short length() { + private short length() { return Util.getShort(heap, (short) (KMType.instanceTable[KM_ARRAY_OFFSET] + TLV_HEADER_SIZE + 2)); } - public byte[] getBuffer() { + private byte[] getBuffer() { return heap; } - public void deleteLastEntry() { + private void deleteLastEntry() { short len = length(); Util.setShort(heap, (short) (instanceTable[KM_ARRAY_OFFSET] + TLV_HEADER_SIZE + 2), (short) (len - 1)); } + + public static void add(short bPtr, short index, short objPtr) { + KMArray.cast(bPtr).add(index, objPtr); + } + + public static short get(short bPtr, short index) { + return KMArray.cast(bPtr).get(index); + } + + public static void swap(short bPtr, short index1, short index2) { + KMArray.cast(bPtr).swap(index1, index2); + } + + public static short containedType(short bPtr) { + return KMArray.cast(bPtr).containedType(); + } + + public static short getStartOff(short bPtr) { + return KMArray.cast(bPtr).getStartOff(); + } + + public static short length(short bPtr) { + return KMArray.cast(bPtr).length(); + } + + public static byte[] getBuffer(short bPtr) { + return KMArray.cast(bPtr).getBuffer(); + } + + public static void deleteLastEntry(short bPtr) { + KMArray.cast(bPtr).deleteLastEntry(); + } + } diff --git a/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java b/Applet/src/com/android/javacard/kmdevice/KMAttestationCertImpl.java similarity index 76% rename from Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java rename to Applet/src/com/android/javacard/kmdevice/KMAttestationCertImpl.java index 961dc1d7..dc3a0557 100644 --- a/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java +++ b/Applet/src/com/android/javacard/kmdevice/KMAttestationCertImpl.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import com.android.javacard.seprovider.KMAESKey; import com.android.javacard.seprovider.KMAttestationCert; @@ -36,95 +36,33 @@ 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 - }; + private static byte[] rsaEncryption; // ecPublicKey - 1.2.840.10045.2.1 - private static final byte[] eccPubKey = { - 0x06, 0x07, 0x2A, (byte) 0x86, 0x48, (byte) 0xCE, 0x3D, 0x02, 0x01 - }; + private static byte[] eccPubKey; // 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 - }; + private static byte[] prime256v1; // Key Usage Extn - 2.5.29.15 - private static final byte[] keyUsageExtn = {0x06, 0x03, 0x55, 0x1D, 0x0F}; + private static byte[] keyUsageExtn; // 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 byte[] androidExtn; private static final short RSA_SIG_LEN = 256; private static final short 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 - }; + private static byte[] X509EcdsaSignAlgIdentifier; // 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 - }; - + private static byte[] X509RsaSignAlgIdentifier; // 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.USAGE_EXPIRE_DATETIME, - KMType.ORIGINATION_EXPIRE_DATETIME, - KMType.ACTIVE_DATETIME, - KMType.UNLOCKED_DEVICE_REQUIRED - }; + private static short[] swTagIds; // 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.AUTH_TIMEOUT, KMType.USER_AUTH_TYPE, - KMType.NO_AUTH_REQUIRED, KMType.USER_SECURE_ID, - KMType.RSA_PUBLIC_EXPONENT, KMType.ECCURVE, KMType.MIN_MAC_LENGTH, - KMType.CALLER_NONCE, KMType.PADDING, KMType.DIGEST, KMType.BLOCK_MODE, - KMType.KEYSIZE, KMType.ALGORITHM, KMType.PURPOSE}; + private static short[] hwTagIds; // Validity is not fixed field // Subject is a fixed field with only CN= Android Keystore Key - same for all the keys - private static final byte[] X509Subject = { - 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 byte[] X509Subject; private static final byte keyUsageSign = (byte) 0x80; // 0 bit private static final byte keyUsageKeyEncipher = (byte) 0x20; // 2nd- bit @@ -134,7 +72,7 @@ public class KMAttestationCertImpl implements KMAttestationCert { private static final byte KEYMASTER_VERSION = 100; private static final byte ATTESTATION_VERSION = 100; - private static final byte[] pubExponent = {0x01, 0x00, 0x01}; + private static byte[] pubExponent; private static final byte SERIAL_NUM = (byte) 0x01; private static final byte X509_VERSION = (byte) 0x02; @@ -179,6 +117,81 @@ public class KMAttestationCertImpl implements KMAttestationCert { private static final byte SERIAL_NUM_MAX_LEN = 20; private static final byte SUBJECT_NAME_MAX_LEN = 32; + public static void initStatics() { + rsaEncryption = new byte[]{ + 0x06, 0x09, 0x2A, (byte) 0x86, 0x48, (byte) 0x86, (byte) 0xF7, 0x0D, 0x01, 0x01, 0x01 + }; + eccPubKey = new byte[] { + 0x06, 0x07, 0x2A, (byte) 0x86, 0x48, (byte) 0xCE, 0x3D, 0x02, 0x01 + }; + prime256v1 = new byte[] { + 0x06, 0x08, 0x2A, (byte) 0x86, 0x48, (byte) 0xCE, 0x3D, 0x03, 0x01, 0x07 + }; + keyUsageExtn = new byte[]{0x06, 0x03, 0x55, 0x1D, 0x0F}; + androidExtn = new byte[]{ + 0x06, 0x0A, 0X2B, 0X06, 0X01, 0X04, 0X01, (byte) 0XD6, 0X79, 0X02, 0X01, 0X11 + }; + X509EcdsaSignAlgIdentifier = new byte[] { + 0x30, + 0x0A, + 0x06, + 0x08, + 0x2A, + (byte) 0x86, + 0x48, + (byte) 0xCE, + (byte) 0x3D, + 0x04, + 0x03, + 0x02 + }; + X509RsaSignAlgIdentifier = new byte[]{ + 0x30, + 0x0D, + 0x06, + 0x09, + 0x2A, + (byte) 0x86, + 0x48, + (byte) 0x86, + (byte) 0xF7, + 0x0D, + 0x01, + 0x01, + 0x0B, + 0x05, + 0x00 + }; + swTagIds = new short[]{ + KMType.ATTESTATION_APPLICATION_ID, + KMType.CREATION_DATETIME, + KMType.USAGE_EXPIRE_DATETIME, + KMType.ORIGINATION_EXPIRE_DATETIME, + KMType.ACTIVE_DATETIME, + KMType.UNLOCKED_DEVICE_REQUIRED + }; + hwTagIds = new short[]{ + 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.AUTH_TIMEOUT, KMType.USER_AUTH_TYPE, + KMType.NO_AUTH_REQUIRED, KMType.USER_SECURE_ID, + KMType.RSA_PUBLIC_EXPONENT, KMType.ECCURVE, KMType.MIN_MAC_LENGTH, + KMType.CALLER_NONCE, KMType.PADDING, KMType.DIGEST, KMType.BLOCK_MODE, + KMType.KEYSIZE, KMType.ALGORITHM, KMType.PURPOSE}; + X509Subject = new byte[]{ + 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 + }; + pubExponent = new byte[]{0x01, 0x00, 0x01}; + } + private KMAttestationCertImpl() { } @@ -260,8 +273,8 @@ public KMAttestationCert notBefore(short obj, boolean derEncoded, byte[] scratch // convert milliseconds to UTC date notBefore = KMUtils.convertToDate(obj, scratchpad, true); }else{ - notBefore = KMByteBlob.instance(KMByteBlob.cast(obj).getBuffer(), - KMByteBlob.cast(obj).getStartOff(), KMByteBlob.cast(obj).length()); + notBefore = KMByteBlob.instance(KMByteBlob.getBuffer(obj), + KMByteBlob.getStartOff(obj), KMByteBlob.length(obj)); } return this; } @@ -285,9 +298,9 @@ public KMAttestationCert notAfter(short usageExpiryTimeObj, boolean derEncoded, //notAfter = certExpirtyTimeObj; } } else { - // notAfter = KMByteBlob.instance(KMByteBlob.cast(usageExpiryTimeObj).getBuffer(), - // KMByteBlob.cast(usageExpiryTimeObj).getStartOff(), - // KMByteBlob.cast(usageExpiryTimeObj).length()); + // notAfter = KMKeymasterApplet.instance(KMKeymasterApplet.cast(usageExpiryTimeObj).getBuffer(), + // KMKeymasterApplet.cast(usageExpiryTimeObj).getStartOff(), + // KMKeymasterApplet.cast(usageExpiryTimeObj).length()); notAfter = usageExpiryTimeObj; } return this; @@ -324,7 +337,7 @@ public KMAttestationCert extensionTag(short tag, boolean hwEnforced) { swParams[swParamsIndex] = tag; swParamsIndex++; } - if (KMTag.getKey(tag) == KMType.PURPOSE) { + if (KMTag.getKMTagKey(tag) == KMType.PURPOSE) { createKeyUsage(tag); } return this; @@ -337,18 +350,18 @@ public KMAttestationCert issuer(short obj) { } private void createKeyUsage(short tag) { - short len = KMEnumArrayTag.cast(tag).length(); + short len = KMEnumArrayTag.length(tag); byte index = 0; while (index < len) { - if (KMEnumArrayTag.cast(tag).get(index) == KMType.SIGN) { + if (KMEnumArrayTag.get(tag, index) == KMType.SIGN) { keyUsage = (byte) (keyUsage | keyUsageSign); - } else if (KMEnumArrayTag.cast(tag).get(index) == KMType.WRAP_KEY) { + } else if (KMEnumArrayTag.get(tag, index) == KMType.WRAP_KEY) { keyUsage = (byte) (keyUsage | keyUsageKeyEncipher); - } else if (KMEnumArrayTag.cast(tag).get(index) == KMType.DECRYPT) { + } else if (KMEnumArrayTag.get(tag, index) == KMType.DECRYPT) { keyUsage = (byte) (keyUsage | keyUsageDataEncipher); - } else if (KMEnumArrayTag.cast(tag).get(index) == KMType.AGREE_KEY){ + } else if (KMEnumArrayTag.get(tag, index)== KMType.AGREE_KEY){ keyUsage = (byte) (keyUsage | keyUsageKeyAgreement); - }else if (KMEnumArrayTag.cast(tag).get(index) == KMType.ATTEST_KEY){ + }else if (KMEnumArrayTag.get(tag, index)== KMType.ATTEST_KEY){ keyUsage = (byte) (keyUsage | keyUsageCertSign); } index++; @@ -361,7 +374,7 @@ private void createKeyUsage(short tag) { } //TODO Serial number, X509Version needa to be passed as parameter - private static void pushTbsCert(boolean rsaCert, boolean rsa) { + private void pushTbsCert(boolean rsaCert, boolean rsa) { short last = stackPtr; if(certMode == KMType.ATTESTATION_CERT || certMode == KMType.FACTORY_PROVISIONED_ATTEST_CERT) { pushExtensions(); @@ -373,14 +386,14 @@ private static void pushTbsCert(boolean rsaCert, boolean rsa) { pushEccSubjectKeyInfo(); } // subject - pushBytes(KMByteBlob.cast(subjectName).getBuffer(), KMByteBlob.cast(subjectName).getStartOff(), - KMByteBlob.cast(subjectName).length()); + pushBytes(KMByteBlob.getBuffer(subjectName), KMByteBlob.getStartOff(subjectName), + KMByteBlob.length(subjectName)); pushValidity(); // issuer - der encoded pushBytes( - KMByteBlob.cast(issuer).getBuffer(), - KMByteBlob.cast(issuer).getStartOff(), - KMByteBlob.cast(issuer).length()); + KMByteBlob.getBuffer(issuer), + KMByteBlob.getStartOff(issuer), + KMByteBlob.length(issuer)); // Algorithm Id if(rsa) { pushAlgorithmId(X509RsaSignAlgIdentifier); @@ -388,9 +401,9 @@ private static void pushTbsCert(boolean rsaCert, boolean rsa) { pushAlgorithmId(X509EcdsaSignAlgIdentifier); } // Serial Number - pushBytes(KMByteBlob.cast(serialNum).getBuffer(), KMByteBlob.cast(serialNum).getStartOff(), - KMByteBlob.cast(serialNum).length()); - pushIntegerHeader(KMByteBlob.cast(serialNum).length()); + pushBytes(KMByteBlob.getBuffer(serialNum), KMByteBlob.getStartOff(serialNum), + KMByteBlob.length(serialNum)); + pushIntegerHeader(KMByteBlob.length(serialNum)); // Version pushByte(X509_VERSION); pushIntegerHeader((short) 1); @@ -400,7 +413,7 @@ private static void pushTbsCert(boolean rsaCert, boolean rsa) { pushSequenceHeader((short) (last - stackPtr)); } - private static void pushExtensions() { + private void pushExtensions() { short last = stackPtr; if (keyUsage != 0) { pushKeyUsage(keyUsage, unusedBits); @@ -413,22 +426,22 @@ private static void pushExtensions() { } // Time SEQUENCE{UTCTime, UTC or Generalized Time) - private static void pushValidity() { + private void pushValidity() { short last = stackPtr; if (notAfter != 0) { pushBytes( - KMByteBlob.cast(notAfter).getBuffer(), - KMByteBlob.cast(notAfter).getStartOff(), - KMByteBlob.cast(notAfter).length()); + KMByteBlob.getBuffer(notAfter), + KMByteBlob.getStartOff(notAfter), + KMByteBlob.length(notAfter)); } else { KMException.throwIt(KMError.INVALID_DATA); } - pushTimeHeader(KMByteBlob.cast(notAfter).length()); + pushTimeHeader(KMByteBlob.length(notAfter)); pushBytes( - KMByteBlob.cast(notBefore).getBuffer(), - KMByteBlob.cast(notBefore).getStartOff(), - KMByteBlob.cast(notBefore).length()); - pushTimeHeader(KMByteBlob.cast(notBefore).length()); + KMByteBlob.getBuffer(notBefore), + KMByteBlob.getStartOff(notBefore), + KMByteBlob.length(notBefore)); + pushTimeHeader(KMByteBlob.length(notBefore)); pushSequenceHeader((short) (last - stackPtr)); } @@ -447,21 +460,21 @@ private static void pushTimeHeader(short len) { // SEQUENCE{SEQUENCE{algId, NULL}, bitString{SEQUENCE{ modulus as positive integer, public // exponent // as positive integer} - private static void pushRsaSubjectKeyInfo() { + private void pushRsaSubjectKeyInfo() { short last = stackPtr; pushBytes(pubExponent, (short) 0, (short) pubExponent.length); pushIntegerHeader((short) pubExponent.length); pushBytes( - KMByteBlob.cast(pubKey).getBuffer(), - KMByteBlob.cast(pubKey).getStartOff(), - KMByteBlob.cast(pubKey).length()); + KMByteBlob.getBuffer(pubKey), + KMByteBlob.getStartOff(pubKey), + KMByteBlob.length(pubKey)); // encode modulus as positive if the MSB is 1. - if (KMByteBlob.cast(pubKey).get((short) 0) < 0) { + if (KMByteBlob.get(pubKey, (short) 0) < 0) { pushByte((byte) 0x00); - pushIntegerHeader((short) (KMByteBlob.cast(pubKey).length() + 1)); + pushIntegerHeader((short) (KMByteBlob.length(pubKey) + 1)); } else { - pushIntegerHeader(KMByteBlob.cast(pubKey).length()); + pushIntegerHeader(KMByteBlob.length(pubKey)); } pushSequenceHeader((short) (last - stackPtr)); pushBitStringHeader((byte) 0x00, (short) (last - stackPtr)); @@ -470,13 +483,13 @@ private static void pushRsaSubjectKeyInfo() { } // SEQUENCE{SEQUENCE{ecPubKey, prime256v1}, bitString{pubKey}} - private static void pushEccSubjectKeyInfo() { + private void pushEccSubjectKeyInfo() { short last = stackPtr; pushBytes( - KMByteBlob.cast(pubKey).getBuffer(), - KMByteBlob.cast(pubKey).getStartOff(), - KMByteBlob.cast(pubKey).length()); - pushBitStringHeader((byte) 0x00, KMByteBlob.cast(pubKey).length()); + KMByteBlob.getBuffer(pubKey), + KMByteBlob.getStartOff(pubKey), + KMByteBlob.length(pubKey)); + pushBitStringHeader((byte) 0x00, KMByteBlob.length(pubKey)); pushEcDsa(); pushSequenceHeader((short) (last - stackPtr)); } @@ -505,22 +518,22 @@ private static void pushRsaEncryption() { // softwareEnforced AuthorizationList, # See below // hardwareEnforced AuthorizationList, # See below // } - private static void pushKeyDescription() { + private void pushKeyDescription() { short last = stackPtr; pushHWParams(); pushSWParams(); if (uniqueId != 0) { pushOctetString( - KMByteBlob.cast(uniqueId).getBuffer(), - KMByteBlob.cast(uniqueId).getStartOff(), - KMByteBlob.cast(uniqueId).length()); + KMByteBlob.getBuffer(uniqueId), + KMByteBlob.getStartOff(uniqueId), + KMByteBlob.length(uniqueId)); } else { pushOctetStringHeader((short) 0); } pushOctetString( - KMByteBlob.cast(attChallenge).getBuffer(), - KMByteBlob.cast(attChallenge).getStartOff(), - KMByteBlob.cast(attChallenge).length()); + KMByteBlob.getBuffer(attChallenge), + KMByteBlob.getStartOff(attChallenge), + KMByteBlob.length(attChallenge)); pushEnumerated(KMType.STRONGBOX); pushByte(KEYMASTER_VERSION); pushIntegerHeader((short) 1); @@ -533,7 +546,7 @@ private static void pushKeyDescription() { pushSequenceHeader((short) (last - stackPtr)); } - private static void pushSWParams() { + private void pushSWParams() { short last = stackPtr; byte index = 0; short length = (short) swTagIds.length; @@ -543,7 +556,7 @@ private static void pushSWParams() { pushSequenceHeader((short) (last - stackPtr)); } - private static void pushHWParams() { + private void pushHWParams() { short last = stackPtr; byte index = 0; short length = (short) hwTagIds.length; @@ -559,10 +572,10 @@ private static void pushHWParams() { pushSequenceHeader((short) (last - stackPtr)); } - private static boolean pushParams(short[] params, short len, short tagId) { + private boolean pushParams(short[] params, short len, short tagId) { short index = 0; while (index < len) { - if (tagId == KMTag.getKey(params[index])) { + if (tagId == KMTag.getKMTagKey(params[index])) { pushTag(params[index]); return true; } @@ -571,50 +584,50 @@ private static boolean pushParams(short[] params, short len, short tagId) { return false; } - private static void pushTag(short tag) { - short type = KMTag.getTagType(tag); - short tagId = KMTag.getKey(tag); + private void pushTag(short tag) { + short type = KMTag.getKMTagType(tag); + short tagId = KMTag.getKMTagKey(tag); short val; switch (type) { case KMType.BYTES_TAG: - val = KMByteTag.cast(tag).getValue(); + val = KMByteTag.getValue(tag); pushBytesTag( tagId, - KMByteBlob.cast(val).getBuffer(), - KMByteBlob.cast(val).getStartOff(), - KMByteBlob.cast(val).length()); + KMByteBlob.getBuffer(val), + KMByteBlob.getStartOff(val), + KMByteBlob.length(val)); break; case KMType.ENUM_TAG: - val = KMEnumTag.cast(tag).getValue(); + val = KMEnumTag.getValue(tag); pushEnumTag(tagId, (byte) val); break; case KMType.ENUM_ARRAY_TAG: - val = KMEnumArrayTag.cast(tag).getValues(); + val = KMEnumArrayTag.getValues(tag); pushEnumArrayTag( tagId, - KMByteBlob.cast(val).getBuffer(), - KMByteBlob.cast(val).getStartOff(), - KMByteBlob.cast(val).length()); + KMByteBlob.getBuffer(val), + KMByteBlob.getStartOff(val), + KMByteBlob.length(val)); break; case KMType.UINT_TAG: case KMType.ULONG_TAG: case KMType.DATE_TAG: - val = KMIntegerTag.cast(tag).getValue(); + val = KMIntegerTag.getValue(tag); pushIntegerTag( tagId, - KMInteger.cast(val).getBuffer(), - KMInteger.cast(val).getStartOff(), - KMInteger.cast(val).length()); + KMInteger.getBuffer(val), + KMInteger.getStartOff(val), + KMInteger.length(val)); break; case KMType.UINT_ARRAY_TAG: case KMType.ULONG_ARRAY_TAG: // According to keymaster 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(); + val = KMIntegerArrayTag.getValues(tag); pushIntegerArrayTag(tagId, val); break; case KMType.BOOL_TAG: - val = KMBoolTag.cast(tag).getVal(); + KMBoolTag.validate(tag); pushBoolTag(tagId); break; default: @@ -635,22 +648,22 @@ private static void pushTag(short tag) { // Unverified (2), // Failed (3), // } - private static void pushRoT() { + private void pushRoT() { short last = stackPtr; // verified boot hash pushOctetString( - KMByteBlob.cast(verifiedHash).getBuffer(), - KMByteBlob.cast(verifiedHash).getStartOff(), - KMByteBlob.cast(verifiedHash).length()); + KMByteBlob.getBuffer(verifiedHash), + KMByteBlob.getStartOff(verifiedHash), + KMByteBlob.length(verifiedHash)); pushEnumerated(verifiedState); pushBoolean(deviceLocked); // verified boot Key pushOctetString( - KMByteBlob.cast(verifiedBootKey).getBuffer(), - KMByteBlob.cast(verifiedBootKey).getStartOff(), - KMByteBlob.cast(verifiedBootKey).length()); + KMByteBlob.getBuffer(verifiedBootKey), + KMByteBlob.getStartOff(verifiedBootKey), + KMByteBlob.length(verifiedBootKey)); // Finally sequence header pushSequenceHeader((short) (last - stackPtr)); @@ -689,17 +702,17 @@ private static void pushEnumArrayTag(short tagId, byte[] buf, short start, short // 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) { + private void pushIntegerArrayTag(short tagId, short arr) { short last = stackPtr; short index = 0; - short len = KMArray.cast(arr).length(); + short len = KMArray.length(arr); short ptr; while (index < len) { - ptr = KMArray.cast(arr).get(index); + ptr = KMArray.get(arr, index); pushInteger( - KMInteger.cast(ptr).getBuffer(), - KMInteger.cast(ptr).getStartOff(), - KMInteger.cast(ptr).length()); + KMInteger.getBuffer(ptr), + KMInteger.getStartOff(ptr), + KMInteger.length(ptr)); index++; } pushSetHeader((short) (last - stackPtr)); @@ -920,12 +933,12 @@ else if (rsaSign) { 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(), + KMByteBlob.getBuffer(attSecret), + KMByteBlob.getStartOff(attSecret), + KMByteBlob.length(attSecret), + KMByteBlob.getBuffer(attMod), + KMByteBlob.getStartOff(attMod), + KMByteBlob.length(attMod), stack, tbsStart, tbsLength, @@ -945,9 +958,9 @@ else if (rsaSign) { } else { sigLen = seProvider .ecSign256( - KMByteBlob.cast(attSecret).getBuffer(), - KMByteBlob.cast(attSecret).getStartOff(), - KMByteBlob.cast(attSecret).length(), + KMByteBlob.getBuffer(attSecret), + KMByteBlob.getStartOff(attSecret), + KMByteBlob.length(attSecret), stack, tbsStart, tbsLength, @@ -1007,18 +1020,18 @@ public KMAttestationCert makeUniqueId(byte[] scratchPad, short scratchPadOff, KMAESKey aesKey = (KMAESKey) masterKey; short mKeyData = KMByteBlob.instance((short) (aesKey.getKeySizeBits() / 8)); aesKey.getKey( - KMByteBlob.cast(mKeyData).getBuffer(), /* Key */ - KMByteBlob.cast(mKeyData).getStartOff()); /* Key start*/ + KMByteBlob.getBuffer(mKeyData), /* Key */ + KMByteBlob.getStartOff(mKeyData)); /* 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*/ + KMByteBlob.getBuffer(mKeyData), /* Key */ + KMByteBlob.getStartOff(mKeyData), /* Key start*/ + KMByteBlob.length(mKeyData), /* Key length*/ scratchPad, /* data */ temp, /* data start */ scratchPadOff, /* data length */ - KMByteBlob.cast(timeOffset).getBuffer(), /* signature buffer */ - KMByteBlob.cast(timeOffset).getStartOff()); /* signature start */ + KMByteBlob.getBuffer(timeOffset), /* signature buffer */ + KMByteBlob.getStartOff(timeOffset)); /* signature start */ if (appIdOff != 32) { KMException.throwIt(KMError.UNKNOWN_ERROR); } @@ -1027,11 +1040,11 @@ public KMAttestationCert makeUniqueId(byte[] scratchPad, short scratchPadOff, @Override public boolean serialNumber(short number){ - short length = KMByteBlob.cast(number).length(); + short length = KMByteBlob.length(number); if(length > SERIAL_NUM_MAX_LEN){ return false; } - byte msb = KMByteBlob.cast(number).get((short)0); + byte msb = KMByteBlob.get(number, (short)0); if(msb < 0 && length > (SERIAL_NUM_MAX_LEN -1)){ return false; } @@ -1042,15 +1055,15 @@ public boolean serialNumber(short number){ @Override public boolean subjectName(short sub){ /* - short length = KMByteBlob.cast(sub).length(); + short length = KMKeymasterApplet.cast(sub).length(); if(length > SUBJECT_NAME_MAX_LEN){ return false; } - Util.arrayCopyNonAtomic(KMByteBlob.cast(sub).getBuffer(), KMByteBlob.cast(sub).getStartOff(), + Util.arrayCopyNonAtomic(KMKeymasterApplet.cast(sub).getBuffer(), KMKeymasterApplet.cast(sub).getStartOff(), subjectName,(short)0,length); subjectLen = length; */ - if(sub == KMType.INVALID_VALUE || KMByteBlob.cast(sub).length() == 0) return false; + if(sub == KMType.INVALID_VALUE || KMByteBlob.length(sub) == 0) return false; subjectName = sub; return true; } diff --git a/Applet/src/com/android/javacard/keymaster/KMBignumTag.java b/Applet/src/com/android/javacard/kmdevice/KMBignumTag.java similarity index 84% rename from Applet/src/com/android/javacard/keymaster/KMBignumTag.java rename to Applet/src/com/android/javacard/kmdevice/KMBignumTag.java index 7b020314..c3a53004 100644 --- a/Applet/src/com/android/javacard/keymaster/KMBignumTag.java +++ b/Applet/src/com/android/javacard/kmdevice/KMBignumTag.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import javacard.framework.ISO7816; import javacard.framework.ISOException; @@ -31,10 +31,14 @@ public class KMBignumTag extends KMTag { private static KMBignumTag prototype; // The allowed tag keys of type bool tag - private static final short[] tags = { - CERTIFICATE_SERIAL_NUM, - }; + private static short[] tags; + public static void initStatics() { + tags = new short[]{ + CERTIFICATE_SERIAL_NUM, + }; + } + private KMBignumTag() { } @@ -77,7 +81,7 @@ public static short instance(short key, short byteBlob) { return ptr; } - public static KMBignumTag cast(short ptr) { + private static KMBignumTag cast(short ptr) { if (heap[ptr] != TAG_TYPE) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } @@ -101,9 +105,25 @@ public short getValue() { public short length() { short blobPtr = Util.getShort(heap, (short) (KMType.instanceTable[KM_BIGNUM_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); - return KMByteBlob.cast(blobPtr).length(); + return KMByteBlob.length(blobPtr); + } + + public static short getKey(short bPtr) { + return KMBignumTag.cast(bPtr).getKey(); + } + + public static short getTagType(short bPtr) { + return KMBignumTag.cast(bPtr).getTagType(); + } + + public static short getValue(short bPtr) { + return KMBignumTag.cast(bPtr).getValue(); } + public static short length(short bPtr) { + return KMBignumTag.cast(bPtr).length(); + } + private static boolean validateKey(short key) { short index = (short) tags.length; while (--index >= 0) { diff --git a/Applet/src/com/android/javacard/keymaster/KMBoolTag.java b/Applet/src/com/android/javacard/kmdevice/KMBoolTag.java similarity index 76% rename from Applet/src/com/android/javacard/keymaster/KMBoolTag.java rename to Applet/src/com/android/javacard/kmdevice/KMBoolTag.java index d1e277a0..eac2e8ce 100644 --- a/Applet/src/com/android/javacard/keymaster/KMBoolTag.java +++ b/Applet/src/com/android/javacard/kmdevice/KMBoolTag.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import com.android.javacard.seprovider.KMException; import javacard.framework.ISO7816; @@ -33,24 +33,29 @@ public class KMBoolTag extends KMTag { private static KMBoolTag prototype; // 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 short[] tags; private KMBoolTag() { } + public static void initStatics() { + // The allowed tag keys of type bool tag. + tags = new short[] { + 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 proto(short ptr) { if (prototype == null) { prototype = new KMBoolTag(); @@ -78,16 +83,20 @@ public static short instance(short key) { return ptr; } - public static KMBoolTag cast(short ptr) { + private static KMBoolTag cast(short ptr) { + validate(ptr); + return proto(ptr); + } + + public static void validate(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); } - + public short getKey() { return Util.getShort(heap, (short) (KMType.instanceTable[KM_BOOL_TAG_OFFSET] + TLV_HEADER_SIZE + 2)); } @@ -99,7 +108,19 @@ public short getTagType() { public byte getVal() { return heap[(short) (KMType.instanceTable[KM_BOOL_TAG_OFFSET] + TLV_HEADER_SIZE + 4)]; } + + public static short getKey(short bPtr) { + return KMBoolTag.cast(bPtr).getKey(); + } + + public static short getTagType(short bPtr) { + return KMBoolTag.cast(bPtr).getTagType(); + } + public static byte getVal(short bPtr) { + return KMBoolTag.cast(bPtr).getVal(); + } + // validate the tag key. private static boolean validateKey(short key) { short index = (short) tags.length; diff --git a/Applet/src/com/android/javacard/keymaster/KMByteBlob.java b/Applet/src/com/android/javacard/kmdevice/KMByteBlob.java similarity index 74% rename from Applet/src/com/android/javacard/keymaster/KMByteBlob.java rename to Applet/src/com/android/javacard/kmdevice/KMByteBlob.java index 0d2627d9..abe9e589 100644 --- a/Applet/src/com/android/javacard/keymaster/KMByteBlob.java +++ b/Applet/src/com/android/javacard/kmdevice/KMByteBlob.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import javacard.framework.ISO7816; import javacard.framework.ISOException; @@ -47,7 +47,7 @@ public static short exp() { // return an empty byte blob instance public static short instance(short length) { - short ptr = KMType.instance(BYTE_BLOB_TYPE, (short)(length+2)); + short ptr = KMType.instance(BYTE_BLOB_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; @@ -61,14 +61,18 @@ public static short instance(byte[] buf, short startOff, short length) { } // cast the ptr to KMByteBlob - public static KMByteBlob cast(short ptr) { + private static KMByteBlob cast(short ptr) { + validate(ptr); + return proto(ptr); + } + + public static void validate(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 @@ -137,4 +141,49 @@ protected short getBaseOffset() { public void setLength(short len){ Util.setShort(heap, (short)(getBaseOffset() + 1), len); } + + + public static void add(short bPtr, short index, byte val) { + cast(bPtr).add(index, val); + } + + public static byte get(short bPtr, short index) { + return cast(bPtr).get(index); + } + + public static short getStartOff(short bPtr) { + return cast(bPtr).getStartOff(); + } + + public static short length(short bPtr) { + return cast(bPtr).length(); + } + + public static byte[] getBuffer(short bPtr) { + return cast(bPtr).getBuffer(); + } + + public static void getValue(short bPtr, byte[] destBuf, short destStart, short destLength) { + cast(bPtr).getValue(destBuf, destStart, destLength); + } + + public static short getValues(short bPtr, byte[] destBuf, short destStart) { + return cast(bPtr).getValues(destBuf, destStart); + } + + public static void setValue(short bPtr, byte[] srcBuf, short srcStart, short srcLength) { + cast(bPtr).setValue(srcBuf, srcStart, srcLength); + } + + public static boolean isValid(short bPtr) { + return cast(bPtr).isValid(); + } + + public static void setStartOff(short bPtr, short offset){ + cast(bPtr).setStartOff(offset); + } + + public static void setLength(short bPtr, short len){ + cast(bPtr).setLength(len); + } } diff --git a/Applet/src/com/android/javacard/keymaster/KMByteTag.java b/Applet/src/com/android/javacard/kmdevice/KMByteTag.java similarity index 74% rename from Applet/src/com/android/javacard/keymaster/KMByteTag.java rename to Applet/src/com/android/javacard/kmdevice/KMByteTag.java index 1677a744..06bf8c4c 100644 --- a/Applet/src/com/android/javacard/keymaster/KMByteTag.java +++ b/Applet/src/com/android/javacard/kmdevice/KMByteTag.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import javacard.framework.ISO7816; import javacard.framework.ISOException; @@ -31,33 +31,37 @@ public class KMByteTag extends KMTag { private static KMByteTag prototype; // The allowed tag keys of type bool tag - private static final short[] tags = { - APPLICATION_ID, - APPLICATION_DATA, - ROOT_OF_TRUST, - UNIQUE_ID, - ATTESTATION_CHALLENGE, - ATTESTATION_APPLICATION_ID, - ATTESTATION_ID_BRAND, - ATTESTATION_ID_DEVICE, - ATTESTATION_ID_PRODUCT, - ATTESTATION_ID_SERIAL, - ATTESTATION_ID_IMEI, - ATTESTATION_ID_MEID, - ATTESTATION_ID_MANUFACTURER, - ATTESTATION_ID_MODEL, - ASSOCIATED_DATA, - NONCE, - CONFIRMATION_TOKEN, - VERIFIED_BOOT_KEY, - VERIFIED_BOOT_HASH, - CERTIFICATE_SERIAL_NUM, - CERTIFICATE_SUBJECT_NAME, - }; + private static short[] tags; private KMByteTag() { } - + + public static void initStatics() { + tags = new short[]{ + APPLICATION_ID, + APPLICATION_DATA, + ROOT_OF_TRUST, + UNIQUE_ID, + ATTESTATION_CHALLENGE, + ATTESTATION_APPLICATION_ID, + ATTESTATION_ID_BRAND, + ATTESTATION_ID_DEVICE, + ATTESTATION_ID_PRODUCT, + ATTESTATION_ID_SERIAL, + ATTESTATION_ID_IMEI, + ATTESTATION_ID_MEID, + ATTESTATION_ID_MANUFACTURER, + ATTESTATION_ID_MODEL, + ASSOCIATED_DATA, + NONCE, + CONFIRMATION_TOKEN, + VERIFIED_BOOT_KEY, + VERIFIED_BOOT_HASH, + CERTIFICATE_SERIAL_NUM, + CERTIFICATE_SUBJECT_NAME, + }; + } + private static KMByteTag proto(short ptr) { if (prototype == null) { prototype = new KMByteTag(); @@ -97,7 +101,7 @@ public static short instance(short key, short byteBlob) { return ptr; } - public static KMByteTag cast(short ptr) { + private static KMByteTag cast(short ptr) { if (heap[ptr] != TAG_TYPE) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } @@ -121,7 +125,23 @@ public short getValue() { public short length() { short blobPtr = Util.getShort(heap, (short) (KMType.instanceTable[KM_BYTE_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); - return KMByteBlob.cast(blobPtr).length(); + return KMByteBlob.length(blobPtr); + } + + public static short getKey(short bPtr) { + return KMByteTag.cast(bPtr).getKey(); + } + + public static short getTagType(short bPtr) { + return KMByteTag.cast(bPtr).getTagType(); + } + + public static short getValue(short bPtr) { + return KMByteTag.cast(bPtr).getValue(); + } + + public static short length(short bPtr) { + return KMByteTag.cast(bPtr).length(); } private static boolean validateKey(short key) { diff --git a/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMConfigurations.java b/Applet/src/com/android/javacard/kmdevice/KMConfigurations.java similarity index 96% rename from Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMConfigurations.java rename to Applet/src/com/android/javacard/kmdevice/KMConfigurations.java index 6e5090a1..b6414ed7 100644 --- a/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMConfigurations.java +++ b/Applet/src/com/android/javacard/kmdevice/KMConfigurations.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; public class KMConfigurations { // Machine types diff --git a/Applet/src/com/android/javacard/keymaster/KMCose.java b/Applet/src/com/android/javacard/kmdevice/KMCose.java similarity index 72% rename from Applet/src/com/android/javacard/keymaster/KMCose.java rename to Applet/src/com/android/javacard/kmdevice/KMCose.java index 6720d8df..f0134771 100644 --- a/Applet/src/com/android/javacard/keymaster/KMCose.java +++ b/Applet/src/com/android/javacard/kmdevice/KMCose.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import javacard.framework.ISO7816; import javacard.framework.ISOException; @@ -84,42 +84,76 @@ public class KMCose { 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 byte[] COSE_TEST_KEY; // -70000 public static final short 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}; + public static byte[] client; + public static byte[] server; //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 + public static byte[] MAC_CONTEXT; // MAC0 + public static byte[] SIGNATURE1_CONTEXT; // Signature1 + public static byte[] ENCRYPT_CONTEXT; // Encrypt //Empty strings - public static final byte[] EMPTY_MAC_KEY = - {0x45, 0x6d, 0x70, 0x74, 0x79, 0x20, 0x4d, 0x41, 0x43, 0x20, 0x6b, 0x65, 0x79}; // "Empty MAC key" + public static byte[] EMPTY_MAC_KEY; // "Empty MAC key" // Certificate payload supported keys public static final byte ISSUER = (byte) 0x01; public static final byte SUBJECT = (byte) 0x02; - public static final byte[] CODE_HASH = {(byte) 0xFF, (byte) 0xB8, (byte) 0xBB, (byte) 0xAF}; - public static final byte[] CODE_DESCRIPTOR = {(byte) 0xFF, (byte) 0xB8, (byte) 0xBB, (byte) 0xAE}; - public static final byte[] CONFIG_HASH = {(byte) 0xFF, (byte) 0xB8, (byte) 0xBB, (byte) 0xAD}; - public static final byte[] CONFIG_DESCRIPTOR = {(byte) 0xFF, (byte) 0xB8, (byte) 0xBB, (byte) 0xAC}; - public static final byte[] AUTHORITY_HASH = {(byte) 0xFF, (byte) 0xB8, (byte) 0xBB, (byte) 0xAB}; - public static final byte[] AUTHORITY_DESCRIPTOR = {(byte) 0xFF, (byte) 0xB8, (byte) 0xBB, (byte) 0xAA}; - public static final byte[] MODE = {(byte) 0xFF, (byte) 0xB8, (byte) 0xBB, (byte) 0xA9}; - 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}; + public static byte[] CODE_HASH; + public static byte[] CODE_DESCRIPTOR; + public static byte[] CONFIG_HASH; + public static byte[] CONFIG_DESCRIPTOR; + public static byte[] AUTHORITY_HASH; + public static byte[] AUTHORITY_DESCRIPTOR; + public static byte[] MODE; + public static byte[] SUBJECT_PUBLIC_KEY; + public static byte[] KEY_USAGE; // 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 byte[] MAC_DERIVE_KEY_CTX = - {0x4B, 0x65, 0x79, 0x20, 0x74, 0x6F, 0x20, 0x4D, 0x41, 0x43, 0x20, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, - 0x20, 0x6B, 0x65, 0x79, 0x73}; // "Key to MAC public keys" + public static byte[] TEST_ISSUER_NAME; // "Issuer" + public static byte[] TEST_SUBJECT_NAME; // "Subject" + public static byte[] KEY_USAGE_SIGN; // Key usage sign + public static byte[] MAC_DERIVE_KEY_CTX; // "Key to MAC public keys" + private static KMCose prototype; + + public static void initStatics() { + COSE_TEST_KEY = new byte[] {(byte) 0xFF, (byte) 0xFE, (byte) 0xEE, (byte) 0x90}; // -70000 + client = new byte[] {0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74}; + server = new byte[] {0x73, 0x65, 0x72, 0x76, 0x65, 0x72}; + MAC_CONTEXT = new byte[] {0x4d, 0x41, 0x43, 0x30}; // MAC0 + SIGNATURE1_CONTEXT = new byte[] + {0x53, 0x69, 0x67, 0x6E, 0x61, 0x74, 0x75, 0x72, 0x65, 0x31}; // Signature1 + ENCRYPT_CONTEXT = new byte[] {0x45, 0x6E, 0x63, 0x72, 0x79, 0x70, 0x74}; // Encrypt + EMPTY_MAC_KEY = new byte[] + {0x45, 0x6d, 0x70, 0x74, 0x79, 0x20, 0x4d, 0x41, 0x43, 0x20, 0x6b, 0x65, 0x79}; // "Empty MAC key" + + + CODE_HASH = new byte[] {(byte) 0xFF, (byte) 0xB8, (byte) 0xBB, (byte) 0xAF}; + CODE_DESCRIPTOR = new byte[] {(byte) 0xFF, (byte) 0xB8, (byte) 0xBB, (byte) 0xAE}; + CONFIG_HASH = new byte[] {(byte) 0xFF, (byte) 0xB8, (byte) 0xBB, (byte) 0xAD}; + CONFIG_DESCRIPTOR = new byte[] {(byte) 0xFF, (byte) 0xB8, (byte) 0xBB, (byte) 0xAC}; + AUTHORITY_HASH = new byte[] {(byte) 0xFF, (byte) 0xB8, (byte) 0xBB, (byte) 0xAB}; + AUTHORITY_DESCRIPTOR = new byte[] {(byte) 0xFF, (byte) 0xB8, (byte) 0xBB, (byte) 0xAA}; + MODE = new byte[] {(byte) 0xFF, (byte) 0xB8, (byte) 0xBB, (byte) 0xA9}; + SUBJECT_PUBLIC_KEY = new byte[] {(byte) 0xFF, (byte) 0xB8, (byte) 0xBB, (byte) 0xA8}; + KEY_USAGE = new byte[] {(byte) 0xFF, (byte) 0xB8, (byte) 0xBB, (byte) 0xA7}; + // text strings + TEST_ISSUER_NAME = new byte[] {(byte) 0x49, 0x73, 0x73, 0x75, 0x65, 0x72}; // "Issuer" + TEST_SUBJECT_NAME = new byte[] {0x53, 0x75, 0x62, 0x6A, 0x65, 0x63, 0x74}; // "Subject" + KEY_USAGE_SIGN = new byte[] {0x20}; // Key usage sign + MAC_DERIVE_KEY_CTX = new byte[] + {0x4B, 0x65, 0x79, 0x20, 0x74, 0x6F, 0x20, 0x4D, 0x41, 0x43, 0x20, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, + 0x20, 0x6B, 0x65, 0x79, 0x73}; // "Key to MAC public keys" + + } + + public static KMCose getInstance() { + if (prototype == null) { + prototype = new KMCose(); + } + return prototype; + } /** * Constructs the Cose MAC structure. * @@ -128,7 +162,7 @@ public class KMCose { * @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) { + public 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", @@ -138,14 +172,14 @@ public static short constructCoseMacStructure(short protectedHeader, short extAa // ] short arrPtr = KMArray.instance(KMCose.COSE_MAC0_ENTRY_COUNT); // 1 - Context - KMArray.cast(arrPtr).add((short) 0, KMTextString.instance(KMCose.MAC_CONTEXT, (short) 0, + KMArray.add(arrPtr, (short) 0, KMTextString.instance(KMCose.MAC_CONTEXT, (short) 0, (short) KMCose.MAC_CONTEXT.length)); // 2 - Protected headers. - KMArray.cast(arrPtr).add((short) 1, protectedHeader); + KMArray.add(arrPtr, (short) 1, protectedHeader); // 3 - external aad - KMArray.cast(arrPtr).add((short) 2, extAad); + KMArray.add(arrPtr, (short) 2, extAad); // 4 - payload. - KMArray.cast(arrPtr).add((short) 3, payload); + KMArray.add(arrPtr, (short) 3, payload); return arrPtr; } @@ -158,8 +192,8 @@ public static short constructCoseMacStructure(short protectedHeader, short extAa * @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 + public short constructCoseMac0(short protectedHeader, short unprotectedHeader, short payload, short tag) { + // Construct Cose_MAC0 // COSE_Mac0 = [ // protectedHeader, // unprotectedHeader, @@ -168,13 +202,13 @@ public static short constructCoseMac0(short protectedHeader, short unprotectedHe // ] short arrPtr = KMArray.instance(KMCose.COSE_MAC0_ENTRY_COUNT); // 1 - protected headers - KMArray.cast(arrPtr).add((short) 0, protectedHeader); + KMArray.add(arrPtr, (short) 0, protectedHeader); // 2 - unprotected headers - KMArray.cast(arrPtr).add((short) 1, unprotectedHeader); + KMArray.add(arrPtr, (short) 1, unprotectedHeader); // 2 - payload - KMArray.cast(arrPtr).add((short) 2, payload); + KMArray.add(arrPtr, (short) 2, payload); // 3 - tag - KMArray.cast(arrPtr).add((short) 3, tag); + KMArray.add(arrPtr, (short) 3, tag); // Do encode. return arrPtr; } @@ -187,7 +221,7 @@ public static short constructCoseMac0(short protectedHeader, short unprotectedHe * @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) { + public short constructCoseSignStructure(short protectedHeader, short extAad, short payload) { // Sig_structure = [ // context : "Signature" / "Signature1" / "CounterSignature", // body_protected : empty_or_serialized_map, @@ -197,14 +231,14 @@ public static short constructCoseSignStructure(short protectedHeader, short extA // ] short arrPtr = KMArray.instance(KMCose.COSE_SIGN1_ENTRY_COUNT); // 1 - Context - KMArray.cast(arrPtr).add((short) 0, KMTextString.instance(KMCose.SIGNATURE1_CONTEXT, (short) 0, + KMArray.add(arrPtr, (short) 0, KMTextString.instance(KMCose.SIGNATURE1_CONTEXT, (short) 0, (short) KMCose.SIGNATURE1_CONTEXT.length)); // 2 - Protected headers. - KMArray.cast(arrPtr).add((short) 1, protectedHeader); + KMArray.add(arrPtr, (short) 1, protectedHeader); // 3 - external aad - KMArray.cast(arrPtr).add((short) 2, extAad); + KMArray.add(arrPtr, (short) 2, extAad); // 4 - payload. - KMArray.cast(arrPtr).add((short) 3, payload); + KMArray.add(arrPtr, (short) 3, payload); return arrPtr; } @@ -217,7 +251,7 @@ public static short constructCoseSignStructure(short protectedHeader, short extA * @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, + public short constructCoseSign1(short protectedHeader, short unProtectedHeader, short payload, short signature) { // COSE_Sign = [ // protectedHeader, @@ -227,13 +261,13 @@ public static short constructCoseSign1(short protectedHeader, short unProtectedH // ] short arrPtr = KMArray.instance(KMCose.COSE_SIGN1_ENTRY_COUNT); // 1 - protected headers - KMArray.cast(arrPtr).add((short) 0, protectedHeader); + KMArray.add(arrPtr, (short) 0, protectedHeader); // 2 - unprotected headers - KMArray.cast(arrPtr).add((short) 1, unProtectedHeader); + KMArray.add(arrPtr, (short) 1, unProtectedHeader); // 2 - payload - KMArray.cast(arrPtr).add((short) 2, payload); + KMArray.add(arrPtr, (short) 2, payload); // 3 - tag - KMArray.cast(arrPtr).add((short) 3, signature); + KMArray.add(arrPtr, (short) 3, signature); return arrPtr; } @@ -244,7 +278,7 @@ public static short constructCoseSign1(short protectedHeader, short unProtectedH * @param includeTestMode flag which indicates if TEST_COSE_KEY should be included or not. * @return instance of KMArray. */ - private static short handleCosePairTags(short[] tags, boolean includeTestMode) { + private short handleCosePairTags(short[] tags, boolean includeTestMode) { short index = 0; // var is used to calculate the length of the array. short var = 0; @@ -263,7 +297,7 @@ private static short handleCosePairTags(short[] tags, boolean includeTestMode) { var = 0; while (index < tags.length) { if (tags[(short) (index + 2)] != KMType.INVALID_VALUE) { - KMArray.cast(arrPtr).add(var++, tags[(short) (index + 2)]); + KMArray.add(arrPtr, var++, tags[(short) (index + 2)]); } index += 3; } @@ -279,12 +313,12 @@ private static short handleCosePairTags(short[] tags, boolean includeTestMode) { * @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) { + public 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); + KMArray.add(certPayload, (short) 0, issuer); + KMArray.add(certPayload, (short) 1, subject); + KMArray.add(certPayload, (short) 2, subPublicKey); + KMArray.add(certPayload, (short) 3, keyUsage); certPayload = KMCoseCertPayload.instance(certPayload); KMCoseCertPayload.cast(certPayload).canonicalize(); return certPayload; @@ -300,7 +334,7 @@ public static short constructCoseCertPayload(short issuer, short subject, short * @param ephemeralKey instance of KMCoseKey. * @return instance of KMCoseHeaders. */ - public static short constructHeaders(short alg, short keyId, short iv, short ephemeralKey) { + public short constructHeaders(short alg, short keyId, short iv, short ephemeralKey) { short[] coseHeaderTags = { KMCose.COSE_LABEL_ALGORITHM, alg, KMType.INVALID_VALUE, KMCose.COSE_LABEL_KEYID, keyId, KMType.INVALID_VALUE, @@ -321,7 +355,7 @@ public static short constructHeaders(short alg, short keyId, short iv, short eph * @param cipherText instance of KMSimple * @return instance of KMArray. */ - public static short constructRecipientsStructure(short protectedHeaders, short unprotectedHeaders, + public short constructRecipientsStructure(short protectedHeaders, short unprotectedHeaders, short cipherText) { // recipients : [+COSE_recipient] // COSE_recipient = [ @@ -331,14 +365,14 @@ public static short constructRecipientsStructure(short protectedHeaders, short u // ] short arrPtr = KMArray.instance(COSE_ENCRYPT_RECIPIENT_ENTRY_COUNT); // 1 - protected headers - KMArray.cast(arrPtr).add((short) 0, protectedHeaders); + KMArray.add(arrPtr, (short) 0, protectedHeaders); // 2 - unprotected headers - KMArray.cast(arrPtr).add((short) 1, unprotectedHeaders); + KMArray.add(arrPtr, (short) 1, unprotectedHeaders); // 2 - payload - KMArray.cast(arrPtr).add((short) 2, cipherText); + KMArray.add(arrPtr, (short) 2, cipherText); short recipientsArrayPtr = KMArray.instance((short) 1); - KMArray.cast(recipientsArrayPtr).add((short) 0, arrPtr); + KMArray.add(recipientsArrayPtr, (short) 0, arrPtr); return recipientsArrayPtr; } @@ -349,7 +383,7 @@ public static short constructRecipientsStructure(short protectedHeaders, short u * @param aad instance of KMByteBlob. * @return instance of KMArray. */ - public static short constructCoseEncryptStructure(short protectedHeader, short aad) { + public short constructCoseEncryptStructure(short protectedHeader, short aad) { // Enc_structure = [ // context : "Encrypt" / "Encrypt0" / "Enc_Recipient" / // "Mac_Recipient" / "Rec_Recipient", @@ -358,12 +392,12 @@ public static short constructCoseEncryptStructure(short protectedHeader, short a // ] 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, + KMArray.add(arrPtr, (short) 0, KMTextString.instance(KMCose.ENCRYPT_CONTEXT, (short) 0, (short) KMCose.ENCRYPT_CONTEXT.length)); // 2 - unprotected headers - KMArray.cast(arrPtr).add((short) 1, protectedHeader); + KMArray.add(arrPtr, (short) 1, protectedHeader); // 2 - payload - KMArray.cast(arrPtr).add((short) 2, aad); + KMArray.add(arrPtr, (short) 2, aad); return arrPtr; } @@ -376,7 +410,7 @@ public static short constructCoseEncryptStructure(short protectedHeader, short a * @param recipients instance of KMArray containing the recipients instance * @return instance of KMArray. */ - public static short constructCoseEncrypt(short protectedHeader, short unProtectedHeader, short cipherText, + public short constructCoseEncrypt(short protectedHeader, short unProtectedHeader, short cipherText, short recipients) { // COSE_Encrypt = [ // protectedHeader, @@ -386,13 +420,13 @@ public static short constructCoseEncrypt(short protectedHeader, short unProtecte // ] short arrPtr = KMArray.instance(KMCose.COSE_ENCRYPT_ENTRY_COUNT); // 1 - protected headers - KMArray.cast(arrPtr).add((short) 0, protectedHeader); + KMArray.add(arrPtr, (short) 0, protectedHeader); // 2 - unprotected headers - KMArray.cast(arrPtr).add((short) 1, unProtectedHeader); + KMArray.add(arrPtr, (short) 1, unProtectedHeader); // 2 - payload - KMArray.cast(arrPtr).add((short) 2, cipherText); + KMArray.add(arrPtr, (short) 2, cipherText); // 3 - tag - KMArray.cast(arrPtr).add((short) 3, recipients); + KMArray.add(arrPtr, (short) 3, recipients); return arrPtr; } @@ -403,8 +437,8 @@ public static short constructCoseEncrypt(short protectedHeader, short unProtecte * @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); + public short buildCosePairTag(byte key, short valuePtr) { + short type = KMType.getKMType(valuePtr); short keyPtr; if (key < 0) { keyPtr = KMNInteger.uint_8(key); @@ -443,7 +477,7 @@ public static short buildCosePairTag(byte key, short valuePtr) { * @param testMode Represents if key is used in test mode or production mode. * @return Instance of the CoseKey structure. */ - public static short constructCoseKey(short keyType, short keyId, short keyAlg, short keyOps, + public short constructCoseKey(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 @@ -473,7 +507,7 @@ public static short constructCoseKey(short keyType, short keyId, short keyAlg, s * @param includeTestKey flag which identifies whether to construct test key or production key. * @return instance of the KMCoseKey object. */ - public static short constructCoseKey(short keyType, short keyId, short keyAlg, short keyOps, short curve, + public short constructCoseKey(short keyType, short keyId, short keyAlg, short keyOps, short curve, short pubX, short pubY, short priv, boolean includeTestKey) { short[] coseKeyTags = { KMCose.COSE_KEY_KEY_TYPE, keyType, KMType.INVALID_VALUE, @@ -490,10 +524,10 @@ public static short constructCoseKey(short keyType, short keyId, short keyAlg, s 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); + KMArray.add(arrPtr, (short) (KMArray.length(arrPtr) - 1), testKey); } arrPtr = KMCoseKey.instance(arrPtr); - KMCoseKey.cast(arrPtr).canonicalize(); + KMCoseKey.canonicalize(arrPtr); return arrPtr; } @@ -509,41 +543,41 @@ public static short constructCoseKey(short keyType, short keyId, short keyAlg, s * @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, + public 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 ? + KMArray.add(senderInfo, index++, KMByteBlob.instance(client, (short) 0, (short) client.length)); + KMArray.add(senderInfo, index++, KMByteBlob.instance((short) 0)); + KMArray.add(senderInfo, 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 ? + KMArray.add(recipientInfo, index++, KMByteBlob.instance(server, (short) 0, (short) server.length)); + KMArray.add(recipientInfo, index++, KMByteBlob.instance((short) 0)); + KMArray.add(recipientInfo, 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)); + KMArray.add(publicInfo, index++, KMInteger.uint_16(AES_GCM_KEY_SIZE_BITS)); + KMArray.add(publicInfo, 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); + KMArray.add(arrPtr, index++, KMInteger.uint_8(COSE_ALG_AES_GCM_256)); + KMArray.add(arrPtr, index++, senderInfo); + KMArray.add(arrPtr, index++, recipientInfo); + KMArray.add(arrPtr, index, publicInfo); return arrPtr; } -} \ No newline at end of file +} diff --git a/Applet/src/com/android/javacard/keymaster/KMCoseCertPayload.java b/Applet/src/com/android/javacard/kmdevice/KMCoseCertPayload.java similarity index 84% rename from Applet/src/com/android/javacard/keymaster/KMCoseCertPayload.java rename to Applet/src/com/android/javacard/kmdevice/KMCoseCertPayload.java index 76535416..0860cf0f 100644 --- a/Applet/src/com/android/javacard/keymaster/KMCoseCertPayload.java +++ b/Applet/src/com/android/javacard/kmdevice/KMCoseCertPayload.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import javacard.framework.ISO7816; import javacard.framework.ISOException; @@ -43,9 +43,8 @@ private static KMCoseCertPayload proto(short ptr) { 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()); + KMArray.add(arrPtr, (short) 0, KMCosePairTextStringTag.exp()); + KMArray.add(arrPtr, (short) 1, KMCosePairByteBlobTag.exp()); return KMCoseCertPayload.instance(arrPtr); } @@ -74,7 +73,7 @@ public short getVals() { @Override public short length() { short arrPtr = getVals(); - return KMArray.cast(arrPtr).length(); + return KMArray.length(arrPtr); } @Override @@ -91,20 +90,20 @@ private short getValueType(short key, short significantKey) { short tagType; boolean found = false; while (index < length) { - tagType = KMCosePairTagType.getTagValueType(KMArray.cast(arr).get(index)); + tagType = KMCosePairTagType.getTagValueType(KMArray.get(arr, index)); switch (tagType) { case KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE: - keyPtr = KMCosePairByteBlobTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + keyPtr = KMCosePairByteBlobTag.cast(KMArray.get(arr, index)).getKeyPtr(); if (key == KMCosePairTagType.getKeyValueShort(keyPtr) && significantKey == KMCosePairTagType.getKeyValueSignificantShort(keyPtr)) { - valPtr = KMCosePairByteBlobTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + valPtr = KMCosePairByteBlobTag.cast(KMArray.get(arr, index)).getValuePtr(); found = true; } break; case KMType.COSE_PAIR_TEXT_STR_TAG_TYPE: - keyPtr = KMCosePairTextStringTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + keyPtr = KMCosePairTextStringTag.cast(KMArray.get(arr, index)).getKeyPtr(); if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { - valPtr = KMCosePairTextStringTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + valPtr = KMCosePairTextStringTag.cast(KMArray.get(arr, index)).getValuePtr(); found = true; } break; diff --git a/Applet/src/com/android/javacard/keymaster/KMCoseHeaders.java b/Applet/src/com/android/javacard/kmdevice/KMCoseHeaders.java similarity index 74% rename from Applet/src/com/android/javacard/keymaster/KMCoseHeaders.java rename to Applet/src/com/android/javacard/kmdevice/KMCoseHeaders.java index 0b82d6cb..d765e46b 100644 --- a/Applet/src/com/android/javacard/keymaster/KMCoseHeaders.java +++ b/Applet/src/com/android/javacard/kmdevice/KMCoseHeaders.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import javacard.framework.ISO7816; import javacard.framework.ISOException; @@ -45,11 +45,10 @@ 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); + KMArray.add(arrPtr, (short) 0, KMCosePairIntegerTag.exp()); + KMArray.add(arrPtr, (short) 1, KMCosePairNegIntegerTag.exp()); + KMArray.add(arrPtr, (short) 2, KMCosePairByteBlobTag.exp()); + KMArray.add(arrPtr, (short) 3, coseKeyValueExp); return KMCoseHeaders.instance(arrPtr); } @@ -79,7 +78,7 @@ public short getVals() { @Override public short length() { short arrPtr = getVals(); - return KMArray.cast(arrPtr).length(); + return KMArray.length(arrPtr); } @Override @@ -96,33 +95,33 @@ private short getValueType(short key) { short keyPtr; boolean found = false; while (index < len) { - tagType = KMCosePairTagType.getTagValueType(KMArray.cast(arr).get(index)); + tagType = KMCosePairTagType.getTagValueType(KMArray.get(arr, index)); switch (tagType) { case KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE: - keyPtr = KMCosePairByteBlobTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + keyPtr = KMCosePairByteBlobTag.cast(KMArray.get(arr, index)).getKeyPtr(); if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { - valPtr = KMCosePairByteBlobTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + valPtr = KMCosePairByteBlobTag.cast(KMArray.get(arr, index)).getValuePtr(); found = true; } break; case KMType.COSE_PAIR_COSE_KEY_TAG_TYPE: - keyPtr = KMCosePairCoseKeyTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + keyPtr = KMCosePairCoseKeyTag.cast(KMArray.get(arr, index)).getKeyPtr(); if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { - valPtr = KMCosePairCoseKeyTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + valPtr = KMCosePairCoseKeyTag.cast(KMArray.get(arr, index)).getValuePtr(); found = true; } break; case KMType.COSE_PAIR_INT_TAG_TYPE: - keyPtr = KMCosePairIntegerTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + keyPtr = KMCosePairIntegerTag.cast(KMArray.get(arr, index)).getKeyPtr(); if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { - valPtr = KMCosePairIntegerTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + valPtr = KMCosePairIntegerTag.cast(KMArray.get(arr, index)).getValuePtr(); found = true; } break; case KMType.COSE_PAIR_NEG_INT_TAG_TYPE: - keyPtr = KMCosePairNegIntegerTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + keyPtr = KMCosePairNegIntegerTag.cast(KMArray.get(arr, index)).getKeyPtr(); if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { - valPtr = KMCosePairNegIntegerTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + valPtr = KMCosePairNegIntegerTag.cast(KMArray.get(arr, index)).getValuePtr(); found = true; } break; @@ -166,25 +165,25 @@ public boolean isDataValid(short alg, short keyIdPtr) { if (value != KMType.INVALID_VALUE) { valid = false; ptr = getValueType(headerTags[tagIndex]); - switch (KMType.getType(ptr)) { + switch (KMType.getKMType(ptr)) { case KMType.BYTE_BLOB_TYPE: - if ((KMByteBlob.cast(value).length() == KMByteBlob.cast(ptr).length()) && + if ((KMByteBlob.length(value) == KMByteBlob.length(ptr)) && (0 == - Util.arrayCompare(KMByteBlob.cast(value).getBuffer(), - KMByteBlob.cast(value).getStartOff(), - KMByteBlob.cast(ptr).getBuffer(), - KMByteBlob.cast(ptr).getStartOff(), - KMByteBlob.cast(ptr).length()))) { + Util.arrayCompare(KMByteBlob.getBuffer(value), + KMByteBlob.getStartOff(value), + KMByteBlob.getBuffer(ptr), + KMByteBlob.getStartOff(ptr), + KMByteBlob.length(ptr)))) { valid = true; } break; case KMType.INTEGER_TYPE: - if (value == KMInteger.cast(ptr).getShort()) { + if (value == KMInteger.getShort(ptr)) { valid = true; } break; case KMType.NEG_INTEGER_TYPE: - if ((byte) value == (byte) KMNInteger.cast(ptr).getShort()) { + if ((byte) value == (byte) KMNInteger.getShort(ptr)) { valid = true; } break; diff --git a/Applet/src/com/android/javacard/keymaster/KMCoseKey.java b/Applet/src/com/android/javacard/kmdevice/KMCoseKey.java similarity index 69% rename from Applet/src/com/android/javacard/keymaster/KMCoseKey.java rename to Applet/src/com/android/javacard/kmdevice/KMCoseKey.java index eafa3006..96df5583 100644 --- a/Applet/src/com/android/javacard/keymaster/KMCoseKey.java +++ b/Applet/src/com/android/javacard/kmdevice/KMCoseKey.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import javacard.framework.ISO7816; import javacard.framework.ISOException; @@ -23,7 +23,7 @@ /** * 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 } + * KMKeymasterApplet, 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 { @@ -43,11 +43,10 @@ private static KMCoseKey proto(short ptr) { 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()); + KMArray.add(arrPtr, (short) 0, KMCosePairIntegerTag.exp()); + KMArray.add(arrPtr, (short) 1, KMCosePairNegIntegerTag.exp()); + KMArray.add(arrPtr, (short) 2, KMCosePairByteBlobTag.exp()); + KMArray.add(arrPtr, (short) 3, KMCosePairSimpleValueTag.exp()); return KMCoseKey.instance(arrPtr); } @@ -77,7 +76,7 @@ public short getVals() { @Override public short length() { short arrPtr = getVals(); - return KMArray.cast(arrPtr).length(); + return KMArray.length(arrPtr); } private short getValueType(short key, short significantKey) { @@ -89,34 +88,34 @@ private short getValueType(short key, short significantKey) { short tagType; boolean found = false; while (index < length) { - tagType = KMCosePairTagType.getTagValueType(KMArray.cast(arr).get(index)); + tagType = KMCosePairTagType.getTagValueType(KMArray.get(arr, index)); switch (tagType) { case KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE: - keyPtr = KMCosePairByteBlobTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + keyPtr = KMCosePairByteBlobTag.cast(KMArray.get(arr, index)).getKeyPtr(); if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { - valPtr = KMCosePairByteBlobTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + valPtr = KMCosePairByteBlobTag.cast(KMArray.get(arr, index)).getValuePtr(); found = true; } break; case KMType.COSE_PAIR_INT_TAG_TYPE: - keyPtr = KMCosePairIntegerTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + keyPtr = KMCosePairIntegerTag.cast(KMArray.get(arr, index)).getKeyPtr(); if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { - valPtr = KMCosePairIntegerTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + valPtr = KMCosePairIntegerTag.cast(KMArray.get(arr, index)).getValuePtr(); found = true; } break; case KMType.COSE_PAIR_NEG_INT_TAG_TYPE: - keyPtr = KMCosePairNegIntegerTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + keyPtr = KMCosePairNegIntegerTag.cast(KMArray.get(arr, index)).getKeyPtr(); if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { - valPtr = KMCosePairNegIntegerTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + valPtr = KMCosePairNegIntegerTag.cast(KMArray.get(arr, index)).getValuePtr(); found = true; } break; case KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE: - keyPtr = KMCosePairSimpleValueTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + keyPtr = KMCosePairSimpleValueTag.cast(KMArray.get(arr, index)).getKeyPtr(); if (key == KMCosePairTagType.getKeyValueShort(keyPtr) && significantKey == KMCosePairTagType.getKeyValueSignificantShort(keyPtr)) { - valPtr = KMCosePairSimpleValueTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + valPtr = KMCosePairSimpleValueTag.cast(KMArray.get(arr, index)).getValuePtr(); found = true; } break; @@ -140,21 +139,21 @@ public short getEcdsa256PublicKey(byte[] pubKey, short 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(); + Util.arrayCopy(KMByteBlob.getBuffer(ptr), KMByteBlob.getStartOff(ptr), + pubKey, pubKeyOff, KMByteBlob.length(ptr)); + pubKeyOff += KMByteBlob.length(ptr); 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(); + Util.arrayCopy(KMByteBlob.getBuffer(ptr), KMByteBlob.getStartOff(ptr), + pubKey, pubKeyOff, KMByteBlob.length(ptr)); + pubKeyOff += KMByteBlob.length(ptr); 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(); + Util.arrayCopy(KMByteBlob.getBuffer(ptr), KMByteBlob.getStartOff(ptr), + priv, privOff, KMByteBlob.length(ptr)); + return KMByteBlob.length(ptr); } public boolean isTestKey() { @@ -165,7 +164,7 @@ public boolean isTestKey() { ); boolean isTestKey = false; if (ptr != 0) - isTestKey = (KMSimpleValue.cast(ptr).getValue() == KMSimpleValue.NULL); + isTestKey = (KMSimpleValue.getValue(ptr) == KMSimpleValue.NULL); return isTestKey; } @@ -173,7 +172,7 @@ public boolean 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 keyIdPtr instance of KMKeymasterApplet containing the key id. * @param keyAlg value of the algorithm. * @param keyOps value of the key operations. * @param curve value of the curve. @@ -196,25 +195,25 @@ public boolean isDataValid(short keyType, short keyIdPtr, short keyAlg, short ke if (value != KMType.INVALID_VALUE) { valid = false; ptr = getValueType(coseKeyTags[tagIndex], KMType.INVALID_VALUE); - switch (KMType.getType(ptr)) { + switch (KMType.getKMType(ptr)) { case KMType.BYTE_BLOB_TYPE: - if ((KMByteBlob.cast(value).length() == KMByteBlob.cast(ptr).length()) && + if ((KMByteBlob.length(value) == KMByteBlob.length(ptr)) && (0 == - Util.arrayCompare(KMByteBlob.cast(value).getBuffer(), - KMByteBlob.cast(value).getStartOff(), - KMByteBlob.cast(ptr).getBuffer(), - KMByteBlob.cast(ptr).getStartOff(), - KMByteBlob.cast(ptr).length()))) { + Util.arrayCompare(KMByteBlob.getBuffer(value), + KMByteBlob.getStartOff(value), + KMByteBlob.getBuffer(ptr), + KMByteBlob.getStartOff(ptr), + KMByteBlob.length(ptr)))) { valid = true; } break; case KMType.INTEGER_TYPE: - if (value == KMInteger.cast(ptr).getShort()) { + if (value == KMInteger.getShort(ptr)) { valid = true; } break; case KMType.NEG_INTEGER_TYPE: - if ((byte) value == (byte) KMNInteger.cast(ptr).getShort()) { + if ((byte) value == (byte) KMNInteger.getShort(ptr)) { valid = true; } break; diff --git a/Applet/src/com/android/javacard/keymaster/KMCoseMap.java b/Applet/src/com/android/javacard/kmdevice/KMCoseMap.java similarity index 86% rename from Applet/src/com/android/javacard/keymaster/KMCoseMap.java rename to Applet/src/com/android/javacard/kmdevice/KMCoseMap.java index ceb67fb2..c5ebf405 100644 --- a/Applet/src/com/android/javacard/keymaster/KMCoseMap.java +++ b/Applet/src/com/android/javacard/kmdevice/KMCoseMap.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import javacard.framework.ISO7816; import javacard.framework.ISOException; @@ -41,7 +41,7 @@ public abstract class KMCoseMap extends KMType { * @return instance type of either KMCoseHeaders or KMCoseKey. */ public static short createInstanceFromType(short typePtr, short arrPtr) { - short mapType = KMType.getType(typePtr); + short mapType = KMType.getKMType(typePtr); switch (mapType) { case KMType.COSE_HEADERS_TYPE: return KMCoseHeaders.instance(arrPtr); @@ -56,7 +56,7 @@ public static short createInstanceFromType(short typePtr, short arrPtr) { } public static short getVals(short ptr) { - short mapType = KMType.getType(ptr); + short mapType = KMType.getKMType(ptr); switch (mapType) { case KMType.COSE_HEADERS_TYPE: return KMCoseHeaders.cast(ptr).getVals(); @@ -104,14 +104,14 @@ private static void createScratchBuffer() { } protected static void canonicalize(short arr) { - canonicalize(arr, KMArray.cast(arr).length()); + canonicalize(arr, KMArray.length(arr)); } private static void swap(short ptr, short firstIndex, short secondIndex) { - if (KMType.getType(ptr) == KMType.ARRAY_TYPE) { - KMArray.cast(ptr).swap(firstIndex, secondIndex); + if (KMType.getKMType(ptr) == KMType.ARRAY_TYPE) { + KMArray.swap(ptr, firstIndex, secondIndex); } else { - KMMap.cast(ptr).swap(firstIndex, secondIndex); + KMMap.swap(ptr, firstIndex, secondIndex); } } @@ -120,15 +120,15 @@ private static boolean compareAndSwap(short ptr, short index) { 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))); + if (KMType.getKMType(ptr) == KMType.ARRAY_TYPE) { + firstKey = getKey(KMArray.get(ptr, index)); + secondKey = getKey(KMArray.get(ptr, (short) (index + 1))); } else { // Map - firstKey = KMMap.cast(ptr).getKey(index); - secondKey = KMMap.cast(ptr).getKey((short) (index + 1)); + firstKey = KMMap.getKey(ptr, index); + secondKey = KMMap.getKey(ptr, (short) (index + 1)); } - firstKeyLen = KMKeymasterApplet.encoder.encode(firstKey, scratchpad, (short) 0); - secondKeyLen = KMKeymasterApplet.encoder.encode(secondKey, scratchpad, firstKeyLen); + firstKeyLen = KMKeymasterDevice.encoder.encode(firstKey, scratchpad, (short) 0); + secondKeyLen = KMKeymasterDevice.encoder.encode(secondKey, scratchpad, firstKeyLen); if ((firstKeyLen > secondKeyLen) || ((firstKeyLen == secondKeyLen) && (0 < Util.arrayCompare(scratchpad, (short) 0, scratchpad, firstKeyLen, firstKeyLen)))) { diff --git a/Applet/src/com/android/javacard/keymaster/KMCosePairByteBlobTag.java b/Applet/src/com/android/javacard/kmdevice/KMCosePairByteBlobTag.java similarity index 93% rename from Applet/src/com/android/javacard/keymaster/KMCosePairByteBlobTag.java rename to Applet/src/com/android/javacard/kmdevice/KMCosePairByteBlobTag.java index 4a03295e..451985d6 100644 --- a/Applet/src/com/android/javacard/keymaster/KMCosePairByteBlobTag.java +++ b/Applet/src/com/android/javacard/kmdevice/KMCosePairByteBlobTag.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import javacard.framework.ISO7816; import javacard.framework.ISOException; @@ -54,7 +54,7 @@ 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) { + if (KMType.getKMType(valuePtr) != BYTE_BLOB_TYPE) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } short ptr = KMType.instance(COSE_PAIR_TAG_TYPE, (short) 6); @@ -71,7 +71,7 @@ public static KMCosePairByteBlobTag cast(short ptr) { } // Validate the value pointer. short valuePtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4)); - if (KMType.getType(valuePtr) != BYTE_BLOB_TYPE) { + if (KMType.getKMType(valuePtr) != BYTE_BLOB_TYPE) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } return proto(ptr); @@ -108,12 +108,12 @@ private static void createKeys() { public static boolean isKeyValueValid(short keyPtr) { createKeys(); - short type = KMType.getType(keyPtr); + short type = KMType.getKMType(keyPtr); short offset = 0; if (type == INTEGER_TYPE) { - offset = KMInteger.cast(keyPtr).getStartOff(); + offset = KMInteger.getStartOff(keyPtr); } else if (type == NEG_INTEGER_TYPE) { - offset = KMNInteger.cast(keyPtr).getStartOff(); + offset = KMNInteger.getStartOff(keyPtr); } else { ISOException.throwIt(ISO7816.SW_DATA_INVALID); } diff --git a/Applet/src/com/android/javacard/keymaster/KMCosePairCoseKeyTag.java b/Applet/src/com/android/javacard/kmdevice/KMCosePairCoseKeyTag.java similarity index 90% rename from Applet/src/com/android/javacard/keymaster/KMCosePairCoseKeyTag.java rename to Applet/src/com/android/javacard/kmdevice/KMCosePairCoseKeyTag.java index 7eb1251e..8f38ac3b 100644 --- a/Applet/src/com/android/javacard/keymaster/KMCosePairCoseKeyTag.java +++ b/Applet/src/com/android/javacard/kmdevice/KMCosePairCoseKeyTag.java @@ -1,4 +1,4 @@ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import javacard.framework.ISO7816; import javacard.framework.ISOException; @@ -10,11 +10,15 @@ */ public class KMCosePairCoseKeyTag extends KMCosePairTagType { - public static final byte[] keys = { - KMCose.COSE_LABEL_COSE_KEY - }; + public static byte[] keys; private static KMCosePairCoseKeyTag prototype; + public static void initStatics() { + keys = new byte[]{ + KMCose.COSE_LABEL_COSE_KEY + }; + } + private KMCosePairCoseKeyTag() { } @@ -39,7 +43,7 @@ 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) { + if (KMType.getKMType(valuePtr) != COSE_KEY_TYPE) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } short ptr = KMType.instance(COSE_PAIR_TAG_TYPE, (short) 6); @@ -56,7 +60,7 @@ public static KMCosePairCoseKeyTag cast(short ptr) { } // Validate the value pointer. short valuePtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4)); - if (KMType.getType(valuePtr) != COSE_KEY_TYPE) { + if (KMType.getKMType(valuePtr) != COSE_KEY_TYPE) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } return proto(ptr); diff --git a/Applet/src/com/android/javacard/keymaster/KMCosePairIntegerTag.java b/Applet/src/com/android/javacard/kmdevice/KMCosePairIntegerTag.java similarity index 95% rename from Applet/src/com/android/javacard/keymaster/KMCosePairIntegerTag.java rename to Applet/src/com/android/javacard/kmdevice/KMCosePairIntegerTag.java index d5fe47b9..84bff2b1 100644 --- a/Applet/src/com/android/javacard/keymaster/KMCosePairIntegerTag.java +++ b/Applet/src/com/android/javacard/kmdevice/KMCosePairIntegerTag.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import javacard.framework.ISO7816; import javacard.framework.ISOException; @@ -52,7 +52,7 @@ public static short exp() { 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())) { + KMInteger.getShort(valuePtr))) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } short ptr = KMType.instance(COSE_PAIR_TAG_TYPE, (short) 6); @@ -69,7 +69,7 @@ public static KMCosePairIntegerTag cast(short ptr) { } // Validate the value ptr. short valuePtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4)); - if (INTEGER_TYPE != getType(valuePtr)) { + if (INTEGER_TYPE != getKMType(valuePtr)) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } return proto(ptr); diff --git a/Applet/src/com/android/javacard/keymaster/KMCosePairNegIntegerTag.java b/Applet/src/com/android/javacard/kmdevice/KMCosePairNegIntegerTag.java similarity index 95% rename from Applet/src/com/android/javacard/keymaster/KMCosePairNegIntegerTag.java rename to Applet/src/com/android/javacard/kmdevice/KMCosePairNegIntegerTag.java index b2abb95d..248c08f3 100644 --- a/Applet/src/com/android/javacard/keymaster/KMCosePairNegIntegerTag.java +++ b/Applet/src/com/android/javacard/kmdevice/KMCosePairNegIntegerTag.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import javacard.framework.ISO7816; import javacard.framework.ISOException; @@ -56,7 +56,7 @@ public static KMCosePairNegIntegerTag cast(short ptr) { } // Validate the value ptr. short valuePtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4)); - if (NEG_INTEGER_TYPE != getType(valuePtr)) { + if (NEG_INTEGER_TYPE != getKMType(valuePtr)) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } return proto(ptr); @@ -65,7 +65,7 @@ public static KMCosePairNegIntegerTag cast(short 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())) { + KMNInteger.getShort(valuePtr))) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } short ptr = KMType.instance(COSE_PAIR_TAG_TYPE, (short) 6); diff --git a/Applet/src/com/android/javacard/keymaster/KMCosePairSimpleValueTag.java b/Applet/src/com/android/javacard/kmdevice/KMCosePairSimpleValueTag.java similarity index 94% rename from Applet/src/com/android/javacard/keymaster/KMCosePairSimpleValueTag.java rename to Applet/src/com/android/javacard/kmdevice/KMCosePairSimpleValueTag.java index 6aea5e30..454be097 100644 --- a/Applet/src/com/android/javacard/keymaster/KMCosePairSimpleValueTag.java +++ b/Applet/src/com/android/javacard/kmdevice/KMCosePairSimpleValueTag.java @@ -1,4 +1,4 @@ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import javacard.framework.ISO7816; import javacard.framework.ISOException; @@ -35,7 +35,7 @@ public static short exp() { 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())) { + KMSimpleValue.getValue(valuePtr))) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } short ptr = KMType.instance(COSE_PAIR_TAG_TYPE, (short) 6); @@ -52,7 +52,7 @@ public static KMCosePairSimpleValueTag cast(short ptr) { } // Validate the value pointer. short valuePtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4)); - if (KMType.getType(valuePtr) != SIMPLE_VALUE_TYPE) { + if (KMType.getKMType(valuePtr) != SIMPLE_VALUE_TYPE) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } return proto(ptr); diff --git a/Applet/src/com/android/javacard/keymaster/KMCosePairTagType.java b/Applet/src/com/android/javacard/kmdevice/KMCosePairTagType.java similarity index 90% rename from Applet/src/com/android/javacard/keymaster/KMCosePairTagType.java rename to Applet/src/com/android/javacard/kmdevice/KMCosePairTagType.java index 053fc633..2f5b99db 100644 --- a/Applet/src/com/android/javacard/keymaster/KMCosePairTagType.java +++ b/Applet/src/com/android/javacard/kmdevice/KMCosePairTagType.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import javacard.framework.ISO7816; import javacard.framework.ISOException; @@ -126,12 +126,12 @@ private static boolean isEqual(byte[] key1, short offset1, short length1, byte[] * @return value of the key as short. */ public static short getKeyValueShort(short keyPtr) { - short type = KMType.getType(keyPtr); + short type = KMType.getKMType(keyPtr); short value = 0; if (type == INTEGER_TYPE) { - value = KMInteger.cast(keyPtr).getShort(); + value = KMInteger.getShort(keyPtr); } else if (type == NEG_INTEGER_TYPE) { - value = KMNInteger.cast(keyPtr).getShort(); + value = KMNInteger.getShort(keyPtr); } else { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } @@ -145,12 +145,12 @@ public static short getKeyValueShort(short keyPtr) { * @return value of the key as short. */ public static short getKeyValueSignificantShort(short keyPtr) { - short type = KMType.getType(keyPtr); + short type = KMType.getKMType(keyPtr); short value = 0; if (type == INTEGER_TYPE) { - value = KMInteger.cast(keyPtr).getSignificantShort(); + value = KMInteger.getSignificantShort(keyPtr); } else if (type == NEG_INTEGER_TYPE) { - value = KMNInteger.cast(keyPtr).getSignificantShort(); + value = KMNInteger.getSignificantShort(keyPtr); } else { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } @@ -158,11 +158,11 @@ public static short getKeyValueSignificantShort(short keyPtr) { } public static void getKeyValue(short keyPtr, byte[] dest, short offset, short len) { - short type = KMType.getType(keyPtr); + short type = KMType.getKMType(keyPtr); if (type == INTEGER_TYPE) { - KMInteger.cast(keyPtr).getValue(dest, offset, len); + KMInteger.getValue(keyPtr, dest, offset, len); } else if (type == NEG_INTEGER_TYPE) { - KMNInteger.cast(keyPtr).getValue(dest, offset, len); + KMNInteger.getValue(keyPtr, dest, offset, len); } else { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } @@ -175,12 +175,12 @@ public static void getKeyValue(short keyPtr, byte[] dest, short offset, short le * @return offset from where the key starts. */ public static short getKeyStartOffset(short keyPtr) { - short type = KMType.getType(keyPtr); + short type = KMType.getKMType(keyPtr); short offset = 0; if (type == INTEGER_TYPE) { - offset = KMInteger.cast(keyPtr).getStartOff(); + offset = KMInteger.getStartOff(keyPtr); } else if (type == NEG_INTEGER_TYPE) { - offset = KMNInteger.cast(keyPtr).getStartOff(); + offset = KMNInteger.getStartOff(keyPtr); } else { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } @@ -194,12 +194,12 @@ public static short getKeyStartOffset(short keyPtr) { * @return length of the key. */ public static short getKeyLength(short keyPtr) { - short type = KMType.getType(keyPtr); + short type = KMType.getKMType(keyPtr); short len = 0; if (type == INTEGER_TYPE) { - len = KMInteger.cast(keyPtr).length(); + len = KMInteger.length(keyPtr); } else if (type == NEG_INTEGER_TYPE) { - len = KMNInteger.cast(keyPtr).length(); + len = KMNInteger.length(keyPtr); } else { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } diff --git a/Applet/src/com/android/javacard/keymaster/KMCosePairTextStringTag.java b/Applet/src/com/android/javacard/kmdevice/KMCosePairTextStringTag.java similarity index 90% rename from Applet/src/com/android/javacard/keymaster/KMCosePairTextStringTag.java rename to Applet/src/com/android/javacard/kmdevice/KMCosePairTextStringTag.java index 165e3d15..bfee89bf 100644 --- a/Applet/src/com/android/javacard/keymaster/KMCosePairTextStringTag.java +++ b/Applet/src/com/android/javacard/kmdevice/KMCosePairTextStringTag.java @@ -1,4 +1,4 @@ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import javacard.framework.ISO7816; import javacard.framework.ISOException; @@ -12,11 +12,15 @@ public class KMCosePairTextStringTag extends KMCosePairTagType { private static KMCosePairTextStringTag prototype; - public static final byte[] keys = { - KMCose.ISSUER, - KMCose.SUBJECT, - }; + public static byte[] keys; + public static void initStatics() { + keys = new byte[]{ + KMCose.ISSUER, + KMCose.SUBJECT, + }; + } + private KMCosePairTextStringTag() { } @@ -41,7 +45,7 @@ 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) { + if (KMType.getKMType(valuePtr) != TEXT_STRING_TYPE) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } short ptr = KMType.instance(COSE_PAIR_TAG_TYPE, (short) 6); @@ -58,7 +62,7 @@ public static KMCosePairTextStringTag cast(short ptr) { } // Validate the value pointer. short valuePtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4)); - if (KMType.getType(valuePtr) != TEXT_STRING_TYPE) { + if (KMType.getKMType(valuePtr) != TEXT_STRING_TYPE) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } return proto(ptr); diff --git a/Applet/src/com/android/javacard/keymaster/KMDecoder.java b/Applet/src/com/android/javacard/kmdevice/KMDecoder.java similarity index 90% rename from Applet/src/com/android/javacard/keymaster/KMDecoder.java rename to Applet/src/com/android/javacard/kmdevice/KMDecoder.java index 923a93e4..3fc78d61 100644 --- a/Applet/src/com/android/javacard/keymaster/KMDecoder.java +++ b/Applet/src/com/android/javacard/kmdevice/KMDecoder.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import com.android.javacard.seprovider.KMException; import javacard.framework.ISO7816; @@ -70,7 +70,7 @@ public short decodeArray(short exp, byte[] buffer, short startOff, short length) scratchBuf[START_OFFSET] = startOff; scratchBuf[LEN_OFFSET] = (short) (startOff + length); short payloadLength = readMajorTypeWithPayloadLength(ARRAY_TYPE); - short expLength = KMArray.cast(exp).length(); + short expLength = KMArray.length(exp); if (payloadLength > expLength) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } @@ -79,16 +79,16 @@ public short decodeArray(short exp, byte[] buffer, short startOff, short length) short type; short arrPtr = KMArray.instance(payloadLength); while (index < payloadLength) { - type = KMArray.cast(exp).get(index); + type = KMArray.get(exp, index); obj = decode(type); - KMArray.cast(arrPtr).add(index, obj); + KMArray.add(arrPtr, index, obj); index++; } return arrPtr; } private short decode(short exp) { - byte type = KMType.getType(exp); + byte type = KMType.getKMType(exp); switch (type) { case KMType.BYTE_BLOB_TYPE: return decodeByteBlob(exp); @@ -124,7 +124,7 @@ private short decode(short exp) { short tagValueType = KMCosePairTagType.getTagValueType(exp); return decodeCosePairTag(tagValueType, exp); case KMType.TAG_TYPE: - short tagType = KMTag.getTagType(exp); + short tagType = KMTag.getKMTagType(exp); return decodeTag(tagType, exp); default: ISOException.throwIt(ISO7816.SW_DATA_INVALID); @@ -158,22 +158,22 @@ private short decodeTag(short tagType, short exp) { } private short decodeVerificationToken(short exp) { - short vals = decode(KMVerificationToken.cast(exp).getVals()); + short vals = decode(KMVerificationToken.getVals(exp)); return KMVerificationToken.instance(vals); } private short decodeHwAuthToken(short exp) { - short vals = decode(KMHardwareAuthToken.cast(exp).getVals()); + short vals = decode(KMHardwareAuthToken.getVals(exp)); return KMHardwareAuthToken.instance(vals); } private short decodeHmacSharingParam(short exp) { - short vals = decode(KMHmacSharingParameters.cast(exp).getVals()); + short vals = decode(KMHmacSharingParameters.getVals(exp)); return KMHmacSharingParameters.instance(vals); } private short decodeKeyChar(short exp) { - short vals = decode(KMKeyCharacteristics.cast(exp).getVals()); + short vals = decode(KMKeyCharacteristics.getVals(exp)); return KMKeyCharacteristics.instance(vals); } @@ -295,7 +295,7 @@ private short decodeCoseMap(short exp) { // get allowed key pairs short allowedKeyPairs = KMCoseMap.getVals(exp); short vals = KMArray.instance(payloadLength); - short length = KMArray.cast(allowedKeyPairs).length(); + short length = KMArray.length(allowedKeyPairs); short index = 0; boolean tagFound; short tagInd; @@ -311,11 +311,11 @@ private short decodeCoseMap(short exp) { cosePairTagType = peekCosePairTagType(); // Check against the allowed tags ... while (tagInd < length) { - tagClass = KMArray.cast(allowedKeyPairs).get(tagInd); + tagClass = KMArray.get(allowedKeyPairs, tagInd); allowedType = KMCosePairTagType.getTagValueType(tagClass); if (allowedType == cosePairTagType) { obj = decode(tagClass); - KMArray.cast(vals).add(index, obj); + KMArray.add(vals, index, obj); tagFound = true; break; } @@ -333,11 +333,11 @@ private short decodeCoseMap(short exp) { 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 allowedTags = KMKeyParameters.getVals(exp); + short tagRule = KMArray.get(allowedTags, (short)0); + boolean ignoreInvalidTags = KMEnum.getVal(tagRule) == KMType.IGNORE_INVALID_TAGS; short vals = KMArray.instance(payloadLength); - short length = KMArray.cast(allowedTags).length(); + short length = KMArray.length(allowedTags); short index = 0; boolean tagFound; short tagInd; @@ -352,15 +352,15 @@ private short decodeKeyParam(short exp) { tagType = peekTagType(); // Check against the allowed tags ... while (tagInd < length) { - tagClass = KMArray.cast(allowedTags).get(tagInd); - allowedType = KMTag.getTagType(tagClass); + tagClass = KMArray.get(allowedTags, tagInd); + allowedType = KMTag.getKMTagType(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(index, obj); + KMArray.add(vals, index, obj); break; }catch(KMException e){ if(KMException.reason() == KMError.INVALID_TAG && @@ -381,34 +381,34 @@ private short decodeKeyParam(short exp) { } private short decodeEnumArrayTag(short exp) { - readTagKey(KMEnumArrayTag.cast(exp).getTagType()); - return KMEnumArrayTag.instance(scratchBuf[TAG_KEY_OFFSET], decode(KMEnumArrayTag.cast(exp).getValues())); + readTagKey(KMEnumArrayTag.getTagType(exp)); + return KMEnumArrayTag.instance(scratchBuf[TAG_KEY_OFFSET], decode(KMEnumArrayTag.getValues(exp))); } private short decodeIntegerArrayTag(short exp) { - readTagKey(KMIntegerArrayTag.cast(exp).getTagType()); + readTagKey(KMIntegerArrayTag.getTagType(exp)); // the values are array of integers. - return KMIntegerArrayTag.instance(KMIntegerArrayTag.cast(exp).getTagType(), - scratchBuf[TAG_KEY_OFFSET], decode(KMIntegerArrayTag.cast(exp).getValues())); + return KMIntegerArrayTag.instance(KMIntegerArrayTag.getTagType(exp), + scratchBuf[TAG_KEY_OFFSET], decode(KMIntegerArrayTag.getValues(exp))); } private short decodeIntegerTag(short exp) { - readTagKey(KMIntegerTag.cast(exp).getTagType()); + readTagKey(KMIntegerTag.getTagType(exp)); // the value is an integer - return KMIntegerTag.instance(KMIntegerTag.cast(exp).getTagType(), - scratchBuf[TAG_KEY_OFFSET], decode(KMIntegerTag.cast(exp).getValue())); + return KMIntegerTag.instance(KMIntegerTag.getTagType(exp), + scratchBuf[TAG_KEY_OFFSET], decode(KMIntegerTag.getValue(exp))); } private short decodeBytesTag(short exp) { - readTagKey(KMByteTag.cast(exp).getTagType()); + readTagKey(KMByteTag.getTagType(exp)); // The value must be byte blob - return KMByteTag.instance(scratchBuf[TAG_KEY_OFFSET], decode(KMByteTag.cast(exp).getValue())); + return KMByteTag.instance(scratchBuf[TAG_KEY_OFFSET], decode(KMByteTag.getValue(exp))); } private short decodeBignumTag(short exp) { - readTagKey(KMBignumTag.cast(exp).getTagType()); + readTagKey(KMBignumTag.getTagType(exp)); // The value must be byte blob - return KMBignumTag.instance(scratchBuf[TAG_KEY_OFFSET], decode(KMBignumTag.cast(exp).getValue())); + return KMBignumTag.instance(scratchBuf[TAG_KEY_OFFSET], decode(KMBignumTag.getValue(exp))); } private short decodeMap(short exp) { @@ -419,11 +419,11 @@ private short decodeMap(short exp) { short keyobj; short valueobj; while (index < payloadLength) { - type = KMMap.cast(exp).getKey(index); + type = KMMap.getKey(exp, index); keyobj = decode(type); - type = KMMap.cast(exp).getKeyValue(index); + type = KMMap.getKeyValue(exp, index); valueobj = decode(type); - KMMap.cast(mapPtr).add(index, keyobj, valueobj); + KMMap.add(mapPtr, index, keyobj, valueobj); index++; } return mapPtr; @@ -436,23 +436,23 @@ private short decodeArray(short exp) { short type; short obj; // check whether array contains one type of objects or multiple types - if (KMArray.cast(exp).containedType() == 0) {// multiple types specified by expression. - if (KMArray.cast(exp).length() != KMArray.ANY_ARRAY_LENGTH) { - if (KMArray.cast(exp).length() != payloadLength) { + if (KMArray.containedType(exp) == 0) {// multiple types specified by expression. + if (KMArray.length(exp) != KMArray.ANY_ARRAY_LENGTH) { + if (KMArray.length(exp) != payloadLength) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } } while (index < payloadLength) { - type = KMArray.cast(exp).get(index); + type = KMArray.get(exp, index); obj = decode(type); - KMArray.cast(arrPtr).add(index, obj); + KMArray.add(arrPtr, index, obj); index++; } } else { // Array is a Vector containing objects of one type - type = KMArray.cast(exp).containedType(); + type = KMArray.containedType(exp); while (index < payloadLength) { obj = decode(type); - KMArray.cast(arrPtr).add(index, obj); + KMArray.add(arrPtr, index, obj); index++; } } @@ -460,7 +460,7 @@ private short decodeArray(short exp) { } private short decodeEnumTag(short exp) { - readTagKey(KMEnumTag.cast(exp).getTagType()); + readTagKey(KMEnumTag.getTagType(exp)); byte[] buffer = (byte[]) bufferRef[0]; short startOff = scratchBuf[START_OFFSET]; // Enum Tag value will always be integer with max 1 byte length. @@ -487,7 +487,7 @@ private short decodeEnumTag(short exp) { } private short decodeBoolTag(short exp) { - readTagKey(KMBoolTag.cast(exp).getTagType()); + readTagKey(KMBoolTag.getTagType(exp)); 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. @@ -524,11 +524,10 @@ private short decodeEnum(short exp) { enumVal = buffer[startOff]; incrementStartOff((short) 1); } - return KMEnum.instance(KMEnum.cast(exp).getEnumType(), enumVal); + return KMEnum.instance(KMEnum.getEnumType(exp), enumVal); } private short decodeSimpleValue(short exp) { - short inst; short startOff = scratchBuf[START_OFFSET]; byte[] buffer = (byte[]) bufferRef[0]; if ((buffer[startOff] & MAJOR_TYPE_MASK) != SIMPLE_VALUE_TYPE) { @@ -599,8 +598,8 @@ private short decodeNegIntegerValue(byte addInfo, byte[] buf, short startOffset) // 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(); + byte[] input = KMByteBlob.getBuffer(scratchpad); + short offset = KMByteBlob.getStartOff(scratchpad); 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); diff --git a/Applet/src/com/android/javacard/keymaster/KMEncoder.java b/Applet/src/com/android/javacard/kmdevice/KMEncoder.java similarity index 87% rename from Applet/src/com/android/javacard/keymaster/KMEncoder.java rename to Applet/src/com/android/javacard/kmdevice/KMEncoder.java index bc5cbdd6..9ae15e7f 100644 --- a/Applet/src/com/android/javacard/keymaster/KMEncoder.java +++ b/Applet/src/com/android/javacard/kmdevice/KMEncoder.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import com.android.javacard.seprovider.KMException; import javacard.framework.ISO7816; @@ -82,8 +82,8 @@ public short encode(short object, byte[] buffer, short startOff) { bufferRef[0] = buffer; scratchBuf[START_OFFSET] = startOff; short len = (short) (buffer.length - startOff); - if ((len < 0) || len > KMKeymasterApplet.MAX_LENGTH) { - scratchBuf[LEN_OFFSET] = KMKeymasterApplet.MAX_LENGTH; + if ((len < 0) || len > KMKeymasterDevice.MAX_LENGTH) { + scratchBuf[LEN_OFFSET] = KMKeymasterDevice.MAX_LENGTH; } else { scratchBuf[LEN_OFFSET] = (short) buffer.length; } @@ -145,7 +145,7 @@ public short encodeError(short errInt32Ptr, byte[] buffer, short startOff, short private void encode() { while (scratchBuf[STACK_PTR_OFFSET] > 0) { short exp = pop(); - byte type = KMType.getType(exp); + byte type = KMType.getKMType(exp); switch (type) { case KMType.BYTE_BLOB_TYPE: encodeByteBlob(exp); @@ -192,7 +192,7 @@ private void encode() { encodeHwAuthToken(exp); break; case KMType.TAG_TYPE: - short tagType = KMTag.getTagType(exp); + short tagType = KMTag.getKMTagType(exp); encodeTag(tagType, exp); break; case KMType.COSE_PAIR_TAG_TYPE: @@ -305,32 +305,32 @@ private void encodeCoseMap(short obj) { } private void encodeKeyParam(short obj) { - encodeAsMap(KMKeyParameters.cast(obj).getVals()); + encodeAsMap(KMKeyParameters.getVals(obj)); } private void encodeKeyChar(short obj) { - encode(KMKeyCharacteristics.cast(obj).getVals()); + encode(KMKeyCharacteristics.getVals(obj)); } private void encodeVeriToken(short obj) { - encode(KMVerificationToken.cast(obj).getVals()); + encode(KMVerificationToken.getVals(obj)); } private void encodeHwAuthToken(short obj) { - encode(KMHardwareAuthToken.cast(obj).getVals()); + encode(KMHardwareAuthToken.getVals(obj)); } private void encodeHmacSharingParam(short obj) { - encode(KMHmacSharingParameters.cast(obj).getVals()); + encode(KMHmacSharingParameters.getVals(obj)); } private void encodeArray(short obj) { - writeMajorTypeWithLength(ARRAY_TYPE, KMArray.cast(obj).length()); - short len = KMArray.cast(obj).length(); + writeMajorTypeWithLength(ARRAY_TYPE, KMArray.length(obj)); + short len = KMArray.length(obj); short index = (short) (len - 1); short subObj; while (index >= 0) { - subObj = KMArray.cast(obj).get(index); + subObj = KMArray.get(obj, index); if (subObj != KMType.INVALID_VALUE) encode(subObj); index--; @@ -345,60 +345,60 @@ public void encodeArrayOnlyLength(short arrLength, byte[] buffer, short offset, } private void encodeMap(short obj) { - writeMajorTypeWithLength(MAP_TYPE, KMMap.cast(obj).length()); - short len = KMMap.cast(obj).length(); + writeMajorTypeWithLength(MAP_TYPE, KMMap.length(obj)); + short len = KMMap.length(obj); short index = (short) (len - 1); while (index >= 0) { - encode(KMMap.cast(obj).getKeyValue(index)); - encode(KMMap.cast(obj).getKey(index)); + encode(KMMap.getKeyValue(obj, index)); + encode(KMMap.getKey(obj, index)); index--; } } private void encodeAsMap(short obj) { - writeMajorTypeWithLength(MAP_TYPE, KMArray.cast(obj).length()); - short len = KMArray.cast(obj).length(); + writeMajorTypeWithLength(MAP_TYPE, KMArray.length(obj)); + short len = KMArray.length(obj); short index = (short) (len - 1); short inst; while (index >= 0) { - inst = KMArray.cast(obj).get(index); + inst = KMArray.get(obj, index); encode(inst); index--; } } private void encodeIntegerArrayTag(short obj) { - writeTag(KMIntegerArrayTag.cast(obj).getTagType(), KMIntegerArrayTag.cast(obj).getKey()); - encode(KMIntegerArrayTag.cast(obj).getValues()); + writeTag(KMIntegerArrayTag.getTagType(obj), KMIntegerArrayTag.getKey(obj)); + encode(KMIntegerArrayTag.getValues(obj)); } private void encodeEnumArrayTag(short obj) { - writeTag(KMEnumArrayTag.cast(obj).getTagType(), KMEnumArrayTag.cast(obj).getKey()); - encode(KMEnumArrayTag.cast(obj).getValues()); + writeTag(KMEnumArrayTag.getTagType(obj), KMEnumArrayTag.getKey(obj)); + encode(KMEnumArrayTag.getValues(obj)); } private void encodeIntegerTag(short obj) { - writeTag(KMIntegerTag.cast(obj).getTagType(), KMIntegerTag.cast(obj).getKey()); - encode(KMIntegerTag.cast(obj).getValue()); + writeTag(KMIntegerTag.getTagType(obj), KMIntegerTag.getKey(obj)); + encode(KMIntegerTag.getValue(obj)); } private void encodeBytesTag(short obj) { - writeTag(KMByteTag.cast(obj).getTagType(), KMByteTag.cast(obj).getKey()); - encode(KMByteTag.cast(obj).getValue()); + writeTag(KMByteTag.getTagType(obj), KMByteTag.getKey(obj)); + encode(KMByteTag.getValue(obj)); } private void encodeBoolTag(short obj) { - writeTag(KMBoolTag.cast(obj).getTagType(), KMBoolTag.cast(obj).getKey()); - writeByteValue(KMBoolTag.cast(obj).getVal()); + writeTag(KMBoolTag.getTagType(obj), KMBoolTag.getKey(obj)); + writeByteValue(KMBoolTag.getVal(obj)); } private void encodeEnumTag(short obj) { - writeTag(KMEnumTag.cast(obj).getTagType(), KMEnumTag.cast(obj).getKey()); - writeByteValue(KMEnumTag.cast(obj).getValue()); + writeTag(KMEnumTag.getTagType(obj), KMEnumTag.getKey(obj)); + writeByteValue(KMEnumTag.getValue(obj)); } private void encodeEnum(short obj) { - writeByteValue(KMEnum.cast(obj).getVal()); + writeByteValue(KMEnum.getVal(obj)); } private void encodeInteger(byte[] val, short len, short startOff, short majorType) { @@ -488,30 +488,30 @@ public void removeNegIntegerEncodingRule(byte[] buf, short offset, short len, sh } private void encodeNegInteger(short obj) { - byte[] val = KMNInteger.cast(obj).getBuffer(); - short len = KMNInteger.cast(obj).length(); - short startOff = KMNInteger.cast(obj).getStartOff(); + byte[] val = KMNInteger.getBuffer(obj); + short len = KMNInteger.length(obj); + short startOff = KMNInteger.getStartOff(obj); short msbIndex = applyNegIntegerEncodingRule(val, startOff, len); encodeInteger(val, len, startOff, NEG_INT_TYPE); removeNegIntegerEncodingRule(val, startOff, len, msbIndex); } private void encodeUnsignedInteger(short obj) { - byte[] val = KMInteger.cast(obj).getBuffer(); - short len = KMInteger.cast(obj).length(); - short startOff = KMInteger.cast(obj).getStartOff(); + byte[] val = KMInteger.getBuffer(obj); + short len = KMInteger.length(obj); + short startOff = KMInteger.getStartOff(obj); encodeInteger(val, len, startOff, UINT_TYPE); } private void encodeSimpleValue(short obj) { - byte value = KMSimpleValue.cast(obj).getValue(); + byte value = KMSimpleValue.getValue(obj); 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()); + writeMajorTypeWithLength(TSTR_TYPE, KMTextString.length(obj)); + writeBytes(KMTextString.getBuffer(obj), KMTextString.getStartOff(obj), + KMTextString.length(obj)); } public short encodeByteBlobHeader(short bufLen, byte[] buffer, short startOff, short length) { @@ -523,14 +523,14 @@ public short encodeByteBlobHeader(short bufLen, byte[] buffer, short startOff, s } 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()); + writeMajorTypeWithLength(BYTES_TYPE, KMByteBlob.length(obj)); + writeBytes(KMByteBlob.getBuffer(obj), KMByteBlob.getStartOff(obj), + KMByteBlob.length(obj)); } public short getEncodedLength(short ptr) { short len = 0; - short type = KMType.getType(ptr); + short type = KMType.getKMType(ptr); switch (type) { case KMType.BYTE_BLOB_TYPE: len += getEncodedByteBlobLength(ptr); @@ -605,24 +605,24 @@ private short getEncodedCosePairTagLen(short tagType, short exp) { } private short getEncodedMapLen(short obj) { - short mapLen = KMMap.cast(obj).length(); + short mapLen = KMMap.length(obj); short len = getEncodedBytesLength(mapLen); short index = 0; while (index < mapLen) { - len += getEncodedLength(KMMap.cast(obj).getKey(index)); - len += getEncodedLength(KMMap.cast(obj).getKeyValue(index)); + len += getEncodedLength(KMMap.getKey(obj, index)); + len += getEncodedLength(KMMap.getKeyValue(obj, index)); index++; } return len; } private short getEncodedArrayLen(short obj) { - short arrLen = KMArray.cast(obj).length(); + short arrLen = KMArray.length(obj); short len = getEncodedBytesLength(arrLen); short index = 0; short subObj; while (index < arrLen) { - subObj = KMArray.cast(obj).get(index); + subObj = KMArray.get(obj, index); if (subObj != KMType.INVALID_VALUE) len += getEncodedLength(subObj); index++; @@ -645,21 +645,21 @@ private short getEncodedBytesLength(short len) { } private short getEncodedByteBlobLength(short obj) { - short len = KMByteBlob.cast(obj).length(); + short len = KMByteBlob.length(obj); len += getEncodedBytesLength(len); return len; } private short getEncodedTextStringLength(short obj) { - short len = KMTextString.cast(obj).length(); - len += getEncodedBytesLength(len); - return len; + //short len = KMTextString.byteBlobLength(obj); + //len += getEncodedBytesLength(len); + return 0;//len; } private short getEncodedNegIntegerLength(short obj) { - byte[] buf = KMNInteger.cast(obj).getBuffer(); - short len = KMNInteger.cast(obj).length(); - short offset = KMNInteger.cast(obj).getStartOff(); + byte[] buf = KMNInteger.getBuffer(obj); + short len = KMNInteger.length(obj); + short offset = KMNInteger.getStartOff(obj); short msbIndex = applyNegIntegerEncodingRule(buf, offset, len); short ret = getEncodedIntegerLength(buf, offset, len); removeNegIntegerEncodingRule(buf, offset, len, msbIndex); @@ -691,9 +691,9 @@ private short getEncodedIntegerLength(byte[] val, short startOff, short len) { } private short getEncodedIntegerLength(short obj) { - byte[] val = KMInteger.cast(obj).getBuffer(); - short len = KMInteger.cast(obj).length(); - short startOff = KMInteger.cast(obj).getStartOff(); + byte[] val = KMInteger.getBuffer(obj); + short len = KMInteger.length(obj); + short startOff = KMInteger.getStartOff(obj); return getEncodedIntegerLength(val, startOff, len); } diff --git a/Applet/src/com/android/javacard/keymaster/KMEnum.java b/Applet/src/com/android/javacard/kmdevice/KMEnum.java similarity index 85% rename from Applet/src/com/android/javacard/keymaster/KMEnum.java rename to Applet/src/com/android/javacard/kmdevice/KMEnum.java index 1cfe5803..d327dad8 100644 --- a/Applet/src/com/android/javacard/keymaster/KMEnum.java +++ b/Applet/src/com/android/javacard/kmdevice/KMEnum.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import javacard.framework.ISO7816; import javacard.framework.ISOException; @@ -30,23 +30,27 @@ 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 short[] types; private static Object[] enums = null; private KMEnum() { } - + + public static void initStatics() { + types = new short[]{ + HARDWARE_TYPE, + KEY_FORMAT, + KEY_DERIVATION_FUNCTION, + VERIFIED_BOOT_STATE, + DEVICE_LOCKED, + USER_AUTH_TYPE, + PURPOSE, + ECCURVE, + RULE + }; + } + private static KMEnum proto(short ptr) { if (prototype == null) { prototype = new KMEnum(); @@ -64,14 +68,18 @@ public short length() { return Util.getShort(heap, (short) (KMType.instanceTable[KM_ENUM_OFFSET] + 1)); } - public static KMEnum cast(short ptr) { + private static KMEnum cast(short ptr) { + validate(ptr); + return proto(ptr); + } + + public static void validate(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) { @@ -164,4 +172,22 @@ private static boolean validateEnum(short key, byte value) { // return false if key does not exist return false; } + + public static void setVal(short bPtr, byte val) { + KMEnum.cast(bPtr).setVal(val); + } + + public static byte getVal(short bPtr) { + return KMEnum.cast(bPtr).getVal(); + } + + public static void setEnumType(short bPtr, short type) { + KMEnum.cast(bPtr).setEnumType(type); + } + + public static short getEnumType(short bPtr) { + return KMEnum.cast(bPtr).getEnumType(); + } + + } diff --git a/Applet/src/com/android/javacard/keymaster/KMEnumArrayTag.java b/Applet/src/com/android/javacard/kmdevice/KMEnumArrayTag.java similarity index 85% rename from Applet/src/com/android/javacard/keymaster/KMEnumArrayTag.java rename to Applet/src/com/android/javacard/kmdevice/KMEnumArrayTag.java index 0f932a9d..e533b769 100644 --- a/Applet/src/com/android/javacard/keymaster/KMEnumArrayTag.java +++ b/Applet/src/com/android/javacard/kmdevice/KMEnumArrayTag.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import javacard.framework.ISO7816; import javacard.framework.ISOException; @@ -29,11 +29,15 @@ 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}; + private static short[] tags; // Tag Values. private static Object[] enums = null; + public static void initStatics() { + tags = new short[]{PURPOSE, BLOCK_MODE, DIGEST, PADDING, RSA_OAEP_MGF_DIGEST}; + } + private KMEnumArrayTag() { } @@ -69,15 +73,14 @@ public static short instance(short key, short byteBlob) { 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()) { + while (byteIndex < KMByteBlob.length(byteBlob)) { enumIndex = 0; validValue = false; while (enumIndex < allowedVals.length) { - if (blob.get(byteIndex) == allowedVals[enumIndex]) { + if (KMByteBlob.get(byteBlob, byteIndex) == allowedVals[enumIndex]) { validValue = true; break; } @@ -95,7 +98,7 @@ public static short instance(short key, short byteBlob) { return ptr; } - public static KMEnumArrayTag cast(short ptr) { + private static KMEnumArrayTag cast(short ptr) { if (heap[ptr] != TAG_TYPE) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } @@ -119,7 +122,7 @@ public short getValues() { 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(); + return KMByteBlob.length(blobPtr); } public static void create() { @@ -151,20 +154,20 @@ private static byte[] getAllowedEnumValues(short key) { } public static short getValues(short tagId, short params, byte[] buf, short start) { - short tag = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, tagId, params); + short tag = KMKeyParameters.findTag(params, KMType.ENUM_ARRAY_TAG, tagId); if (tag == KMType.INVALID_VALUE) { return KMType.INVALID_VALUE; } tag = KMEnumArrayTag.cast(tag).getValues(); - return KMByteBlob.cast(tag).getValues(buf, start); + return KMByteBlob.getValues(tag, buf, start); } public short get(short index) { - return KMByteBlob.cast(getValues()).get(index); + return KMByteBlob.get(getValues(), index); } public static boolean contains(short tagId, short tagValue, short params) { - short tag = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, tagId, params); + short tag = KMKeyParameters.findTag(params, KMType.ENUM_ARRAY_TAG, tagId); if (tag != KMType.INVALID_VALUE) { short index = 0; while (index < KMEnumArrayTag.cast(tag).length()) { @@ -178,7 +181,7 @@ public static boolean contains(short tagId, short tagValue, short params) { } public static short length(short tagId, short params) { - short tag = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, tagId, params); + short tag = KMKeyParameters.findTag(params, KMType.ENUM_ARRAY_TAG, tagId); if (tag != KMType.INVALID_VALUE) { return KMEnumArrayTag.cast(tag).length(); } @@ -300,4 +303,30 @@ public boolean isValidBlockMode(byte alg) { return false; } } + + public static short get(short bPtr, short index) { + return KMEnumArrayTag.cast(bPtr).get(index); + } + + public static short length(short bPtr) { + return KMEnumArrayTag.cast(bPtr).length(); + } + + public static short getValues(short bPtr) { + return KMEnumArrayTag.cast(bPtr).getValues(); + } + + public static short getTagType(short bPtr) { + return KMType.ENUM_ARRAY_TAG; + } + + public static short getKey(short bPtr) { + return KMEnumArrayTag.cast(bPtr).getKey(); + } + + public static boolean contains(short bPtr, short tagValue) { + return KMEnumArrayTag.cast(bPtr).contains(tagValue); + } + + } diff --git a/Applet/src/com/android/javacard/keymaster/KMEnumTag.java b/Applet/src/com/android/javacard/kmdevice/KMEnumTag.java similarity index 87% rename from Applet/src/com/android/javacard/keymaster/KMEnumTag.java rename to Applet/src/com/android/javacard/kmdevice/KMEnumTag.java index 3f45c304..5866295d 100644 --- a/Applet/src/com/android/javacard/keymaster/KMEnumTag.java +++ b/Applet/src/com/android/javacard/kmdevice/KMEnumTag.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import javacard.framework.ISO7816; import javacard.framework.ISOException; @@ -30,12 +30,16 @@ 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 short[] tags; private static Object[] enums = null; + public static void initStatics() { + tags = new short[]{ + ALGORITHM, ECCURVE, BLOB_USAGE_REQ, USER_AUTH_TYPE, ORIGIN, HARDWARE_TYPE + }; + } + private KMEnumTag() { } @@ -75,7 +79,7 @@ public static short instance(short key, byte val) { return ptr; } - public static KMEnumTag cast(short ptr) { + private static KMEnumTag cast(short ptr) { if (heap[ptr] != TAG_TYPE) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } @@ -144,10 +148,23 @@ private static boolean validateEnum(short key, byte value) { } public static short getValue(short tagKey, short keyParameters) { - short tagPtr = KMKeyParameters.findTag(KMType.ENUM_TAG, tagKey, keyParameters); + short tagPtr = KMKeyParameters.findTag(keyParameters, KMType.ENUM_TAG, tagKey); if (tagPtr != KMType.INVALID_VALUE) { return heap[(short) (tagPtr + TLV_HEADER_SIZE + 4)]; } return KMType.INVALID_VALUE; } + + public static byte getValue(short bPtr) { + return KMEnumTag.cast(bPtr).getValue(); + } + + public static short getTagType(short bPtr) { + return KMEnumTag.cast(bPtr).getTagType(); + } + + public static short getKey(short bPtr) { + return KMEnumTag.cast(bPtr).getKey(); + } + } diff --git a/Applet/src/com/android/javacard/keymaster/KMError.java b/Applet/src/com/android/javacard/kmdevice/KMError.java similarity index 98% rename from Applet/src/com/android/javacard/keymaster/KMError.java rename to Applet/src/com/android/javacard/kmdevice/KMError.java index ddc9493f..649507b3 100644 --- a/Applet/src/com/android/javacard/keymaster/KMError.java +++ b/Applet/src/com/android/javacard/kmdevice/KMError.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; /** * KMError includes all the error codes from android keymaster hal specifications. The values are @@ -102,7 +102,6 @@ public class KMError { public static final short CRYPTO_UNINITIALIZED_KEY = 10012; //Generic Unknown error. public static final short GENERIC_UNKNOWN_ERROR = 10013; - public static final short PUBLIC_KEY_OPERATION = 10014; // Remote key provisioning error codes. public static final short STATUS_FAILED = 32000; diff --git a/Applet/src/com/android/javacard/keymaster/KMHardwareAuthToken.java b/Applet/src/com/android/javacard/kmdevice/KMHardwareAuthToken.java similarity index 52% rename from Applet/src/com/android/javacard/keymaster/KMHardwareAuthToken.java rename to Applet/src/com/android/javacard/kmdevice/KMHardwareAuthToken.java index deb53cf6..76311a66 100644 --- a/Applet/src/com/android/javacard/keymaster/KMHardwareAuthToken.java +++ b/Applet/src/com/android/javacard/kmdevice/KMHardwareAuthToken.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import javacard.framework.ISO7816; import javacard.framework.ISOException; @@ -43,13 +43,12 @@ 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()); + KMArray.add(arrPtr, CHALLENGE, KMInteger.exp()); + KMArray.add(arrPtr, USER_ID, KMInteger.exp()); + KMArray.add(arrPtr, AUTHENTICATOR_ID, KMInteger.exp()); + KMArray.add(arrPtr, HW_AUTHENTICATOR_TYPE, KMEnum.instance(KMType.USER_AUTH_TYPE)); + KMArray.add(arrPtr, TIMESTAMP, KMInteger.exp()); + KMArray.add(arrPtr, MAC, KMByteBlob.exp()); return instance(arrPtr); } @@ -63,19 +62,17 @@ private static KMHardwareAuthToken proto(short ptr) { 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)); + KMArray.add(arrPtr, CHALLENGE, KMInteger.uint_16((short) 0)); + KMArray.add(arrPtr, USER_ID, KMInteger.uint_16((short) 0)); + KMArray.add(arrPtr, AUTHENTICATOR_ID, KMInteger.uint_16((short) 0)); + KMArray.add(arrPtr, HW_AUTHENTICATOR_TYPE, KMEnum.instance(KMType.USER_AUTH_TYPE, KMType.USER_AUTH_NONE)); + KMArray.add(arrPtr, TIMESTAMP, KMInteger.uint_16((short) 0)); + KMArray.add(arrPtr, MAC, KMByteBlob.instance((short) 0)); return instance(arrPtr); } public static short instance(short vals) { - KMArray arr = KMArray.cast(vals); - if (arr.length() != 6) { + if (KMArray.length(vals) != 6) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } short ptr = KMType.instance(HW_AUTH_TOKEN_TYPE, (short) 2); @@ -83,7 +80,7 @@ public static short instance(short vals) { return ptr; } - public static KMHardwareAuthToken cast(short ptr) { + private static KMHardwareAuthToken cast(short ptr) { if (heap[ptr] != HW_AUTH_TOKEN_TYPE) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } @@ -100,72 +97,128 @@ public short getVals() { public short length() { short arrPtr = getVals(); - return KMArray.cast(arrPtr).length(); + return KMArray.length(arrPtr); } public short getChallenge() { short arrPtr = getVals(); - return KMArray.cast(arrPtr).get(CHALLENGE); + return KMArray.get(arrPtr, CHALLENGE); } public void setChallenge(short vals) { - KMInteger.cast(vals); + KMInteger.validate(vals); short arrPtr = getVals(); - KMArray.cast(arrPtr).add(CHALLENGE, vals); + KMArray.add(arrPtr, CHALLENGE, vals); } public short getUserId() { short arrPtr = getVals(); - return KMArray.cast(arrPtr).get(USER_ID); + return KMArray.get(arrPtr, USER_ID); } public void setUserId(short vals) { - KMInteger.cast(vals); + KMInteger.validate(vals); short arrPtr = getVals(); - KMArray.cast(arrPtr).add(USER_ID, vals); + KMArray.add(arrPtr, USER_ID, vals); } public short getAuthenticatorId() { short arrPtr = getVals(); - return KMArray.cast(arrPtr).get(AUTHENTICATOR_ID); + return KMArray.get(arrPtr, AUTHENTICATOR_ID); } public void setAuthenticatorId(short vals) { - KMInteger.cast(vals); + KMInteger.validate(vals); short arrPtr = getVals(); - KMArray.cast(arrPtr).add(AUTHENTICATOR_ID, vals); + KMArray.add(arrPtr, AUTHENTICATOR_ID, vals); } public short getHwAuthenticatorType() { short arrPtr = getVals(); - return KMArray.cast(arrPtr).get(HW_AUTHENTICATOR_TYPE); + return KMArray.get(arrPtr, HW_AUTHENTICATOR_TYPE); } public void setHwAuthenticatorType(short vals) { - KMEnum.cast(vals); + KMEnum.validate(vals); short arrPtr = getVals(); - KMArray.cast(arrPtr).add(HW_AUTHENTICATOR_TYPE, vals); + KMArray.add(arrPtr, HW_AUTHENTICATOR_TYPE, vals); } public short getTimestamp() { short arrPtr = getVals(); - return KMArray.cast(arrPtr).get(TIMESTAMP); + return KMArray.get(arrPtr, TIMESTAMP); } public void setTimestamp(short vals) { - KMInteger.cast(vals); + KMInteger.validate(vals); short arrPtr = getVals(); - KMArray.cast(arrPtr).add(TIMESTAMP, vals); + KMArray.add(arrPtr, TIMESTAMP, vals); } public short getMac() { short arrPtr = getVals(); - return KMArray.cast(arrPtr).get(MAC); + return KMArray.get(arrPtr, MAC); } public void setMac(short vals) { - KMByteBlob.cast(vals); + KMByteBlob.validate(vals); short arrPtr = getVals(); - KMArray.cast(arrPtr).add(MAC, vals); + KMArray.add(arrPtr, MAC, vals); } + + public static short getVals(short bPtr) { + return KMHardwareAuthToken.cast(bPtr).getVals(); + } + + public static short length(short bPtr) { + return KMHardwareAuthToken.cast(bPtr).length(); + } + + public static short getChallenge(short bPtr) { + return KMHardwareAuthToken.cast(bPtr).getChallenge(); + } + + public static void setChallenge(short bPtr, short vals) { + KMHardwareAuthToken.cast(bPtr).setChallenge(vals); + } + + public static short getUserId(short bPtr) { + return KMHardwareAuthToken.cast(bPtr).getUserId(); + } + + public static void setUserId(short bPtr, short vals) { + KMHardwareAuthToken.cast(bPtr).setUserId(vals); + } + + public static short getAuthenticatorId(short bPtr) { + return KMHardwareAuthToken.cast(bPtr).getAuthenticatorId(); + } + + public static void setAuthenticatorId(short bPtr, short vals) { + KMHardwareAuthToken.cast(bPtr).setAuthenticatorId(vals); + } + + public static short getHwAuthenticatorType(short bPtr) { + return KMHardwareAuthToken.cast(bPtr).getHwAuthenticatorType(); + } + + public static void setHwAuthenticatorType(short bPtr, short vals) { + KMHardwareAuthToken.cast(bPtr).setHwAuthenticatorType(vals); + } + + public static short getTimestamp(short bPtr) { + return KMHardwareAuthToken.cast(bPtr).getTimestamp(); + } + + public static void setTimestamp(short bPtr, short vals) { + KMHardwareAuthToken.cast(bPtr).setTimestamp(vals); + } + + public static short getMac(short bPtr) { + return KMHardwareAuthToken.cast(bPtr).getMac(); + } + + public static void setMac(short bPtr, short vals) { + KMHardwareAuthToken.cast(bPtr).setMac(vals); + } } diff --git a/Applet/src/com/android/javacard/keymaster/KMHmacSharingParameters.java b/Applet/src/com/android/javacard/kmdevice/KMHmacSharingParameters.java similarity index 71% rename from Applet/src/com/android/javacard/keymaster/KMHmacSharingParameters.java rename to Applet/src/com/android/javacard/kmdevice/KMHmacSharingParameters.java index 49a14ebe..b1d53d69 100644 --- a/Applet/src/com/android/javacard/keymaster/KMHmacSharingParameters.java +++ b/Applet/src/com/android/javacard/kmdevice/KMHmacSharingParameters.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import javacard.framework.ISO7816; import javacard.framework.ISOException; @@ -38,9 +38,8 @@ 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()); + KMArray.add(arrPtr, SEED, KMByteBlob.exp()); + KMArray.add(arrPtr, NONCE, KMByteBlob.exp()); return instance(arrPtr); } @@ -59,14 +58,14 @@ public static short instance() { public static short instance(short vals) { short ptr = KMType.instance(HMAC_SHARING_PARAM_TYPE, (short) 2); - if (KMArray.cast(vals).length() != 2) { + if (KMArray.length(vals) != 2) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); return ptr; } - public static KMHmacSharingParameters cast(short ptr) { + private static KMHmacSharingParameters cast(short ptr) { if (heap[ptr] != HMAC_SHARING_PARAM_TYPE) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } @@ -83,28 +82,52 @@ public short getVals() { public short length() { short arrPtr = getVals(); - return KMArray.cast(arrPtr).length(); + return KMArray.length(arrPtr); } public void setSeed(short vals) { - KMByteBlob.cast(vals); + KMByteBlob.validate(vals); short arrPtr = getVals(); - KMArray.cast(arrPtr).add(SEED, vals); + KMArray.add(arrPtr, SEED, vals); } public void setNonce(short vals) { - KMByteBlob.cast(vals); + KMByteBlob.validate(vals); short arrPtr = getVals(); - KMArray.cast(arrPtr).add(NONCE, vals); + KMArray.add(arrPtr, NONCE, vals); } public short getNonce() { short arrPtr = getVals(); - return KMArray.cast(arrPtr).get(NONCE); + return KMArray.get(arrPtr, NONCE); } public short getSeed() { short arrPtr = getVals(); - return KMArray.cast(arrPtr).get(SEED); + return KMArray.get(arrPtr, SEED); + } + + public static short getVals(short bPtr) { + return KMHmacSharingParameters.cast(bPtr).getVals(); + } + + public static short length(short bPtr) { + return KMHmacSharingParameters.cast(bPtr).length(); + } + + public static void setSeed(short bPtr, short vals) { + KMHmacSharingParameters.cast(bPtr).setSeed(vals); + } + + public static void setNonce(short bPtr, short vals) { + KMHmacSharingParameters.cast(bPtr).setNonce(vals); + } + + public static short getNonce(short bPtr) { + return KMHmacSharingParameters.cast(bPtr).getNonce(); + } + + public static short getSeed(short bPtr) { + return KMHmacSharingParameters.cast(bPtr).getSeed(); } } diff --git a/Applet/src/com/android/javacard/keymaster/KMInteger.java b/Applet/src/com/android/javacard/kmdevice/KMInteger.java similarity index 72% rename from Applet/src/com/android/javacard/keymaster/KMInteger.java rename to Applet/src/com/android/javacard/kmdevice/KMInteger.java index c9ed7a3a..838de8b7 100644 --- a/Applet/src/com/android/javacard/keymaster/KMInteger.java +++ b/Applet/src/com/android/javacard/kmdevice/KMInteger.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import com.android.javacard.seprovider.KMException; import javacard.framework.ISO7816; @@ -74,7 +74,12 @@ public static short instance(byte[] num, short srcOff, short length) { } } - public static KMInteger cast(short ptr) { + private static KMInteger cast(short ptr) { + validate(ptr); + return proto(ptr); + } + + public static void validate(short ptr) { byte[] heap = repository.getHeap(); if (heap[ptr] != INTEGER_TYPE) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); @@ -82,9 +87,8 @@ public static KMInteger cast(short ptr) { 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); @@ -114,21 +118,21 @@ public static short uint_64(byte[] num, short offset) { } // Get the length of the integer - public short length() { + private short length() { return Util.getShort(heap, (short) (getBaseOffset() + 1)); } // Get the buffer pointer in which blob is contained. - public byte[] getBuffer() { + private byte[] getBuffer() { return heap; } // Get the start of value - public short getStartOff() { + private short getStartOff() { return (short) (getBaseOffset() + TLV_HEADER_SIZE); } - public void getValue(byte[] dest, short destOff, short length) { + private void getValue(byte[] dest, short destOff, short length) { if (length < length()) { KMException.throwIt(KMError.UNKNOWN_ERROR); } @@ -139,15 +143,15 @@ public void getValue(byte[] dest, short destOff, short length) { Util.arrayCopyNonAtomic(heap, getStartOff(), dest, destOff, length); } - public void setValue(byte[] src, short srcOff) { + private void setValue(byte[] src, short srcOff) { Util.arrayCopyNonAtomic(src, srcOff, heap, getStartOff(), length()); } - public short value(byte[] dest, short destOff) { + private short value(byte[] dest, short destOff) { Util.arrayCopyNonAtomic(heap, getStartOff(), dest, destOff, length()); return length(); } - public short toLittleEndian(byte[] dest, short destOff) { + private 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)]; @@ -156,19 +160,19 @@ public short toLittleEndian(byte[] dest, short destOff) { return length(); } - public short getShort() { + protected short getShort() { return Util.getShort(heap, (short) (getStartOff() + 2)); } - public short getSignificantShort() { + private short getSignificantShort() { return Util.getShort(heap, getStartOff()); } - public byte getByte() { + private byte getByte() { return heap[(short) (getStartOff() + 3)]; } - public boolean isZero() { + private boolean isZero() { if (getShort() == 0 && getSignificantShort() == 0) { return true; } @@ -180,10 +184,10 @@ public static short compare(short num1, short num2) { 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); + short len = KMInteger.length(num1); + KMInteger.getValue(num1, repository.getHeap(), (short) (num1Buf + (short) (8 - len)), len); + len = KMInteger.length(num2); + KMInteger.getValue(num2, repository.getHeap(), (short) (num2Buf + (short) (8 - len)), len); return KMInteger.unsignedByteArrayCompare( repository.getHeap(), num1Buf, repository.getHeap(), num2Buf, @@ -213,4 +217,51 @@ public static byte unsignedByteArrayCompare(byte[] a1, short offset1, byte[] a2, protected short getBaseOffset() { return instanceTable[KM_INTEGER_OFFSET]; } + + // Get the length of the integer + public static short length(short bPtr) { + return KMInteger.cast(bPtr).length(); + } + + // Get the buffer pointer in which blob is contained. + public static byte[] getBuffer(short bPtr) { + return KMInteger.cast(bPtr).getBuffer(); + } + + // Get the start of value + public static short getStartOff(short bPtr) { + return KMInteger.cast(bPtr).getStartOff(); + } + + public static void getValue(short bPtr, byte[] dest, short destOff, short length) { + KMInteger.cast(bPtr).getValue(dest, destOff, length); + } + + public static void setValue(short bPtr, byte[] src, short srcOff) { + KMInteger.cast(bPtr).setValue(src, srcOff); + } + + public static short value(short bPtr, byte[] dest, short destOff) { + return KMInteger.cast(bPtr).value(dest, destOff); + } + + public static short toLittleEndian(short bPtr, byte[] dest, short destOff) { + return KMInteger.cast(bPtr).toLittleEndian(dest, destOff); + } + + public static short getShort(short bPtr) { + return KMInteger.cast(bPtr).getShort(); + } + + public static short getSignificantShort(short bPtr) { + return KMInteger.cast(bPtr).getSignificantShort(); + } + + public static byte getByte(short bPtr) { + return KMInteger.cast(bPtr).getByte(); + } + + public static boolean isZero(short bPtr) { + return KMInteger.cast(bPtr).isZero(); + } } diff --git a/Applet/src/com/android/javacard/keymaster/KMIntegerArrayTag.java b/Applet/src/com/android/javacard/kmdevice/KMIntegerArrayTag.java similarity index 79% rename from Applet/src/com/android/javacard/keymaster/KMIntegerArrayTag.java rename to Applet/src/com/android/javacard/kmdevice/KMIntegerArrayTag.java index e62ce9fe..04c729bf 100644 --- a/Applet/src/com/android/javacard/keymaster/KMIntegerArrayTag.java +++ b/Applet/src/com/android/javacard/kmdevice/KMIntegerArrayTag.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import javacard.framework.ISO7816; import javacard.framework.ISOException; @@ -29,7 +29,11 @@ public class KMIntegerArrayTag extends KMTag { private static KMIntegerArrayTag prototype; - private static final short[] tags = {USER_SECURE_ID}; + private static short[] tags; + + public static void initStatics() { + tags = new short[]{USER_SECURE_ID}; + } private KMIntegerArrayTag() { } @@ -82,7 +86,7 @@ public static short instance(short tagType, short key, short arrObj) { return ptr; } - public static KMIntegerArrayTag cast(short ptr) { + private static KMIntegerArrayTag cast(short ptr) { if (heap[ptr] != TAG_TYPE) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } @@ -107,17 +111,15 @@ public short getValues() { public short length() { short ptr = getValues(); - return KMArray.cast(ptr).length(); + return KMArray.length(ptr); } public void add(short index, short val) { - KMArray arr = KMArray.cast(getValues()); - arr.add(index, val); + KMArray.add(getValues(), index, val); } public short get(short index) { - KMArray arr = KMArray.cast(getValues()); - return arr.get(index); + return KMArray.get(getValues(), index); } private static boolean validateKey(short key) { @@ -136,12 +138,12 @@ private static boolean validateTagType(short tagType) { public static boolean contains(short tagId, short tagValue, short params) { short tag = - KMKeyParameters.findTag(KMType.UINT_ARRAY_TAG, tagId, params); + KMKeyParameters.findTag(params, KMType.UINT_ARRAY_TAG, tagId); 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) { + while (index < KMArray.length(tag)) { + if (KMInteger.compare(tagValue, KMArray.get(tag, index)) == 0) { return true; } index++; @@ -160,4 +162,30 @@ public boolean contains(short tagValue) { } return false; } + + public static boolean contains(short bPtr, short tagValue) { + return KMIntegerArrayTag.cast(bPtr).contains(tagValue); + } + + public static short getValues(short bPtr) { + return KMIntegerArrayTag.cast(bPtr).getValues(); + } + + public static short get(short bPtr, short index) { + return KMIntegerArrayTag.cast(bPtr).get(index); + } + + public static short getTagType(short bPtr) { + return KMIntegerArrayTag.cast(bPtr).getTagType(); + } + + public static short getKey(short bPtr) { + return KMIntegerArrayTag.cast(bPtr).getKey(); + } + + public static short length(short bPtr) { + return KMIntegerArrayTag.cast(bPtr).length(); + } + + } diff --git a/Applet/src/com/android/javacard/keymaster/KMIntegerTag.java b/Applet/src/com/android/javacard/kmdevice/KMIntegerTag.java similarity index 76% rename from Applet/src/com/android/javacard/keymaster/KMIntegerTag.java rename to Applet/src/com/android/javacard/kmdevice/KMIntegerTag.java index 5311cbd8..2c14f3da 100644 --- a/Applet/src/com/android/javacard/keymaster/KMIntegerTag.java +++ b/Applet/src/com/android/javacard/kmdevice/KMIntegerTag.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import javacard.framework.ISO7816; import javacard.framework.ISOException; @@ -30,33 +30,37 @@ public class KMIntegerTag extends KMTag { private static KMIntegerTag prototype; // 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 short[] tags; + + public static void initStatics() { + tags = new short[]{ + // 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 KMIntegerTag() { } @@ -108,7 +112,7 @@ public static short instance(short tagType, short key, short intObj) { return ptr; } - public static KMIntegerTag cast(short ptr) { + private static KMIntegerTag cast(short ptr) { if (heap[ptr] != TAG_TYPE) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } @@ -132,8 +136,7 @@ public short getValue() { } public short length() { - KMInteger obj = KMInteger.cast(getValue()); - return obj.length(); + return KMInteger.length(getValue()); } private static boolean validateKey(short key) { @@ -153,11 +156,11 @@ private static boolean validateTagType(short tagType) { public static short getShortValue(short tagType, short tagKey, short keyParameters) { short ptr; if (tagType == UINT_TAG) { - ptr = KMKeyParameters.findTag(KMType.UINT_TAG, tagKey, keyParameters); + ptr = KMKeyParameters.findTag(keyParameters, KMType.UINT_TAG, tagKey); if (ptr != KMType.INVALID_VALUE) { ptr = KMIntegerTag.cast(ptr).getValue(); - if (KMInteger.cast(ptr).getSignificantShort() == 0) { - return KMInteger.cast(ptr).getShort(); + if (KMInteger.getSignificantShort(ptr) == 0) { + return KMInteger.getShort(ptr); } } } @@ -168,10 +171,10 @@ 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); + ptr = KMKeyParameters.findTag(keyParameters, tagType, tagKey); if (ptr != KMType.INVALID_VALUE) { ptr = KMIntegerTag.cast(ptr).getValue(); - return KMInteger.cast(ptr).value(buf, offset); + return KMInteger.value(ptr, buf, offset); } } return KMType.INVALID_VALUE; @@ -179,10 +182,10 @@ public static short getValue( public boolean isValidKeySize(byte alg) { short val = KMIntegerTag.cast(KMType.instanceTable[KM_INTEGER_TAG_OFFSET]).getValue(); - if (KMInteger.cast(val).getSignificantShort() != 0) { + if (KMInteger.getSignificantShort(val) != 0) { return false; } - val = KMInteger.cast(val).getShort(); + val = KMInteger.getShort(val); switch (alg) { case KMType.RSA: if (val == 2048) { @@ -214,4 +217,20 @@ public boolean isValidKeySize(byte alg) { } return false; } + + public static boolean isValidKeySize(short bPtr, byte alg) { + return KMIntegerTag.cast(bPtr).isValidKeySize(alg); + } + + public static short getTagType(short bPtr) { + return KMIntegerTag.cast(bPtr).getTagType(); + } + + public static short getValue(short bPtr) { + return KMIntegerTag.cast(bPtr).getValue(); + } + + public static short getKey(short bPtr) { + return KMIntegerTag.cast(bPtr).getKey(); + } } diff --git a/Applet/src/com/android/javacard/keymaster/KMKeyCharacteristics.java b/Applet/src/com/android/javacard/kmdevice/KMKeyCharacteristics.java similarity index 57% rename from Applet/src/com/android/javacard/keymaster/KMKeyCharacteristics.java rename to Applet/src/com/android/javacard/kmdevice/KMKeyCharacteristics.java index ddf7abab..ea79b110 100644 --- a/Applet/src/com/android/javacard/keymaster/KMKeyCharacteristics.java +++ b/Applet/src/com/android/javacard/kmdevice/KMKeyCharacteristics.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import javacard.framework.ISO7816; import javacard.framework.ISOException; @@ -36,16 +36,25 @@ public class KMKeyCharacteristics extends KMType { private KMKeyCharacteristics() { } - public static short exp() { + public static short keymasterExp() { + short sb = KMKeyParameters.exp(); + short keystore = KMKeyParameters.exp(); + short arrPtr = KMArray.instance((short) 2); + + KMArray.add(arrPtr, STRONGBOX_ENFORCED, sb); + KMArray.add(arrPtr, KEYSTORE_ENFORCED, keystore); + return instance(arrPtr); + } + + public static short keymintExp() { short sb = KMKeyParameters.exp(); short tee = KMKeyParameters.exp(); short keystore = KMKeyParameters.exp(); short arrPtr = KMArray.instance((short) 3); - KMArray arr = KMArray.cast(arrPtr); - arr.add(STRONGBOX_ENFORCED, sb); - arr.add(TEE_ENFORCED, tee); - arr.add(KEYSTORE_ENFORCED, keystore); + KMArray.add(arrPtr, STRONGBOX_ENFORCED, sb); + KMArray.add(arrPtr, TEE_ENFORCED, tee); + KMArray.add(arrPtr, KEYSTORE_ENFORCED, keystore); return instance(arrPtr); } @@ -57,21 +66,27 @@ private static KMKeyCharacteristics proto(short ptr) { return prototype; } + public static short instance2() { + short arrPtr = KMArray.instance((short) 2); + return instance(arrPtr); + } + 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) { + short length = KMArray.length(vals); + short ptr = KMType.instance(KEY_CHAR_TYPE, length); + if (length != 3 && length != 2) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); return ptr; } - public static KMKeyCharacteristics cast(short ptr) { + private static KMKeyCharacteristics cast(short ptr) { if (heap[ptr] != KEY_CHAR_TYPE) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } @@ -88,39 +103,75 @@ public short getVals() { public short length() { short arrPtr = getVals(); - return KMArray.cast(arrPtr).length(); + return KMArray.length(arrPtr); } public short getKeystoreEnforced() { short arrPtr = getVals(); - return KMArray.cast(arrPtr).get(KEYSTORE_ENFORCED); + return KMArray.get(arrPtr, KEYSTORE_ENFORCED); } public short getTeeEnforced() { short arrPtr = getVals(); - return KMArray.cast(arrPtr).get(TEE_ENFORCED); + if (length() <= TEE_ENFORCED) { + return KMType.INVALID_VALUE; + } + return KMArray.get(arrPtr, TEE_ENFORCED); } public short getStrongboxEnforced() { short arrPtr = getVals(); - return KMArray.cast(arrPtr).get(STRONGBOX_ENFORCED); + return KMArray.get(arrPtr, STRONGBOX_ENFORCED); } public void setKeystoreEnforced(short ptr) { - KMKeyParameters.cast(ptr); + KMKeyParameters.validate(ptr); short arrPtr = getVals(); - KMArray.cast(arrPtr).add(KEYSTORE_ENFORCED, ptr); + KMArray.add(arrPtr, KEYSTORE_ENFORCED, ptr); } public void setTeeEnforced(short ptr) { - KMKeyParameters.cast(ptr); + KMKeyParameters.validate(ptr); short arrPtr = getVals(); - KMArray.cast(arrPtr).add(TEE_ENFORCED, ptr); + KMArray.add(arrPtr, TEE_ENFORCED, ptr); } public void setStrongboxEnforced(short ptr) { - KMKeyParameters.cast(ptr); + KMKeyParameters.validate(ptr); short arrPtr = getVals(); - KMArray.cast(arrPtr).add(STRONGBOX_ENFORCED, ptr); + KMArray.add(arrPtr, STRONGBOX_ENFORCED, ptr); + } + + public static short getVals(short bPtr) { + return KMKeyCharacteristics.cast(bPtr).getVals(); + } + + public static short length(short bPtr) { + return KMKeyCharacteristics.cast(bPtr).length(); + } + + public static short getKeystoreEnforced(short bPtr) { + return KMKeyCharacteristics.cast(bPtr).getKeystoreEnforced(); + } + + public static short getTeeEnforced(short bPtr) { + return KMKeyCharacteristics.cast(bPtr).getTeeEnforced(); + } + + public static short getStrongboxEnforced(short bPtr) { + return KMKeyCharacteristics.cast(bPtr).getStrongboxEnforced(); + } + + public static void setKeystoreEnforced(short bPtr, short ptr) { + KMKeyCharacteristics.cast(bPtr).setKeystoreEnforced(ptr); + } + + public static void setTeeEnforced(short bPtr, short ptr) { + KMKeyCharacteristics.cast(bPtr).setTeeEnforced(ptr); + } + + public static void setStrongboxEnforced(short bPtr, short ptr) { + KMKeyCharacteristics.cast(bPtr).setStrongboxEnforced(ptr); } + } diff --git a/Applet/src/com/android/javacard/keymaster/KMKeyParameters.java b/Applet/src/com/android/javacard/kmdevice/KMKeyParameters.java similarity index 56% rename from Applet/src/com/android/javacard/keymaster/KMKeyParameters.java rename to Applet/src/com/android/javacard/kmdevice/KMKeyParameters.java index d4cbc323..5ed35a0e 100644 --- a/Applet/src/com/android/javacard/keymaster/KMKeyParameters.java +++ b/Applet/src/com/android/javacard/kmdevice/KMKeyParameters.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import com.android.javacard.seprovider.KMException; import javacard.framework.ISO7816; @@ -30,13 +30,73 @@ public class KMKeyParameters extends KMType { private static KMKeyParameters prototype; - private static final short[] customTags = { - KMType.ULONG_TAG, KMType.AUTH_TIMEOUT_MILLIS, - }; - + private static short[] customTags; + private static short[] invalidTagsArr; + private static short[] unsupportedTagArr; + private static short[] hwEnforcedTagArr; + private static short[] swEnforcedTagsArr; + private static short[] teeEnforcedTagsArr; + private KMKeyParameters() { } + public static void initStatics() { + customTags = new short[] { + KMType.ULONG_TAG, KMType.AUTH_TIMEOUT_MILLIS, + }; + invalidTagsArr = new short[] { + KMType.BYTES_TAG, KMType.NONCE, + KMType.BYTES_TAG, KMType.ASSOCIATED_DATA, + KMType.BYTES_TAG, KMType.UNIQUE_ID, + KMType.UINT_TAG, KMType.MAC_LENGTH, + }; + unsupportedTagArr = new short[] { + // Unsupported tags. + KMType.BOOL_TAG, KMType.TRUSTED_USER_PRESENCE_REQUIRED, + KMType.UINT_TAG, KMType.MIN_SEC_BETWEEN_OPS + }; + hwEnforcedTagArr = new short[] { + // HW Enforced + KMType.ENUM_TAG, KMType.ORIGIN, + 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.UNLOCKED_DEVICE_REQUIRED, + KMType.BOOL_TAG, KMType.RESET_SINCE_ID_ROTATION, + KMType.BOOL_TAG, KMType.EARLY_BOOT_ONLY, + KMType.BOOL_TAG, KMType.BOOTLOADER_ONLY, + KMType.UINT_TAG, KMType.MAX_USES_PER_BOOT, + KMType.BOOL_TAG, KMType.TRUSTED_CONFIRMATION_REQUIRED, + }; + swEnforcedTagsArr = new short[] { + 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 + }; + teeEnforcedTagsArr = new short[] { + KMType.ULONG_ARRAY_TAG, KMType.USER_SECURE_ID, + KMType.UINT_TAG, KMType.AUTH_TIMEOUT, + KMType.ENUM_TAG, KMType.USER_AUTH_TYPE, + }; + + } + private static KMKeyParameters proto(short ptr) { if (prototype == null) { prototype = new KMKeyParameters(); @@ -47,35 +107,33 @@ private static KMKeyParameters proto(short ptr) { 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()); + KMArray.add(arrPtr, (short) 0, KMEnum.instance(KMType.RULE, KMType.FAIL_ON_INVALID_TAGS)); + KMArray.add(arrPtr, (short) 1, KMIntegerTag.exp(UINT_TAG)); + KMArray.add(arrPtr, (short) 2, KMIntegerArrayTag.exp(UINT_ARRAY_TAG)); + KMArray.add(arrPtr, (short) 3, KMIntegerTag.exp(ULONG_TAG)); + KMArray.add(arrPtr, (short) 4, KMIntegerTag.exp(DATE_TAG)); + KMArray.add(arrPtr, (short) 5, KMIntegerArrayTag.exp(ULONG_ARRAY_TAG)); + KMArray.add(arrPtr, (short) 6, KMEnumTag.exp()); + KMArray.add(arrPtr, (short) 7, KMEnumArrayTag.exp()); + KMArray.add(arrPtr, (short) 8, KMByteTag.exp()); + KMArray.add(arrPtr, (short) 9, KMBoolTag.exp()); + KMArray.add(arrPtr, (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()); + KMArray.add(arrPtr, (short) 0, KMEnum.instance(KMType.RULE, KMType.IGNORE_INVALID_TAGS)); + KMArray.add(arrPtr, (short) 1, KMIntegerTag.exp(UINT_TAG)); + KMArray.add(arrPtr, (short) 2, KMIntegerArrayTag.exp(UINT_ARRAY_TAG)); + KMArray.add(arrPtr, (short) 3, KMIntegerTag.exp(ULONG_TAG)); + KMArray.add(arrPtr, (short) 4, KMIntegerTag.exp(DATE_TAG)); + KMArray.add(arrPtr, (short) 5, KMIntegerArrayTag.exp(ULONG_ARRAY_TAG)); + KMArray.add(arrPtr, (short) 6, KMEnumTag.exp()); + KMArray.add(arrPtr, (short) 7, KMEnumArrayTag.exp()); + KMArray.add(arrPtr, (short) 8, KMByteTag.exp()); + KMArray.add(arrPtr, (short) 9, KMBoolTag.exp()); + KMArray.add(arrPtr, (short) 10, KMBignumTag.exp()); return instance(arrPtr); } @@ -85,7 +143,12 @@ public static short instance(short vals) { return ptr; } - public static KMKeyParameters cast(short ptr) { + private static KMKeyParameters cast(short ptr) { + validate(ptr); + return proto(ptr); + } + + public static void validate(short ptr) { if (heap[ptr] != KEY_PARAM_TYPE) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } @@ -93,7 +156,6 @@ public static KMKeyParameters cast(short ptr) { if (heap[arrPtr] != ARRAY_TYPE) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } - return proto(ptr); } public short getVals() { @@ -102,26 +164,28 @@ public short getVals() { public short length() { short arrPtr = getVals(); - return KMArray.cast(arrPtr).length(); + return KMArray.length(arrPtr); } - public static short findTag(short tagType, short tagKey, short keyParam) { - KMKeyParameters instParam = KMKeyParameters.cast(keyParam); - return instParam.findTag(tagType, tagKey); + public static short getVals(short bPtr) { + return KMKeyParameters.cast(bPtr).getVals(); } + public static short length(short bPtr) { + return KMKeyParameters.cast(bPtr).length(); + } + public short findTag(short tagType, short tagKey) { - KMArray vals = KMArray.cast(getVals()); short index = 0; - short length = vals.length(); + short length = KMArray.length(getVals()); 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); + obj = KMArray.get(getVals(), index); + key = KMTag.getKMTagKey(obj); + type = KMTag.getKMTagType(obj); if ((tagKey == key) && (tagType == type)) { ret = obj; break; @@ -131,27 +195,27 @@ public short findTag(short tagType, short tagKey) { return ret; } - public static boolean hasUnsupportedTags(short keyParamsPtr) { - final short[] tagArr = { - // Unsupported tags. - KMType.BOOL_TAG, KMType.TRUSTED_USER_PRESENCE_REQUIRED, - KMType.UINT_TAG, KMType.MIN_SEC_BETWEEN_OPS - }; + public static short findTag(short bPtr, short tagType, short tagKey) { + return KMKeyParameters.cast(bPtr).findTag(tagType, tagKey); + } + + public 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(); + short arrPtr = KMKeyParameters.getVals(keyParamsPtr); + short len = KMArray.length(arrPtr); 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)) { + tagPtr = KMArray.get(arrPtr, index); + tagKey = KMTag.getKMTagKey(tagPtr); + tagType = KMTag.getKMTagType(tagPtr); + while (tagInd < (short) unsupportedTagArr.length) { + if ((unsupportedTagArr[tagInd] == tagType) + && (unsupportedTagArr[(short) (tagInd + 1)] == tagKey)) { return true; } tagInd += 2; @@ -165,44 +229,20 @@ public static boolean hasUnsupportedTags(short keyParamsPtr) { public static short makeSbEnforced(short keyParamsPtr, byte origin, short osVersionObjPtr, short osPatchObjPtr, short vendorPatchObjPtr, short bootPatchObjPtr, byte[] scratchPad) { - final short[] hwEnforcedTagArr = { - // HW Enforced - KMType.ENUM_TAG, KMType.ORIGIN, - 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.UNLOCKED_DEVICE_REQUIRED, - KMType.BOOL_TAG, KMType.RESET_SINCE_ID_ROTATION, - KMType.BOOL_TAG, KMType.EARLY_BOOT_ONLY, - KMType.BOOL_TAG, KMType.BOOTLOADER_ONLY, - KMType.UINT_TAG, KMType.MAX_USES_PER_BOOT, - KMType.BOOL_TAG, KMType.TRUSTED_CONFIRMATION_REQUIRED, - }; + 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(); + short arrPtr = KMKeyParameters.getVals(keyParamsPtr); + short len = KMArray.length(arrPtr); while (index < len) { tagInd = 0; - tagPtr = KMArray.cast(arrPtr).get(index); - tagKey = KMTag.getKey(tagPtr); - tagType = KMTag.getTagType(tagPtr); + tagPtr = KMArray.get(arrPtr, index); + tagKey = KMTag.getKMTagKey(tagPtr); + tagType = KMTag.getKMTagType(tagPtr); if (!isValidTag(tagType, tagKey)) { KMException.throwIt(KMError.INVALID_KEY_BLOB); } @@ -238,47 +278,19 @@ public static short makeSbEnforced(short keyParamsPtr, byte origin, } public static short makeSbEnforced(short keyParamsPtr, byte[] scratchPad) { - final short[] hwEnforcedTagArr = { - // HW Enforced - KMType.ENUM_TAG, KMType.ORIGIN, - KMType.UINT_TAG, KMType.OS_VERSION, - KMType.UINT_TAG, KMType.OS_PATCH_LEVEL, - KMType.UINT_TAG, KMType.VENDOR_PATCH_LEVEL, - KMType.UINT_TAG, KMType.BOOT_PATCH_LEVEL, - 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.UNLOCKED_DEVICE_REQUIRED, - KMType.BOOL_TAG, KMType.RESET_SINCE_ID_ROTATION, - KMType.BOOL_TAG, KMType.EARLY_BOOT_ONLY, - KMType.BOOL_TAG, KMType.BOOTLOADER_ONLY, - KMType.BOOL_TAG, KMType.TRUSTED_CONFIRMATION_REQUIRED, - }; 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(); + short arrPtr = KMKeyParameters.getVals(keyParamsPtr); + short len = KMArray.length(arrPtr); while (index < len) { tagInd = 0; - tagPtr = KMArray.cast(arrPtr).get(index); - tagKey = KMTag.getKey(tagPtr); - tagType = KMTag.getTagType(tagPtr); + tagPtr = KMArray.get(arrPtr, index); + tagKey = KMTag.getKMTagKey(tagPtr); + tagType = KMTag.getKMTagType(tagPtr); if (!isValidTag(tagType, tagKey)) { KMException.throwIt(KMError.INVALID_KEY_BLOB); } @@ -297,26 +309,26 @@ public static short makeSbEnforced(short keyParamsPtr, byte[] scratchPad) { } public static short makeHwEnforced(short sb, short tee){ - short len = KMKeyParameters.cast(sb).length(); - len += KMKeyParameters.cast(tee).length(); + short len = KMKeyParameters.length(sb); + len += KMKeyParameters.length(tee); short hwEnf = KMArray.instance(len); - sb = KMKeyParameters.cast(sb).getVals(); - tee = KMKeyParameters.cast(tee).getVals(); - len = KMArray.cast(sb).length(); + sb = KMKeyParameters.getVals(sb); + tee = KMKeyParameters.getVals(tee); + len = KMArray.length(sb); short src = 0; short dest =0; short val = 0; while(src < len){ - val = KMArray.cast(sb).get(src); - KMArray.cast(hwEnf).add(dest, val); + val = KMArray.get(sb, src); + KMArray.add(hwEnf, dest, val); src++; dest++; } src = 0; - len = KMArray.cast(tee).length(); + len = KMArray.length(tee); while(src < len){ - val = KMArray.cast(tee).get(src); - KMArray.cast(hwEnf).add(dest, val); + val = KMArray.get(tee, src); + KMArray.add(hwEnf, dest, val); src++; dest++; } @@ -324,28 +336,19 @@ public static short makeHwEnforced(short sb, short tee){ } // ALL_USERS, EXPORTABLE missing from types.hal public static short makeKeystoreEnforced(short keyParamsPtr, byte[] scratchPad) { - 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 - }; 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(); + short arrPtr = KMKeyParameters.getVals(keyParamsPtr); + short len = KMArray.length(arrPtr); while (index < len) { tagInd = 0; - tagPtr = KMArray.cast(arrPtr).get(index); - tagKey = KMTag.getKey(tagPtr); - tagType = KMTag.getTagType(tagPtr); + tagPtr = KMArray.get(arrPtr, index); + tagKey = KMTag.getKMTagKey(tagPtr); + tagType = KMTag.getKMTagType(tagPtr); if (!isValidTag(tagType, tagKey)) { KMException.throwIt(KMError.INVALID_KEY_BLOB); } @@ -364,24 +367,19 @@ public static short makeKeystoreEnforced(short keyParamsPtr, byte[] scratchPad) } public static short makeTeeEnforced(short keyParamsPtr, byte[] scratchPad) { - final short[] teeEnforcedTagsArr = { - KMType.ULONG_ARRAY_TAG, KMType.USER_SECURE_ID, - KMType.UINT_TAG, KMType.AUTH_TIMEOUT, - KMType.ENUM_TAG, KMType.USER_AUTH_TYPE, - }; 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(); + short arrPtr = KMKeyParameters.getVals(keyParamsPtr); + short len = KMArray.length(arrPtr); while (index < len) { tagInd = 0; - tagPtr = KMArray.cast(arrPtr).get(index); - tagKey = KMTag.getKey(tagPtr); - tagType = KMTag.getTagType(tagPtr); + tagPtr = KMArray.get(arrPtr, index); + tagKey = KMTag.getKMTagKey(tagPtr); + tagType = KMTag.getKMTagType(tagPtr); if (!isValidTag(tagType, tagKey)) { KMException.throwIt(KMError.INVALID_KEY_BLOB); } @@ -403,14 +401,14 @@ public static short makeTeeEnforced(short keyParamsPtr, byte[] scratchPad) { } public static short makeHidden(short keyParamsPtr, short rootOfTrustBlob, byte[] scratchPad) { - short appId = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_ID, keyParamsPtr); + short appId = KMKeyParameters.findTag(keyParamsPtr, KMType.BYTES_TAG, KMType.APPLICATION_ID); if (appId != KMTag.INVALID_VALUE) { - appId = KMByteTag.cast(appId).getValue(); + appId = KMByteTag.getValue(appId); } short appData = - KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_DATA, keyParamsPtr); + KMKeyParameters.findTag(keyParamsPtr, KMType.BYTES_TAG, KMType.APPLICATION_DATA); if (appData != KMTag.INVALID_VALUE) { - appData = KMByteTag.cast(appData).getValue(); + appData = KMByteTag.getValue(appData); } return makeHidden(appId, appData, rootOfTrustBlob, scratchPad); } @@ -419,11 +417,11 @@ public static short makeHidden(short appIdBlob, short appDataBlob, short rootOfT byte[] scratchPad) { // Order in which the hidden array is created should not change. short index = 0; - KMByteBlob.cast(rootOfTrustBlob); + KMByteBlob.validate(rootOfTrustBlob); Util.setShort(scratchPad, index, rootOfTrustBlob); index += 2; if (appIdBlob != KMTag.INVALID_VALUE) { - KMByteBlob.cast(appIdBlob); + KMByteBlob.validate(appIdBlob); Util.setShort(scratchPad, index, appIdBlob); index += 2; } @@ -436,12 +434,6 @@ public static short makeHidden(short appIdBlob, short appDataBlob, short rootOfT } public static boolean isValidTag(short tagType, short tagKey) { - 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, - }; short index = 0; if (tagKey == KMType.INVALID_TAG) { return false; @@ -460,7 +452,7 @@ public static short createKeyParameters(byte[] ptrArr, short len) { short index = 0; short ptr = 0; while (index < len) { - KMArray.cast(arrPtr).add(index, Util.getShort(ptrArr, ptr)); + KMArray.add(arrPtr, index, Util.getShort(ptrArr, ptr)); index++; ptr += 2; } @@ -477,7 +469,7 @@ public static short addCustomTags(short keyParams, byte[] scratchPad, short offs switch(tagType) { case KMType.AUTH_TIMEOUT_MILLIS: short authTimeOutTag = - KMKeyParameters.cast(keyParams).findTag(KMType.UINT_TAG, KMType.AUTH_TIMEOUT); + KMKeyParameters.findTag(keyParams, KMType.UINT_TAG, KMType.AUTH_TIMEOUT); if (authTimeOutTag != KMType.INVALID_VALUE) { tagPtr = createAuthTimeOutMillisTag(authTimeOutTag, scratchPad, offset); Util.setShort(scratchPad, offset, tagPtr); @@ -499,21 +491,25 @@ public void deleteCustomTags() { while (index >= 0) { obj = findTag(customTags[(short) (index - 1)], customTags[index]); if (obj != KMType.INVALID_VALUE) { - KMArray.cast(arrPtr).deleteLastEntry(); + KMArray.deleteLastEntry(arrPtr); } index -= 2; } } + + public static void deleteCustomTags(short bPtr) { + KMKeyParameters.cast(bPtr).deleteCustomTags(); + } public static short createAuthTimeOutMillisTag(short authTimeOutTag, byte[] scratchPad, short offset) { - short authTime = KMIntegerTag.cast(authTimeOutTag).getValue(); + short authTime = KMIntegerTag.getValue(authTimeOutTag); Util.arrayFillNonAtomic(scratchPad, offset, (short) 40, (byte) 0); Util.arrayCopyNonAtomic( - KMInteger.cast(authTime).getBuffer(), - KMInteger.cast(authTime).getStartOff(), + KMInteger.getBuffer(authTime), + KMInteger.getStartOff(authTime), scratchPad, - (short) (offset + 8 - KMInteger.cast(authTime).length()), - KMInteger.cast(authTime).length()); + (short) (offset + 8 - KMInteger.length(authTime)), + KMInteger.length(authTime)); 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))); diff --git a/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java b/Applet/src/com/android/javacard/kmdevice/KMKeymasterDevice.java similarity index 63% rename from Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java rename to Applet/src/com/android/javacard/kmdevice/KMKeymasterDevice.java index addc5409..5bf413b3 100644 --- a/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java +++ b/Applet/src/com/android/javacard/kmdevice/KMKeymasterDevice.java @@ -14,31 +14,28 @@ * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import com.android.javacard.seprovider.KMAttestationCert; import com.android.javacard.seprovider.KMDeviceUniqueKey; import com.android.javacard.seprovider.KMException; 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 { +public class KMKeymasterDevice { // Constants. - public static final byte[] F4 = {0x01, 0x00, 0x01}; + public static byte[] F4; public static final byte AES_BLOCK_SIZE = 16; public static final byte DES_BLOCK_SIZE = 8; public static final short MAX_LENGTH = 15000; @@ -49,78 +46,56 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe public static final short VERIFIED_BOOT_HASH_SIZE = 32; public static final short BOOT_PATCH_LVL_SIZE = 4; - protected static final byte CLA_ISO7816_NO_SM_NO_CHAN = (byte) 0x80; - protected static final short KM_HAL_VERSION = (short) 0x5000; - protected static final short KEYMASTER_HAL_VERSION = (short) 0x4000; + public static final byte CLA_ISO7816_NO_SM_NO_CHAN = (byte) 0x80; + public static final short KM_HAL_VERSION = (short) 0x5000; + public static final short KEYMASTER_HAL_VERSION = (short) 0x4000; private static final short MAX_AUTH_DATA_SIZE = (short) 512; private static final short DERIVE_KEY_INPUT_SIZE = (short) 256; 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 - }; + public static byte[] sharingCheck; // "KeymasterSharedMac" - public static final byte[] ckdfLable = { - 0x4B, 0x65, 0x79, 0x6D, 0x61, 0x73, 0x74, 0x65, 0x72, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, - 0x4D, - 0x61, 0x63 - }; + public static byte[] ckdfLable; // "Auth Verification" - public static final byte[] authVerification = { - 0x41, 0x75, 0x74, 0x68, 0x20, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6F, - 0x6E - }; + public static byte[] authVerification; // "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 byte[] confirmationToken; // 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 byte[] defaultSubject; public static final short MAX_COSE_BUF_SIZE = (short) 1024; // Top 32 commands are reserved for provisioning. private static final byte KEYMINT_CMD_APDU_START = 0x20; - public static final byte INS_GENERATE_KEY_CMD = KEYMINT_CMD_APDU_START + 1; //0x21 - public static final byte INS_IMPORT_KEY_CMD = KEYMINT_CMD_APDU_START + 2; //0x22 - public static final byte INS_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 3; //0x23 - public static final byte INS_EXPORT_KEY_CMD = KEYMINT_CMD_APDU_START + 4; //0x24 - public static final byte INS_ATTEST_KEY_CMD = KEYMINT_CMD_APDU_START + 5; //0x25 - public static final byte INS_UPGRADE_KEY_CMD = KEYMINT_CMD_APDU_START + 6; //0x26 - public static final byte INS_DELETE_KEY_CMD = KEYMINT_CMD_APDU_START + 7; //0x27 - public static final byte INS_DELETE_ALL_KEYS_CMD = KEYMINT_CMD_APDU_START + 8; //0x28 - public static final byte INS_ADD_RNG_ENTROPY_CMD = KEYMINT_CMD_APDU_START + 9; //0x29 - public static final byte INS_COMPUTE_SHARED_HMAC_CMD = KEYMINT_CMD_APDU_START + 10; //0x2A - public static final byte INS_DESTROY_ATT_IDS_CMD = KEYMINT_CMD_APDU_START + 11; //0x2B - public static final byte INS_VERIFY_AUTHORIZATION_CMD = KEYMINT_CMD_APDU_START + 12; //0x2C - public static final byte INS_GET_HMAC_SHARING_PARAM_CMD = KEYMINT_CMD_APDU_START + 13; //0x2D - public static final byte INS_GET_KEY_CHARACTERISTICS_CMD = KEYMINT_CMD_APDU_START + 14; //0x2E - public static final byte INS_GET_HW_INFO_CMD = KEYMINT_CMD_APDU_START + 15; //0x2F - public static final byte INS_BEGIN_OPERATION_CMD = KEYMINT_CMD_APDU_START + 16; //0x30 - public static final byte INS_UPDATE_OPERATION_CMD = KEYMINT_CMD_APDU_START + 17; //0x31 - public static final byte INS_FINISH_OPERATION_CMD = KEYMINT_CMD_APDU_START + 18; //0x32 - public static final byte INS_ABORT_OPERATION_CMD = KEYMINT_CMD_APDU_START + 19; //0x33 - public static final byte INS_DEVICE_LOCKED_CMD = KEYMINT_CMD_APDU_START + 20;//0x34 - public static final byte INS_EARLY_BOOT_ENDED_CMD = KEYMINT_CMD_APDU_START + 21; //0x35 - public static final byte INS_GET_CERT_CHAIN_CMD = KEYMINT_CMD_APDU_START + 22; //0x36 - public static final byte INS_UPDATE_AAD_OPERATION_CMD = KEYMINT_CMD_APDU_START + 23; //0x37 - public static final byte INS_BEGIN_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 24; //0x38 - public static final byte INS_FINISH_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 25; //0x39 - public static final byte INS_INIT_STRONGBOX_CMD = KEYMINT_CMD_APDU_START + 26; //0x3A + 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 // 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 @@ -194,35 +169,39 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe // ComputeHMAC constants private static final short HMAC_SHARED_PARAM_MAX_SIZE = 64; protected static final short MAX_CERT_SIZE = 2048; + +//getHardwareInfo constants. + private static byte[] JAVACARD_KEYMASTER_DEVICE; + private static byte[] GOOGLE; + private static byte[] X509Subject; + +private static short[] ATTEST_ID_TAGS; + private static final byte SERIAL_NUM = (byte) 0x01; protected static RemotelyProvisionedComponentDevice rkp; - protected static KMEncoder encoder; protected static KMDecoder decoder; protected static KMRepository repository; + protected static KMEncoder encoder; protected static KMSEProvider seProvider; protected static KMOperationState[] opTable; - protected static KMSpecification specification; - protected static short[] tmpVariables; protected static short[] data; protected static byte[] wrappingKey; - + private KMCose kmCoseInst; + /** * Registers this applet. */ - protected KMKeymasterApplet(KMSEProvider seImpl, short specificationVer) { - seProvider = seImpl; - this.specification = - (specificationVer == KEYMASTER_SPECIFICATION) ? - new KMKeymasterSpecification() : - new KMKeymintSpecification(); + public KMKeymasterDevice(KMSEProvider seImpl, KMRepository repoInst, KMDecoder decoderInst) { + initKMDeviceStatics(); + seProvider = seImpl; boolean isUpgrading = seProvider.isUpgrading(); if (!isUpgrading) { seProvider.createMasterKey(MASTER_KEY_SIZE); } - repository = new KMRepository(isUpgrading); + repository = repoInst; encoder = new KMEncoder(); - decoder = new KMDecoder(); + decoder = decoderInst; data = JCSystem.makeTransientShortArray(DATA_ARRAY_SIZE, JCSystem.CLEAR_ON_DESELECT); tmpVariables = JCSystem.makeTransientShortArray(TMP_VARIABLE_ARRAY_SIZE, JCSystem.CLEAR_ON_DESELECT); @@ -239,9 +218,95 @@ protected KMKeymasterApplet(KMSEProvider seImpl, short specificationVer) { // initialize default values initHmacNonceAndSeed(); initSystemBootParams((short)0,(short)0,(short)0,(short)0); - rkp = new RemotelyProvisionedComponentDevice(encoder, decoder, repository, seProvider); + kmCoseInst = KMCose.getInstance(); + rkp = new RemotelyProvisionedComponentDevice(this, encoder, decoder, repository, seProvider); } - + + public static void initStatics(){ + F4 = new byte[] {0x01, 0x00, 0x01}; + // "Keymaster HMAC Verification" - used for HMAC key verification. + sharingCheck = new byte[] { + 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" + ckdfLable = new byte[] { + 0x4B, 0x65, 0x79, 0x6D, 0x61, 0x73, 0x74, 0x65, 0x72, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, + 0x4D, + 0x61, 0x63 + }; + + // "Auth Verification" + authVerification = new byte[] { + 0x41, 0x75, 0x74, 0x68, 0x20, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6F, + 0x6E + }; + // "confirmation token" + confirmationToken = new byte[] { + 0x63, 0x6F, 0x6E, 0x66, 0x69, 0x72, 0x6D, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x74, 0x6F, + 0x6B, + 0x65, 0x6E + }; + // Subject is a fixed field with only CN= Android Keystore Key - same for all the keys + defaultSubject = new byte[] { + 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 + }; + //getHardwareInfo constants. + JAVACARD_KEYMASTER_DEVICE = new byte[] { + 0x4A, 0x61, 0x76, 0x61, 0x63, 0x61, 0x72, 0x64, 0x4B, 0x65, 0x79, 0x6D, 0x61, 0x73, 0x74, + 0x65, 0x72, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, + }; + GOOGLE = new byte[] {0x47, 0x6F, 0x6F, 0x67, 0x6C, 0x65}; + X509Subject = new byte[] { + 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 + }; + + ATTEST_ID_TAGS = new short[] { + 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 void initKMDeviceStatics() { + initStatics(); + KMAttestationCertImpl.initStatics(); + KMRepository.initStatics(); + KMBignumTag.initStatics(); + KMCosePairCoseKeyTag.initStatics(); + KMEnumTag.initStatics(); + KMCosePairTextStringTag.initStatics(); + KMByteTag.initStatics(); + KMEnum.initStatics(); + KMIntegerTag.initStatics(); + KMCose.initStatics(); + KMKeyParameters.initStatics(); + KMUtils.initStatics(); + KMBoolTag.initStatics(); + KMPKCS8Decoder.initStatics(); + } + + + public void clean() { + repository.clean(); + } + protected void initHmacNonceAndSeed(){ short nonce = repository.alloc((short)32); seProvider.newRandomNumber(repository.getHeap(), nonce, KMRepository.HMAC_SEED_NONCE_SIZE); @@ -262,9 +327,9 @@ private KMOperationState reserveOperation(short algorithm, short opHandle){ 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()); + opTable[index].setHandle(KMInteger.getBuffer(opHandle), + KMInteger.getStartOff(opHandle), + KMInteger.length(opHandle)); return opTable[index]; } index++; @@ -273,9 +338,9 @@ private KMOperationState reserveOperation(short algorithm, short opHandle){ } private KMOperationState findOperation(short handle){ - return findOperation(KMInteger.cast(handle).getBuffer(), - KMInteger.cast(handle).getStartOff(), - KMInteger.cast(handle).length()); + return findOperation(KMInteger.getBuffer(handle), + KMInteger.getStartOff(handle), + KMInteger.length(handle)); } private KMOperationState findOperation(byte[] opHandle, short start, short len){ @@ -299,8 +364,7 @@ private void releaseOperation(KMOperationState op){ * * @return Returns true if the keymaster is in correct state */ - @Override - public boolean select() { + public boolean onSelect() { repository.onSelect(); return true; } @@ -308,20 +372,15 @@ public boolean select() { /** * De-selects this applet. */ - @Override - public void deselect() { + public void onDeselect() { repository.onDeselect(); } - /** - * Uninstalls the applet after cleaning the repository. - */ - @Override - public void uninstall() { + public void onUninstall() { repository.onUninstall(); } - private short mapISOErrorToKMError(short reason) { + public short mapISOErrorToKMError(short reason) { switch (reason) { case ISO7816.SW_CLA_NOT_SUPPORTED: return KMError.UNSUPPORTED_CLA; @@ -343,7 +402,7 @@ private short mapISOErrorToKMError(short reason) { } } - private short mapCryptoErrorToKMError(short reason) { + public short mapCryptoErrorToKMError(short reason) { switch (reason) { case CryptoException.ILLEGAL_USE: return KMError.CRYPTO_ILLEGAL_USE; @@ -375,25 +434,20 @@ protected short validateApduHeader(APDU apdu) { * Processes an incoming APDU and handles it using command objects. * @param apdu the incoming APDU */ - @Override public void process(APDU apdu) { try { + resetData(); repository.onProcess(); - // If this is select applet apdu which is selecting this applet then return - if (apdu.isISOInterindustryCLA()) { - if (selectingApplet()) { - return; - } - } // Validate APDU Header. short apduIns = validateApduHeader(apdu); + switch (apduIns) { case INS_INIT_STRONGBOX_CMD: processInitStrongBoxCmd(apdu); sendError(apdu, KMError.OK); return; case INS_GENERATE_KEY_CMD: - processGenerateKey(apdu); + processGenerateKey1(apdu); break; case INS_ATTEST_KEY_CMD: processAttestKeyCmd(apdu); @@ -485,7 +539,6 @@ public void process(APDU apdu) { resetWrappingKey(); sendError(apdu, KMException.reason()); } catch (ISOException exp) { - sendError(apdu, mapISOErrorToKMError(exp.getReason())); freeOperations(); resetWrappingKey(); sendError(apdu, mapISOErrorToKMError(exp.getReason())); @@ -497,8 +550,7 @@ public void process(APDU apdu) { freeOperations(); resetWrappingKey(); sendError(apdu, KMError.GENERIC_UNKNOWN_ERROR); - } finally { - resetData(); + } finally { repository.clean(); } } @@ -525,24 +577,24 @@ private void processEarlyBootEndedCmd(APDU apdu) { private short deviceLockedCmd(APDU apdu){ short cmd = KMArray.instance((short) 2); // passwordOnly - KMArray.cast(cmd).add((short) 0, KMInteger.exp()); + KMArray.add(cmd, (short) 0, KMInteger.exp()); // verification token - KMArray.cast(cmd).add((short) 1, specification.getKMVerificationTokenExp()); + KMArray.add(cmd, (short) 1, getKMVerificationTokenExp()); 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(); + short passwordOnly = KMArray.get(cmd, (short) 0); + short verToken = KMArray.get(cmd, (short) 1); + passwordOnly = KMInteger.getByte(passwordOnly); validateVerificationToken(verToken, scratchPad); - short verTime = KMVerificationToken.cast(verToken).getTimestamp(); + short verTime = KMVerificationToken.getTimestamp(verToken); short lastDeviceLockedTime = repository.getDeviceTimeStamp(); if (KMInteger.compare(verTime, lastDeviceLockedTime) > 0) { Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 8, (byte) 0); - KMInteger.cast(verTime).getValue(scratchPad, (short) 0, (short) 8); + KMInteger.getValue(verTime, scratchPad, (short) 0, (short) 8); repository.setDeviceLock(true); repository.setDeviceLockPasswordOnly(passwordOnly == 0x01); repository.setDeviceLockTimestamp(scratchPad, (short) 0, (short) 8); @@ -561,12 +613,12 @@ private boolean isValidWrappingKey(){ } private void setWrappingKey(short key){ - if(KMByteBlob.cast(key).length() != WRAPPING_KEY_SIZE){ + if(KMByteBlob.length(key) != 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); + Util.arrayCopyNonAtomic(KMByteBlob.getBuffer(key), + KMByteBlob.getStartOff(key),wrappingKey,(short)1,WRAPPING_KEY_SIZE); } private short getWrappingKey(){ @@ -608,29 +660,32 @@ public static void sendOutgoing(APDU apdu, short resp) { /** * Receives data, which can be extended data, as requested by the command instance. */ - public static short receiveIncoming(APDU apdu, short reqExp) { + public short receiveIncoming(APDU apdu, short reqExp) { + short bufferLength = apdu.getIncomingLength(); + short bufferStartOffset = repository.allocReclaimableMemory(bufferLength); + short req = receiveIncoming(apdu, reqExp, repository.getHeap(), bufferLength, bufferStartOffset); + repository.reclaimMemory(bufferLength); + return req; + } + + public short receiveIncoming(APDU apdu, short reqExp, byte[] reclamBuf, short bLen, short bStartOffset) { 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); + short index = bStartOffset; + while (recvLen > 0 && ((short) (index - bStartOffset) < bLen)) { + Util.arrayCopyNonAtomic(srcBuffer, srcOffset, reclamBuf, index, recvLen); index += recvLen; recvLen = apdu.receiveBytes(srcOffset); } - short req = decoder.decode(reqExp, buffer, bufferStartOffset, bufferLength); - repository.reclaimMemory(bufferLength); - return req; + return decoder.decode(reqExp, reclamBuf, bStartOffset, bLen); } - + private void processGetHwInfoCmd(APDU apdu) { // No arguments expected - short respPtr = specification.getHardwareInfo(); + short respPtr = getHardwareInfo(); // send buffer to master sendOutgoing(apdu, respPtr); } @@ -638,7 +693,7 @@ private void processGetHwInfoCmd(APDU apdu) { private short addRngEntropyCmd(APDU apdu){ short cmd = KMArray.instance((short) 1); // Rng entropy - KMArray.cast(cmd).add((short) 0, KMByteBlob.exp()); + KMArray.add(cmd, (short) 0, KMByteBlob.exp()); return receiveIncoming(apdu, cmd); } @@ -646,20 +701,20 @@ private void processAddRngEntropyCmd(APDU apdu) { // Receive the incoming request fully from the master. short cmd = addRngEntropyCmd(apdu); // Process - KMByteBlob blob = KMByteBlob.cast(KMArray.cast(cmd).get((short) 0)); + short blob = KMArray.get(cmd, (short) 0); // Maximum 2KiB of seed is allowed. - if (blob.length() > MAX_SEED_SIZE) { + if (KMByteBlob.length(blob) > MAX_SEED_SIZE) { KMException.throwIt(KMError.INVALID_INPUT_LENGTH); } - seProvider.addRngEntropy(blob.getBuffer(), blob.getStartOff(), blob.length()); + seProvider.addRngEntropy(KMByteBlob.getBuffer(blob), KMByteBlob.getStartOff(blob), KMByteBlob.length(blob)); sendError(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()); + KMArray.add(cmd, (short) 0, KMByteBlob.exp()); + KMArray.add(cmd, (short) 1, KMByteBlob.exp()); + KMArray.add(cmd, (short) 2, KMByteBlob.exp()); return receiveIncoming(apdu, cmd); } @@ -668,13 +723,13 @@ private void processGetKeyCharacteristicsCmd(APDU apdu) { 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]).isValid()) { + data[KEY_BLOB] = KMArray.get(cmd, (short) 0); + data[APP_ID] = KMArray.get(cmd, (short) 1); + data[APP_DATA] = KMArray.get(cmd, (short) 2); + if (!KMByteBlob.isValid(data[APP_ID])) { data[APP_ID] = KMType.INVALID_VALUE; } - if (!KMByteBlob.cast(data[APP_DATA]).isValid()) { + if (!KMByteBlob.isValid(data[APP_DATA])) { data[APP_DATA] = KMType.INVALID_VALUE; } // Parse Key Blob @@ -682,14 +737,14 @@ private void processGetKeyCharacteristicsCmd(APDU apdu) { // Check Version and Patch Level checkVersionAndPatchLevel(scratchPad); // Remove custom tags from key characteristics - short teeParams = KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getTeeEnforced(); + short teeParams = KMKeyCharacteristics.getTeeEnforced(data[KEY_CHARACTERISTICS]); if (teeParams != KMType.INVALID_VALUE) { - KMKeyParameters.cast(teeParams).deleteCustomTags(); + KMKeyParameters.deleteCustomTags(teeParams); } // 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]); + KMArray.add(resp, (short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.add(resp, (short) 1, data[KEY_CHARACTERISTICS]); sendOutgoing(apdu, resp); } @@ -699,12 +754,12 @@ private void processGetHmacSharingParamCmd(APDU apdu) { short params = KMHmacSharingParameters.instance(); short nonce = repository.getHmacNonce(); short seed = KMByteBlob.instance((short) 0); - KMHmacSharingParameters.cast(params).setNonce(nonce); - KMHmacSharingParameters.cast(params).setSeed(seed); + KMHmacSharingParameters.setNonce(params, nonce); + KMHmacSharingParameters.setSeed(params, 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); + KMArray.add(resp, (short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.add(resp, (short) 1, params); sendOutgoing(apdu, resp); } @@ -716,35 +771,35 @@ private void processDeleteAllKeysCmd(APDU apdu) { private short deleteKeyCmd(APDU apdu){ short cmd = KMArray.instance((short) 1); - KMArray.cast(cmd).add((short) 0, KMByteBlob.exp()); + KMArray.add(cmd, (short) 0, KMByteBlob.exp()); return receiveIncoming(apdu, cmd); } private short keyBlob(){ short keyBlob = KMArray.instance((short) 5); - KMArray.cast(keyBlob).add(KMKeymasterApplet.KEY_BLOB_SECRET, KMByteBlob.exp()); - KMArray.cast(keyBlob).add(KMKeymasterApplet.KEY_BLOB_AUTH_TAG, KMByteBlob.exp()); - KMArray.cast(keyBlob).add(KMKeymasterApplet.KEY_BLOB_NONCE, KMByteBlob.exp()); - short keyChar = KMKeyCharacteristics.exp(); - KMArray.cast(keyBlob).add(KMKeymasterApplet.KEY_BLOB_PARAMS, keyChar); - KMArray.cast(keyBlob).add(KMKeymasterApplet.KEY_BLOB_PUB_KEY, KMByteBlob.exp()); + KMArray.add(keyBlob, KMKeymasterDevice.KEY_BLOB_SECRET, KMByteBlob.exp()); + KMArray.add(keyBlob, KMKeymasterDevice.KEY_BLOB_AUTH_TAG, KMByteBlob.exp()); + KMArray.add(keyBlob, KMKeymasterDevice.KEY_BLOB_NONCE, KMByteBlob.exp()); + short keyChar = getKeyCharacteristicsExp(); + KMArray.add(keyBlob, KMKeymasterDevice.KEY_BLOB_PARAMS, keyChar); + KMArray.add(keyBlob, KMKeymasterDevice.KEY_BLOB_PUB_KEY, KMByteBlob.exp()); return keyBlob; } private void processDeleteKeyCmd(APDU apdu) { short cmd = deleteKeyCmd(apdu); - data[KEY_BLOB] = KMArray.cast(cmd).get((short) 0); + data[KEY_BLOB] = KMArray.get(cmd, (short) 0); try { data[KEY_BLOB] = decoder.decodeArray(keyBlob(), - KMByteBlob.cast(data[KEY_BLOB]).getBuffer(), - KMByteBlob.cast(data[KEY_BLOB]).getStartOff(), - KMByteBlob.cast(data[KEY_BLOB]).length()); + KMByteBlob.getBuffer(data[KEY_BLOB]), + KMByteBlob.getStartOff(data[KEY_BLOB]), + KMByteBlob.length(data[KEY_BLOB])); } catch (ISOException e) { // As per VTS, deleteKey should return KMError.OK but in case if // input is empty then VTS accepts UNIMPLEMENTED errorCode as well. KMException.throwIt(KMError.UNIMPLEMENTED); } - if (KMArray.cast(data[KEY_BLOB]).length() < 4) { + if (KMArray.length(data[KEY_BLOB]) < 4) { KMException.throwIt(KMError.INVALID_KEY_BLOB); } // Send ok @@ -755,7 +810,7 @@ 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); + KMArray.add(cmd, (short) 0, paramsVec); return receiveIncoming(apdu, cmd); } @@ -763,10 +818,10 @@ private void processComputeSharedHmacCmd(APDU apdu) { // Receive the incoming request fully from the master into buffer. short cmd = computeSharedHmacCmd(apdu); byte[] scratchPad = apdu.getBuffer(); - data[HMAC_SHARING_PARAMS] = KMArray.cast(cmd).get((short) 0); + data[HMAC_SHARING_PARAMS] = KMArray.get(cmd, (short) 0); // Concatenate HMAC Params //tmpVariables[0] - short paramsLen = KMArray.cast(data[HMAC_SHARING_PARAMS]).length(); // total number of params + short paramsLen = KMArray.length(data[HMAC_SHARING_PARAMS]); // total number of params //tmpVariables[1] short concateBuffer = repository.alloc((short) (paramsLen * HMAC_SHARED_PARAM_MAX_SIZE)); //tmpVariables[2] @@ -783,18 +838,18 @@ private void processComputeSharedHmacCmd(APDU apdu) { while (paramIndex < paramsLen) { // read HmacSharingParam //tmpVariables[4] - short param = KMArray.cast(data[HMAC_SHARING_PARAMS]).get(paramIndex); + short param = KMArray.get(data[HMAC_SHARING_PARAMS], paramIndex); // get seed - 32 bytes max //tmpVariables[5] - short seed = KMHmacSharingParameters.cast(param).getSeed(); + short seed = KMHmacSharingParameters.getSeed(param); //tmpVariables[6] - short seedLength = KMByteBlob.cast(seed).length(); + short seedLength = KMByteBlob.length(seed); // if seed is present if (seedLength != 0) { // then copy that to concatenation buffer Util.arrayCopyNonAtomic( - KMByteBlob.cast(seed).getBuffer(), - KMByteBlob.cast(seed).getStartOff(), + KMByteBlob.getBuffer(seed), + KMByteBlob.getStartOff(seed), repository.getHeap(), (short) (concateBuffer + bufferIndex), // concat index seedLength); @@ -804,16 +859,16 @@ private void processComputeSharedHmacCmd(APDU apdu) { } // if nonce is present get nonce - 32 bytes //tmpVariables[5] - short paramNonce = KMHmacSharingParameters.cast(param).getNonce(); - short nonceLen = KMByteBlob.cast(paramNonce).length(); + short paramNonce = KMHmacSharingParameters.getNonce(param); + short nonceLen = KMByteBlob.length(paramNonce); // 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(), + KMByteBlob.getBuffer(paramNonce), + KMByteBlob.getStartOff(paramNonce), repository.getHeap(), (short) (concateBuffer + bufferIndex), // index nonceLen); @@ -825,8 +880,8 @@ private void processComputeSharedHmacCmd(APDU apdu) { == Util.arrayCompare( repository.getHeap(), (short) (concateBuffer + bufferIndex), - KMByteBlob.cast(nonce).getBuffer(), - KMByteBlob.cast(nonce).getStartOff(), + KMByteBlob.getBuffer(nonce), + KMByteBlob.getStartOff(nonce), nonceLen)) { found = 2; // hmac nonce for this keymaster found. } else { @@ -872,23 +927,23 @@ private void processComputeSharedHmacCmd(APDU apdu) { 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); + KMArray.add(resp, (short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.add(resp, (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 + KMArray.add(cmd, (short) 0, KMByteBlob.exp()); // Key Blob + KMArray.add(cmd, (short) 1, keyParams); // Key Params return receiveIncoming(apdu, cmd); } private boolean isKeyUpgradeRequired(short tag, short systemParam) { // 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 tagValue = KMKeyParameters.findTag(data[HW_PARAMETERS], KMType.UINT_TAG, tag); + tagValue = KMIntegerTag.getValue(tagValue); 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 @@ -915,18 +970,18 @@ private void processUpgradeKeyCmd(APDU apdu) { short cmd = upgradeKeyCmd(apdu); byte[] scratchPad = apdu.getBuffer(); - data[KEY_BLOB] = KMArray.cast(cmd).get((short) 0); - data[KEY_PARAMETERS] = KMArray.cast(cmd).get((short) 1); + data[KEY_BLOB] = KMArray.get(cmd, (short) 0); + data[KEY_PARAMETERS] = KMArray.get(cmd ,(short) 1); //tmpVariables[0] short appId = - KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_ID, data[KEY_PARAMETERS]); + KMKeyParameters.findTag(data[KEY_PARAMETERS], KMType.BYTES_TAG, KMType.APPLICATION_ID); if (appId != KMTag.INVALID_VALUE) { - data[APP_ID] = KMByteTag.cast(appId).getValue(); + data[APP_ID] = KMByteTag.getValue(appId); } short appData = - KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_DATA, data[KEY_PARAMETERS]); + KMKeyParameters.findTag(data[KEY_PARAMETERS], KMType.BYTES_TAG, KMType.APPLICATION_DATA); if (appData != KMTag.INVALID_VALUE) { - data[APP_DATA] = KMByteTag.cast(appData).getValue(); + data[APP_DATA] = KMByteTag.getValue(appData); } // parse existing key blob parseEncryptedKeyBlob(data[KEY_BLOB], data[APP_ID], data[APP_DATA], scratchPad); @@ -952,8 +1007,8 @@ private void processUpgradeKeyCmd(APDU apdu) { } // 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]); + KMArray.add(resp, (short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.add(resp, (short) 1, data[KEY_BLOB]); sendOutgoing(apdu, resp); } @@ -963,14 +1018,14 @@ private void processExportKeyCmd(APDU apdu) { private void processWrappingKeyBlob(short keyBlob, short wrapParams, byte[] scratchPad) { // Read App Id and App Data if any from un wrapping key params short appId = - KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_ID, wrapParams); + KMKeyParameters.findTag(wrapParams, KMType.BYTES_TAG, KMType.APPLICATION_ID); short appData = - KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_DATA, wrapParams); + KMKeyParameters.findTag(wrapParams, KMType.BYTES_TAG, KMType.APPLICATION_DATA); if (appId != KMTag.INVALID_VALUE) { - appId = KMByteTag.cast(appId).getValue(); + appId = KMByteTag.getValue(appId); } if (appData != KMTag.INVALID_VALUE) { - appData = KMByteTag.cast(appData).getValue(); + appData = KMByteTag.getValue(appData); } data[APP_ID] = appId; data[APP_DATA] = appData; @@ -1002,15 +1057,15 @@ private void validateWrappingKeyBlob(){ 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(), + KMByteBlob.getBuffer(privExp), + KMByteBlob.getStartOff(privExp), + KMByteBlob.length(privExp), + KMByteBlob.getBuffer(modulus), + KMByteBlob.getStartOff(modulus), + KMByteBlob.length(modulus), + KMByteBlob.getBuffer(transportKey), + KMByteBlob.getStartOff(transportKey), + KMByteBlob.length(transportKey), scratchPad, (short) 0); return KMByteBlob.instance(scratchPad, (short) 0, length); @@ -1018,8 +1073,8 @@ private short decryptTransportKey(short privExp, short modulus, short transportK } private void unmask(short data, short maskingKey){ - short dataLength = KMByteBlob.cast(data).length(); - short maskLength = KMByteBlob.cast(maskingKey).length(); + short dataLength = KMByteBlob.length(data); + short maskLength = KMByteBlob.length(maskingKey); // Length of masking key and transport key must be same. if (maskLength != dataLength) { KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); @@ -1028,21 +1083,22 @@ private void unmask(short data, short maskingKey){ // 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) (((short) KMByteBlob.get(maskingKey, index)) & 0x00FF); short var2 = - (short) (((short) KMByteBlob.cast(data).get(index)) & 0x00FF); - KMByteBlob.cast(data).add(index, (byte) (var1 ^ var2)); + (short) (((short) KMByteBlob.get(data, index)) & 0x00FF); + KMByteBlob.add(data, 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 + KMArray.add(cmd, (short) 0, KMByteBlob.exp()); // Encrypted Transport Key + KMArray.add(cmd, (short) 1, KMByteBlob.exp()); // Wrapping Key KeyBlob + KMArray.add(cmd, (short) 2, KMByteBlob.exp()); // Masking Key params = KMKeyParameters.exp(); - KMArray.cast(cmd).add((short) 3, params); // Wrapping key blob Params + KMArray.add(cmd, (short) 3, params); // Wrapping key blob Params return receiveIncoming(apdu, cmd); } @@ -1052,97 +1108,74 @@ private void processBeginImportWrappedKeyCmd(APDU apdu) { byte[] scratchPad = apdu.getBuffer(); // Step -1 parse the wrapping key blob // read wrapping key blob - short keyBlob = KMArray.cast(cmd).get((short) 1); + short keyBlob = KMArray.get(cmd, (short) 1); // read un wrapping key params - short wrappingKeyParameters = KMArray.cast(cmd).get((short) 3); + short wrappingKeyParameters = KMArray.get(cmd, (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); + KMArray.get(cmd, (short) 0), scratchPad); // Step 3 - XOR the decrypted AES-GCM key with with masking key - unmask(transportKey, KMArray.cast(cmd).get((short) 2)); + unmask(transportKey, KMArray.get(cmd, (short) 2)); if(isValidWrappingKey()){ KMException.throwIt(KMError.UNKNOWN_ERROR); } setWrappingKey(transportKey); sendError(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); + Util.arrayFillNonAtomic(scratchPad, (short) 0, KMByteBlob.length(input), (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(), + KMByteBlob.getBuffer(aesSecret), + KMByteBlob.getStartOff(aesSecret), + KMByteBlob.length(aesSecret), + KMByteBlob.getBuffer(input), + KMByteBlob.getStartOff(input), + KMByteBlob.length(input), 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())) { + KMByteBlob.getBuffer(nonce), + KMByteBlob.getStartOff(nonce), + KMByteBlob.length(nonce), + KMByteBlob.getBuffer(authData), + KMByteBlob.getStartOff(authData), + KMByteBlob.length(authData), + KMByteBlob.getBuffer(authTag), + KMByteBlob.getStartOff(authTag), + KMByteBlob.length(authTag))) { KMException.throwIt(KMError.VERIFICATION_FAILED); } - return KMByteBlob.instance(scratchPad, (short) 0, KMByteBlob.cast(input).length()); + return KMByteBlob.instance(scratchPad, (short) 0, KMByteBlob.length(input)); } 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 + KMArray.add(cmd, (short) 0, params); // Key Params of wrapped key + KMArray.add(cmd, (short) 1, KMEnum.instance(KMType.KEY_FORMAT)); // Key Format + KMArray.add(cmd, (short) 2, KMByteBlob.exp()); // Wrapped Import Key Blob + KMArray.add(cmd, (short) 3, KMByteBlob.exp()); // Auth Tag + KMArray.add(cmd, (short) 4, KMByteBlob.exp()); // IV - Nonce + KMArray.add(cmd, (short) 5, KMByteBlob.exp()); // Wrapped Key ASSOCIATED AUTH DATA + KMArray.add(cmd, (short) 6, KMInteger.exp()); // Password Sid + KMArray.add(cmd, (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(); + short keyParameters = KMArray.get(cmd, (short) 0); + short keyFmt = KMArray.get(cmd, (short) 1); + keyFmt = KMEnum.getVal(keyFmt); 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); + data[INPUT_DATA] = KMArray.get(cmd, (short) 2); + data[AUTH_TAG] = KMArray.get(cmd, (short) 3); + data[NONCE] = KMArray.get(cmd, (short) 4); + data[AUTH_DATA] = KMArray.get(cmd, (short) 5); if(!isValidWrappingKey()){ KMException.throwIt(KMError.UNKNOWN_ERROR); @@ -1160,16 +1193,16 @@ private void processFinishImportWrappedKeyCmd(APDU apdu){ private KMAttestationCert makeAttestationCert(short attKeyBlob, short attKeyParam, short attChallenge, short issuer, short hwParameters, short swParameters, short keyParams, byte[] scratchPad) { - KMAttestationCert cert = specification.makeCommonCert(swParameters, hwParameters, + KMAttestationCert cert = makeCommonCert(swParameters, hwParameters, keyParams, scratchPad, seProvider); - short subject = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.CERTIFICATE_SUBJECT_NAME, keyParams); + short subject = KMKeyParameters.findTag(keyParams, KMType.BYTES_TAG, KMType.CERTIFICATE_SUBJECT_NAME); // If no subject name is specified then use the default subject name. - if(subject == KMType.INVALID_VALUE || KMByteTag.cast(subject).length() == 0){ + if(subject == KMType.INVALID_VALUE || KMByteTag.length(subject) == 0){ subject = KMByteBlob.instance(defaultSubject, (short) 0, (short) defaultSubject.length); }else{ - subject = KMByteTag.cast(subject).getValue(); + subject = KMByteTag.getValue(subject); } cert.subjectName(subject); @@ -1178,37 +1211,37 @@ private KMAttestationCert makeAttestationCert(short attKeyBlob, short attKeyPara short appData = KMType.INVALID_VALUE; if(attKeyParam != KMType.INVALID_VALUE) { appId = - KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_ID, attKeyParam); + KMKeyParameters.findTag(attKeyParam, KMType.BYTES_TAG, KMType.APPLICATION_ID); if (appId != KMTag.INVALID_VALUE) { - appId = KMByteTag.cast(appId).getValue(); + appId = KMByteTag.getValue(appId); } appData = - KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_DATA, attKeyParam); + KMKeyParameters.findTag(attKeyParam, KMType.BYTES_TAG, KMType.APPLICATION_DATA); if (appData != KMTag.INVALID_VALUE) { - appData = KMByteTag.cast(appData).getValue(); + appData = KMByteTag.getValue(appData); } } //TODO remove following line short origBlob = data[KEY_BLOB]; short pubKey = data[PUB_KEY]; short keyBlob = parseEncryptedKeyBlob(attKeyBlob, appId, appData, scratchPad); - short attestationKeySecret = KMArray.cast(keyBlob).get(KEY_BLOB_SECRET); - short attestParam = KMArray.cast(keyBlob).get(KEY_BLOB_PARAMS); - attestParam = KMKeyCharacteristics.cast(attestParam).getStrongboxEnforced(); + short attestationKeySecret = KMArray.get(keyBlob, KEY_BLOB_SECRET); + short attestParam = KMArray.get(keyBlob, KEY_BLOB_PARAMS); + attestParam = KMKeyCharacteristics.getStrongboxEnforced(attestParam); short attKeyPurpose = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, attestParam); // If the attest key's purpose is not "attest key" then error. - if (!KMEnumArrayTag.cast(attKeyPurpose).contains(KMType.ATTEST_KEY)) { + if (!KMEnumArrayTag.contains(attKeyPurpose, KMType.ATTEST_KEY)) { KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); } // If issuer is not present then it is an error - if (KMByteBlob.cast(issuer).length() <= 0) { + if (KMByteBlob.length(issuer) <= 0) { KMException.throwIt(KMError.MISSING_ISSUER_SUBJECT_NAME); } short alg = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, attestParam); - if(KMEnumTag.cast(alg).getValue() == KMType.RSA) { - short attestationKeyPublic = KMArray.cast(keyBlob).get(KEY_BLOB_PUB_KEY); + if(KMEnumTag.getValue(alg) == KMType.RSA) { + short attestationKeyPublic = KMArray.get(keyBlob, KEY_BLOB_PUB_KEY); cert.rsaAttestKey(attestationKeySecret, attestationKeyPublic, KMType.ATTESTATION_CERT); }else{ cert.ecAttestKey(attestationKeySecret, KMType.ATTESTATION_CERT); @@ -1248,7 +1281,7 @@ private KMAttestationCert makeAttestationCert(short attKeyBlob, short attKeyPara } private KMAttestationCert makeCertWithFactoryProvisionedKey(short attChallenge, byte[] scratchPad) { - KMAttestationCert cert = specification.makeCommonCert(data[SW_PARAMETERS], data[HW_PARAMETERS], + KMAttestationCert cert = makeCommonCert(data[SW_PARAMETERS], data[HW_PARAMETERS], data[KEY_PARAMETERS], scratchPad, seProvider); cert.attestationChallenge(attChallenge); cert.publicKey(data[PUB_KEY]); @@ -1256,7 +1289,7 @@ private KMAttestationCert makeCertWithFactoryProvisionedKey(short attChallenge, // Save attestation application id - must be present. short attAppId = - KMKeyParameters.findTag( KMType.BYTES_TAG, KMType.ATTESTATION_APPLICATION_ID, data[KEY_PARAMETERS]); + KMKeyParameters.findTag(data[KEY_PARAMETERS], KMType.BYTES_TAG, KMType.ATTESTATION_APPLICATION_ID); if (attAppId == KMType.INVALID_VALUE) { KMException.throwIt(KMError.ATTESTATION_APPLICATION_ID_MISSING); } @@ -1285,237 +1318,32 @@ private KMAttestationCert makeCertWithFactoryProvisionedKey(short attChallenge, private KMAttestationCert makeSelfSignedCert(short attPrivKey, short attPubKey, byte[] scratchPad) { //KMAttestationCert cert = makeCommonCert(scratchPad); KMAttestationCert cert = - specification.makeCommonCert(data[SW_PARAMETERS], data[HW_PARAMETERS], + makeCommonCert(data[SW_PARAMETERS], data[HW_PARAMETERS], data[KEY_PARAMETERS], scratchPad, seProvider); - short alg = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, data[KEY_PARAMETERS]); + short alg = KMKeyParameters.findTag(data[KEY_PARAMETERS], KMType.ENUM_TAG, KMType.ALGORITHM); byte mode = KMType.FAKE_CERT; if(attPrivKey != KMType.INVALID_VALUE){ mode = KMType.SELF_SIGNED_CERT; } - short subject = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.CERTIFICATE_SUBJECT_NAME, data[KEY_PARAMETERS]); + short subject = KMKeyParameters.findTag(data[KEY_PARAMETERS], KMType.BYTES_TAG, KMType.CERTIFICATE_SUBJECT_NAME); // If no subject name is specified then use the default subject name. - if(subject == KMType.INVALID_VALUE || KMByteTag.cast(subject).length() == 0){ + if(subject == KMType.INVALID_VALUE || KMByteTag.length(subject) == 0){ subject = KMByteBlob.instance(defaultSubject, (short) 0, (short) defaultSubject.length); }else{ - subject = KMByteTag.cast(subject).getValue(); + subject = KMByteTag.getValue(subject); } - if(KMEnumTag.cast(alg).getValue() == KMType.RSA) { + if(KMEnumTag.getValue(alg) == KMType.RSA) { cert.rsaAttestKey(attPrivKey, attPubKey, mode); }else{ cert.ecAttestKey(attPrivKey, mode); } -// cert.attestKey(attPrivKey, rsaAttest, KMType.SELF_SIGNED_CERT); -// cert.attestKey(KMType.INVALID_VALUE, rsaAttest, KMType.FAKE_CERT); cert.issuer(subject); cert.subjectName(subject); cert.publicKey(attPubKey); return cert; } - // private KMAttestationCert makeFakeCert(byte[] scratchPad) { - // KMAttestationCert cert = makeCommonCert(scratchPad); - // short alg = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, data[KEY_PARAMETERS]); - // boolean rsaAttest = KMEnumTag.cast(alg).getValue() == KMType.RSA; - // - // // If the generated/imported key can sign then mode is self signed - // 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.attestKey(KMType.INVALID_VALUE, rsaAttest,KMType.FAKE_CERT); - // cert.issuer(subject); - // cert.subjectName(subject); - // cert.publicKey(data[PUB_KEY]); - // return cert; - // } - - /** - * 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. - */ - // private KMAttestationCert makeCert(short attKeyBlob, short attKeyParam, byte[] scratchPad) { - // short appId = KMType.INVALID_VALUE; - // short appData = KMType.INVALID_VALUE; - // if(attKeyParam != KMType.INVALID_VALUE) { - // appId = - // KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_ID, attKeyParam); - // if (appId != KMTag.INVALID_VALUE) { - // appId = KMByteTag.cast(appId).getValue(); - // } - // appData = - // KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_DATA, attKeyParam); - // if (appData != KMTag.INVALID_VALUE) { - // appData = KMByteTag.cast(appData).getValue(); - // } - // } - // byte mode = KMType.FAKE_CERT; - // boolean rsaCert = true; - // short attestationKey = KMType.INVALID_VALUE; - // short attestParam = KMType.INVALID_VALUE; - // boolean rsaAttest = true; - // // No attestation required for symmetric keys - // short alg = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, data[KEY_PARAMETERS]); - // if(KMEnumTag.cast(alg).getValue() != KMType.RSA && - // KMEnumTag.cast(alg).getValue() != KMType.EC){ - // return null; - // } - // // Device unique attestation not supported - // short deviceUniqueAttest = KMKeyParameters.findTag(KMType.BOOL_TAG, - // KMType.DEVICE_UNIQUE_ATTESTATION, data[KEY_PARAMETERS]); - // if(deviceUniqueAttest != KMType.INVALID_VALUE){ - // KMException.throwIt(KMError.CANNOT_ATTEST_IDS); - // } - // if(KMEnumTag.cast(alg).getValue() == KMType.EC) { - // rsaCert = false; - // rsaAttest = false; - // } - // KMAttestationCert cert = seProvider.getAttestationCert(rsaCert); - // // 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(); - // } - // 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 attestation key is given by the caller - // if(attKeyBlob != KMType.INVALID_VALUE && KMByteBlob.cast(attKeyBlob).length() > 0) { - // short keyBlob = KMType.INVALID_VALUE; - // keyBlob = parseEncryptedKeyBlob(attKeyBlob, appId, appData, scratchPad); - // - // // If no attestation challenge present then it is an error - // if (attChallenge == KMType.INVALID_VALUE || KMByteBlob.cast(attChallenge).length() <= 0) { - // KMException.throwIt(KMError.INVALID_ARGUMENT); - // } - // mode = KMType.ATTESTATION_CERT; - // attestationKey = KMArray.cast(keyBlob).get(KEY_BLOB_SECRET); - // attestParam = KMArray.cast(keyBlob).get(KEY_BLOB_PARAMS); - // attestParam = KMKeyCharacteristics.cast(attestParam).getStrongboxEnforced(); - // alg = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, attestParam); - // if(KMEnumTag.cast(alg).getValue() == KMType.EC){ - // rsaAttest = false; - // } - // // If issuer is not present then it is an error - // if (KMByteBlob.cast(data[ATTEST_KEY_ISSUER]).length() <= 0) { - // KMException.throwIt(KMError.MISSING_ISSUER_SUBJECT_NAME); - // } - // // TODO KeyMint should parse issuer subject name to validate that it is valid der encoded - // // string. - // // Currently not done as X509 parsing not yet supported. - // - // short attKeyPurpose = - // KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, attestParam); - // // If the attest key's purpose is not "attest key" then error. - // if (!KMEnumArrayTag.cast(attKeyPurpose).contains(KMType.ATTEST_KEY)) { - // KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); - // } - // }else if (attChallenge != KMType.INVALID_VALUE && KMByteBlob.cast(attChallenge).length() > 0) { - // // If the challenge is present and attest key is absent then it is an error. - // // Note: The keymint applet does not support factory provisioned attestation keys. - // KMException.throwIt(KMError.ATTESTATION_KEYS_NOT_PROVISIONED); - // }else { - // // Both attestation key and challenge are are absent. - // short purpose = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, data[KEY_PARAMETERS]); - // if(purpose != KMType.INVALID_VALUE && KMEnumArrayTag.cast(purpose).contains(KMType.SIGN)) { - // // If the generated/imported key can sign then mode is self signed - // mode = KMType.SELF_SIGNED_CERT; - // attestationKey = data[SECRET]; - // attestParam = data[KEY_PARAMETERS]; - // data[ATTEST_KEY_ISSUER] = subject; - // }else{ - // // else a fake cert i.e. certificated without signature must be generated - // mode = KMType.FAKE_CERT; - // attestationKey = KMType.INVALID_VALUE; - // data[ATTEST_KEY_ISSUER] = subject; - // } - // } - // cert.attestKey(attestationKey, rsaAttest,mode); - // cert.attestationChallenge(attChallenge); - // cert.issuer(data[ATTEST_KEY_ISSUER]); - // 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); - // - // //Extension - // if(mode == KMType.ATTESTATION_CERT){ - // // 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. - // setUniqueId(cert, 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(seProvider.isDeviceBootLocked()); - // // VB data - // cert.verifiedBootHash(getVerifiedBootHash(scratchPad)); - // cert.verifiedBootKey(getBootKey(scratchPad)); - // cert.verifiedBootState((byte)seProvider.getBootState()); - // } - // - // // Public key - // cert.publicKey(data[PUB_KEY]); - // return cert; - // } - protected short getBootKey(byte[] scratchPad){ Util.arrayFillNonAtomic(scratchPad, (short)0, VERIFIED_BOOT_KEY_SIZE, (byte)0); short len = seProvider.getBootKey(scratchPad, (short) 0); @@ -1540,75 +1368,64 @@ protected short getVerifiedBootHash(byte[] scratchPad){ // id values of both the requested parameters and the provisioned parameters // then throw INVALID_TAG error. private void addAttestationIds(KMAttestationCert cert, byte[] scratchPad) { - final short[] attTags = - new short[]{ - 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 - }; 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]); + while (index < (short) ATTEST_ID_TAGS.length) { + attIdTag = KMKeyParameters.findTag(data[KEY_PARAMETERS], KMType.BYTES_TAG, ATTEST_ID_TAGS[index]); if (attIdTag != KMType.INVALID_VALUE) { - attIdTagValue = KMByteTag.cast(attIdTag).getValue(); - storedAttIdLen = seProvider.getAttestationId(attTags[index],scratchPad, (short)0); + attIdTagValue = KMByteTag.getValue(attIdTag); + storedAttIdLen = seProvider.getAttestationId(ATTEST_ID_TAGS[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()) || + if ((storedAttIdLen != KMByteBlob.length(attIdTagValue)) || (0 != Util.arrayCompare(scratchPad, (short) 0, - KMByteBlob.cast(attIdTagValue).getBuffer(), - KMByteBlob.cast(attIdTagValue).getStartOff(), + KMByteBlob.getBuffer(attIdTagValue), + KMByteBlob.getStartOff(attIdTagValue), storedAttIdLen))) { KMException.throwIt(KMError.INVALID_TAG); } short blob = KMByteBlob.instance(scratchPad, (short)0, storedAttIdLen); - cert.extensionTag(KMByteTag.instance(attTags[index], blob), true); + cert.extensionTag(KMByteTag.instance(ATTEST_ID_TAGS[index], blob), true); } index++; } } - private static void addTags(short params, boolean hwEnforced, KMAttestationCert cert) { + private void addTags(short params, boolean hwEnforced, KMAttestationCert cert) { short index = 0; - short arr = KMKeyParameters.cast(params).getVals(); - short len = KMArray.cast(arr).length(); + short arr = KMKeyParameters.getVals(params); + short len = KMArray.length(arr); short tag; while (index < len) { - tag = KMArray.cast(arr).get(index); + tag = KMArray.get(arr, index); cert.extensionTag(tag, hwEnforced); index++; } } - private static void setUniqueId(KMAttestationCert cert, byte[] scratchPad) { + private void setUniqueId(KMAttestationCert cert, byte[] scratchPad) { if(!KMTag.isPresent(data[HW_PARAMETERS],KMType.BOOL_TAG, KMType.INCLUDE_UNIQUE_ID)){ return; } // temporal count T - short time = KMKeyParameters.findTag(KMType.DATE_TAG, KMType.CREATION_DATETIME, data[SW_PARAMETERS]); + short time = KMKeyParameters.findTag(data[SW_PARAMETERS], KMType.DATE_TAG, KMType.CREATION_DATETIME); if(time == KMType.INVALID_VALUE){ KMException.throwIt(KMError.INVALID_TAG); } - time = KMIntegerTag.cast(time).getValue(); + time = KMIntegerTag.getValue(time); // Application Id C - short appId = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.ATTESTATION_APPLICATION_ID, data[KEY_PARAMETERS]); + short appId = KMKeyParameters.findTag(data[KEY_PARAMETERS], KMType.BYTES_TAG, KMType.ATTESTATION_APPLICATION_ID); if (appId == KMType.INVALID_VALUE) { KMException.throwIt(KMError.ATTESTATION_APPLICATION_ID_MISSING); } - appId = KMByteTag.cast(appId).getValue(); + appId = KMByteTag.getValue(appId); // Reset After Rotation R - it will be part of HW Enforced key // characteristics @@ -1617,9 +1434,9 @@ private static void setUniqueId(KMAttestationCert cert, byte[] scratchPad) { resetAfterRotation = 0x01; } - cert.makeUniqueId(scratchPad, (short) 0, KMInteger.cast(time).getBuffer(), - KMInteger.cast(time).getStartOff(), KMInteger.cast(time).length(), - KMByteBlob.cast(appId).getBuffer(), KMByteBlob.cast(appId).getStartOff(), KMByteBlob.cast(appId).length(), resetAfterRotation, + cert.makeUniqueId(scratchPad, (short) 0, KMInteger.getBuffer(time), + KMInteger.getStartOff(time), KMInteger.length(time), + KMByteBlob.getBuffer(appId), KMByteBlob.getStartOff(appId), KMByteBlob.length(appId), resetAfterRotation, seProvider.getMasterKey()); } @@ -1634,13 +1451,13 @@ private void processVerifyAuthorizationCmd(APDU apdu) { private short abortOperationCmd(APDU apdu){ short cmd = KMArray.instance((short) 1); - KMArray.cast(cmd).add((short) 0, KMInteger.exp()); + KMArray.add(cmd, (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); + data[OP_HANDLE] = KMArray.get(cmd, (short) 0); KMOperationState op = findOperation(data[OP_HANDLE]); if (op == null) { sendError(apdu,KMError.INVALID_OPERATION_HANDLE); @@ -1651,14 +1468,31 @@ private void processAbortOperationCmd(APDU apdu) { } private short finishOperationCmd(APDU apdu){ - return receiveIncoming(apdu, specification.prepareFinishExp()); + short cmd = KMArray.instance((short) 7); + KMArray.add(cmd, (short) 0, KMInteger.exp());//op handle + short keyParam = KMKeyParameters.exp(); + KMArray.add(cmd, (short) 1, keyParam);// Key Parameters + KMArray.add(cmd, (short) 2, KMByteBlob.exp());// input data + KMArray.add(cmd, (short) 3, KMByteBlob.exp()); // signature + short authToken = KMHardwareAuthToken.exp(); + KMArray.add(cmd, (short) 4, authToken); // auth token + short verToken = getKMVerificationTokenExp(); + KMArray.add(cmd, (short) 5, verToken); // time stamp token + KMArray.add(cmd, (short) 6, KMByteBlob.exp()); //confirmation token + return receiveIncoming(apdu, cmd); } private void processFinishOperationCmd(APDU apdu) { short cmd = finishOperationCmd(apdu); byte[] scratchPad = apdu.getBuffer(); - specification.getFinishInputParameters(cmd, data, OP_HANDLE, KEY_PARAMETERS, INPUT_DATA, - SIGNATURE, HW_TOKEN, VERIFICATION_TOKEN, CONFIRMATION_TOKEN); + data[OP_HANDLE] = KMArray.get(cmd, (short) 0); + data[KEY_PARAMETERS] = KMArray.get(cmd, (short) 1); + data[INPUT_DATA] = KMArray.get(cmd, (short) 2); + data[SIGNATURE] = KMArray.get(cmd, (short) 3); + data[HW_TOKEN] = KMArray.get(cmd, (short) 4); + data[VERIFICATION_TOKEN] = KMArray.get(cmd, (short) 5); + data[CONFIRMATION_TOKEN] = KMArray.get(cmd, (short) 6); + // Check Operation Handle KMOperationState op = findOperation(data[OP_HANDLE]); if (op == null) { @@ -1679,11 +1513,7 @@ private void processFinishOperationCmd(APDU apdu) { finishDecryptOperation(op, scratchPad); break; case KMType.AGREE_KEY: - if(specification.isKeyAgreementSupported()) { - finishKeyAgreementOperation(op, scratchPad); - } else { - KMException.throwIt(KMError.UNIMPLEMENTED); - } + finishKeyAgreementOperation(op, scratchPad); break; } if (data[OUTPUT_DATA] == KMType.INVALID_VALUE) { @@ -1693,7 +1523,13 @@ private void processFinishOperationCmd(APDU apdu) { releaseOperation(op); // make response - sendOutgoing(apdu, specification.prepareFinishResp(data[OUTPUT_DATA])); + short keyParam = KMArray.instance((short) 0); + keyParam = KMKeyParameters.instance(keyParam); + short resp = KMArray.instance((short) 3); + KMArray.add(resp, (short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.add(resp, (short) 1, keyParam); + KMArray.add(resp, (short) 2, data[OUTPUT_DATA]); + sendOutgoing(apdu, resp); } private void finishEncryptOperation(KMOperationState op, byte[] scratchPad) { @@ -1704,7 +1540,7 @@ private void finishEncryptOperation(KMOperationState op, byte[] scratchPad) { } private void finishDecryptOperation(KMOperationState op, byte[] scratchPad) { - short len = KMByteBlob.cast(data[INPUT_DATA]).length(); + short len = KMByteBlob.length(data[INPUT_DATA]); switch (op.getAlgorithm()) { case KMType.RSA: // Fill the scratch pad with zero @@ -1714,7 +1550,7 @@ private void finishDecryptOperation(KMOperationState op, byte[] scratchPad) { } len = op.getOperation().finish( - KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.getBuffer(data[INPUT_DATA]), KMByteBlob.getStartOff(data[INPUT_DATA]), len, scratchPad, (short) 0); data[OUTPUT_DATA] = KMByteBlob.instance(scratchPad, (short) 0, len); @@ -1727,7 +1563,7 @@ private void finishDecryptOperation(KMOperationState op, byte[] scratchPad) { } private void finishAesDesOperation(KMOperationState op){ - short len = KMByteBlob.cast(data[INPUT_DATA]).length(); + short len = KMByteBlob.length(data[INPUT_DATA]); short blockSize = AES_BLOCK_SIZE; if (op.getAlgorithm() == KMType.DES) { blockSize= DES_BLOCK_SIZE; @@ -1744,7 +1580,7 @@ private void finishAesDesOperation(KMOperationState op){ KMException.throwIt(KMError.INVALID_INPUT_LENGTH); } // update aad if there is any - updateAAD(op, (byte) 0x01); + updateAAD(op, KMType.INVALID_VALUE, (byte) 0x01); if(op.isAesGcmUpdateAllowed()){ op.setAesGcmUpdateComplete(); } @@ -1757,40 +1593,21 @@ private void finishAesDesOperation(KMOperationState op){ 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()); + KMByteBlob.getBuffer(data[INPUT_DATA]), + KMByteBlob.getStartOff(data[INPUT_DATA]), + KMByteBlob.length(data[INPUT_DATA]), + KMByteBlob.getBuffer(data[OUTPUT_DATA]), + KMByteBlob.getStartOff(data[OUTPUT_DATA])); } catch (CryptoException e) { if (e.getReason() == CryptoException.ILLEGAL_USE) { KMException.throwIt(KMError.INVALID_INPUT_LENGTH); } } - KMByteBlob.cast(data[OUTPUT_DATA]).setLength(len); + KMByteBlob.setLength(data[OUTPUT_DATA], len); } - private void finishKeyAgreementOperation(KMOperationState op, byte[] scratchPad) { - try { - KMPKCS8Decoder pkcs8 = KMPKCS8Decoder.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); - } + public void finishKeyAgreementOperation(KMOperationState op, byte[] scratchPad) { + KMException.throwIt(KMError.UNIMPLEMENTED); } private void finishSigningVerifyingOperation(KMOperationState op, byte[] scratchPad) { @@ -1802,17 +1619,17 @@ private void finishSigningVerifyingOperation(KMOperationState op, byte[] scratch 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, + KMByteBlob.getBuffer(data[INPUT_DATA]), + KMByteBlob.getStartOff(data[INPUT_DATA]), + KMByteBlob.length(data[INPUT_DATA]), 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), + KMByteBlob.getBuffer(data[OUTPUT_DATA]), + (short) (KMByteBlob.getStartOff(data[OUTPUT_DATA]) + 256 - len), len); } else { KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); @@ -1822,7 +1639,7 @@ private void finishSigningVerifyingOperation(KMOperationState op, byte[] scratch } break; case KMType.EC: - short len = KMByteBlob.cast(data[INPUT_DATA]).length(); + short len = KMByteBlob.length(data[INPUT_DATA]); // If DIGEST NONE then truncate the input data to 32 bytes. if (op.getDigest() == KMType.DIGEST_NONE && len > 32) { len = 32; @@ -1832,8 +1649,8 @@ private void finishSigningVerifyingOperation(KMOperationState op, byte[] scratch len = op.getOperation() .sign( - KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), - KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.getBuffer(data[INPUT_DATA]), + KMByteBlob.getStartOff(data[INPUT_DATA]), len, scratchPad, (short) 0); @@ -1856,9 +1673,9 @@ private void finishSigningVerifyingOperation(KMOperationState op, byte[] scratch // instead of using op.getPurpose(). op.getOperation() .sign( - KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), - KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), - KMByteBlob.cast(data[INPUT_DATA]).length(), + KMByteBlob.getBuffer(data[INPUT_DATA]), + KMByteBlob.getStartOff(data[INPUT_DATA]), + KMByteBlob.length(data[INPUT_DATA]), scratchPad, (short) 0); if(op.getPurpose() == KMType.SIGN) { @@ -1869,9 +1686,9 @@ private void finishSigningVerifyingOperation(KMOperationState op, byte[] scratch if (0 != Util.arrayCompare( scratchPad, (short)0, - KMByteBlob.cast(data[SIGNATURE]).getBuffer(), - KMByteBlob.cast(data[SIGNATURE]).getStartOff(), - KMByteBlob.cast(data[SIGNATURE]).length())) { + KMByteBlob.getBuffer(data[SIGNATURE]), + KMByteBlob.getStartOff(data[SIGNATURE]), + KMByteBlob.length(data[SIGNATURE]))) { KMException.throwIt(KMError.VERIFICATION_FAILED); } data[OUTPUT_DATA] = KMByteBlob.instance((short) 0); @@ -1889,12 +1706,12 @@ private void authorizeUpdateFinishOperation(KMOperationState op, byte[] scratchP // Validate Verification Token. validateVerificationToken(data[VERIFICATION_TOKEN], scratchPad); // validate operation handle. - short ptr = KMVerificationToken.cast(data[VERIFICATION_TOKEN]).getChallenge(); + short ptr = KMVerificationToken.getChallenge(data[VERIFICATION_TOKEN]); if (KMInteger.compare(ptr, op.getHandle()) != 0) { KMException.throwIt(KMError.VERIFICATION_FAILED); } tmpVariables[0] = op.getAuthTime(); - tmpVariables[2] = KMVerificationToken.cast(data[VERIFICATION_TOKEN]).getTimestamp(); + tmpVariables[2] = KMVerificationToken.getTimestamp(data[VERIFICATION_TOKEN]); if (tmpVariables[2] == KMType.INVALID_VALUE) { KMException.throwIt(KMError.VERIFICATION_FAILED); } @@ -1903,7 +1720,7 @@ private void authorizeUpdateFinishOperation(KMOperationState op, byte[] scratchP } op.setAuthTimeoutValidated(true); } else if (op.isAuthPerOperationReqd()) { // If Auth per operation is required - tmpVariables[0] = KMHardwareAuthToken.cast(data[HW_TOKEN]).getChallenge(); + tmpVariables[0] = KMHardwareAuthToken.getChallenge(data[HW_TOKEN]); if (KMInteger.compare(data[OP_HANDLE], tmpVariables[0]) != 0) { KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); } @@ -1960,14 +1777,13 @@ 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]); + KMKeyParameters.findTag(data[HW_PARAMETERS], KMType.BOOL_TAG, KMType.UNLOCKED_DEVICE_REQUIRED); if (ptr != KMType.INVALID_VALUE && repository.getDeviceLock()) { if (!validateHwToken(data[HW_TOKEN], scratchPad)) { KMException.throwIt(KMError.DEVICE_LOCKED); } - ptr = KMHardwareAuthToken.cast(data[HW_TOKEN]).getTimestamp(); + ptr = KMHardwareAuthToken.getTimestamp(data[HW_TOKEN]); // Check if the current auth time stamp is greater than device locked time stamp short ts = repository.getDeviceTimeStamp(); if (KMInteger.compare(ptr, ts) <= 0) { @@ -1976,8 +1792,8 @@ private void authorizeDeviceUnlock(byte[] scratchPad) { // Now check if the device unlock requires password only authentication and whether // auth token is generated through password authentication or not. if (repository.getDeviceLockPasswordOnly()) { - ptr = KMHardwareAuthToken.cast(data[HW_TOKEN]).getHwAuthenticatorType(); - ptr = KMEnum.cast(ptr).getVal(); + ptr = KMHardwareAuthToken.getHwAuthenticatorType(data[HW_TOKEN]); + ptr = KMEnum.getVal(ptr); if (((byte) ptr & KMType.PASSWORD) == 0) { KMException.throwIt(KMError.DEVICE_LOCKED); } @@ -1998,35 +1814,33 @@ private boolean verifyVerificationTokenMacInBigEndian(short verToken, byte[] scr (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) (8 - KMInteger.cast(ptr).length()))); + short ptr = KMVerificationToken.getChallenge(verToken); + KMInteger.value(ptr, scratchPad, (short) (len + (short) (8 - KMInteger.length(ptr)))); len += 8; // concatenate timestamp -8 bytes - ptr = KMVerificationToken.cast(verToken).getTimestamp(); - KMInteger.cast(ptr) - .value(scratchPad, (short) (len + (short) (8 - KMInteger.cast(ptr).length()))); + ptr = KMVerificationToken.getTimestamp(verToken); + KMInteger.value(ptr, scratchPad, (short) (len + (short) (8 - KMInteger.length(ptr)))); len += 8; // concatenate security level - 4 bytes scratchPad[(short) (len + 3)] = TRUSTED_ENVIRONMENT; len += 4; // hmac the data - ptr = specification.getMacFromVerificationToken(verToken); + ptr = getMacFromVerificationToken(verToken); return seProvider.hmacVerify( seProvider.getComputedHmacKey(), scratchPad, (short) 0, len, - KMByteBlob.cast(ptr).getBuffer(), - KMByteBlob.cast(ptr).getStartOff(), - KMByteBlob.cast(ptr).length()); + KMByteBlob.getBuffer(ptr), + KMByteBlob.getStartOff(ptr), + KMByteBlob.length(ptr)); } private void validateVerificationToken(short verToken, byte[] scratchPad) { - short ptr = specification.getMacFromVerificationToken(verToken); + short ptr = getMacFromVerificationToken(verToken); // If mac length is zero then token is empty. - if (KMByteBlob.cast(ptr).length() == 0) { + if (KMByteBlob.length(ptr) == 0) { KMException.throwIt(KMError.INVALID_MAC_LENGTH); } if (!verifyVerificationTokenMacInBigEndian(verToken, scratchPad)) { @@ -2036,14 +1850,27 @@ private void validateVerificationToken(short verToken, byte[] scratchPad) { } private short updateOperationCmd(APDU apdu){ - return receiveIncoming(apdu, specification.prepareUpdateExp()); + short cmd = KMArray.instance((short) 5); + // Arguments + short keyParams = KMKeyParameters.exp(); + KMArray.add(cmd, (short) 0, KMInteger.exp()); + KMArray.add(cmd, (short) 1, keyParams); + KMArray.add(cmd, (short) 2, KMByteBlob.exp()); + short authToken = KMHardwareAuthToken.exp(); + KMArray.add(cmd, (short) 3, authToken); + short verToken = getKMVerificationTokenExp(); + KMArray.add(cmd, (short) 4, verToken); + return receiveIncoming(apdu, cmd); } private void processUpdateOperationCmd(APDU apdu) { short cmd = updateOperationCmd(apdu); byte[] scratchPad = apdu.getBuffer(); - specification.getUpdateInputParameters(cmd, data, OP_HANDLE, KEY_PARAMETERS, - INPUT_DATA, HW_TOKEN, VERIFICATION_TOKEN); + data[OP_HANDLE] = KMArray.get(cmd, (short) 0); + data[KEY_PARAMETERS] = KMArray.get(cmd, (short) 1); //will be passed as INVALID_VALUE from keymint HAL + data[INPUT_DATA] = KMArray.get(cmd, (short) 2); + data[HW_TOKEN] = KMArray.get(cmd, (short) 3); + data[VERIFICATION_TOKEN] = KMArray.get(cmd, (short) 4); // Input data must be present even if it is zero length. if (data[INPUT_DATA] == KMType.INVALID_VALUE) { @@ -2063,9 +1890,9 @@ private void processUpdateOperationCmd(APDU apdu) { // update the data. op.getOperation() .update( - KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), - KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), - KMByteBlob.cast(data[INPUT_DATA]).length()); + KMByteBlob.getBuffer(data[INPUT_DATA]), + KMByteBlob.getStartOff(data[INPUT_DATA]), + KMByteBlob.length(data[INPUT_DATA])); // update trusted confirmation operation updateTrustedConfirmationOperation(op); @@ -2076,14 +1903,14 @@ private void processUpdateOperationCmd(APDU apdu) { if (op.getAlgorithm() == KMType.RSA) { KMException.throwIt(KMError.OPERATION_CANCELLED); } - short len = KMByteBlob.cast(data[INPUT_DATA]).length(); + short len = KMByteBlob.length(data[INPUT_DATA]); short blockSize = DES_BLOCK_SIZE; if (op.getAlgorithm() == KMType.AES) { blockSize = AES_BLOCK_SIZE; if (op.getBlockMode() == KMType.GCM) { // data[KEY_PARAMETERS] will be invalid for keymint if(data[KEY_PARAMETERS] != KMType.INVALID_VALUE) { - updateAAD(op, (byte) 0x00); + updateAAD(op, KMType.INVALID_VALUE, (byte) 0x00); } // if input data present if (len > 0) { @@ -2104,84 +1931,97 @@ private void processUpdateOperationCmd(APDU apdu) { 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()); + KMByteBlob.getBuffer(data[INPUT_DATA]), + KMByteBlob.getStartOff(data[INPUT_DATA]), + KMByteBlob.length(data[INPUT_DATA]), + KMByteBlob.getBuffer(data[OUTPUT_DATA]), + KMByteBlob.getStartOff(data[OUTPUT_DATA])); } catch (CryptoException e) { KMException.throwIt(KMError.INVALID_TAG); } // 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); + KMByteBlob.setLength(data[OUTPUT_DATA], 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 - sendOutgoing(apdu, specification.prepareUpdateResp(data[OUTPUT_DATA], KMInteger.uint_16(inputConsumed))); + short resp = KMArray.instance((short) 4); + KMArray.add(resp, (short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.add(resp, (short) 1, data[OUTPUT_DATA]); + short keyParm = KMKeyParameters.instance(KMArray.instance((short) 0)); + KMArray.add(resp, (short) 2, keyParm); // will be ignored in keymint HAL + KMArray.add(resp, (short) 3, KMInteger.uint_16(inputConsumed)); // will be ignored in keymint HAL + + 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()); + KMArray.add(cmd, (short) 0, KMInteger.exp()); + KMArray.add(cmd, (short) 1, KMByteBlob.exp()); short authToken = KMHardwareAuthToken.exp(); - KMArray.cast(cmd).add((short) 2, authToken); - short verToken = specification.getKMVerificationTokenExp(); - KMArray.cast(cmd).add((short) 3, verToken); + KMArray.add(cmd, (short) 2, authToken); + short verToken = getKMVerificationTokenExp(); + KMArray.add(cmd, (short) 3, verToken); return receiveIncoming(apdu, cmd); } //update operation should send 0x00 for finish variable, where as finish operation // should send 0x01 for finish variable. - private void updateAAD(KMOperationState op, byte finish) { - if (!specification.isAssociatedDataTagSupported()) { - return; - } - // Is input data absent - if (data[INPUT_DATA] == KMType.INVALID_VALUE) { - KMException.throwIt(KMError.INVALID_ARGUMENT); - } - // Update can be called either to update auth data, update input data or both. - // But if it is called for neither then return error. - short len = KMByteBlob.cast(data[INPUT_DATA]).length(); - short tag = - KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.ASSOCIATED_DATA, data[KEY_PARAMETERS]); - // For Finish operation the input data can be zero length and associated data can be - // INVALID_VALUE - // For update operation either input data or associated data should be present. - if (tag == KMType.INVALID_VALUE && len <= 0 && finish == 0x00) { - KMException.throwIt(KMError.INVALID_INPUT_LENGTH); - } - // Check if associated data is present and update aad still allowed by the operation. - if (tag != KMType.INVALID_VALUE) { - // If allowed the update the aad - if (!op.isAesGcmUpdateAllowed()) { - KMException.throwIt(KMError.INVALID_TAG); + private void updateAAD(KMOperationState op, short associatedData, byte finish) { + + short aData = associatedData; + if(associatedData == KMType.INVALID_VALUE) { + // Is input data absent + if (data[INPUT_DATA] == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // Update can be called either to update auth data, update input data or both. + // But if it is called for neither then return error. + short len = KMByteBlob.length(data[INPUT_DATA]); + short tag = + KMKeyParameters.findTag(data[KEY_PARAMETERS], KMType.BYTES_TAG, KMType.ASSOCIATED_DATA); + // For Finish operation the input data can be zero length and associated data can be + // INVALID_VALUE + // For update operation either input data or associated data should be present. + if (tag == KMType.INVALID_VALUE && len <= 0 && finish == 0x00) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + // Check if associated data is present and update aad still allowed by the operation. + if (tag != KMType.INVALID_VALUE) { + // If allowed the update the aad + if (!op.isAesGcmUpdateAllowed()) { + KMException.throwIt(KMError.INVALID_TAG); + } + aData = KMByteTag.getValue(tag); + } + } + try { + if(aData != KMType.INVALID_VALUE) { + op.getOperation() + .updateAAD( + KMByteBlob.getBuffer(aData), + KMByteBlob.getStartOff(aData), + KMByteBlob.length(aData)); } - // If allowed the update the aad - short aData = KMByteTag.cast(tag).getValue(); - - op.getOperation() - .updateAAD( - KMByteBlob.cast(aData).getBuffer(), - KMByteBlob.cast(aData).getStartOff(), - KMByteBlob.cast(aData).length()); + } catch(CryptoException exp){ + KMException.throwIt(KMError.UNKNOWN_ERROR); } } 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); + data[OP_HANDLE] = KMArray.get(cmd, (short) 0); + data[INPUT_DATA] = KMArray.get(cmd, (short) 1); + data[HW_TOKEN] = KMArray.get(cmd, (short) 2); + data[VERIFICATION_TOKEN] = KMArray.get(cmd, (short) 3); // Input data must be present even if it is zero length. if (data[INPUT_DATA] == KMType.INVALID_VALUE) { @@ -2207,19 +2047,11 @@ private void processUpdateAadOperationCmd(APDU apdu) { } // 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); - } + updateAAD(op, data[INPUT_DATA], (byte)0x00); // make response short resp = KMArray.instance((short) 1); - KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.add(resp, (short) 0, KMInteger.uint_16(KMError.OK)); sendOutgoing(apdu, resp); } @@ -2227,56 +2059,39 @@ 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); + KMArray.add(cmd, (short) 0, KMEnum.instance(KMType.PURPOSE)); + KMArray.add(cmd, (short) 1, KMByteBlob.exp()); + KMArray.add(cmd, (short) 2, params); short authToken = KMHardwareAuthToken.exp(); - KMArray.cast(cmd).add((short) 3, authToken); + KMArray.add(cmd, (short) 3, authToken); return receiveIncoming(apdu, cmd); } - - void assertPrivateOperation(short purpose, short algorithm) { - switch(algorithm) { - case KMType.RSA: - if (purpose == KMType.ENCRYPT || purpose == KMType.VERIFY) { - KMException.throwIt(KMError.PUBLIC_KEY_OPERATION); - } - break; - case KMType.EC: - if (purpose == KMType.VERIFY) { - KMException.throwIt(KMError.PUBLIC_KEY_OPERATION); - } - break; - default: - break; - } - } private void processBeginOperationCmd(APDU apdu) { // Receive the incoming request fully from the master 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(); + short purpose = KMArray.get(cmd, (short) 0); + data[KEY_BLOB] = KMArray.get(cmd, (short) 1); + data[KEY_PARAMETERS] = KMArray.get(cmd, (short) 2); + data[HW_TOKEN] = KMArray.get(cmd, (short) 3); + purpose = KMEnum.getVal(purpose); // Check for app id and app data. data[APP_ID] = - KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_ID, data[KEY_PARAMETERS]); + KMKeyParameters.findTag(data[KEY_PARAMETERS], KMType.BYTES_TAG, KMType.APPLICATION_ID); data[APP_DATA] = - KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_DATA, data[KEY_PARAMETERS]); + KMKeyParameters.findTag(data[KEY_PARAMETERS], KMType.BYTES_TAG, KMType.APPLICATION_DATA); if (data[APP_ID] != KMTag.INVALID_VALUE) { - data[APP_ID] = KMByteTag.cast(data[APP_ID]).getValue(); + data[APP_ID] = KMByteTag.getValue(data[APP_ID]); } if (data[APP_DATA] != KMTag.INVALID_VALUE) { - data[APP_DATA] = KMByteTag.cast(data[APP_DATA]).getValue(); + data[APP_DATA] = KMByteTag.getValue(data[APP_DATA]); } // Parse the encrypted blob and decrypt it. parseEncryptedKeyBlob(data[KEY_BLOB], data[APP_ID], data[APP_DATA], scratchPad); KMTag.assertPresence(data[SB_PARAMETERS],KMType.ENUM_TAG,KMType.ALGORITHM,KMError.UNSUPPORTED_ALGORITHM); short algorithm = KMEnumTag.getValue(KMType.ALGORITHM,data[SB_PARAMETERS]); - + //TODO should be removed for keymint // 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)){ @@ -2288,21 +2103,21 @@ private void processBeginOperationCmd(APDU apdu) { // 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()); + KMByteBlob.getBuffer(buf), + KMByteBlob.getStartOff(buf), + KMByteBlob.length(buf)); /* 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()); + KMByteBlob.getBuffer(buf), + KMByteBlob.getStartOff(buf), + KMByteBlob.length(buf)); 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()); + op.setKeySize(KMByteBlob.length(data[SECRET])); authorizeAndBeginOperation(op, scratchPad); switch (op.getPurpose()) { case KMType.SIGN: @@ -2315,11 +2130,7 @@ private void processBeginOperationCmd(APDU apdu) { beginCipherOperation(op); break; case KMType.AGREE_KEY: - if(specification.isKeyAgreementSupported()) { - beginKeyAgreementOperation(op); - } else { - KMException.throwIt(KMError.UNIMPLEMENTED); - } + beginKeyAgreementOperation(op); break; default: KMException.throwIt(KMError.UNIMPLEMENTED); @@ -2338,33 +2149,28 @@ private void processBeginOperationCmd(APDU apdu) { // 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(), + KMByteBlob.getBuffer(data[IV]), + KMByteBlob.getStartOff(data[IV]), + KMByteBlob.getBuffer(ivBlob), + KMByteBlob.getStartOff(ivBlob), (short) 8); data[IV] = ivBlob; } - KMArray.cast(iv).add((short) 0, KMByteTag.instance(KMType.NONCE, data[IV])); + KMArray.add(iv, (short) 0, KMByteTag.instance(KMType.NONCE, data[IV])); } else { iv = KMArray.instance((short) 0); } short params = KMKeyParameters.instance(iv); - short resp = - specification.prepareBeginResp(params, data[OP_HANDLE], KMInteger.uint_8(op.getBufferingMode()), - KMInteger.uint_16((short) (op.getMacLength() / 8))); + short resp = KMArray.instance((short) 5); + KMArray.add(resp, (short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.add(resp, (short) 1, params); + KMArray.add(resp, (short) 2, data[OP_HANDLE]); + KMArray.add(resp, (short) 3, KMInteger.uint_8(op.getBufferingMode())); + KMArray.add(resp, (short) 4, KMInteger.uint_16((short) (op.getMacLength() / 8))); 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: @@ -2399,16 +2205,16 @@ private void authorizePurpose(KMOperationState op) { private void authorizeDigest(KMOperationState op) { short digests = - KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.DIGEST, data[HW_PARAMETERS]); + KMKeyParameters.findTag(data[HW_PARAMETERS], KMType.ENUM_ARRAY_TAG, KMType.DIGEST); op.setDigest(KMType.DIGEST_NONE); short param = - KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.DIGEST, data[KEY_PARAMETERS]); + KMKeyParameters.findTag(data[KEY_PARAMETERS], KMType.ENUM_ARRAY_TAG, KMType.DIGEST); if (param != KMType.INVALID_VALUE) { - if (KMEnumArrayTag.cast(param).length() != 1) { + if (KMEnumArrayTag.length(param) != 1) { KMException.throwIt(KMError.UNSUPPORTED_DIGEST); } - param = KMEnumArrayTag.cast(param).get((short) 0); - if (!KMEnumArrayTag.cast(digests).contains(param)) { + param = KMEnumArrayTag.get(param, (short) 0); + if (!KMEnumArrayTag.contains(digests, param)) { KMException.throwIt(KMError.INCOMPATIBLE_DIGEST); } op.setDigest((byte) param); @@ -2416,13 +2222,13 @@ private void authorizeDigest(KMOperationState op) { KMException.throwIt(KMError.UNSUPPORTED_DIGEST); } short paramPadding = - KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PADDING, data[KEY_PARAMETERS]); + KMKeyParameters.findTag(data[KEY_PARAMETERS], KMType.ENUM_ARRAY_TAG, KMType.PADDING); if (paramPadding != KMType.INVALID_VALUE) { - if (KMEnumArrayTag.cast(paramPadding).length() != 1) { + if (KMEnumArrayTag.length(paramPadding) != 1) { //TODO vts fails because it expects UNSUPPORTED_PADDING_MODE KMException.throwIt(KMError.UNSUPPORTED_PADDING_MODE); } - paramPadding = KMEnumArrayTag.cast(paramPadding).get((short) 0); + paramPadding = KMEnumArrayTag.get(paramPadding, (short) 0); } switch (op.getAlgorithm()) { case KMType.RSA: @@ -2444,16 +2250,16 @@ private void authorizeDigest(KMOperationState op) { private void authorizePadding(KMOperationState op) { short paddings = - KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PADDING, data[HW_PARAMETERS]); + KMKeyParameters.findTag(data[HW_PARAMETERS], KMType.ENUM_ARRAY_TAG, KMType.PADDING); op.setPadding(KMType.PADDING_NONE); short param = - KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PADDING, data[KEY_PARAMETERS]); + KMKeyParameters.findTag(data[KEY_PARAMETERS], KMType.ENUM_ARRAY_TAG, KMType.PADDING); if (param != KMType.INVALID_VALUE) { - if (KMEnumArrayTag.cast(param).length() != 1) { + if (KMEnumArrayTag.length(param) != 1) { KMException.throwIt(KMError.INVALID_ARGUMENT); } - param = KMEnumArrayTag.cast(param).get((short) 0); - if (!KMEnumArrayTag.cast(paddings).contains(param)) { + param = KMEnumArrayTag.get(param, (short) 0); + if (!KMEnumArrayTag.contains(paddings, param)) { KMException.throwIt(KMError.INCOMPATIBLE_PADDING_MODE); } } @@ -2484,7 +2290,7 @@ private void authorizePadding(KMOperationState op) { } if(param == KMType.RSA_OAEP) { op.setMgfDigest( - (byte) specification.getMgf1Digest(data[KEY_PARAMETERS], data[HW_PARAMETERS])); + (byte) getMgf1Digest(data[KEY_PARAMETERS], data[HW_PARAMETERS])); } op.setPadding((byte) param); break; @@ -2502,12 +2308,12 @@ private void authorizePadding(KMOperationState op) { private void authorizeBlockModeAndMacLength(KMOperationState op) { short param = - KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.BLOCK_MODE, data[KEY_PARAMETERS]); + KMKeyParameters.findTag(data[KEY_PARAMETERS], KMType.ENUM_ARRAY_TAG, KMType.BLOCK_MODE); if (param != KMType.INVALID_VALUE) { - if (KMEnumArrayTag.cast(param).length() != 1) { + if (KMEnumArrayTag.length(param) != 1) { KMException.throwIt(KMError.UNSUPPORTED_BLOCK_MODE); } - param = KMEnumArrayTag.cast(param).get((short) 0); + param = KMEnumArrayTag.get(param, (short) 0); } if (KMType.AES == op.getAlgorithm() || KMType.DES == op.getAlgorithm()) { if (!KMEnumArrayTag.contains(KMType.BLOCK_MODE, param, data[HW_PARAMETERS])) { @@ -2599,7 +2405,6 @@ private void authorizeAndBeginOperation(KMOperationState op, byte[] scratchPad) authorizeDigest(op); authorizePadding(op); authorizeBlockModeAndMacLength(op); - assertPrivateOperation(op.getPurpose(), op.getAlgorithm()); authorizeUserSecureIdAuthTimeout(op, scratchPad); authorizeDeviceUnlock(scratchPad); authorizeKeyUsageForCount(scratchPad); @@ -2626,26 +2431,26 @@ private void authorizeAndBeginOperation(KMOperationState op, byte[] scratchPad) KMException.throwIt(KMError.CALLER_NONCE_PROHIBITED); } - short nonce = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.NONCE, data[KEY_PARAMETERS]); + short nonce = KMKeyParameters.findTag(data[KEY_PARAMETERS], KMType.BYTES_TAG, KMType.NONCE); // If Nonce is present then check whether the size of nonce is correct. if (nonce != KMType.INVALID_VALUE) { - data[IV] = KMByteTag.cast(nonce).getValue(); + data[IV] = KMByteTag.getValue(nonce); // For CBC mode - iv must be 8 bytes if (op.getBlockMode() == KMType.CBC && op.getAlgorithm() == KMType.DES - && KMByteBlob.cast(data[IV]).length() != 8) { + && KMByteBlob.length(data[IV]) != 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) { + if (KMByteBlob.length(data[IV]) != 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) { + && KMByteBlob.length(data[IV]) != 16) { KMException.throwIt(KMError.INVALID_NONCE); } } else if (op.getAlgorithm() == KMType.AES || op.getAlgorithm() == KMType.DES) { @@ -2670,30 +2475,15 @@ private void authorizeAndBeginOperation(KMOperationState op, byte[] scratchPad) } data[IV] = KMByteBlob.instance(ivLen); seProvider.newRandomNumber( - KMByteBlob.cast(data[IV]).getBuffer(), - KMByteBlob.cast(data[IV]).getStartOff(), - KMByteBlob.cast(data[IV]).length()); + KMByteBlob.getBuffer(data[IV]), + KMByteBlob.getStartOff(data[IV]), + KMByteBlob.length(data[IV])); } } } - 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)); + public void beginKeyAgreementOperation(KMOperationState op) { + KMException.throwIt(KMError.UNIMPLEMENTED); } private void beginCipherOperation(KMOperationState op) { @@ -2708,12 +2498,12 @@ private void beginCipherOperation(KMOperationState op) { (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())); + KMByteBlob.getBuffer(data[SECRET]), + KMByteBlob.getStartOff(data[SECRET]), + KMByteBlob.length(data[SECRET]), + KMByteBlob.getBuffer(data[PUB_KEY]), + KMByteBlob.getStartOff(data[PUB_KEY]), + KMByteBlob.length(data[PUB_KEY]))); } else { KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); } @@ -2734,12 +2524,12 @@ private void beginCipherOperation(KMOperationState op) { (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(), + KMByteBlob.getBuffer(data[SECRET]), + KMByteBlob.getStartOff(data[SECRET]), + KMByteBlob.length(data[SECRET]), + KMByteBlob.getBuffer(data[IV]), + KMByteBlob.getStartOff(data[IV]), + KMByteBlob.length(data[IV]), op.getMacLength())); } catch (CryptoException exception) { if (exception.getReason() == CryptoException.ILLEGAL_VALUE) { @@ -2753,8 +2543,7 @@ private void beginCipherOperation(KMOperationState op) { 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) { + if (KMKeyParameters.findTag(data[HW_PARAMETERS], KMType.BOOL_TAG, KMType.TRUSTED_CONFIRMATION_REQUIRED) != KMType.INVALID_VALUE) { op.setTrustedConfirmationSigner( seProvider.initTrustedConfirmationSymmetricOperation(seProvider.getComputedHmacKey())); @@ -2779,12 +2568,12 @@ private void beginSignVerifyOperation(KMOperationState op) { (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())); + KMByteBlob.getBuffer(data[SECRET]), + KMByteBlob.getStartOff(data[SECRET]), + KMByteBlob.length(data[SECRET]), + KMByteBlob.getBuffer(data[PUB_KEY]), + KMByteBlob.getStartOff(data[PUB_KEY]), + KMByteBlob.length(data[PUB_KEY]))); } else { KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); } @@ -2802,9 +2591,9 @@ private void beginSignVerifyOperation(KMOperationState op) { (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.getBuffer(data[SECRET]), + KMByteBlob.getStartOff(data[SECRET]), + KMByteBlob.length(data[SECRET]), null, (short) 0, (short) 0)); @@ -2836,9 +2625,9 @@ private void beginSignVerifyOperation(KMOperationState op) { (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.getBuffer(data[SECRET]), + KMByteBlob.getStartOff(data[SECRET]), + KMByteBlob.length(data[SECRET]), null, (short) 0, (short) 0, @@ -2855,15 +2644,15 @@ private void beginSignVerifyOperation(KMOperationState op) { private boolean isHwAuthTokenContainsMatchingSecureId(short hwAuthToken, short secureUserIdsObj) { - short secureUserId = KMHardwareAuthToken.cast(hwAuthToken).getUserId(); - if (!KMInteger.cast(secureUserId).isZero()) { - if (KMIntegerArrayTag.cast(secureUserIdsObj).contains(secureUserId)) + short secureUserId = KMHardwareAuthToken.getUserId(hwAuthToken); + if (!KMInteger.isZero(secureUserId)) { + if (KMIntegerArrayTag.contains(secureUserIdsObj, secureUserId)) return true; } - short authenticatorId = KMHardwareAuthToken.cast(hwAuthToken).getAuthenticatorId(); - if (!KMInteger.cast(authenticatorId).isZero()) { - if (KMIntegerArrayTag.cast(secureUserIdsObj).contains(authenticatorId)) + short authenticatorId = KMHardwareAuthToken.getAuthenticatorId(hwAuthToken); + if (!KMInteger.isZero(authenticatorId)) { + if (KMIntegerArrayTag.contains(secureUserIdsObj, authenticatorId)) return true; } return false; @@ -2878,8 +2667,8 @@ private boolean authTokenMatches(short userSecureIdsPtr, short authType, return false; } // check auth type - tmpVariables[2] = KMHardwareAuthToken.cast(data[HW_TOKEN]).getHwAuthenticatorType(); - tmpVariables[2] = KMEnum.cast(tmpVariables[2]).getVal(); + tmpVariables[2] = KMHardwareAuthToken.getHwAuthenticatorType(data[HW_TOKEN]); + tmpVariables[2] = KMEnum.getVal(tmpVariables[2]); if (((byte) tmpVariables[2] & (byte) authType) == 0) { return false; } @@ -2891,11 +2680,11 @@ private void authorizeUserSecureIdAuthTimeout(KMOperationState op, byte[] scratc short authType; // Authorize User Secure Id and Auth timeout short userSecureIdPtr = - KMKeyParameters.findTag(KMType.ULONG_ARRAY_TAG, KMType.USER_SECURE_ID, data[HW_PARAMETERS]); + KMKeyParameters.findTag(data[HW_PARAMETERS], KMType.ULONG_ARRAY_TAG, KMType.USER_SECURE_ID); if (userSecureIdPtr != KMType.INVALID_VALUE) { // Authentication required. if (KMType.INVALID_VALUE != - KMKeyParameters.findTag(KMType.BOOL_TAG, KMType.NO_AUTH_REQUIRED, data[HW_PARAMETERS])) { + KMKeyParameters.findTag(data[HW_PARAMETERS], KMType.BOOL_TAG, KMType.NO_AUTH_REQUIRED)) { // Key has both USER_SECURE_ID and NO_AUTH_REQUIRED KMException.throwIt(KMError.INVALID_KEY_BLOB); } @@ -2907,7 +2696,7 @@ private void authorizeUserSecureIdAuthTimeout(KMOperationState op, byte[] scratc } short authTimeoutTagPtr = - KMKeyParameters.findTag(KMType.UINT_TAG, KMType.AUTH_TIMEOUT, data[HW_PARAMETERS]); + KMKeyParameters.findTag(data[HW_PARAMETERS], KMType.UINT_TAG, KMType.AUTH_TIMEOUT); if (authTimeoutTagPtr != KMType.INVALID_VALUE) { // authenticate user if (!authTokenMatches(userSecureIdPtr, authType, scratchPad)) { @@ -2919,15 +2708,15 @@ private void authorizeUserSecureIdAuthTimeout(KMOperationState op, byte[] scratc if (authTimeoutTagPtr == KMType.INVALID_VALUE) { KMException.throwIt(KMError.INVALID_KEY_BLOB); } - authTime = KMIntegerTag.cast(authTimeoutTagPtr).getValue(); + authTime = KMIntegerTag.getValue(authTimeoutTagPtr); // 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); + KMHardwareAuthToken.getTimestamp(data[HW_TOKEN]), scratchPad); op.setAuthTime( - KMInteger.cast(authTime).getBuffer(), KMInteger.cast(authTime).getStartOff()); + KMInteger.getBuffer(authTime), KMInteger.getStartOff(authTime)); // auth time validation will happen in update or finish op.setAuthTimeoutValidated(false); } else { @@ -2950,40 +2739,36 @@ private boolean verifyHwTokenMacInBigEndian(short hwToken, byte[] scratchPad) { 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) (8 - KMInteger.cast(ptr).length()))); + short ptr = KMHardwareAuthToken.getChallenge(hwToken); + KMInteger.value(ptr, scratchPad, (short) (len + (short) (8 - KMInteger.length(ptr)))); len += 8; // concatenate user id - 8 bytes - ptr = KMHardwareAuthToken.cast(hwToken).getUserId(); - KMInteger.cast(ptr) - .value(scratchPad, (short) (len + (short) (8 - KMInteger.cast(ptr).length()))); + ptr = KMHardwareAuthToken.getUserId(hwToken); + KMInteger.value(ptr, scratchPad, (short) (len + (short) (8 - KMInteger.length(ptr)))); len += 8; // concatenate authenticator id - 8 bytes - ptr = KMHardwareAuthToken.cast(hwToken).getAuthenticatorId(); - KMInteger.cast(ptr) - .value(scratchPad, (short) (len + (short) (8 - KMInteger.cast(ptr).length()))); + ptr = KMHardwareAuthToken.getAuthenticatorId(hwToken); + KMInteger.value(ptr, scratchPad, (short) (len + (short) (8 - KMInteger.length(ptr)))); len += 8; // concatenate authenticator type - 4 bytes - ptr = KMHardwareAuthToken.cast(hwToken).getHwAuthenticatorType(); - scratchPad[(short) (len + 3)] = KMEnum.cast(ptr).getVal(); + ptr = KMHardwareAuthToken.getHwAuthenticatorType(hwToken); + scratchPad[(short) (len + 3)] = KMEnum.getVal(ptr); len += 4; // concatenate timestamp -8 bytes - ptr = KMHardwareAuthToken.cast(hwToken).getTimestamp(); - KMInteger.cast(ptr) - .value(scratchPad, (short) (len + (short) (8 - KMInteger.cast(ptr).length()))); + ptr = KMHardwareAuthToken.getTimestamp(hwToken); + KMInteger.value(ptr, scratchPad, (short) (len + (short) (8 - KMInteger.length(ptr)))); len += 8; - ptr = KMHardwareAuthToken.cast(hwToken).getMac(); + ptr = KMHardwareAuthToken.getMac(hwToken); return seProvider.hmacVerify( seProvider.getComputedHmacKey(), scratchPad, (short) 0, len, - KMByteBlob.cast(ptr).getBuffer(), - KMByteBlob.cast(ptr).getStartOff(), - KMByteBlob.cast(ptr).length()); + KMByteBlob.getBuffer(ptr), + KMByteBlob.getStartOff(ptr), + KMByteBlob.length(ptr)); } private boolean verifyHwTokenMacInLittleEndian(short hwToken, byte[] scratchPad) { @@ -2994,44 +2779,43 @@ private boolean verifyHwTokenMacInLittleEndian(short hwToken, byte[] scratchPad) 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); + short ptr = KMHardwareAuthToken.getChallenge(hwToken); + KMInteger.toLittleEndian(ptr, scratchPad, len); len += 8; // concatenate user id - 8 bytes - ptr = KMHardwareAuthToken.cast(hwToken).getUserId(); - KMInteger.cast(ptr).toLittleEndian(scratchPad, len); + ptr = KMHardwareAuthToken.getUserId(hwToken); + KMInteger.toLittleEndian(ptr, scratchPad, len); len += 8; // concatenate authenticator id - 8 bytes - ptr = KMHardwareAuthToken.cast(hwToken).getAuthenticatorId(); - KMInteger.cast(ptr).toLittleEndian(scratchPad, len); + ptr = KMHardwareAuthToken.getAuthenticatorId(hwToken); + KMInteger.toLittleEndian(ptr, scratchPad, len); len += 8; // concatenate authenticator type - 4 bytes - ptr = KMHardwareAuthToken.cast(hwToken).getHwAuthenticatorType(); - scratchPad[(short) (len + 3)] = KMEnum.cast(ptr).getVal(); + ptr = KMHardwareAuthToken.getHwAuthenticatorType(hwToken); + scratchPad[(short) (len + 3)] = KMEnum.getVal(ptr); len += 4; // concatenate timestamp - 8 bytes - ptr = KMHardwareAuthToken.cast(hwToken).getTimestamp(); - KMInteger.cast(ptr) - .value(scratchPad, (short) (len + (short) (8 - KMInteger.cast(ptr).length()))); + ptr = KMHardwareAuthToken.getTimestamp(hwToken); + KMInteger.value(ptr, scratchPad, (short) (len + (short) (8 - KMInteger.length(ptr)))); len += 8; - ptr = KMHardwareAuthToken.cast(hwToken).getMac(); + ptr = KMHardwareAuthToken.getMac(hwToken); return seProvider.hmacVerify( seProvider.getComputedHmacKey(), scratchPad, (short) 0, len, - KMByteBlob.cast(ptr).getBuffer(), - KMByteBlob.cast(ptr).getStartOff(), - KMByteBlob.cast(ptr).length()); + KMByteBlob.getBuffer(ptr), + KMByteBlob.getStartOff(ptr), + KMByteBlob.length(ptr)); } private boolean validateHwToken(short hwToken, byte[] scratchPad) { // CBOR Encoding is always big endian - short ptr = KMHardwareAuthToken.cast(hwToken).getMac(); + short ptr = KMHardwareAuthToken.getMac(hwToken); // If mac length is zero then token is empty. - if (KMByteBlob.cast(ptr).length() == 0) { + if (KMByteBlob.length(ptr) == 0) { return false; } if (KMConfigurations.TEE_MACHINE_TYPE == KMConfigurations.LITTLE_ENDIAN) { @@ -3042,12 +2826,15 @@ private boolean validateHwToken(short hwToken, byte[] scratchPad) { } private short importKeyCmd(APDU apdu){ - short cmd = KMArray.instance((short) 3); + 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.add(cmd, (short) 0, params); + KMArray.add(cmd, (short) 1, KMEnum.instance(KMType.KEY_FORMAT)); + KMArray.add(cmd, (short) 2, KMByteBlob.exp()); + KMArray.add(cmd, (short) 3, KMByteBlob.exp()); //attest key + KMArray.add(cmd, (short) 4, params); //attest key params + KMArray.add(cmd, (short) 5, KMByteBlob.exp()); //issuer return receiveIncoming(apdu, cmd); } @@ -3055,10 +2842,13 @@ private void processImportKeyCmd(APDU apdu) { // Receive the incoming request fully from the master 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); - keyFmt = KMEnum.cast(keyFmt).getVal(); + data[KEY_PARAMETERS] = KMArray.get(cmd, (short) 0); + short keyFmt = KMArray.get(cmd, (short) 1); + data[IMPORTED_KEY_BLOB] = KMArray.get(cmd, (short) 2); + data[ATTEST_KEY_BLOB] = KMArray.get(cmd, (short) 3); + data[ATTEST_KEY_PARAMS] = KMArray.get(cmd, (short) 4); + data[ATTEST_KEY_ISSUER] = KMArray.get(cmd, (short) 5); + keyFmt = KMEnum.getVal(keyFmt); data[CERTIFICATE] = KMArray.instance((short)0); //by default the cert is empty. data[ORIGIN] = KMType.IMPORTED; @@ -3070,19 +2860,18 @@ private short importWrappedKeyCmd(APDU apdu){ // Arguments short params = KMKeyParameters.exp(); short bBlob = KMByteBlob.exp(); - 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, bBlob); // Wrapped Import Key Blob - KMArray.cast(cmd).add((short) 3, bBlob); // Auth Tag - KMArray.cast(cmd).add((short) 4, bBlob); // IV - Nonce - KMArray.cast(cmd).add((short) 5, bBlob); // Encrypted Transport Key - KMArray.cast(cmd).add((short) 6, bBlob); // Wrapping Key KeyBlob - KMArray.cast(cmd).add((short) 7, bBlob); // Masking Key - KMArray.cast(cmd).add((short) 8, params); // Un-wrapping Params - KMArray.cast(cmd) - .add((short) 9, bBlob); // Wrapped Key ASSOCIATED AUTH DATA - KMArray.cast(cmd).add((short) 10, KMInteger.exp()); // Password Sid - KMArray.cast(cmd).add((short) 11, KMInteger.exp()); // Biometric Sid + KMArray.add(cmd, (short) 0, params); // Key Params of wrapped key + KMArray.add(cmd, (short) 1, KMEnum.instance(KMType.KEY_FORMAT)); // Key Format + KMArray.add(cmd, (short) 2, bBlob); // Wrapped Import Key Blob + KMArray.add(cmd, (short) 3, bBlob); // Auth Tag + KMArray.add(cmd, (short) 4, bBlob); // IV - Nonce + KMArray.add(cmd, (short) 5, bBlob); // Encrypted Transport Key + KMArray.add(cmd, (short) 6, bBlob); // Wrapping Key KeyBlob + KMArray.add(cmd, (short) 7, bBlob); // Masking Key + KMArray.add(cmd, (short) 8, params); // Un-wrapping Params + KMArray.add(cmd, (short) 9, bBlob); // Wrapped Key ASSOCIATED AUTH DATA + KMArray.add(cmd, (short) 10, KMInteger.exp()); // Password Sid + KMArray.add(cmd, (short) 11, KMInteger.exp()); // Biometric Sid return receiveIncoming(apdu, cmd); } @@ -3093,11 +2882,11 @@ private void processImportWrappedKeyCmd(APDU apdu) { // Step -0 - check whether the key format and algorithm supported // read algorithm - tmpVariables[0] = KMArray.cast(cmd).get((short) 0); + tmpVariables[0] = KMArray.get(cmd, (short) 0); tmpVariables[1] = KMEnumTag.getValue(KMType.ALGORITHM, tmpVariables[0]); // read key format - tmpVariables[2] = KMArray.cast(cmd).get((short) 1); - byte keyFormat = KMEnum.cast(tmpVariables[2]).getVal(); + tmpVariables[2] = KMArray.get(cmd, (short) 1); + byte keyFormat = KMEnum.getVal(tmpVariables[2]); if ((tmpVariables[1] == KMType.RSA || tmpVariables[1] == KMType.EC) && (keyFormat != KMType.PKCS8)) { KMException.throwIt(KMError.UNIMPLEMENTED); @@ -3105,19 +2894,19 @@ private void processImportWrappedKeyCmd(APDU apdu) { // Step -1 parse the wrapping key blob // read wrapping key blob - data[KEY_BLOB] = KMArray.cast(cmd).get((short) 6); + data[KEY_BLOB] = KMArray.get(cmd, (short) 6); // read un wrapping key params - data[KEY_PARAMETERS] = KMArray.cast(cmd).get((short) 8); + data[KEY_PARAMETERS] = KMArray.get(cmd, (short) 8); // Read App Id and App Data if any from un wrapping key params data[APP_ID] = - KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_ID, data[KEY_PARAMETERS]); + KMKeyParameters.findTag(data[KEY_PARAMETERS], KMType.BYTES_TAG, KMType.APPLICATION_ID); data[APP_DATA] = - KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_DATA, data[KEY_PARAMETERS]); + KMKeyParameters.findTag(data[KEY_PARAMETERS], KMType.BYTES_TAG, KMType.APPLICATION_DATA); if (data[APP_ID] != KMTag.INVALID_VALUE) { - data[APP_ID] = KMByteTag.cast(data[APP_ID]).getValue(); + data[APP_ID] = KMByteTag.getValue(data[APP_ID]); } if (data[APP_DATA] != KMTag.INVALID_VALUE) { - data[APP_DATA] = KMByteTag.cast(data[APP_DATA]).getValue(); + data[APP_DATA] = KMByteTag.getValue(data[APP_DATA]); } // parse the wrapping key blob parseEncryptedKeyBlob(data[KEY_BLOB], data[APP_ID], data[APP_DATA], scratchPad); @@ -3139,77 +2928,76 @@ private void processImportWrappedKeyCmd(APDU apdu) { // Step 2 - decrypt the encrypted transport key - 32 bytes AES-GCM key // create rsa decipher // read encrypted transport key from args - tmpVariables[0] = KMArray.cast(cmd).get((short) 5); + tmpVariables[0] = KMArray.get(cmd, (short) 5); // Decrypt the transport key tmpVariables[1] = seProvider.rsaDecipherOAEP256( - 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(), - KMByteBlob.cast(tmpVariables[0]).getBuffer(), - KMByteBlob.cast(tmpVariables[0]).getStartOff(), - KMByteBlob.cast(tmpVariables[0]).length(), + KMByteBlob.getBuffer(data[SECRET]), + KMByteBlob.getStartOff(data[SECRET]), + KMByteBlob.length(data[SECRET]), + KMByteBlob.getBuffer(data[PUB_KEY]), + KMByteBlob.getStartOff(data[PUB_KEY]), + KMByteBlob.length(data[PUB_KEY]), + KMByteBlob.getBuffer(tmpVariables[0]), + KMByteBlob.getStartOff(tmpVariables[0]), + KMByteBlob.length(tmpVariables[0]), scratchPad, (short) 0); data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, tmpVariables[1]); // Step 3 - XOR the decrypted AES-GCM key with with masking key // read masking key - tmpVariables[0] = KMArray.cast(cmd).get((short) 7); - tmpVariables[1] = KMByteBlob.cast(tmpVariables[0]).length(); + tmpVariables[0] = KMArray.get(cmd, (short) 7); + tmpVariables[1] = KMByteBlob.length(tmpVariables[0]); // Length of masking key and transport key must be same. - if (tmpVariables[1] != KMByteBlob.cast(data[SECRET]).length()) { + if (tmpVariables[1] != KMByteBlob.length(data[SECRET])) { KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); } tmpVariables[2] = 0; // index // Xor every byte of masking and key and store the result in data[SECRET] while (tmpVariables[2] < tmpVariables[1]) { tmpVariables[3] = - (short) (((short) KMByteBlob.cast(tmpVariables[0]).get(tmpVariables[2])) & 0x00FF); + (short) (((short) KMByteBlob.get(tmpVariables[0], tmpVariables[2])) & 0x00FF); tmpVariables[4] = - (short) (((short) KMByteBlob.cast(data[SECRET]).get(tmpVariables[2])) & 0x00FF); - KMByteBlob.cast(data[SECRET]) - .add(tmpVariables[2], (byte) (tmpVariables[3] ^ tmpVariables[4])); + (short) (((short) KMByteBlob.get(data[SECRET], tmpVariables[2])) & 0x00FF); + KMByteBlob.add(data[SECRET], tmpVariables[2], (byte) (tmpVariables[3] ^ tmpVariables[4])); tmpVariables[2]++; } // Step 4 - AES-GCM decrypt the wrapped key - data[INPUT_DATA] = KMArray.cast(cmd).get((short) 2); - data[AUTH_DATA] = KMArray.cast(cmd).get((short) 9); - data[AUTH_TAG] = KMArray.cast(cmd).get((short) 3); - data[NONCE] = KMArray.cast(cmd).get((short) 4); + data[INPUT_DATA] = KMArray.get(cmd, (short) 2); + data[AUTH_DATA] = KMArray.get(cmd, (short) 9); + data[AUTH_TAG] = KMArray.get(cmd, (short) 3); + data[NONCE] = KMArray.get(cmd, (short) 4); Util.arrayFillNonAtomic( - scratchPad, (short) 0, KMByteBlob.cast(data[INPUT_DATA]).length(), (byte) 0); + scratchPad, (short) 0, KMByteBlob.length(data[INPUT_DATA]), (byte) 0); if (!seProvider.aesGCMDecrypt( - KMByteBlob.cast(data[SECRET]).getBuffer(), - KMByteBlob.cast(data[SECRET]).getStartOff(), - KMByteBlob.cast(data[SECRET]).length(), - KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), - KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), - KMByteBlob.cast(data[INPUT_DATA]).length(), + KMByteBlob.getBuffer(data[SECRET]), + KMByteBlob.getStartOff(data[SECRET]), + KMByteBlob.length(data[SECRET]), + KMByteBlob.getBuffer(data[INPUT_DATA]), + KMByteBlob.getStartOff(data[INPUT_DATA]), + KMByteBlob.length(data[INPUT_DATA]), scratchPad, (short) 0, - KMByteBlob.cast(data[NONCE]).getBuffer(), - KMByteBlob.cast(data[NONCE]).getStartOff(), - KMByteBlob.cast(data[NONCE]).length(), - KMByteBlob.cast(data[AUTH_DATA]).getBuffer(), - KMByteBlob.cast(data[AUTH_DATA]).getStartOff(), - KMByteBlob.cast(data[AUTH_DATA]).length(), - KMByteBlob.cast(data[AUTH_TAG]).getBuffer(), - KMByteBlob.cast(data[AUTH_TAG]).getStartOff(), - KMByteBlob.cast(data[AUTH_TAG]).length())) { + KMByteBlob.getBuffer(data[NONCE]), + KMByteBlob.getStartOff(data[NONCE]), + KMByteBlob.length(data[NONCE]), + KMByteBlob.getBuffer(data[AUTH_DATA]), + KMByteBlob.getStartOff(data[AUTH_DATA]), + KMByteBlob.length(data[AUTH_DATA]), + KMByteBlob.getBuffer(data[AUTH_TAG]), + KMByteBlob.getStartOff(data[AUTH_TAG]), + KMByteBlob.length(data[AUTH_TAG]))) { KMException.throwIt(KMError.VERIFICATION_FAILED); } // Step 5 - Import decrypted key data[ORIGIN] = KMType.SECURELY_IMPORTED; - data[KEY_PARAMETERS] = KMArray.cast(cmd).get((short) 0); + data[KEY_PARAMETERS] = KMArray.get(cmd, (short) 0); // create key blob array - data[IMPORTED_KEY_BLOB] = KMByteBlob.instance(scratchPad, (short) 0, KMByteBlob.cast(data[INPUT_DATA]).length()); + data[IMPORTED_KEY_BLOB] = KMByteBlob.instance(scratchPad, (short) 0, KMByteBlob.length(data[INPUT_DATA])); importKey(apdu, keyFormat, scratchPad); } @@ -3260,34 +3048,32 @@ private void importKey(APDU apdu, short keyFmt, byte[] scratchPad) { createEncryptedKeyBlob(scratchPad); // prepare the response short resp = KMArray.instance((short) 3); - KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); - KMArray.cast(resp).add((short) 1, data[KEY_BLOB]); - KMArray.cast(resp).add((short) 2, data[KEY_CHARACTERISTICS]); + KMArray.add(resp, (short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.add(resp, (short) 1, data[KEY_BLOB]); + KMArray.add(resp, (short) 2, data[KEY_CHARACTERISTICS]); sendOutgoing(apdu, resp); } - protected short decodeRawECKey(short rawBlob) { + public short decodeRawECKey(short rawBlob) { // Decode key material short arrPtr = KMArray.instance((short) 2); - KMArray.cast(arrPtr).add((short) 0, KMByteBlob.exp()); // secret - KMArray.cast(arrPtr).add((short) 1, KMByteBlob.exp()); // public key + KMArray.add(arrPtr, (short) 0, KMByteBlob.exp()); // secret + KMArray.add(arrPtr, (short) 1, KMByteBlob.exp()); // public key arrPtr = decoder.decode( arrPtr, - KMByteBlob.cast(rawBlob).getBuffer(), - KMByteBlob.cast(rawBlob).getStartOff(), - KMByteBlob.cast(rawBlob).length()); + KMByteBlob.getBuffer(rawBlob), + KMByteBlob.getStartOff(rawBlob), + KMByteBlob.length(rawBlob)); return arrPtr; - //data[SECRET] = KMArray.cast(arrPtr).get((short) 0); - //data[PUB_KEY] = KMArray.cast(arrPtr).get((short) 1); } private void importECKeys(byte[] scratchPad) { // Decode key material KMPKCS8Decoder pkcs8 = KMPKCS8Decoder.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); + data[PUB_KEY] = KMArray.get(keyBlob, (short) 0); + data[SECRET] = KMArray.get(keyBlob, (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. @@ -3296,8 +3082,8 @@ private void importECKeys(byte[] scratchPad) { if (keySize != KMType.INVALID_VALUE) { // As per NIST.SP.800-186 page 9, secret for 256 curve should be between // 256-383 - if (((256 <= (short) (KMByteBlob.cast(data[SECRET]).length() * 8)) - && (383 >= (short) (KMByteBlob.cast(data[SECRET]).length() * 8))) + if (((256 <= (short) (KMByteBlob.length(data[SECRET]) * 8)) + && (383 >= (short) (KMByteBlob.length(data[SECRET]) * 8))) ^ keySize == 256) { KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); } @@ -3305,6 +3091,10 @@ private void importECKeys(byte[] scratchPad) { KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); } } else { + if ((256 <= (short) (KMByteBlob.length(data[SECRET]) * 8)) + && (383 >= (short) (KMByteBlob.length(data[SECRET]) * 8))){ + 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); @@ -3316,8 +3106,8 @@ private void importECKeys(byte[] scratchPad) { 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 <= (short) (KMByteBlob.cast(data[SECRET]).length() * 8)) - && (383 >= (short) (KMByteBlob.cast(data[SECRET]).length() * 8))) + if (((256 <= (short) (KMByteBlob.length(data[SECRET]) * 8)) + && (383 >= (short) (KMByteBlob.length(data[SECRET]) * 8))) ^ curve == KMType.P_256) { KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); } @@ -3325,6 +3115,10 @@ private void importECKeys(byte[] scratchPad) { KMException.throwIt(KMError.UNSUPPORTED_EC_CURVE); } } else { + if ((256 <= (short) (KMByteBlob.length(data[SECRET]) * 8)) + && (383 >= (short) (KMByteBlob.length(data[SECRET]) * 8))){ + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } // add the curve to scratchPad curve = KMEnumTag.instance(KMType.ECCURVE, KMType.P_256); Util.setShort(scratchPad, index, curve); @@ -3334,19 +3128,17 @@ private void importECKeys(byte[] scratchPad) { // 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()); + KMByteBlob.getBuffer(data[SECRET]), + KMByteBlob.getStartOff(data[SECRET]), + KMByteBlob.length(data[SECRET]), + KMByteBlob.getBuffer(data[PUB_KEY]), + KMByteBlob.getStartOff(data[PUB_KEY]), + KMByteBlob.length(data[PUB_KEY])); // add scratch pad to key parameters updateKeyParameters(scratchPad, index); - // validate updated key parameters. - validateECKeys(); data[KEY_BLOB] = KMArray.instance((short) 5); - KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PUB_KEY, data[PUB_KEY]); + KMArray.add(data[KEY_BLOB], KEY_BLOB_PUB_KEY, data[PUB_KEY]); } private void importHmacKey(byte[] scratchPad) { @@ -3363,7 +3155,7 @@ private void importHmacKey(byte[] scratchPad) { } } else { // add the key size to scratchPad - keysize = KMInteger.uint_16((short) (KMByteBlob.cast(data[SECRET]).length() * 8)); + keysize = KMInteger.uint_16((short) (KMByteBlob.length(data[SECRET]) * 8)); short keySizeTag = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, keysize); Util.setShort(scratchPad, index, keySizeTag); index += 2; @@ -3372,9 +3164,9 @@ private void importHmacKey(byte[] scratchPad) { seProvider.importSymmetricKey( KMType.HMAC, keysize, - KMByteBlob.cast(data[SECRET]).getBuffer(), - KMByteBlob.cast(data[SECRET]).getStartOff(), - KMByteBlob.cast(data[SECRET]).length()); + KMByteBlob.getBuffer(data[SECRET]), + KMByteBlob.getStartOff(data[SECRET]), + KMByteBlob.length(data[SECRET])); // update the key parameters list updateKeyParameters(scratchPad, index); @@ -3405,9 +3197,9 @@ private void importTDESKey(byte[] scratchPad) { seProvider.importSymmetricKey( KMType.DES, keysize, - KMByteBlob.cast(data[SECRET]).getBuffer(), - KMByteBlob.cast(data[SECRET]).getStartOff(), - KMByteBlob.cast(data[SECRET]).length()); + KMByteBlob.getBuffer(data[SECRET]), + KMByteBlob.getStartOff(data[SECRET]), + KMByteBlob.length(data[SECRET])); // update the key parameters list updateKeyParameters(scratchPad, index); // validate TDES Key parameters @@ -3433,7 +3225,7 @@ private void importAESKey(byte[] scratchPad) { validateAesKeySize(keysize); } else { // add the key size to scratchPad - keysize = (short) ( 8 * KMByteBlob.cast(data[SECRET]).length()); + keysize = (short) ( 8 * KMByteBlob.length(data[SECRET])); validateAesKeySize(keysize); keysize = KMInteger.uint_16(keysize); short keysizeTag = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, keysize); @@ -3444,9 +3236,9 @@ private void importAESKey(byte[] scratchPad) { seProvider.importSymmetricKey( KMType.AES, keysize, - KMByteBlob.cast(data[SECRET]).getBuffer(), - KMByteBlob.cast(data[SECRET]).getStartOff(), - KMByteBlob.cast(data[SECRET]).length()); + KMByteBlob.getBuffer(data[SECRET]), + KMByteBlob.getStartOff(data[SECRET]), + KMByteBlob.length(data[SECRET])); // update the key parameters list updateKeyParameters(scratchPad, index); @@ -3459,14 +3251,14 @@ private void importRSAKey(byte[] scratchPad) { // Decode key material KMPKCS8Decoder pkcs8 = KMPKCS8Decoder.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()){ + data[PUB_KEY] = KMArray.get(keyblob, (short) 0); + short pubKeyExp = KMArray.get(keyblob, (short)1); + data[SECRET] = KMArray.get(keyblob, (short) 2); + if(F4.length != KMByteBlob.length(pubKeyExp)){ KMException.throwIt(KMError.INVALID_KEY_BLOB); } - if(Util.arrayCompare(F4, (short)0, KMByteBlob.cast(pubKeyExp).getBuffer(), - KMByteBlob.cast(pubKeyExp).getStartOff(), (short)F4.length) != 0){ + if(Util.arrayCompare(F4, (short)0, KMByteBlob.getBuffer(pubKeyExp), + KMByteBlob.getStartOff(pubKeyExp), (short)F4.length) != 0){ KMException.throwIt(KMError.INVALID_ARGUMENT); } short index = 0; // index in scratchPad for update parameters. @@ -3500,7 +3292,7 @@ private void importRSAKey(byte[] scratchPad) { KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); if (keysize != KMType.INVALID_VALUE) { if (keysize != 2048 - || keysize != (short) (KMByteBlob.cast(data[SECRET]).length() * 8)) { + || keysize != (short) (KMByteBlob.length(data[SECRET]) * 8)) { KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); } } else { @@ -3514,19 +3306,19 @@ private void importRSAKey(byte[] scratchPad) { // 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()); + KMByteBlob.getBuffer(data[SECRET]), + KMByteBlob.getStartOff(data[SECRET]), + KMByteBlob.length(data[SECRET]), + KMByteBlob.getBuffer(data[PUB_KEY]), + KMByteBlob.getStartOff(data[PUB_KEY]), + KMByteBlob.length(data[PUB_KEY])); // update the key parameters list updateKeyParameters(scratchPad, index); // validate RSA Key parameters validateRSAKey(scratchPad); data[KEY_BLOB] = KMArray.instance((short) 5); - KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PUB_KEY, data[PUB_KEY]); + KMArray.add(data[KEY_BLOB], KEY_BLOB_PUB_KEY, data[PUB_KEY]); } private void updateKeyParameters(byte[] newParams, short len) { @@ -3534,26 +3326,26 @@ private void updateKeyParameters(byte[] newParams, short len) { 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 params = KMKeyParameters.getVals(data[KEY_PARAMETERS]); + len = (short) (KMArray.length(params) + (short) (len / 2)); short updatedParams = KMArray.instance(len); // update params - len = KMArray.cast(params).length(); + len = KMArray.length(params); 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); + short tag = KMArray.get(params, index); + KMArray.add(updatedParams, index, tag); index++; } // copy new parameters to updated array - len = KMArray.cast(updatedParams).length(); + len = KMArray.length(updatedParams); short newParamIndex = 0; // index in ptrArr while (index < len) { short tag = Util.getShort(newParams, newParamIndex); - KMArray.cast(updatedParams).add(index, tag); + KMArray.add(updatedParams, index, tag); index++; newParamIndex += 2; } @@ -3563,9 +3355,9 @@ private void updateKeyParameters(byte[] newParams, short len) { 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 + KMArray.add(cmd, (short) 0, KMInteger.exp()); //OS version + KMArray.add(cmd, (short) 1, KMInteger.exp()); //OS patch level + KMArray.add(cmd, (short) 2, KMInteger.exp()); //Vendor patch level return receiveIncoming(apdu, cmd); } @@ -3576,11 +3368,10 @@ private short initStrongBoxCmd(APDU apdu){ // 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); + short osVersion = KMArray.get(cmd, (short) 0); + short osPatchLevel = KMArray.get(cmd, (short) 1); + short vendorPatchLevel = KMArray.get(cmd, (short) 2); setOsVersion(osVersion); setOsPatchLevel(osPatchLevel); setVendorPatchLevel(vendorPatchLevel); @@ -3612,51 +3403,46 @@ protected void initSystemBootParams(short osVersion, protected void setOsVersion(short version){ repository.setOsVersion( - KMInteger.cast(version).getBuffer(), - KMInteger.cast(version).getStartOff(), - KMInteger.cast(version).length()); + KMInteger.getBuffer(version), + KMInteger.getStartOff(version), + KMInteger.length(version)); } protected void setOsPatchLevel(short patch){ repository.setOsPatch( - KMInteger.cast(patch).getBuffer(), - KMInteger.cast(patch).getStartOff(), - KMInteger.cast(patch).length()); + KMInteger.getBuffer(patch), + KMInteger.getStartOff(patch), + KMInteger.length(patch)); } protected void setVendorPatchLevel(short patch){ repository.setVendorPatchLevel( - KMInteger.cast(patch).getBuffer(), - KMInteger.cast(patch).getStartOff(), - KMInteger.cast(patch).length()); + KMInteger.getBuffer(patch), + KMInteger.getStartOff(patch), + KMInteger.length(patch)); } private short generateKeyCmd(APDU apdu){ short params = KMKeyParameters.expAny(); - short blob = KMByteBlob.exp(); // Array of expected arguments short cmd = KMArray.instance((short) 1); - KMArray.cast(cmd).add((short) 0, params); //key params + KMArray.add(cmd, (short) 0, params); //key params return receiveIncoming(apdu, cmd); } - private void processGenerateKey(APDU apdu) { + private void processGenerateKey1(APDU apdu) { // Receive the incoming request fully from the master 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[KEY_PARAMETERS] = KMArray.get(cmd, (short) 0); data[CERTIFICATE] = KMArray.instance((short)0); //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); // BOOTLOADER_ONLY keys not supported. KMTag.assertAbsence(data[KEY_PARAMETERS], KMType.BOOL_TAG, KMType.BOOTLOADER_ONLY, KMError.INVALID_KEY_BLOB); // As per specification Early boot keys may be created after early boot ended. - if (!specification.canCreateEarlyBootKeys() && repository.getEarlyBootEndedStatus()) { - //Validate early boot - KMTag.assertAbsence(data[KEY_PARAMETERS], KMType.BOOL_TAG, KMType.EARLY_BOOT_ONLY, - KMError.INVALID_KEY_BLOB); - } + validateEarlyBoot(); // Algorithm must be present KMTag.assertPresence(data[KEY_PARAMETERS], KMType.ENUM_TAG, KMType.ALGORITHM, KMError.INVALID_ARGUMENT); short alg = KMEnumTag.getValue(KMType.ALGORITHM, data[KEY_PARAMETERS]); @@ -3688,13 +3474,13 @@ private void processGenerateKey(APDU apdu) { createEncryptedKeyBlob(scratchPad); // prepare the response short resp = KMArray.instance((short) 3); - KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); - KMArray.cast(resp).add((short) 1, data[KEY_BLOB]); - KMArray.cast(resp).add((short) 2, data[KEY_CHARACTERISTICS]); + KMArray.add(resp, (short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.add(resp, (short) 1, data[KEY_BLOB]); + KMArray.add(resp, (short) 2, data[KEY_CHARACTERISTICS]); sendOutgoing(apdu, resp); } - protected void processAttestationCertDataCmd(APDU apdu) { + public void processAttestationCertDataCmd(APDU apdu) { // TODO optimize this function. byte[] srcBuffer = apdu.getBuffer(); short recvLen = apdu.setIncomingAndReceive(); @@ -3712,28 +3498,37 @@ protected void processAttestationCertDataCmd(APDU apdu) { // in the bufferRef[0] buffer. short var = KMByteBlob.instance((short) 12); // These variables point to the appropriate positions in the var buffer. - short certChainPos = KMByteBlob.cast(var).getStartOff(); - short certIssuerPos = (short) (KMByteBlob.cast(var).getStartOff() + 4); - short certExpiryPos = (short) (KMByteBlob.cast(var).getStartOff() + 8); + short certChainPos = KMByteBlob.getStartOff(var); + short certIssuerPos = (short) (KMByteBlob.getStartOff(var) + 4); + short certExpiryPos = (short) (KMByteBlob.getStartOff(var) + 8); decoder.decodeCertificateData((short) 3, buffer, bufferStartOffset, bufferLength, - KMByteBlob.cast(var).getBuffer(), KMByteBlob.cast(var).getStartOff()); + KMByteBlob.getBuffer(var), KMByteBlob.getStartOff(var)); // persist data seProvider.persistProvisionData( (byte[]) buffer, - Util.getShort(KMByteBlob.cast(var).getBuffer(), certChainPos), // offset - Util.getShort(KMByteBlob.cast(var).getBuffer(), (short) (certChainPos + 2)), // length - Util.getShort(KMByteBlob.cast(var).getBuffer(), certIssuerPos), // offset - Util.getShort(KMByteBlob.cast(var).getBuffer(), (short) (certIssuerPos + 2)), // length - Util.getShort(KMByteBlob.cast(var).getBuffer(), certExpiryPos), // offset - Util.getShort(KMByteBlob.cast(var).getBuffer(), (short) (certExpiryPos + 2))); // length + Util.getShort(KMByteBlob.getBuffer(var), certChainPos), // offset + Util.getShort(KMByteBlob.getBuffer(var), (short) (certChainPos + 2)), // length + Util.getShort(KMByteBlob.getBuffer(var), certIssuerPos), // offset + Util.getShort(KMByteBlob.getBuffer(var), (short) (certIssuerPos + 2)), // length + Util.getShort(KMByteBlob.getBuffer(var), certExpiryPos), // offset + Util.getShort(KMByteBlob.getBuffer(var), (short) (certExpiryPos + 2))); // length // reclaim memory repository.reclaimMemory(bufferLength); } private short generateAttestKeyCmd(APDU apdu) { - return receiveIncoming(apdu, specification.generateAttestKeyExp()); + short params = KMKeyParameters.expAny(); + short blob = KMByteBlob.exp(); + // Array of expected arguments + short cmd = KMArray.instance((short) 5); + KMArray.add(cmd, (short) 0, blob); //key blob + KMArray.add(cmd, (short) 1, params); //keyparamters to be attested. + KMArray.add(cmd, (short) 2, blob); //attest key blob + KMArray.add(cmd, (short) 3, params); //attest key params + KMArray.add(cmd, (short) 4, blob); //attest issuer + return receiveIncoming(apdu, cmd); } private void processGetCertChainCmd(APDU apdu) { @@ -3747,27 +3542,27 @@ private void processGetCertChainCmd(APDU apdu) { encoder.getEncodedLength(int32Ptr); short totalLen = (short) (arrayHeaderLen + encoder.getEncodedLength(int32Ptr) + maxByteHeaderLen + certChainLen); short certChain = KMByteBlob.instance(totalLen); - //bufferRef[0] = KMByteBlob.cast(tmpVariables[1]).getBuffer(); - //bufferProp[BUF_START_OFFSET] = KMByteBlob.cast(tmpVariables[1]).getStartOff(); - //bufferProp[BUF_LEN_OFFSET] = KMByteBlob.cast(tmpVariables[1]).length(); + //bufferRef[0] = (tmpVariables[1]).getBuffer(); + //bufferProp[BUF_START_OFFSET] = (tmpVariables[1]).getStartOff(); + //bufferProp[BUF_LEN_OFFSET] = (tmpVariables[1]).length(); // copy the certificate chain to the end of the buffer. seProvider.readProvisionedData( KMSEProvider.CERTIFICATE_CHAIN, - KMByteBlob.cast(certChain).getBuffer(), - (short) (KMByteBlob.cast(certChain).getStartOff() + totalLen - certChainLen)); + KMByteBlob.getBuffer(certChain), + (short) (KMByteBlob.getStartOff(certChain) + totalLen - certChainLen)); // Encode cert chain. encoder.encodeCertChain( - KMByteBlob.cast(certChain).getBuffer(), - KMByteBlob.cast(certChain).getStartOff(), - KMByteBlob.cast(certChain).length(), + KMByteBlob.getBuffer(certChain), + KMByteBlob.getStartOff(certChain), + KMByteBlob.length(certChain), int32Ptr, // uint32 ptr - (short) (KMByteBlob.cast(certChain).getStartOff() + totalLen - certChainLen), // start pos of cert chain. + (short) (KMByteBlob.getStartOff(certChain) + totalLen - certChainLen), // start pos of cert chain. certChainLen); apdu.setOutgoing(); - apdu.setOutgoingLength(KMByteBlob.cast(certChain).length()); - apdu.sendBytesLong(KMByteBlob.cast(certChain).getBuffer(), - KMByteBlob.cast(certChain).getStartOff(), - KMByteBlob.cast(certChain).length()); + apdu.setOutgoingLength(KMByteBlob.length(certChain)); + apdu.sendBytesLong(KMByteBlob.getBuffer(certChain), + KMByteBlob.getStartOff(certChain), + KMByteBlob.length(certChain)); } private void processAttestKeyCmd(APDU apdu) { @@ -3775,20 +3570,23 @@ private void processAttestKeyCmd(APDU apdu) { short cmd = generateAttestKeyCmd(apdu); // Re-purpose the apdu buffer as scratch pad. byte[] scratchPad = apdu.getBuffer(); - specification.getAttestKeyInputParameters(cmd, data, KEY_BLOB, KEY_PARAMETERS, ATTEST_KEY_BLOB, - ATTEST_KEY_PARAMS, ATTEST_KEY_ISSUER); + data[KEY_BLOB] = KMArray.get(cmd, (short) 0); + data[KEY_PARAMETERS] = KMArray.get(cmd, (short) 1); + data[ATTEST_KEY_BLOB] = KMArray.get(cmd, (short) 2); + data[ATTEST_KEY_PARAMS] = KMArray.get(cmd, (short) 3); + data[ATTEST_KEY_ISSUER] = KMArray.get(cmd, (short) 4); data[CERTIFICATE] = KMArray.instance((short)0); //by default the cert is empty. // Check for app id and app data. data[APP_ID] = - KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_ID, data[KEY_PARAMETERS]); + KMKeyParameters.findTag(data[KEY_PARAMETERS], KMType.BYTES_TAG, KMType.APPLICATION_ID); data[APP_DATA] = - KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_DATA, data[KEY_PARAMETERS]); + KMKeyParameters.findTag(data[KEY_PARAMETERS], KMType.BYTES_TAG, KMType.APPLICATION_DATA); if (data[APP_ID] != KMTag.INVALID_VALUE) { - data[APP_ID] = KMByteTag.cast(data[APP_ID]).getValue(); + data[APP_ID] = KMByteTag.getValue(data[APP_ID]); } if (data[APP_DATA] != KMTag.INVALID_VALUE) { - data[APP_DATA] = KMByteTag.cast(data[APP_DATA]).getValue(); + data[APP_DATA] = KMByteTag.getValue(data[APP_DATA]); } // parse key blob parseEncryptedKeyBlob(data[KEY_BLOB], data[APP_ID], data[APP_DATA], scratchPad); @@ -3801,40 +3599,29 @@ private void processAttestKeyCmd(APDU apdu) { generateAttestation(data[ATTEST_KEY_BLOB], data[ATTEST_KEY_PARAMS], scratchPad); short resp = KMArray.instance((short) 2); - KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); - KMArray.cast(resp).add((short) 1, data[CERTIFICATE]); + KMArray.add(resp, (short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.add(resp, (short) 1, data[CERTIFICATE]); sendOutgoing(apdu, resp); } - private short getAttestationMode(short attKeyBlob, short attChallenge){ - short alg = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, data[HW_PARAMETERS]); + short alg = KMKeyParameters.findTag(data[HW_PARAMETERS], KMType.ENUM_TAG, KMType.ALGORITHM); short mode = KMType.NO_CERT; // TODO Keymaster specification: Symmetric keys with challenge should return error. - if(KMEnumTag.cast(alg).getValue() != KMType.RSA && - KMEnumTag.cast(alg).getValue() != KMType.EC){ + if(KMEnumTag.getValue(alg) != KMType.RSA && + KMEnumTag.getValue(alg) != KMType.EC){ return mode; } - // If attestation keyblob preset - if (attKeyBlob != KMType.INVALID_VALUE && KMByteBlob.cast(attKeyBlob).length() > 0) { + // If attestation keyblob present + if (attKeyBlob != KMType.INVALID_VALUE && KMByteBlob.length(attKeyBlob) > 0) { // No attestation challenge present then it is an error - if (attChallenge == KMType.INVALID_VALUE || KMByteBlob.cast(attChallenge).length() <= 0) { + if (attChallenge == KMType.INVALID_VALUE || KMByteBlob.length(attChallenge) <= 0) { KMException.throwIt(KMError.ATTESTATION_CHALLENGE_MISSING); } else { mode = KMType.ATTESTATION_CERT; } - } else if (specification.isFactoryAttestationSupported()) { - mode = KMType.FACTORY_PROVISIONED_ATTEST_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])) { - mode = KMType.SELF_SIGNED_CERT; - }else{ - mode = KMType.FAKE_CERT; - } + } else { + mode = getSupportedAttestationMode(attChallenge); } return mode; } @@ -3844,9 +3631,9 @@ private void generateAttestation(short attKeyBlob, short attKeyParam, byte[] sc 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]); + KMKeyParameters.findTag(data[KEY_PARAMETERS], KMType.BYTES_TAG, KMType.ATTESTATION_CHALLENGE); if(attChallenge != KMType.INVALID_VALUE){ - attChallenge = KMByteTag.cast(attChallenge).getValue(); + attChallenge = KMByteTag.getValue(attChallenge); } // No attestation required for symmetric keys short mode = getAttestationMode(attKeyBlob, attChallenge); @@ -3875,57 +3662,18 @@ private void generateAttestation(short attKeyBlob, short attKeyParam, byte[] sc // Allocate memory short certData = KMByteBlob.instance(MAX_CERT_SIZE); - cert.buffer(KMByteBlob.cast(certData).getBuffer(), - KMByteBlob.cast(certData).getStartOff(), - KMByteBlob.cast(certData).length()); - - // Build the certificate - this will sign the cert - cert.build(); - // Adjust the start and length of the certificate in the blob - KMByteBlob.cast(certData).setStartOff(cert.getCertStart()); - KMByteBlob.cast(certData).setLength(cert.getCertLength()); - // Initialize the certificate as array of blob - data[CERTIFICATE] = KMArray.instance((short)1); - KMArray.cast(data[CERTIFICATE]).add((short)0, certData); - -/* - boolean rsaCert = KMEnumTag.cast(alg).getValue() == KMType.EC; - short appId = KMType.INVALID_VALUE; - short appData = KMType.INVALID_VALUE; - if(attKeyParam != KMType.INVALID_VALUE) { - appId = - KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_ID, attKeyParam); - if (appId != KMTag.INVALID_VALUE) { - appId = KMByteTag.cast(appId).getValue(); - } - appData = - KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_DATA, attKeyParam); - if (appData != KMTag.INVALID_VALUE) { - appData = KMByteTag.cast(appData).getValue(); - } - } - - KMAttestationCert cert = makeCert(attKeyBlob, appId, appData, scratchPad); - if(cert == null) {// No certificate - data[CERTIFICATE] = KMArray.instance((short)0); - return; - } + cert.buffer(KMByteBlob.getBuffer(certData), + KMByteBlob.getStartOff(certData), + KMByteBlob.length(certData)); - // Allocate memory - short certData = KMByteBlob.instance(MAX_CERT_SIZE); - cert.buffer(KMByteBlob.cast(certData).getBuffer(), - KMByteBlob.cast(certData).getStartOff(), - KMByteBlob.cast(certData).length()); // Build the certificate - this will sign the cert cert.build(); // Adjust the start and length of the certificate in the blob - KMByteBlob.cast(certData).setStartOff(cert.getCertStart()); - KMByteBlob.cast(certData).setLength(cert.getCertLength()); - + KMByteBlob.setStartOff(certData, cert.getCertStart()); + KMByteBlob.setLength(certData, cert.getCertLength()); // Initialize the certificate as array of blob data[CERTIFICATE] = KMArray.instance((short)1); - KMArray.cast(data[CERTIFICATE]).add((short)0, certData); - */ + KMArray.add(data[CERTIFICATE], (short)0, certData); } /** @@ -3948,7 +3696,7 @@ private static void validateRSAKey(byte[] scratchPad) { } // Generate key handlers - private static void generateRSAKey(byte[] scratchPad) { + private void generateRSAKey(byte[] scratchPad) { // Validate RSA Key validateRSAKey(scratchPad); // Now generate 2048 bit RSA keypair for the given exponent @@ -3957,16 +3705,16 @@ private static void generateRSAKey(byte[] scratchPad) { 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(), + KMByteBlob.getBuffer(data[SECRET]), + KMByteBlob.getStartOff(data[SECRET]), + KMByteBlob.length(data[SECRET]), + KMByteBlob.getBuffer(data[PUB_KEY]), + KMByteBlob.getStartOff(data[PUB_KEY]), + KMByteBlob.length(data[PUB_KEY]), lengths); data[KEY_BLOB] = KMArray.instance((short) 5); - KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PUB_KEY, data[PUB_KEY]); + KMArray.add(data[KEY_BLOB], KEY_BLOB_PUB_KEY, data[PUB_KEY]); } private static void validateAESKey() { @@ -3977,20 +3725,20 @@ private static void validateAESKey() { // 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]); + KMKeyParameters.findTag(data[KEY_PARAMETERS], KMType.ENUM_ARRAY_TAG, KMType.BLOCK_MODE); // If it is a GCM mode - if (KMEnumArrayTag.cast(blockModes).contains(KMType.GCM)){ + if (KMEnumArrayTag.contains(blockModes, 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(); + KMKeyParameters.findTag(data[KEY_PARAMETERS], KMType.UINT_TAG, KMType.MIN_MAC_LENGTH); + macLength = KMIntegerTag.getValue(macLength); // 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) { + if (KMInteger.getSignificantShort(macLength) != 0 + || KMInteger.getShort(macLength) > 128 + || KMInteger.getShort(macLength) < 96 + || (KMInteger.getShort(macLength) % 8) != 0) { KMException.throwIt(KMError.UNSUPPORTED_MIN_MAC_LENGTH); } } @@ -4007,7 +3755,7 @@ private static void generateAESKey(byte[] scratchPad) { data[KEY_BLOB] = KMArray.instance((short) 4); } - private static void validateECKeys() { + public void validateECKeys() { // Read key size short eccurve = KMEnumTag.getValue(KMType.ECCURVE, data[KEY_PARAMETERS]); if(!KMTag.isValidKeySize(data[KEY_PARAMETERS])){ @@ -4019,7 +3767,7 @@ private static void validateECKeys() { } } - private static void generateECKeys(byte[] scratchPad) { + private void generateECKeys(byte[] scratchPad) { validateECKeys(); short[] lengths = tmpVariables; seProvider.createAsymmetricKey(KMType.EC, scratchPad, (short) 0, (short) 128, scratchPad, (short) 128, @@ -4027,7 +3775,7 @@ private static void generateECKeys(byte[] scratchPad) { data[PUB_KEY] = KMByteBlob.instance(scratchPad, (short) 128, lengths[1]); data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, lengths[0]); data[KEY_BLOB] = KMArray.instance((short) 5); - KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PUB_KEY, data[PUB_KEY]); + KMArray.add(data[KEY_BLOB], KEY_BLOB_PUB_KEY, data[PUB_KEY]); } private static void validateTDESKey() { @@ -4087,8 +3835,8 @@ private void checkVersionAndPatchLevel(byte[] scratchPad) { short provOsVersion = repository.getOsVersion(); short status = KMInteger.unsignedByteArrayCompare( - KMInteger.cast(provOsVersion).getBuffer(), - KMInteger.cast(provOsVersion).getStartOff(), + KMInteger.getBuffer(provOsVersion), + KMInteger.getStartOff(provOsVersion), scratchPad, (short) 0, len); @@ -4106,8 +3854,8 @@ private void checkVersionAndPatchLevel(byte[] scratchPad) { short osPatch = repository.getOsPatch(); short status = KMInteger.unsignedByteArrayCompare( - KMInteger.cast(osPatch).getBuffer(), - KMInteger.cast(osPatch).getStartOff(), + KMInteger.getBuffer(osPatch), + KMInteger.getStartOff(osPatch), scratchPad, (short) 0, len); @@ -4118,6 +3866,7 @@ private void checkVersionAndPatchLevel(byte[] scratchPad) { } } } + protected static short getBootPatchLevel(byte[] scratchPad){ Util.arrayFillNonAtomic(scratchPad,(short)0, BOOT_PATCH_LVL_SIZE, (byte)0); short len = seProvider.getBootPatchLevel(scratchPad,(short)0); @@ -4127,22 +3876,9 @@ protected static short getBootPatchLevel(byte[] scratchPad){ return KMInteger.uint_32(scratchPad, (short)0); } - private static void makeKeyCharacteristics(byte[] scratchPad) { - /*short osVersion = repository.getOsVersion(); - short osPatch = repository.getOsPatch(); - short vendorPatch = repository.getVendorPatchLevel(); - short bootPatch = getBootPatchLevel(scratchPad); - 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 void makeKeyCharacteristics(byte[] scratchPad) { data[KEY_CHARACTERISTICS] = - specification.makeKeyCharacteristics( + makeKeyCharacteristics( data[KEY_PARAMETERS], repository.getOsVersion(), repository.getOsPatch(), @@ -4150,13 +3886,13 @@ private static void makeKeyCharacteristics(byte[] scratchPad) { getBootPatchLevel(scratchPad), data[ORIGIN], scratchPad); - data[TEE_PARAMETERS] = KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getTeeEnforced(); - data[SW_PARAMETERS] = KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getKeystoreEnforced(); - data[SB_PARAMETERS] = KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getStrongboxEnforced(); - data[HW_PARAMETERS] = specification.getHardwareParamters(data[SB_PARAMETERS], data[TEE_PARAMETERS]); + data[TEE_PARAMETERS] = KMKeyCharacteristics.getTeeEnforced(data[KEY_CHARACTERISTICS]); + data[SW_PARAMETERS] = KMKeyCharacteristics.getKeystoreEnforced(data[KEY_CHARACTERISTICS]); + data[SB_PARAMETERS] = KMKeyCharacteristics.getStrongboxEnforced(data[KEY_CHARACTERISTICS]); + data[HW_PARAMETERS] = getHardwareParamters(data[SB_PARAMETERS], data[TEE_PARAMETERS]); } - private static void createEncryptedKeyBlob(byte[] scratchPad) { + private void createEncryptedKeyBlob(byte[] scratchPad) { // make root of trust blob data[ROT] = readROT(scratchPad); if (data[ROT] == KMType.INVALID_VALUE) { @@ -4170,9 +3906,9 @@ private static void createEncryptedKeyBlob(byte[] 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.add(data[KEY_BLOB], KEY_BLOB_SECRET, data[SECRET]); + KMArray.add(data[KEY_BLOB], KEY_BLOB_AUTH_TAG, data[AUTH_TAG]); + KMArray.add(data[KEY_BLOB], KEY_BLOB_NONCE, data[NONCE]); //TODO remove the following temporary creation of keyblob. /* short tempChar = KMKeyCharacteristics.instance(); @@ -4182,9 +3918,9 @@ private static void createEncryptedKeyBlob(byte[] scratchPad) { KMKeyCharacteristics.cast(tempChar).setKeystoreEnforced(emptyParam); KMKeyCharacteristics.cast(tempChar).setTeeEnforced(data[TEE_PARAMETERS]); KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PARAMS, tempChar);*/ - short keyChars = specification.makeKeyCharacteristicsForKeyblob(data[SW_PARAMETERS], data[SB_PARAMETERS], + short keyChars = makeKeyCharacteristicsForKeyblob(data[SW_PARAMETERS], data[SB_PARAMETERS], data[TEE_PARAMETERS]); - KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PARAMS, keyChars); + KMArray.add(data[KEY_BLOB], KEY_BLOB_PARAMS, keyChars); // allocate reclaimable memory. short buffer = repository.alloc((short) 1024); @@ -4200,27 +3936,27 @@ private short parseEncryptedKeyBlob(short keyBlob, short appId, short appData, b } try { parsedBlob = decoder.decodeArray(keyBlob(), - KMByteBlob.cast(keyBlob).getBuffer(), - KMByteBlob.cast(keyBlob).getStartOff(), - KMByteBlob.cast(keyBlob).length()); - if (KMArray.cast(parsedBlob).length() < 4) { + KMByteBlob.getBuffer(keyBlob), + KMByteBlob.getStartOff(keyBlob), + KMByteBlob.length(keyBlob)); + if (KMArray.length(parsedBlob) < 4) { KMException.throwIt(KMError.INVALID_KEY_BLOB); } // initialize data - data[SECRET] = KMArray.cast(parsedBlob).get(KEY_BLOB_SECRET); - data[NONCE]= KMArray.cast(parsedBlob).get(KEY_BLOB_NONCE); - data[AUTH_TAG] = KMArray.cast(parsedBlob).get(KEY_BLOB_AUTH_TAG); - data[KEY_CHARACTERISTICS] = KMArray.cast(parsedBlob).get(KEY_BLOB_PARAMS); + data[SECRET] = KMArray.get(parsedBlob, KEY_BLOB_SECRET); + data[NONCE]= KMArray.get(parsedBlob, KEY_BLOB_NONCE); + data[AUTH_TAG] = KMArray.get(parsedBlob, KEY_BLOB_AUTH_TAG); + data[KEY_CHARACTERISTICS] = KMArray.get(parsedBlob, KEY_BLOB_PARAMS); data[PUB_KEY] = KMType.INVALID_VALUE; - if (KMArray.cast(parsedBlob).length() == 5) { - data[PUB_KEY] = KMArray.cast(parsedBlob).get(KEY_BLOB_PUB_KEY); + if (KMArray.length(parsedBlob) == 5) { + data[PUB_KEY] = KMArray.get(parsedBlob, KEY_BLOB_PUB_KEY); } - 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] = specification.getHardwareParamters( data[SB_PARAMETERS], data[TEE_PARAMETERS]); + data[TEE_PARAMETERS] = KMKeyCharacteristics.getTeeEnforced(data[KEY_CHARACTERISTICS]); + data[SB_PARAMETERS] = KMKeyCharacteristics.getStrongboxEnforced(data[KEY_CHARACTERISTICS]); + data[SW_PARAMETERS] = KMKeyCharacteristics.getKeystoreEnforced(data[KEY_CHARACTERISTICS]); + data[HW_PARAMETERS] = getHardwareParamters( data[SB_PARAMETERS], data[TEE_PARAMETERS]); data[HIDDEN_PARAMETERS] = KMKeyParameters.makeHidden(appId, appData, rot, scratchPad); data[KEY_BLOB] = parsedBlob; @@ -4228,7 +3964,7 @@ private short parseEncryptedKeyBlob(short keyBlob, short appId, short appData, b makeAuthData(scratchPad); // Decrypt Secret and verify auth tag decryptSecret(scratchPad); - KMArray.cast(parsedBlob).add(KEY_BLOB_SECRET, data[SECRET]); + KMArray.add(parsedBlob, KEY_BLOB_SECRET, data[SECRET]); } catch (Exception e) { KMException.throwIt(KMError.INVALID_KEY_BLOB); } @@ -4255,69 +3991,69 @@ private void decryptSecret(byte[] scratchPad) { // derive master key - stored in derivedKey short len = deriveKey(scratchPad); 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(), + KMByteBlob.getBuffer(data[DERIVED_KEY]), + KMByteBlob.getStartOff(data[DERIVED_KEY]), + KMByteBlob.length(data[DERIVED_KEY]), + KMByteBlob.getBuffer(data[SECRET]), + KMByteBlob.getStartOff(data[SECRET]), + KMByteBlob.length(data[SECRET]), scratchPad, (short) 0, - KMByteBlob.cast(data[NONCE]).getBuffer(), - KMByteBlob.cast(data[NONCE]).getStartOff(), - KMByteBlob.cast(data[NONCE]).length(), + KMByteBlob.getBuffer(data[NONCE]), + KMByteBlob.getStartOff(data[NONCE]), + KMByteBlob.length(data[NONCE]), repository.getHeap(), data[AUTH_DATA], data[AUTH_DATA_LENGTH], - KMByteBlob.cast(data[AUTH_TAG]).getBuffer(), - KMByteBlob.cast(data[AUTH_TAG]).getStartOff(), - KMByteBlob.cast(data[AUTH_TAG]).length())) { + KMByteBlob.getBuffer(data[AUTH_TAG]), + KMByteBlob.getStartOff(data[AUTH_TAG]), + KMByteBlob.length(data[AUTH_TAG]))) { KMException.throwIt(KMError.INVALID_KEY_BLOB); } // Copy the decrypted secret data[SECRET] = - KMByteBlob.instance(scratchPad, (short) 0, KMByteBlob.cast(data[SECRET]).length()); + KMByteBlob.instance(scratchPad, (short) 0, KMByteBlob.length(data[SECRET])); } - private static void encryptSecret(byte[] scratchPad) { + private void encryptSecret(byte[] scratchPad) { // make nonce data[NONCE] = KMByteBlob.instance((short) AES_GCM_NONCE_LENGTH); data[AUTH_TAG] = KMByteBlob.instance(AES_GCM_AUTH_TAG_LENGTH); Util.arrayCopyNonAtomic( - KMByteBlob.cast(data[NONCE]).getBuffer(), - KMByteBlob.cast(data[NONCE]).getStartOff(), + KMByteBlob.getBuffer(data[NONCE]), + KMByteBlob.getStartOff(data[NONCE]), scratchPad, (short) 0, - KMByteBlob.cast(data[NONCE]).length()); + KMByteBlob.length(data[NONCE])); seProvider.newRandomNumber( - KMByteBlob.cast(data[NONCE]).getBuffer(), - KMByteBlob.cast(data[NONCE]).getStartOff(), - KMByteBlob.cast(data[NONCE]).length()); + KMByteBlob.getBuffer(data[NONCE]), + KMByteBlob.getStartOff(data[NONCE]), + KMByteBlob.length(data[NONCE])); // 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(), + KMByteBlob.getBuffer(data[DERIVED_KEY]), + KMByteBlob.getStartOff(data[DERIVED_KEY]), + KMByteBlob.length(data[DERIVED_KEY]), + KMByteBlob.getBuffer(data[SECRET]), + KMByteBlob.getStartOff(data[SECRET]), + KMByteBlob.length(data[SECRET]), scratchPad, (short) 0, - KMByteBlob.cast(data[NONCE]).getBuffer(), - KMByteBlob.cast(data[NONCE]).getStartOff(), - KMByteBlob.cast(data[NONCE]).length(), + KMByteBlob.getBuffer(data[NONCE]), + KMByteBlob.getStartOff(data[NONCE]), + KMByteBlob.length(data[NONCE]), repository.getHeap(), data[AUTH_DATA], data[AUTH_DATA_LENGTH], - KMByteBlob.cast(data[AUTH_TAG]).getBuffer(), - KMByteBlob.cast(data[AUTH_TAG]).getStartOff(), - KMByteBlob.cast(data[AUTH_TAG]).length()); + KMByteBlob.getBuffer(data[AUTH_TAG]), + KMByteBlob.getStartOff(data[AUTH_TAG]), + KMByteBlob.length(data[AUTH_TAG])); - if (len > 0 && len != KMByteBlob.cast(data[SECRET]).length()) { + if (len > 0 && len != KMByteBlob.length(data[SECRET])) { KMException.throwIt(KMError.INVALID_KEY_BLOB); } data[SECRET] = KMByteBlob.instance(scratchPad, (short)0, len); } - private static void makeAuthData(byte[] scratchPad) { + private void makeAuthData(byte[] scratchPad) { /*short arrayLen = 2; if (KMArray.cast(data[KEY_BLOB]).length() == 5) { arrayLen = 3; @@ -4330,16 +4066,16 @@ private static void makeAuthData(byte[] scratchPad) { KMArray.cast(params).add((short) 2, data[PUB_KEY]); }*/ short params = - specification.concatParamsForAuthData(data[KEY_BLOB], data[HW_PARAMETERS], + concatParamsForAuthData(data[KEY_BLOB], data[HW_PARAMETERS], data[SW_PARAMETERS], data[HIDDEN_PARAMETERS], data[PUB_KEY]); short authIndex = repository.alloc(MAX_AUTH_DATA_SIZE); short index = 0; short len = 0; - short paramsLen = KMArray.cast(params).length(); + short paramsLen = KMArray.length(params); Util.arrayFillNonAtomic(repository.getHeap(), authIndex, (short) MAX_AUTH_DATA_SIZE, (byte) 0); while (index < paramsLen) { - short tag = KMArray.cast(params).get(index); + short tag = KMArray.get(params, index); len = encoder.encode(tag, repository.getHeap(), (short) (authIndex + 32)); Util.arrayCopyNonAtomic(repository.getHeap(), (short) authIndex, repository.getHeap(), (short) (authIndex + len + 32), (short) 32); @@ -4354,21 +4090,7 @@ private static void makeAuthData(byte[] scratchPad) { data[AUTH_DATA_LENGTH] = len; } - private static short addPtrToAAD(short dataArrPtr, byte[] aadBuf, short offset) { - short index = (short) (offset * 2); - short tagInd = 0; - short tagPtr; - short arrLen = KMArray.cast(dataArrPtr).length(); - while (tagInd < arrLen) { - tagPtr = KMArray.cast(dataArrPtr).get(tagInd); - Util.setShort(aadBuf, index, tagPtr); - index += 2; - tagInd++; - } - return tagInd; - } - - private static short deriveKey(byte[] scratchPad) { + private short deriveKey(byte[] scratchPad) { // KeyDerivation: // 1. Do HMAC Sign, Auth data. // 2. HMAC Sign generates an output of 32 bytes length. @@ -4389,58 +4111,43 @@ private static short deriveKey(byte[] scratchPad) { return len; } - public static void sendError(APDU apdu, short err) { + public void sendError(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); + KMArray.add(resp, (short)0, error); sendOutgoing(apdu, resp); } 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(), + KMInteger.getBuffer(authTime), + KMInteger.getStartOff(authTime), scratchPad, - (short) (8 - KMInteger.cast(timeStamp).length()), - KMInteger.cast(timeStamp).length()); + (short) (8 - KMInteger.length(timeStamp)), + KMInteger.length(timeStamp)); // Copy timestamp to scratchpad Util.arrayCopyNonAtomic( - KMInteger.cast(timeStamp).getBuffer(), - KMInteger.cast(timeStamp).getStartOff(), + KMInteger.getBuffer(timeStamp), + KMInteger.getStartOff(timeStamp), scratchPad, - (short) (16 - KMInteger.cast(timeStamp).length()), - KMInteger.cast(timeStamp).length()); + (short) (16 - KMInteger.length(timeStamp)), + KMInteger.length(timeStamp)); // add authTime in millis to timestamp. KMUtils.add(scratchPad, (short) 0, (short) 8, (short) 16); return KMInteger.uint_64(scratchPad, (short) 16); } - private void add(byte[] buf, short op1, short op2, short result) { - byte index = 7; - byte carry = 0; - short tmp; - while (index >= 0) { - tmp = (short) (buf[(short) (op1 + index)] + buf[(short) (op2 + index)] + carry); - carry = 0; - if (tmp > 255) { - carry = 1; // max unsigned byte value is 255 - } - buf[(short) (result + index)] = (byte) (tmp & (byte) 0xFF); - index--; - } - } - public void powerReset() { //TODO handle power reset signal. releaseAllOperations(); resetWrappingKey(); } - public static void generateRkpKey(byte[] scratchPad, short keyParams) { + public void generateRkpKey(byte[] scratchPad, short keyParams) { data[KEY_PARAMETERS] = keyParams; generateECKeys(scratchPad); // create key blob @@ -4448,6 +4155,7 @@ public static void generateRkpKey(byte[] scratchPad, short keyParams) { makeKeyCharacteristics(scratchPad); createEncryptedKeyBlob(scratchPad); } + public static short getPubKey() { return data[PUB_KEY]; } @@ -4475,9 +4183,9 @@ public static short encodeToApduBuffer(short object, byte[] apduBuf, short apduO return len; } - public static short validateCertChain(boolean validateEekRoot, byte expCertAlg, + public short validateCertChain(boolean validateEekRoot, byte expCertAlg, byte expLeafCertAlg, short certChainArr, byte[] scratchPad, Object[] authorizedEekRoots) { - short len = KMArray.cast(certChainArr).length(); + short len = KMArray.length(certChainArr); short coseHeadersExp = KMCoseHeaders.exp(); //prepare exp for coseky short coseKeyExp = KMCoseKey.exp(); @@ -4490,20 +4198,20 @@ public static short validateCertChain(boolean validateEekRoot, byte expCertAlg, short alg = expCertAlg; short index; for (index = 0; index < len; index++) { - ptr1 = KMArray.cast(certChainArr).get(index); + ptr1 = KMArray.get(certChainArr, 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()); + ptr2 = KMArray.get(ptr1, KMCose.COSE_SIGN1_PROTECTED_PARAMS_OFFSET); + ptr2 = decoder.decode(coseHeadersExp, KMByteBlob.getBuffer(ptr2), + KMByteBlob.getStartOff(ptr2), KMByteBlob.length(ptr2)); if (!KMCoseHeaders.cast(ptr2).isDataValid(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()); + ptr2 = KMArray.get(ptr1, KMCose.COSE_SIGN1_PAYLOAD_OFFSET); + ptr2 = decoder.decode(coseKeyExp, KMByteBlob.getBuffer(ptr2), + KMByteBlob.getStartOff(ptr2), KMByteBlob.length(ptr2)); if ((index == (short) (len - 1)) && len > 1) { alg = expLeafCertAlg; } @@ -4535,17 +4243,17 @@ public static short validateCertChain(boolean validateEekRoot, byte expCertAlg, } // Validate signature. signStructure = - KMCose.constructCoseSignStructure( - KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_PROTECTED_PARAMS_OFFSET), + kmCoseInst.constructCoseSignStructure( + KMArray.get(ptr1, 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); + KMArray.get(ptr1, KMCose.COSE_SIGN1_PAYLOAD_OFFSET)); + encodedLen = KMKeymasterDevice.encodeToApduBuffer(signStructure, scratchPad, + keySize, KMKeymasterDevice.MAX_COSE_BUF_SIZE); if (!seProvider.ecVerify256(scratchPad, (short) 0, keySize, scratchPad, keySize, encodedLen, - 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.cast(KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_SIGNATURE_OFFSET)).length())) { + KMByteBlob.getBuffer(KMArray.get(ptr1, KMCose.COSE_SIGN1_SIGNATURE_OFFSET)), + KMByteBlob.getStartOff(KMArray.get(ptr1, KMCose.COSE_SIGN1_SIGNATURE_OFFSET)), + KMByteBlob.length(KMArray.get(ptr1, KMCose.COSE_SIGN1_SIGNATURE_OFFSET)))) { KMException.throwIt(KMError.STATUS_FAILED); } prevCoseKey = ptr2; @@ -4554,14 +4262,14 @@ public static short validateCertChain(boolean validateEekRoot, byte expCertAlg, } - public static short generateBcc(boolean testMode, byte[] scratchPad) { + public short generateBcc(boolean testMode, byte[] scratchPad) { if (!testMode && seProvider.isProvisionLocked()) { KMException.throwIt(KMError.STATUS_FAILED); } KMDeviceUniqueKey deviceUniqueKey = seProvider.getDeviceUniqueKey(testMode); short temp = deviceUniqueKey.getPublicKey(scratchPad, (short) 0); short coseKey = - KMCose.constructCoseKey( + kmCoseInst.constructCoseKey( KMInteger.uint_8(KMCose.COSE_KEY_TYPE_EC2), KMType.INVALID_VALUE, KMNInteger.uint_8(KMCose.COSE_ALG_ES256), @@ -4573,11 +4281,11 @@ public static short generateBcc(boolean testMode, byte[] scratchPad) { KMType.INVALID_VALUE, false ); - temp = KMKeymasterApplet.encodeToApduBuffer(coseKey, scratchPad, (short) 0, - KMKeymasterApplet.MAX_COSE_BUF_SIZE); + temp = KMKeymasterDevice.encodeToApduBuffer(coseKey, scratchPad, (short) 0, + KMKeymasterDevice.MAX_COSE_BUF_SIZE); // Construct payload. short payload = - KMCose.constructCoseCertPayload( + kmCoseInst.constructCoseCertPayload( KMCosePairTextStringTag.instance(KMInteger.uint_8(KMCose.ISSUER), KMTextString.instance(KMCose.TEST_ISSUER_NAME, (short) 0, (short) KMCose.TEST_ISSUER_NAME.length)), @@ -4591,17 +4299,17 @@ public static short generateBcc(boolean testMode, byte[] scratchPad) { (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); + temp = KMKeymasterDevice.encodeToApduBuffer(payload, scratchPad, (short) 0, + KMKeymasterDevice.MAX_COSE_BUF_SIZE); payload = KMByteBlob.instance(scratchPad, (short) 0, temp); // protected header short protectedHeader = - KMCose.constructHeaders(KMNInteger.uint_8(KMCose.COSE_ALG_ES256), KMType.INVALID_VALUE, + kmCoseInst.constructHeaders(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); + temp = KMKeymasterDevice.encodeToApduBuffer(protectedHeader, scratchPad, (short) 0, + KMKeymasterDevice.MAX_COSE_BUF_SIZE); protectedHeader = KMByteBlob.instance(scratchPad, (short) 0, temp); //unprotected headers. @@ -4610,11 +4318,11 @@ public static short generateBcc(boolean testMode, byte[] scratchPad) { // construct cose sign structure. short coseSignStructure = - KMCose.constructCoseSignStructure(protectedHeader, KMByteBlob.instance((short) 0), payload); + kmCoseInst.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); + temp = KMKeymasterDevice.encodeToApduBuffer(coseSignStructure, scratchPad, (short) 0, + KMKeymasterDevice.MAX_COSE_BUF_SIZE); // do sign short len = seProvider.ecSign256( @@ -4629,34 +4337,174 @@ public static short generateBcc(boolean testMode, byte[] scratchPad) { // construct cose_sign1 short coseSign1 = - KMCose.constructCoseSign1(protectedHeader, unprotectedHeader, payload, coseSignStructure); + kmCoseInst.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); + KMArray.add(bcc, (short) 0, coseKey); + KMArray.add(bcc, (short) 1, coseSign1); return bcc; } 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()); + op.getTrustedConfirmationSigner().update(KMByteBlob.getBuffer(data[INPUT_DATA]), + KMByteBlob.getStartOff(data[INPUT_DATA]), KMByteBlob.length(data[INPUT_DATA])); } } private void finishTrustedConfirmationOperation(KMOperationState op) { // Perform trusted confirmation if required if (op.isTrustedConfirmationRequired()) { - short confToken = specification.getConfirmationToken(data[CONFIRMATION_TOKEN], data[KEY_PARAMETERS]); - 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(confToken).getBuffer(), - KMByteBlob.cast(confToken).getStartOff(), - KMByteBlob.cast(confToken).length()); + short confToken = getConfirmationToken(data[CONFIRMATION_TOKEN], data[KEY_PARAMETERS]); + boolean verified = op.getTrustedConfirmationSigner().verify(KMByteBlob.getBuffer(data[INPUT_DATA]), + KMByteBlob.getStartOff(data[INPUT_DATA]), KMByteBlob.length(data[INPUT_DATA]), + KMByteBlob.getBuffer(confToken), + KMByteBlob.getStartOff(confToken), + KMByteBlob.length(confToken)); if (!verified) { KMException.throwIt(KMError.NO_USER_CONFIRMATION); } } } + + + + public short getHardwareInfo() { + short respPtr = KMArray.instance((short) 3); + KMArray.add(respPtr, (short) 0, KMEnum.instance(KMType.HARDWARE_TYPE, KMType.STRONGBOX)); + KMArray.add(respPtr, + (short) 1, + KMByteBlob.instance( + JAVACARD_KEYMASTER_DEVICE, (short) 0, (short) JAVACARD_KEYMASTER_DEVICE.length)); + KMArray.add(respPtr, (short) 2, KMByteBlob.instance(GOOGLE, (short) 0, (short) GOOGLE.length)); + return respPtr; + } + + public short makeKeyCharacteristics(short keyParams, short osVersion, short osPatch, + short vendorPatch, short bootPatch, short origin, byte[] scratchPad) { + short strongboxParams = KMKeyParameters.makeSbEnforced( + keyParams, (byte) origin, osVersion, osPatch, vendorPatch, bootPatch, scratchPad); + short teeParams = KMKeyParameters.makeTeeEnforced(keyParams, scratchPad); + short swParams = KMKeyParameters.makeKeystoreEnforced(keyParams, scratchPad); + short hwParams = KMKeyParameters.makeHwEnforced(strongboxParams, teeParams); + short keyCharacteristics = KMKeyCharacteristics.instance2(); + KMKeyCharacteristics.setStrongboxEnforced(keyCharacteristics, hwParams); + KMKeyCharacteristics.setKeystoreEnforced(keyCharacteristics, swParams); + return keyCharacteristics; + } + + public short makeKeyCharacteristicsForKeyblob(short swParams, short sbParams, short teeParams) { + short keyChars = KMKeyCharacteristics.instance2(); + KMKeyCharacteristics.setStrongboxEnforced(keyChars, sbParams); + KMKeyCharacteristics.setKeystoreEnforced(keyChars, swParams); + return keyChars; + } + + public short getKeyCharacteristicsExp() { + return KMKeyCharacteristics.keymasterExp(); + } + + public void validateEarlyBoot() { + if (repository.getEarlyBootEndedStatus()) { + //Validate early boot + KMTag.assertAbsence(data[KEY_PARAMETERS], KMType.BOOL_TAG, KMType.EARLY_BOOT_ONLY, + KMError.INVALID_KEY_BLOB); + } + } + + public short getHardwareParamters(short sbParams, short teeParams) { + return sbParams; + } + + public short concatParamsForAuthData(short keyBlobPtr, short hwParams, short swParams, + short hiddenParams, short pubKey) { + short arrayLen = 3; + if (pubKey != KMType.INVALID_VALUE) { + arrayLen = 4; + } + short params = KMArray.instance((short) arrayLen); + KMArray.add(params, (short) 0, KMKeyParameters.getVals(hwParams)); + KMArray.add(params, (short) 1, KMKeyParameters.getVals(swParams)); + KMArray.add(params, (short) 2, KMKeyParameters.getVals(hiddenParams)); + if (4 == arrayLen) { + KMArray.add(params, (short) 3, pubKey); + } + return params; + } + + public short getSupportedAttestationMode(short attChallenge) { + return KMType.FACTORY_PROVISIONED_ATTEST_CERT; + } + + public KMAttestationCert makeCommonCert(short swParams, short hwParams, short keyParams, + byte[] scratchPad, KMSEProvider seProvider) { + boolean rsaCert = (KMEnumTag.getValue(KMType.ALGORITHM, hwParams) == KMType.RSA); + KMAttestationCert cert = KMAttestationCertImpl.instance(rsaCert, seProvider); + // notBefore + short notBefore = + KMKeyParameters.findTag(swParams, KMType.DATE_TAG, KMType.ACTIVE_DATETIME); + if (notBefore == KMType.INVALID_VALUE) { + notBefore = + KMKeyParameters.findTag(swParams, KMType.DATE_TAG, KMType.CREATION_DATETIME); + if (notBefore == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + } + notBefore = KMIntegerTag.getValue(notBefore); + cert.notBefore(notBefore, false, scratchPad); + // notAfter + // expiry time - byte blob + boolean derEncoded = false; + short notAfter = + KMKeyParameters.findTag(swParams, KMType.DATE_TAG, KMType.USAGE_EXPIRE_DATETIME); + if (notAfter == KMType.INVALID_VALUE) { + notAfter = getProvisionedCertificateData(seProvider, KMSEProvider.CERTIFICATE_EXPIRY); + derEncoded = true; + } + cert.notAfter(notAfter, derEncoded, scratchPad); + // SubjectName + cert.subjectName(KMByteBlob.instance(X509Subject, (short) 0, (short) X509Subject.length)); + // Serial + short serialNumber = KMByteBlob.instance((short) 1); + KMByteBlob.add(serialNumber, (short) 0, SERIAL_NUM); + cert.serialNumber(serialNumber); + // Issuer. + cert.issuer(getProvisionedCertificateData(seProvider, KMSEProvider.CERTIFICATE_ISSUER)); + return cert; + } + + private short getProvisionedCertificateData(KMSEProvider kmseProvider, byte dataType) { + short len = seProvider.getProvisionedDataLength(dataType); + if (len == 0) { + KMException.throwIt(KMError.INVALID_DATA); + } + short ptr = KMByteBlob.instance(len); + seProvider.readProvisionedData( + dataType, + KMByteBlob.getBuffer(ptr), + KMByteBlob.getStartOff(ptr)); + return ptr; + } + + public short getConfirmationToken(short confToken, short keyParams) { + short cToken = + KMKeyParameters.findTag(keyParams, KMType.BYTES_TAG, KMType.CONFIRMATION_TOKEN); + if (cToken == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.NO_USER_CONFIRMATION); + } + return KMByteTag.getValue(cToken); + } + + public short getKMVerificationTokenExp() { + return KMVerificationToken.verificationTokenExp(); + } + + public short getMacFromVerificationToken(short verToken) { + return KMVerificationToken.getMac(verToken, (short) 0x04); + } + + public short getMgf1Digest(short keyParams, short hwParams) { + return KMType.SHA1; + } } diff --git a/Applet/src/com/android/javacard/kmdevice/KMKeymintDevice.java b/Applet/src/com/android/javacard/kmdevice/KMKeymintDevice.java new file mode 100644 index 00000000..92722568 --- /dev/null +++ b/Applet/src/com/android/javacard/kmdevice/KMKeymintDevice.java @@ -0,0 +1,276 @@ +package com.android.javacard.kmdevice; + +import com.android.javacard.seprovider.KMAttestationCert; +import com.android.javacard.seprovider.KMException; +import com.android.javacard.seprovider.KMSEProvider; + +import javacard.framework.Util; +import javacard.security.CryptoException; + +public class KMKeymintDevice extends KMKeymasterDevice { + + public static byte[] JAVACARD_KEYMINT_DEVICE; + private static byte[] GOOGLE; + + private static byte[] dec319999Ms; + + private static byte[] dec319999; + + private static byte[] jan01970; + + public KMKeymintDevice(KMSEProvider seImpl, KMRepository repoInst, KMDecoder decoderInst) { + super(seImpl, repoInst, decoderInst); + initStatics(); + } + + public static void initStatics() { + JAVACARD_KEYMINT_DEVICE = new byte[] { + 0x4a, 0x61, 0x76, 0x61, 0x63, 0x61, 0x72, 0x64, + 0x4b, 0x65, 0x79, 0x6d, 0x69, 0x6e, 0x74, + 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, + }; + GOOGLE = new byte[] {0x47, 0x6F, 0x6F, 0x67, 0x6C, 0x65}; + dec319999Ms = new byte[] {(byte) 0, (byte) 0, (byte) 0xE6, (byte) 0x77, + (byte) 0xD2, (byte) 0x1F, (byte) 0xD8, (byte) 0x18}; + dec319999 = new byte[] { + 0x39, 0x39, 0x39, 0x39, 0x31, 0x32, 0x33, 0x31, 0x32, 0x33, 0x35, + 0x39, 0x35, 0x39, 0x5a, + }; + jan01970 = new byte[] { + 0x37, 0x30, 0x30, 0x31, 0x30, 0x31, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x30, 0x5a, + }; + } + + @Override + public short getHardwareInfo() { + final byte version = 1; + // Make the response + short respPtr = KMArray.instance((short) 6); + KMArray.add(respPtr, (short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.add(respPtr, (short) 1, KMInteger.uint_8(version)); + KMArray.add(respPtr, (short) 2, KMEnum.instance(KMType.HARDWARE_TYPE, KMType.STRONGBOX)); + KMArray.add(respPtr, + (short) 3, + KMByteBlob.instance( + JAVACARD_KEYMINT_DEVICE, (short) 0, (short) JAVACARD_KEYMINT_DEVICE.length)); + KMArray.add(respPtr, (short) 4, KMByteBlob.instance(GOOGLE, (short) 0, (short) GOOGLE.length)); + KMArray.add(respPtr, (short) 5, KMInteger.uint_8((byte) 1)); + return respPtr; + } + + @Override + public short makeKeyCharacteristics(short keyParams, short osVersion, short osPatch, + short vendorPatch, short bootPatch, short origin, byte[] scratchPad) { + short strongboxParams = KMKeyParameters.makeSbEnforced( + keyParams, (byte) origin, osVersion, osPatch, vendorPatch, bootPatch, scratchPad); + short teeParams = KMKeyParameters.makeTeeEnforced(keyParams, scratchPad); + short swParams = KMKeyParameters.makeKeystoreEnforced(keyParams, scratchPad); + //short emptyParam = KMArray.instance((short) 0); + short keyCharacteristics = KMKeyCharacteristics.instance(); + KMKeyCharacteristics.setStrongboxEnforced(keyCharacteristics, strongboxParams); + KMKeyCharacteristics.setKeystoreEnforced(keyCharacteristics, swParams); + KMKeyCharacteristics.setTeeEnforced(keyCharacteristics, teeParams); + return keyCharacteristics; + } + + @Override + public short makeKeyCharacteristicsForKeyblob(short swParams, short sbParams, short teeParams) { + short keyChars = KMKeyCharacteristics.instance(); + short emptyParam = KMArray.instance((short) 0); + emptyParam = KMKeyParameters.instance(emptyParam); + KMKeyCharacteristics.setStrongboxEnforced(keyChars, sbParams); + KMKeyCharacteristics.setKeystoreEnforced(keyChars, emptyParam); + KMKeyCharacteristics.setTeeEnforced(keyChars, teeParams); + return keyChars; + } + + @Override + public short getKeyCharacteristicsExp() { + return KMKeyCharacteristics.keymintExp(); + } + + @Override + public short getHardwareParamters(short sbParams, short teeParams) { + return KMKeyParameters.makeHwEnforced(sbParams, teeParams); + } + + @Override + public short concatParamsForAuthData(short keyBlobPtr, short hwParams, short swParams, + short hiddenParams, short pubKey) { + short arrayLen = 2; + if (pubKey != KMType.INVALID_VALUE) { + arrayLen = 3; + } + short params = KMArray.instance((short) arrayLen); + KMArray.add(params, (short) 0, KMKeyParameters.getVals(hwParams)); + KMArray.add(params, (short) 1, KMKeyParameters.getVals(hiddenParams)); + if (3 == arrayLen) { + KMArray.add(params, (short) 2, pubKey); + } + return params; + } + + @Override + public short getSupportedAttestationMode(short attChallenge) { + // Attestation challenge present then it is an error because no factory provisioned attest key + short mode = KMType.NO_CERT; //TODO check what should be the default value + if (attChallenge != KMType.INVALID_VALUE && KMByteBlob.length(attChallenge) > 0) { + KMException.throwIt(KMError.ATTESTATION_KEYS_NOT_PROVISIONED); + } + if(KMEnumArrayTag.contains(KMType.PURPOSE, KMType.ATTEST_KEY, data[HW_PARAMETERS]) || + KMEnumArrayTag.contains(KMType.PURPOSE, KMType.SIGN, data[HW_PARAMETERS])) { + mode = KMType.SELF_SIGNED_CERT; + }else{ + mode = KMType.FAKE_CERT; + } + return mode; + } + + @Override + public KMAttestationCert makeCommonCert(short swParams, short hwParams, short keyParams, + byte[] scratchPad, KMSEProvider seProvider) { + short alg = KMKeyParameters.findTag(keyParams, KMType.ENUM_TAG, KMType.ALGORITHM); + boolean rsaCert = KMEnumTag.getValue(alg) == KMType.RSA; + KMAttestationCert cert = KMAttestationCertImpl.instance(rsaCert, seProvider); + + // Validity period must be specified + short notBefore = KMKeyParameters.findTag(keyParams, KMType.DATE_TAG, KMType.CERTIFICATE_NOT_BEFORE); + if (notBefore == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.MISSING_NOT_BEFORE); + } + notBefore = KMIntegerTag.getValue(notBefore); + short notAfter = KMKeyParameters.findTag(keyParams, KMType.DATE_TAG, KMType.CERTIFICATE_NOT_AFTER); + if (notAfter == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.MISSING_NOT_AFTER); + } + notAfter = KMIntegerTag.getValue(notAfter); + // 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, keyParams); + if (serialNum != KMType.INVALID_VALUE) { + serialNum = KMBignumTag.getValue(serialNum); + } else { + serialNum = KMByteBlob.instance((short) 1); + KMByteBlob.add(serialNum, (short) 0, (byte) 1); + } + cert.serialNumber(serialNum); + return cert; + } + + @Override + public short getMgf1Digest(short keyParams, short hwParams) { + short mgfDigest = KMKeyParameters.findTag(keyParams, KMType.ENUM_ARRAY_TAG, + KMType.RSA_OAEP_MGF_DIGEST); + if (mgfDigest != KMType.INVALID_VALUE) { + if (KMEnumArrayTag.length(mgfDigest) != 1) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + mgfDigest = KMEnumArrayTag.get(mgfDigest, (short) 0); + if (mgfDigest == KMType.DIGEST_NONE) { + KMException.throwIt(KMError.UNSUPPORTED_MGF_DIGEST); + } + if (!KMEnumArrayTag + .contains(KMType.RSA_OAEP_MGF_DIGEST, mgfDigest, hwParams)) { + KMException.throwIt(KMError.INCOMPATIBLE_MGF_DIGEST); + } + if (mgfDigest != KMType.SHA1 && mgfDigest != KMType.SHA2_256) { + KMException.throwIt(KMError.UNSUPPORTED_MGF_DIGEST); + } + } + return mgfDigest; + } + + @Override + public 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.getBuffer(data[SECRET]), + KMByteBlob.getStartOff(data[SECRET]), + KMByteBlob.length(data[SECRET]), + null, + (short) 0, + (short) 0)); + } + + @Override + public void finishKeyAgreementOperation(KMOperationState op, byte[] scratchPad) { + try { + KMPKCS8Decoder pkcs8 = KMPKCS8Decoder.instance(); + short blob = pkcs8.decodeEcSubjectPublicKeyInfo(data[INPUT_DATA]); + short len = op.getOperation().finish( + KMByteBlob.getBuffer(blob), + KMByteBlob.getStartOff(blob), + KMByteBlob.length(blob), + scratchPad, + (short) 0 + ); + data[OUTPUT_DATA] = KMByteBlob.instance((short) 32); + Util.arrayCopyNonAtomic( + scratchPad, + (short) 0, + KMByteBlob.getBuffer(data[OUTPUT_DATA]), + KMByteBlob.getStartOff(data[OUTPUT_DATA]), + len); + } catch (CryptoException e) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + } + + @Override + public short getConfirmationToken(short confToken, short keyParams) { + if (0 == KMByteBlob.length(confToken)) { + KMException.throwIt(KMError.NO_USER_CONFIRMATION); + } + return confToken; + } + + @Override + public short getKMVerificationTokenExp() { + return KMVerificationToken.timeStampTokenExp(); + } + + @Override + public short getMacFromVerificationToken(short verToken) { + return KMVerificationToken.getMac(verToken, (short) 0x02); + } + + @Override + public void validateECKeys() { + // Read key size + short eccurve = KMEnumTag.getValue(KMType.ECCURVE, data[KEY_PARAMETERS]); + if (eccurve == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } else { + if (eccurve != KMType.P_256) { + KMException.throwIt(KMError.UNSUPPORTED_EC_CURVE); + } + } + } + +} diff --git a/Applet/src/com/android/javacard/keymaster/KMMap.java b/Applet/src/com/android/javacard/kmdevice/KMMap.java similarity index 87% rename from Applet/src/com/android/javacard/keymaster/KMMap.java rename to Applet/src/com/android/javacard/kmdevice/KMMap.java index 4b559fa3..02b3831f 100644 --- a/Applet/src/com/android/javacard/keymaster/KMMap.java +++ b/Applet/src/com/android/javacard/kmdevice/KMMap.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import javacard.framework.ISO7816; import javacard.framework.ISOException; @@ -56,7 +56,7 @@ public static short instance(short length, byte type) { return ptr; } - public static KMMap cast(short ptr) { + private static KMMap cast(short ptr) { if (heap[ptr] != MAP_TYPE) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } @@ -150,4 +150,30 @@ public short length() { public byte[] getBuffer() { return heap; } + + public static void add(short bPtr, short index, short keyPtr, short valPtr) { + KMMap.cast(bPtr).add(index, keyPtr, valPtr); + } + + public static short getKeyValue(short bPtr, short index) { + return KMMap.cast(bPtr).getKeyValue(index); + } + + public static void swap(short bPtr, short index1, short index2) { + KMMap.cast(bPtr).swap(index1, index2); + } + + public static short getKey(short bPtr, short index) { + return KMMap.cast(bPtr).getKey(index); + } + + public static short length(short bPtr) { + return KMMap.cast(bPtr).length(); + } + + public static void canonicalize(short bPtr) { + KMMap.cast(bPtr).canonicalize(); + } + + } diff --git a/Applet/src/com/android/javacard/keymaster/KMNInteger.java b/Applet/src/com/android/javacard/kmdevice/KMNInteger.java similarity index 97% rename from Applet/src/com/android/javacard/keymaster/KMNInteger.java rename to Applet/src/com/android/javacard/kmdevice/KMNInteger.java index c9f8e5ab..e062e6e8 100644 --- a/Applet/src/com/android/javacard/keymaster/KMNInteger.java +++ b/Applet/src/com/android/javacard/kmdevice/KMNInteger.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import javacard.framework.ISO7816; import javacard.framework.ISOException; @@ -67,7 +67,7 @@ public static short instance(byte[] num, short srcOff, short length) { } } - public static KMNInteger cast(short ptr) { + private static KMNInteger cast(short ptr) { byte[] heap = repository.getHeap(); if (heap[ptr] != NEG_INTEGER_TYPE) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); diff --git a/Applet/src/com/android/javacard/keymaster/KMOperationState.java b/Applet/src/com/android/javacard/kmdevice/KMOperationState.java similarity index 95% rename from Applet/src/com/android/javacard/keymaster/KMOperationState.java rename to Applet/src/com/android/javacard/kmdevice/KMOperationState.java index 83370b8e..e4a97345 100644 --- a/Applet/src/com/android/javacard/keymaster/KMOperationState.java +++ b/Applet/src/com/android/javacard/kmdevice/KMOperationState.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import com.android.javacard.seprovider.KMException; import com.android.javacard.seprovider.KMOperation; @@ -185,14 +185,14 @@ public short getUserSecureId() { short obj; while (index < length) { obj = KMInteger.instance(userSecureIds, (short) (offset + index * 8), (short) 8); - KMArray.cast(arrObj).add(index, obj); + KMArray.add(arrObj, 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(); + short length = KMIntegerArrayTag.length(integerArrayPtr); if (length > MAX_SECURE_USER_IDS) { KMException.throwIt(KMError.INVALID_KEY_BLOB); } @@ -202,13 +202,13 @@ public void setUserSecureId(short integerArrayPtr) { short offset = 0; offset = Util.setShort(userSecureIds, offset, length); while (index < length) { - obj = KMIntegerArrayTag.cast(integerArrayPtr).get(index); + obj = KMIntegerArrayTag.get(integerArrayPtr, index); Util.arrayCopyNonAtomic( - KMInteger.cast(obj).getBuffer(), - KMInteger.cast(obj).getStartOff(), + KMInteger.getBuffer(obj), + KMInteger.getStartOff(obj), userSecureIds, - (short) (8 - KMInteger.cast(obj).length() + offset + 8 * index), - KMInteger.cast(obj).length() + (short) (8 - KMInteger.length(obj) + offset + 8 * index), + KMInteger.length(obj) ); index++; } diff --git a/Applet/src/com/android/javacard/keymaster/KMPKCS8Decoder.java b/Applet/src/com/android/javacard/kmdevice/KMPKCS8Decoder.java similarity index 76% rename from Applet/src/com/android/javacard/keymaster/KMPKCS8Decoder.java rename to Applet/src/com/android/javacard/kmdevice/KMPKCS8Decoder.java index eb860e2d..6c5cb6a4 100644 --- a/Applet/src/com/android/javacard/keymaster/KMPKCS8Decoder.java +++ b/Applet/src/com/android/javacard/kmdevice/KMPKCS8Decoder.java @@ -1,4 +1,4 @@ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import com.android.javacard.seprovider.KMException; import javacard.framework.Util; @@ -10,19 +10,9 @@ public class KMPKCS8Decoder { 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[] 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 byte[] EC_CURVE; + public static byte[] RSA_ALGORITHM; + public static byte[] EC_ALGORITHM; private byte[] data; private short start; private short length; @@ -34,6 +24,22 @@ private KMPKCS8Decoder(){ cur = 0; } + public static void initStatics() { + EC_CURVE = new byte[] { + 0x06,0x08,0x2a,(byte)0x86,0x48,(byte)0xce,0x3d,0x03, + 0x01,0x07 + }; + RSA_ALGORITHM = new byte[] { + 0x06,0x09,0x2A,(byte)0x86,0x48,(byte)0x86, + (byte)0xF7,0x0D,0x01,0x01,0x01,0x05,0x00 + }; + EC_ALGORITHM = new byte[] { + 0x06,0x07,0x2a,(byte)0x86,0x48,(byte)0xce, + 0x3d,0x02,0x01,0x06,0x08,0x2a,(byte)0x86,0x48, + (byte)0xce,0x3d,0x03,0x01,0x07 + }; + } + public short decodeRsa(short blob){ init(blob); decodeCommon((short)0, RSA_ALGORITHM); @@ -53,10 +59,10 @@ public short decodeEcSubjectPublicKeyInfo(short blob) { short ecPublicInfo = KMByteBlob.instance(len); getBytes(ecPublicInfo); if(Util.arrayCompare( - KMByteBlob.cast(ecPublicInfo).getBuffer(), - KMByteBlob.cast(ecPublicInfo).getStartOff(), + KMByteBlob.getBuffer(ecPublicInfo), + KMByteBlob.getStartOff(ecPublicInfo), EC_ALGORITHM, - (short)0,KMByteBlob.cast(ecPublicInfo).length()) !=0){ + (short)0,KMByteBlob.length(ecPublicInfo)) !=0){ KMException.throwIt(KMError.UNKNOWN_ERROR); } len = header(ASN1_BIT_STRING); @@ -88,19 +94,19 @@ public short decodeRsaPrivateKey(short version){ len = header(ASN1_INTEGER); short privKey = KMByteBlob.instance(len); getBytes(privKey); - KMArray.cast(resp).add((short)0, modulus); - KMArray.cast(resp).add((short)1, pubKey); - KMArray.cast(resp).add((short)2, privKey); + KMArray.add(resp, (short)0, modulus); + KMArray.add(resp, (short)1, pubKey); + KMArray.add(resp, (short)2, privKey); return resp; } private void updateModulus(short blob) { - byte[] buffer = KMByteBlob.cast(blob).getBuffer(); - short startOff = KMByteBlob.cast(blob).getStartOff(); - short len = KMByteBlob.cast(blob).length(); + byte[] buffer = KMByteBlob.getBuffer(blob); + short startOff = KMByteBlob.getStartOff(blob); + short len = KMByteBlob.length(blob); if(0 == buffer[startOff] && len > 256) { - KMByteBlob.cast(blob).setStartOff(++startOff); - KMByteBlob.cast(blob).setLength(--len); + KMByteBlob.setStartOff(blob, ++startOff); + KMByteBlob.setLength(blob, --len); } } @@ -115,10 +121,10 @@ public void decodeCommon(short version, byte[] alg){ short blob = KMByteBlob.instance(len); getBytes(blob); if(Util.arrayCompare( - KMByteBlob.cast(blob).getBuffer(), - KMByteBlob.cast(blob).getStartOff(), + KMByteBlob.getBuffer(blob), + KMByteBlob.getStartOff(blob), alg, - (short)0,KMByteBlob.cast(blob).length()) !=0){ + (short)0,KMByteBlob.length(blob)) !=0){ KMException.throwIt(KMError.UNKNOWN_ERROR); } } @@ -144,8 +150,8 @@ public short decodeEcPrivateKey(short version){ 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); + KMArray.add(resp, (short)0, pubKey); + KMArray.add(resp, (short)1, privKey); return resp; } private void validateTag0IfPresent(){ @@ -174,9 +180,9 @@ private short getShort(){ } private void getBytes(short blob){ - short len = KMByteBlob.cast(blob).length(); - Util.arrayCopyNonAtomic(data, cur, KMByteBlob.cast(blob).getBuffer(), - KMByteBlob.cast(blob).getStartOff(), len); + short len = KMByteBlob.length(blob); + Util.arrayCopyNonAtomic(data, cur, KMByteBlob.getBuffer(blob), + KMByteBlob.getStartOff(blob), len); incrementCursor(len); } @@ -197,9 +203,9 @@ public static KMPKCS8Decoder instance() { } public void init(short blob) { - data = KMByteBlob.cast(blob).getBuffer(); - start = KMByteBlob.cast(blob).getStartOff(); - length = KMByteBlob.cast(blob).length(); + data = KMByteBlob.getBuffer(blob); + start = KMByteBlob.getStartOff(blob); + length = KMByteBlob.length(blob); cur = start; } diff --git a/Applet/src/com/android/javacard/keymaster/KMRepository.java b/Applet/src/com/android/javacard/kmdevice/KMRepository.java similarity index 90% rename from Applet/src/com/android/javacard/keymaster/KMRepository.java rename to Applet/src/com/android/javacard/kmdevice/KMRepository.java index 03791210..eef060ad 100644 --- a/Applet/src/com/android/javacard/keymaster/KMRepository.java +++ b/Applet/src/com/android/javacard/kmdevice/KMRepository.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import com.android.javacard.seprovider.KMException; import com.android.javacard.seprovider.KMUpgradable; @@ -67,6 +67,7 @@ public class KMRepository implements KMUpgradable { public static final short AUTH_TAG_LENGTH = 16; public static final short AUTH_TAG_COUNTER_SIZE = 4; public static final short AUTH_TAG_ENTRY_SIZE = (AUTH_TAG_LENGTH + AUTH_TAG_COUNTER_SIZE + 1); + public static byte[] zero; // Class Attributes private byte[] heap; @@ -78,6 +79,10 @@ public class KMRepository implements KMUpgradable { // Singleton instance private static KMRepository repository; + public static void initStatics() { + zero = new byte[]{0, 0, 0, 0, 0, 0, 0, 0}; + } + public static KMRepository instance() { return repository; } @@ -187,9 +192,7 @@ private void clearDataEntry(short id) { 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(); + Util.arrayFill(dataTable, dataPtr, dataLen, (byte) 0); } } @@ -202,8 +205,8 @@ private void writeDataEntry(short id, byte[] buf, short offset, short 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(); + Util.arrayCopy(buf, offset, dataTable, dataPtr, len); } else { if (len != dataLen) { KMException.throwIt(KMError.UNKNOWN_ERROR); @@ -246,19 +249,17 @@ 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()); + readDataEntry(id, KMByteBlob.getBuffer(blob), KMByteBlob.getStartOff(blob)); return blob; } return KMType.INVALID_VALUE; } - private static final byte[] zero = {0, 0, 0, 0, 0, 0, 0, 0}; - public short getOsVersion() { short blob = readData(BOOT_OS_VERSION); if (blob != KMType.INVALID_VALUE) { return KMInteger.uint_32( - KMByteBlob.cast(blob).getBuffer(), KMByteBlob.cast(blob).getStartOff()); + KMByteBlob.getBuffer(blob), KMByteBlob.getStartOff(blob)); } else { return KMInteger.uint_32(zero, (short) 0); } @@ -268,7 +269,7 @@ public short getVendorPatchLevel() { short blob = readData(VENDOR_PATCH_LEVEL); if (blob != KMType.INVALID_VALUE) { return KMInteger.uint_32( - KMByteBlob.cast(blob).getBuffer(), KMByteBlob.cast(blob).getStartOff()); + KMByteBlob.getBuffer(blob), KMByteBlob.getStartOff(blob)); } else { return KMInteger.uint_32(zero, (short) 0); } @@ -278,7 +279,7 @@ public short getOsPatch() { short blob = readData(BOOT_OS_PATCH_LEVEL); if (blob != KMType.INVALID_VALUE) { return KMInteger.uint_32( - KMByteBlob.cast(blob).getBuffer(), KMByteBlob.cast(blob).getStartOff()); + KMByteBlob.getBuffer(blob), KMByteBlob.getStartOff(blob)); } else { return KMInteger.uint_32(zero, (short) 0); } @@ -289,7 +290,7 @@ private boolean readBoolean(short id) { if (blob == KMType.INVALID_VALUE) { KMException.throwIt(KMError.INVALID_DATA); } - return (byte) ((getHeap())[KMByteBlob.cast(blob).getStartOff()]) == 0x01; + return (byte) ((getHeap())[KMByteBlob.getStartOff(blob)]) == 0x01; } public boolean getDeviceLock() { @@ -311,8 +312,8 @@ public boolean getBootEndedStatus() { public short getDeviceTimeStamp() { short blob = readData(DEVICE_LOCKED_TIME); if (blob != KMType.INVALID_VALUE) { - return KMInteger.uint_64(KMByteBlob.cast(blob).getBuffer(), - KMByteBlob.cast(blob).getStartOff()); + return KMInteger.uint_64(KMByteBlob.getBuffer(blob), + KMByteBlob.getStartOff(blob)); } else { return KMInteger.uint_64(zero, (short) 0); } @@ -387,7 +388,7 @@ private void writeAuthTagState(byte[] buf, short offset, byte state) { public boolean persistAuthTag(short authTag) { - if (KMByteBlob.cast(authTag).length() != AUTH_TAG_LENGTH) { + if (KMByteBlob.length(authTag) != AUTH_TAG_LENGTH) { KMException.throwIt(KMError.INVALID_INPUT_LENGTH); } @@ -396,8 +397,8 @@ public boolean persistAuthTag(short authTag) { byte[] scratchPad = getHeap(); writeAuthTagState(getHeap(), authTagEntry, (byte) 1); Util.arrayCopyNonAtomic( - KMByteBlob.cast(authTag).getBuffer(), - KMByteBlob.cast(authTag).getStartOff(), + KMByteBlob.getBuffer(authTag), + KMByteBlob.getStartOff(authTag), getHeap(), (short) (authTagEntry + 1), AUTH_TAG_LENGTH); Util.setShort(getHeap(), (short) (authTagEntry + AUTH_TAG_LENGTH + 1 + 2), (short) 1); @@ -427,7 +428,7 @@ public boolean isAuthTagPersisted(short authTag) { } private short findTag(short authTag) { - if (KMByteBlob.cast(authTag).length() != AUTH_TAG_LENGTH) { + if (KMByteBlob.length(authTag) != AUTH_TAG_LENGTH) { KMException.throwIt(KMError.INVALID_INPUT_LENGTH); } short index = 0; @@ -441,8 +442,8 @@ private short findTag(short authTag) { Util.arrayCompare( getHeap(), (short) (offset + 1), - KMByteBlob.cast(authTag).getBuffer(), - KMByteBlob.cast(authTag).getStartOff(), + KMByteBlob.getBuffer(authTag), + KMByteBlob.getStartOff(authTag), AUTH_TAG_LENGTH); if (found == 0) { return (short) (index + AUTH_TAG_1); @@ -459,8 +460,8 @@ public short getRateLimitedKeyCount(short authTag, byte[] out, short outOff) { if (tag != KMType.INVALID_VALUE) { blob = readData(tag); Util.arrayCopyNonAtomic( - KMByteBlob.cast(blob).getBuffer(), - (short) (KMByteBlob.cast(blob).getStartOff() + AUTH_TAG_LENGTH + 1), + KMByteBlob.getBuffer(blob), + (short) (KMByteBlob.getStartOff(blob) + AUTH_TAG_LENGTH + 1), out, outOff, AUTH_TAG_COUNTER_SIZE); @@ -476,13 +477,13 @@ public void setRateLimitedKeyCount(short authTag, byte[] buf, short off, short l Util.arrayCopyNonAtomic( buf, off, - KMByteBlob.cast(dataPtr).getBuffer(), - (short) (KMByteBlob.cast(dataPtr).getStartOff() + AUTH_TAG_LENGTH + 1), + KMByteBlob.getBuffer(dataPtr), + (short) (KMByteBlob.getStartOff(dataPtr) + AUTH_TAG_LENGTH + 1), len); writeDataEntry(tag, - KMByteBlob.cast(dataPtr).getBuffer(), - KMByteBlob.cast(dataPtr).getStartOff(), - KMByteBlob.cast(dataPtr).length()); + KMByteBlob.getBuffer(dataPtr), + KMByteBlob.getStartOff(dataPtr), + KMByteBlob.length(dataPtr)); } } diff --git a/Applet/src/com/android/javacard/keymaster/KMSimpleValue.java b/Applet/src/com/android/javacard/kmdevice/KMSimpleValue.java similarity index 89% rename from Applet/src/com/android/javacard/keymaster/KMSimpleValue.java rename to Applet/src/com/android/javacard/kmdevice/KMSimpleValue.java index 794f3f92..135f9142 100644 --- a/Applet/src/com/android/javacard/keymaster/KMSimpleValue.java +++ b/Applet/src/com/android/javacard/kmdevice/KMSimpleValue.java @@ -1,4 +1,4 @@ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import javacard.framework.ISO7816; import javacard.framework.ISOException; @@ -32,7 +32,7 @@ public short length() { return Util.getShort(heap, (short) (instanceTable[KM_SIMPLE_VALUE_OFFSET] + 1)); } - public static KMSimpleValue cast(short ptr) { + private static KMSimpleValue cast(short ptr) { if (heap[ptr] != SIMPLE_VALUE_TYPE) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } @@ -55,6 +55,10 @@ public byte getValue() { return heap[(short) (instanceTable[KM_SIMPLE_VALUE_OFFSET] + 3)]; } + public static byte getValue(short bPtr) { + return KMSimpleValue.cast(bPtr).getValue(); + } + private static boolean isSimpleValueValid(byte value) { switch (value) { case TRUE: diff --git a/Applet/src/com/android/javacard/keymaster/KMTag.java b/Applet/src/com/android/javacard/kmdevice/KMTag.java similarity index 83% rename from Applet/src/com/android/javacard/keymaster/KMTag.java rename to Applet/src/com/android/javacard/kmdevice/KMTag.java index 0cac460e..ccb68c95 100644 --- a/Applet/src/com/android/javacard/keymaster/KMTag.java +++ b/Applet/src/com/android/javacard/kmdevice/KMTag.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import com.android.javacard.seprovider.KMException; import javacard.framework.Util; @@ -29,11 +29,11 @@ * of KMType. */ public class KMTag extends KMType { - public static short getTagType(short ptr) { + public static short getKMTagType(short ptr) { return Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); } - public static short getKey(short ptr) { + public static short getKMTagKey(short ptr) { return Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2)); } @@ -49,7 +49,7 @@ public static void assertAbsence(short params, short tagType, short tagKey, shor } public static boolean isPresent(short params, short tagType, short tagKey){ - short tag = KMKeyParameters.findTag(tagType, tagKey, params); + short tag = KMKeyParameters.findTag(params, tagType, tagKey); return tag != KMType.INVALID_VALUE; } @@ -76,24 +76,24 @@ public static void assertTrue(boolean condition, short error){ } public static boolean isValidPublicExponent(short params) { - short pubExp = KMKeyParameters.findTag(KMType.ULONG_TAG, KMType.RSA_PUBLIC_EXPONENT, params); + short pubExp = KMKeyParameters.findTag(params, KMType.ULONG_TAG, KMType.RSA_PUBLIC_EXPONENT); if(pubExp == KMType.INVALID_VALUE){ return false; } - pubExp = KMIntegerTag.cast(pubExp).getValue(); - if(!(KMInteger.cast(pubExp).getShort() == 0x01 && - KMInteger.cast(pubExp).getSignificantShort() == 0x01)){ + pubExp = KMIntegerTag.getValue(pubExp); + if(!(KMInteger.getShort(pubExp) == 0x01 && + KMInteger.getSignificantShort(pubExp) == 0x01)){ return false; } return true; } public static boolean isValidKeySize(short params){ - short keysize = KMKeyParameters.findTag(KMType.UINT_TAG, KMType.KEYSIZE, params); + short keysize = KMKeyParameters.findTag(params, KMType.UINT_TAG, KMType.KEYSIZE); if(keysize == KMType.INVALID_VALUE){ return false; } short alg = KMEnumTag.getValue(KMType.ALGORITHM, params); - return KMIntegerTag.cast(keysize).isValidKeySize((byte)alg); + return KMIntegerTag.isValidKeySize(keysize, (byte)alg); } } diff --git a/Applet/src/com/android/javacard/keymaster/KMTextString.java b/Applet/src/com/android/javacard/kmdevice/KMTextString.java similarity index 74% rename from Applet/src/com/android/javacard/keymaster/KMTextString.java rename to Applet/src/com/android/javacard/kmdevice/KMTextString.java index 310cfa01..68f6da3d 100644 --- a/Applet/src/com/android/javacard/keymaster/KMTextString.java +++ b/Applet/src/com/android/javacard/kmdevice/KMTextString.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import javacard.framework.ISO7816; import javacard.framework.ISOException; @@ -25,8 +25,7 @@ * 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 short OFFSET_SIZE = 2; +public class KMTextString extends KMType { private static KMTextString prototype; @@ -48,9 +47,7 @@ public static short exp() { // 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)); + short ptr = KMType.instance(TEXT_STRING_TYPE, (short) (length )); Util.setShort(heap, (short)(ptr + 1), length); return ptr; } @@ -59,12 +56,12 @@ public static short instance(short length) { 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); + (short) (ptr + TLV_HEADER_SIZE), length); return ptr; } // cast the ptr to KMTextString - public static KMTextString cast(short ptr) { + private static KMTextString cast(short ptr) { if (heap[ptr] != TEXT_STRING_TYPE) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } @@ -77,4 +74,30 @@ public static KMTextString cast(short ptr) { protected short getBaseOffset() { return instanceTable[KM_TEXT_STRING_OFFSET]; } + + // Get the length of the blob + private short length() { + return Util.getShort(heap, (short) (getBaseOffset() + 1)); + } + + private byte[] getBuffer() { + return heap; + } + + // Get the start of blob + public short getStartOff() { + return Util.getShort(heap, (short)(getBaseOffset() + TLV_HEADER_SIZE)); + } + + public static short length(short bPtr) { + return cast(bPtr).length(); + } + + public static byte[] getBuffer(short bPtr) { + return cast(bPtr).getBuffer(); + } + + public static short getStartOff(short bPtr) { + return cast(bPtr).getStartOff(); + } } diff --git a/Applet/src/com/android/javacard/keymaster/KMType.java b/Applet/src/com/android/javacard/kmdevice/KMType.java similarity index 98% rename from Applet/src/com/android/javacard/keymaster/KMType.java rename to Applet/src/com/android/javacard/kmdevice/KMType.java index 5be6796d..4e86580a 100644 --- a/Applet/src/com/android/javacard/keymaster/KMType.java +++ b/Applet/src/com/android/javacard/kmdevice/KMType.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import javacard.framework.ISO7816; import javacard.framework.ISOException; @@ -370,15 +370,15 @@ public static void initialize() { KMType.heap = repository.getHeap(); } - public static byte getType(short ptr) { + public static byte getKMType(short ptr) { return heap[ptr]; } - public static short length(short ptr) { + public static short getKMTypeLength(short ptr) { return Util.getShort(heap, (short) (ptr + 1)); } - public static short getValue(short ptr) { + public static short getKMTypeValue(short ptr) { return Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); } diff --git a/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMUtils.java b/Applet/src/com/android/javacard/kmdevice/KMUtils.java similarity index 85% rename from Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMUtils.java rename to Applet/src/com/android/javacard/kmdevice/KMUtils.java index f21eb868..108f2378 100644 --- a/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMUtils.java +++ b/Applet/src/com/android/javacard/kmdevice/KMUtils.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import com.android.javacard.seprovider.KMException; @@ -22,42 +22,64 @@ 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 + public static byte[] oneSecMsec; // 1000 msec + public static byte[] oneMinMsec; // 60000 msec + public static byte[] oneHourMsec; // 3600000 msec + public static byte[] oneDayMsec; // 86400000 msec + public static byte[] oneMonthMsec; // 2629746000 msec + public static byte[] leapYearMsec; //31622400000; + public static byte[] yearMsec; //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[] firstJan2051 = { - 0, 0, 0x02, 0x53, 0x26, (byte) 0x0E, (byte) 0x1C, 0x00}; // 2556144000000 + public static byte[] fourYrsMsec;//126230400000 + public static byte[] firstJan2020; // 1577836800000 msec + public static byte[] firstJan2051; // 2556144000000 // 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 byte[] febMonthLeapMSec; //2505600000 + public static byte[] febMonthMsec; //2419200000 + public static byte[] ThirtyOneDaysMonthMsec;//2678400000 + public static byte[] ThirtDaysMonthMsec;//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 byte[] SEC_TO_MILLIS_SHIFT_POS; // -------------------------------------- + public static void initStatics() { + oneSecMsec = new byte[] { + 0, 0, 0, 0, 0, 0, 0x03, (byte) 0xE8}; // 1000 msec + oneMinMsec = new byte[] { + 0, 0, 0, 0, 0, 0, (byte) 0xEA, 0x60}; // 60000 msec + oneHourMsec = new byte[] { + 0, 0, 0, 0, 0, 0x36, (byte) 0xEE, (byte) 0x80}; // 3600000 msec + oneDayMsec = new byte[] { + 0, 0, 0, 0, 0x05, 0x26, 0x5C, 0x00}; // 86400000 msec + oneMonthMsec = new byte[] { + 0, 0, 0, 0, (byte) 0x9C, (byte) 0xBE, (byte) 0xBD, 0x50}; // 2629746000 msec + leapYearMsec = new byte[] { + 0, 0, 0, 0x07, (byte) 0x5C, (byte) 0xD7, (byte) 0x88, 0x00}; //31622400000; + yearMsec = new byte[] { + 0, 0, 0, 0x07, 0x57, (byte) 0xB1, 0x2C, 0x00}; //31536000000 + //Leap year(366) + 3 * 365 + fourYrsMsec = new byte[] { + 0, 0, 0, 0x1D, 0x63, (byte) 0xEB, 0x0C, 0x00};//126230400000 + firstJan2020 = new byte[] { + 0, 0, 0x01, 0x6F, 0x5E, 0x66, (byte) 0xE8, 0x00}; // 1577836800000 msec + firstJan2051 = new byte[] { + 0, 0, 0x02, 0x53, 0x26, (byte) 0x0E, (byte) 0x1C, 0x00}; // 2556144000000 + // msec + febMonthLeapMSec = new byte[] { + 0, 0, 0, 0, (byte) 0x95, 0x58, 0x6C, 0x00}; //2505600000 + febMonthMsec = new byte[] { + 0, 0, 0, 0, (byte) 0x90, 0x32, 0x10, 0x00}; //2419200000 + ThirtyOneDaysMonthMsec = new byte[] { + 0, 0, 0, 0, (byte) 0x9F, (byte) 0xA5, 0x24, 0x00};//2678400000 + ThirtDaysMonthMsec = new byte[] { + 0, 0, 0, 0, (byte) 0x9A, 0x7E, (byte) 0xC8, 0x00};//2592000000 + // Convert to milliseconds constants + SEC_TO_MILLIS_SHIFT_POS = new byte[] {9, 8, 7, 6, 5, 3}; + + } + public static short convertToDate(short time, byte[] scratchPad, boolean utcFlag) { @@ -70,10 +92,9 @@ public static short convertToDate(short time, byte[] scratchPad, 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()); + Util.arrayCopyNonAtomic(KMInteger.getBuffer(time), + KMInteger.getStartOff(time), scratchPad, + (short) (8 - KMInteger.length(time)), KMInteger.length(time)); // 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) { diff --git a/Applet/src/com/android/javacard/keymaster/KMVerificationToken.java b/Applet/src/com/android/javacard/kmdevice/KMVerificationToken.java similarity index 59% rename from Applet/src/com/android/javacard/keymaster/KMVerificationToken.java rename to Applet/src/com/android/javacard/kmdevice/KMVerificationToken.java index f10f049e..29d09f2b 100644 --- a/Applet/src/com/android/javacard/keymaster/KMVerificationToken.java +++ b/Applet/src/com/android/javacard/kmdevice/KMVerificationToken.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import javacard.framework.ISO7816; import javacard.framework.ISOException; @@ -41,24 +41,22 @@ public class KMVerificationToken extends KMType { private KMVerificationToken() { } - public static short exp1() { + public static short timeStampTokenExp() { short arrPtr = KMArray.instance((short) 3); - KMArray arr = KMArray.cast(arrPtr); - arr.add(CHALLENGE, KMInteger.exp()); - arr.add(TIMESTAMP, KMInteger.exp()); - arr.add(MAC1, KMByteBlob.exp()); + KMArray.add(arrPtr, CHALLENGE, KMInteger.exp()); + KMArray.add(arrPtr, TIMESTAMP, KMInteger.exp()); + KMArray.add(arrPtr, MAC1, KMByteBlob.exp()); return instance(arrPtr); } - public static short exp2() { + public static short verificationTokenExp() { short arrPtr = KMArray.instance((short) 5); - KMArray arr = KMArray.cast(arrPtr); - arr.add(CHALLENGE, KMInteger.exp()); - arr.add(TIMESTAMP, KMInteger.exp()); + KMArray.add(arrPtr, CHALLENGE, KMInteger.exp()); + KMArray.add(arrPtr, TIMESTAMP, KMInteger.exp()); //arr.add(PARAMETERS_VERIFIED, KMKeyParameters.exp()); - arr.add(PARAMETERS_VERIFIED, KMByteBlob.exp()); - arr.add(SECURITY_LEVEL, KMEnum.instance(KMType.HARDWARE_TYPE)); - arr.add(MAC2, KMByteBlob.exp()); + KMArray.add(arrPtr, PARAMETERS_VERIFIED, KMByteBlob.exp()); + KMArray.add(arrPtr, SECURITY_LEVEL, KMEnum.instance(KMType.HARDWARE_TYPE)); + KMArray.add(arrPtr, MAC2, KMByteBlob.exp()); return instance(arrPtr); } @@ -72,26 +70,23 @@ private static KMVerificationToken proto(short ptr) { public static short instance1() { 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(MAC1, KMByteBlob.instance((short) 0)); + KMArray.add(arrPtr, CHALLENGE, KMInteger.uint_16((short) 0)); + KMArray.add(arrPtr, TIMESTAMP, KMInteger.uint_16((short) 0)); + KMArray.add(arrPtr, MAC1, KMByteBlob.instance((short) 0)); return instance(arrPtr); } public static short instance2() { short arrPtr = KMArray.instance((short) 5); - KMArray arr = KMArray.cast(arrPtr); - arr.add(CHALLENGE, KMInteger.uint_16((short) 0)); - arr.add(TIMESTAMP, KMInteger.uint_16((short) 0)); - arr.add(PARAMETERS_VERIFIED, KMByteBlob.instance((short) 0)); - arr.add(SECURITY_LEVEL, KMEnum.instance(KMType.HARDWARE_TYPE, KMType.STRONGBOX)); - arr.add(MAC2, KMByteBlob.instance((short) 0)); + KMArray.add(arrPtr, CHALLENGE, KMInteger.uint_16((short) 0)); + KMArray.add(arrPtr, TIMESTAMP, KMInteger.uint_16((short) 0)); + KMArray.add(arrPtr, PARAMETERS_VERIFIED, KMByteBlob.instance((short) 0)); + KMArray.add(arrPtr, SECURITY_LEVEL, KMEnum.instance(KMType.HARDWARE_TYPE, KMType.STRONGBOX)); + KMArray.add(arrPtr, MAC2, KMByteBlob.instance((short) 0)); return instance(arrPtr); } public static short instance(short vals) { - KMArray arr = KMArray.cast(vals); - if (arr.length() != 3 && arr.length() != 5) { + if (KMArray.length(vals) != 3 && KMArray.length(vals) != 5) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } short ptr = KMType.instance(VERIFICATION_TOKEN_TYPE, (short) 2); @@ -99,7 +94,7 @@ public static short instance(short vals) { return ptr; } - public static KMVerificationToken cast(short ptr) { + private static KMVerificationToken cast(short ptr) { if (heap[ptr] != VERIFICATION_TOKEN_TYPE) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } @@ -116,33 +111,63 @@ public short getVals() { public short length() { short arrPtr = getVals(); - return KMArray.cast(arrPtr).length(); + return KMArray.length(arrPtr); } public short getChallenge() { short arrPtr = getVals(); - return KMArray.cast(arrPtr).get(CHALLENGE); + return KMArray.get(arrPtr, CHALLENGE); } public void setChallenge(short vals) { - KMInteger.cast(vals); + KMInteger.validate(vals); short arrPtr = getVals(); - KMArray.cast(arrPtr).add(CHALLENGE, vals); + KMArray.add(arrPtr, CHALLENGE, vals); } public short getTimestamp() { short arrPtr = getVals(); - return KMArray.cast(arrPtr).get(TIMESTAMP); + return KMArray.get(arrPtr, TIMESTAMP); } public void setTimestamp(short vals) { - KMInteger.cast(vals); + KMInteger.validate(vals); short arrPtr = getVals(); - KMArray.cast(arrPtr).add(TIMESTAMP, vals); + KMArray.add(arrPtr, TIMESTAMP, vals); } public short getMac(short macIndex) { short arrPtr = getVals(); - return KMArray.cast(arrPtr).get(macIndex); + return KMArray.get(arrPtr, macIndex); } + + + public static short getVals(short bPtr) { + return KMVerificationToken.cast(bPtr).getVals(); + } + + public static short length(short bPtr) { + return KMVerificationToken.cast(bPtr).length(); + } + + public static short getChallenge(short bPtr) { + return KMVerificationToken.cast(bPtr).getChallenge(); + } + + public static void setChallenge(short bPtr, short vals) { + KMVerificationToken.cast(bPtr).setChallenge(vals); + } + + public static short getTimestamp(short bPtr) { + return KMVerificationToken.cast(bPtr).getTimestamp(); + } + + public static void setTimestamp(short bPtr, short vals) { + KMVerificationToken.cast(bPtr).setTimestamp(vals); + } + + public static short getMac(short bPtr, short macIndex) { + return KMVerificationToken.cast(bPtr).getMac(macIndex); + } + } diff --git a/Applet/src/com/android/javacard/keymaster/RemotelyProvisionedComponentDevice.java b/Applet/src/com/android/javacard/kmdevice/RemotelyProvisionedComponentDevice.java similarity index 73% rename from Applet/src/com/android/javacard/keymaster/RemotelyProvisionedComponentDevice.java rename to Applet/src/com/android/javacard/kmdevice/RemotelyProvisionedComponentDevice.java index adc1684a..655f939a 100644 --- a/Applet/src/com/android/javacard/keymaster/RemotelyProvisionedComponentDevice.java +++ b/Applet/src/com/android/javacard/kmdevice/RemotelyProvisionedComponentDevice.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.javacard.keymaster; +package com.android.javacard.kmdevice; import com.android.javacard.seprovider.KMDeviceUniqueKey; import com.android.javacard.seprovider.KMException; @@ -47,49 +47,34 @@ public class RemotelyProvisionedComponentDevice { private static final byte BOOT_PATCH_LEVEL_ID = 0x02; private static final byte VENDOR_PATCH_LEVEL_ID = 0x03; // 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[] BOARD = {0x62, 0x6F, 0x61, 0x72, 0x64}; - 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[] ATTEST_ID_STATE = - {0x61, 0x74, 0x74, 0x5f, 0x69, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65}; + public static byte[] BRAND; + public static byte[] MANUFACTURER; + public static byte[] PRODUCT; + public static byte[] MODEL; + public static byte[] BOARD; + public static byte[] VB_STATE; + public static byte[] BOOTLOADER_STATE; + public static byte[] VB_META_DIGEST; + public static byte[] OS_VERSION; + public static byte[] SYSTEM_PATCH_LEVEL; + public static byte[] BOOT_PATCH_LEVEL; + public static byte[] VENDOR_PATCH_LEVEL; + public static byte[] DEVICE_INFO_VERSION; + public static byte[] SECURITY_LEVEL; + public static byte[] ATTEST_ID_STATE; // 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}; + public static byte[] VB_STATE_GREEN; + public static byte[] VB_STATE_YELLOW; + public static byte[] VB_STATE_ORANGE; + public static byte[] VB_STATE_RED; // 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}; + public static byte[] UNLOCKED; + public static byte[] LOCKED; // Device info CDDL schema version public static final byte DI_SCHEMA_VERSION = 1; - public static final byte[] DI_SECURITY_LEVEL = {0x73, 0x74, 0x72, 0x6F, 0x6E, 0x67, 0x62, 0x6F, - 0x78}; - public static final byte[] ATTEST_ID_LOCKED = {0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64}; - public static final byte[] ATTEST_ID_OPEN = {0x6f, 0x70, 0x65, 0x6e}; + public static byte[] DI_SECURITY_LEVEL; + public static byte[] ATTEST_ID_LOCKED; + public static byte[] ATTEST_ID_OPEN; private static final short MAX_SEND_DATA = 1024; // more data or no data private static final byte MORE_DATA = 0x01; // flag to denote more data to retrieve @@ -138,20 +123,70 @@ public class RemotelyProvisionedComponentDevice { private Object[] operation; private short[] dataIndex; public static Object[] authorizedEekRoots; + private KMKeymasterDevice KMAppletInst; + private KMCose kmCoseInst; - public RemotelyProvisionedComponentDevice(KMEncoder encoder, KMDecoder decoder, + public RemotelyProvisionedComponentDevice(KMKeymasterDevice KMApplet, KMEncoder encoder, KMDecoder decoder, KMRepository repository, KMSEProvider seProvider) { this.encoder = encoder; this.decoder = decoder; this.repository = repository; this.seProvider = seProvider; + this.KMAppletInst = KMApplet; 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); operation[0] = null; createAuthorizedEEKRoot(); + kmCoseInst = KMCose.getInstance(); } + public static void initStatics() { + // Device Info labels + BRAND = new byte[] {0x62, 0x72, 0x61, 0x6E, 0x64}; + MANUFACTURER = new byte[] {0x6D, 0x61, 0x6E, 0x75, 0x66, 0x61, 0x63, 0x74, 0x75, + 0x72, 0x65, 0x72}; + PRODUCT = new byte[] {0x70, 0x72, 0x6F, 0x64, 0x75, 0x63, 0x74}; + MODEL = new byte[] {0x6D, 0x6F, 0x64, 0x65, 0x6C}; + BOARD = new byte[] {0x62, 0x6F, 0x61, 0x72, 0x64}; + VB_STATE = new byte[] {0x76, 0x62, 0x5F, 0x73, 0x74, 0x61, 0x74, 0x65}; + BOOTLOADER_STATE = new byte[] + {0x62, 0x6F, 0x6F, 0x74, 0x6C, 0x6F, 0x61, 0x64, 0x65, 0x72, 0x5F, 0x73, 0x74, 0x61, 0x74, + 0x65}; + VB_META_DIGEST = new byte[] + {0X76, 0X62, 0X6D, 0X65, 0X74, 0X61, 0X5F, 0X64, 0X69, 0X67, 0X65, 0X73, 0X74}; + OS_VERSION = new byte[] {0x6F, 0x73, 0x5F, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, + 0x6E}; + SYSTEM_PATCH_LEVEL = new byte[] + {0x73, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x5F, 0x70, 0x61, 0x74, 0x63, 0x68, 0x5F, 0x6C, 0x65, + 0x76, 0x65, 0x6C}; + BOOT_PATCH_LEVEL = new byte[] + {0x62, 0x6F, 0x6F, 0x74, 0x5F, 0x70, 0x61, 0x74, 0x63, 0x68, 0x5F, 0x6C, 0x65, 0x76, 0x65, + 0x6C}; + VENDOR_PATCH_LEVEL = new byte[] + {0x76, 0x65, 0x6E, 0x64, 0x6F, 0x72, 0x5F, 0x70, 0x61, 0x74, 0x63, 0x68, 0x5F, 0x6C, 0x65, + 0x76, 0x65, 0x6C}; + DEVICE_INFO_VERSION = new byte[] + {0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E}; + SECURITY_LEVEL = new byte[] + {0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x5F, 0x6C, 0x65, 0x76, 0x65, 0x6C}; + ATTEST_ID_STATE = new byte[] + {0x61, 0x74, 0x74, 0x5f, 0x69, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65}; + // Verified boot state values + VB_STATE_GREEN = new byte[] {0x67, 0x72, 0x65, 0x65, 0x6E}; + VB_STATE_YELLOW = new byte[] {0x79, 0x65, 0x6C, 0x6C, 0x6F, 0x77}; + VB_STATE_ORANGE = new byte[] {0x6F, 0x72, 0x61, 0x6E, 0x67, 0x65}; + VB_STATE_RED = new byte[] {0x72, 0x65, 0x64}; + // Boot loader state values + UNLOCKED = new byte[] {0x75, 0x6E, 0x6C, 0x6F, 0x63, 0x6B, 0x65, 0x64}; + LOCKED = new byte[] {0x6C, 0x6F, 0x63, 0x6B, 0x65, 0x64}; + + DI_SECURITY_LEVEL = new byte[] {0x73, 0x74, 0x72, 0x6F, 0x6E, 0x67, 0x62, 0x6F, + 0x78}; + ATTEST_ID_LOCKED = new byte[] {0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64}; + ATTEST_ID_OPEN = new byte[] {0x6f, 0x70, 0x65, 0x6e}; + } + private void createAuthorizedEEKRoot() { if (authorizedEekRoots == null) { authorizedEekRoots = @@ -226,11 +261,10 @@ private void processGetRkpHwInfoCmd(APDU apdu) { // Author name - Google. final byte[] google = {0x47, 0x6F, 0x6F, 0x67, 0x6C, 0x65}; short respPtr = KMArray.instance((short) 3); - KMArray resp = KMArray.cast(respPtr); - resp.add((short) 0, KMInteger.uint_16(RKP_VERSION)); - resp.add((short) 1, KMByteBlob.instance(google, (short) 0, (short) google.length)); - resp.add((short) 2, KMInteger.uint_8(KMType.RKP_CURVE_P256)); - KMKeymasterApplet.sendOutgoing(apdu, respPtr); + KMArray.add(respPtr, (short) 0, KMInteger.uint_16(RKP_VERSION)); + KMArray.add(respPtr, (short) 1, KMByteBlob.instance(google, (short) 0, (short) google.length)); + KMArray.add(respPtr, (short) 2, KMInteger.uint_8(KMType.RKP_CURVE_P256)); + KMKeymasterDevice.sendOutgoing(apdu, respPtr); } /** @@ -239,32 +273,32 @@ private void processGetRkpHwInfoCmd(APDU apdu) { */ public void processGenerateRkpKey(APDU apdu) { short arr = KMArray.instance((short) 1); - KMArray.cast(arr).add((short) 0, KMSimpleValue.exp()); - arr = KMKeymasterApplet.receiveIncoming(apdu, arr); + KMArray.add(arr, (short) 0, KMSimpleValue.exp()); + arr = KMAppletInst.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(); + (KMSimpleValue.TRUE == KMSimpleValue.getValue(KMArray.get(arr, (short) 0))); + KMAppletInst.generateRkpKey(scratchPad, getEcAttestKeyParameters()); + short pubKey = KMKeymasterDevice.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); + KMArray.add(arr, (short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.add(arr, (short) 1, coseMac0); + KMArray.add(arr, (short) 2, KMKeymasterDevice.getPivateKey()); + KMKeymasterDevice.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); + KMArray.add(arr, (short) 0, KMInteger.exp()); // Array length + KMArray.add(arr, (short) 1, KMInteger.exp()); // Total length of the encoded CoseKeys. + KMArray.add(arr, (short) 2, KMSimpleValue.exp()); + arr = KMAppletInst.receiveIncoming(apdu, arr); // Re-purpose the apdu buffer as scratch pad. byte[] scratchPad = apdu.getBuffer(); // Generate ephemeral mac key. @@ -274,22 +308,22 @@ public void processBeginSendData(APDU apdu) throws Exception { 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()); + KMInteger.getShort(KMArray.get(arr, (short) 0)), + KMInteger.getShort(KMArray.get(arr, (short) 1))); // 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()); + KMInteger.getShort(KMArray.get(arr, (short) 0))); // 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()) ? + (KMSimpleValue.TRUE == KMSimpleValue.getValue(KMArray.get(arr, (short) 2))) ? TRUE : FALSE; // Store the current csr status, which is BEGIN. createEntry(GENERATE_CSR_PHASE, BYTE_SIZE); updateState(BEGIN); // Send response. - KMKeymasterApplet.sendError(apdu, KMError.OK); + KMAppletInst.sendError(apdu, KMError.OK); } catch (Exception e) { clearDataTable(); releaseOperation(); @@ -304,21 +338,21 @@ public void processUpdateKey(APDU apdu) throws Exception { 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()); + KMArray.add(arrInst, (short) 0, KMByteBlob.exp()); + KMArray.add(arrInst, (short) 1, headers); + KMArray.add(arrInst, (short) 2, KMByteBlob.exp()); + KMArray.add(arrInst, (short) 3, KMByteBlob.exp()); short arr = KMArray.exp(arrInst); - arr = KMKeymasterApplet.receiveIncoming(apdu, arr); - arrInst = KMArray.cast(arr).get((short) 0); + arr = KMAppletInst.receiveIncoming(apdu, arr); + arrInst = KMArray.get(arr, (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); + short length = KMKeymasterDevice.encodeToApduBuffer(coseKey, scratchPad, (short) 0, + KMKeymasterDevice.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. @@ -332,7 +366,7 @@ public void processUpdateKey(APDU apdu) throws Exception { // Update the csr state updateState(UPDATE); // Send response. - KMKeymasterApplet.sendError(apdu, KMError.OK); + KMAppletInst.sendError(apdu, KMError.OK); } catch (Exception e) { clearDataTable(); releaseOperation(); @@ -346,13 +380,13 @@ public void processUpdateEekChain(APDU apdu) throws Exception { 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()); + KMArray.add(arrInst, (short) 0, KMByteBlob.exp()); + KMArray.add(arrInst, (short) 1, headers); + KMArray.add(arrInst, (short) 2, KMByteBlob.exp()); + KMArray.add(arrInst, (short) 3, KMByteBlob.exp()); short arrSignPtr = KMArray.exp(arrInst); - arrInst = KMKeymasterApplet.receiveIncoming(apdu, arrSignPtr); - if (KMArray.cast(arrInst).length() == 0) { + arrInst = KMAppletInst.receiveIncoming(apdu, arrSignPtr); + if (KMArray.length(arrInst) == 0) { KMException.throwIt(KMError.STATUS_INVALID_EEK); } // Re-purpose the apdu buffer as scratch pad. @@ -361,13 +395,13 @@ public void processUpdateEekChain(APDU apdu) throws Exception { 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()); + short dataEntryIndex = createEntry(EEK_KEY_ID, KMByteBlob.length(eekKeyId)); Util.arrayCopyNonAtomic( - KMByteBlob.cast(eekKeyId).getBuffer(), - KMByteBlob.cast(eekKeyId).getStartOff(), + KMByteBlob.getBuffer(eekKeyId), + KMByteBlob.getStartOff(eekKeyId), data, dataEntryIndex, - KMByteBlob.cast(eekKeyId).length() + KMByteBlob.length(eekKeyId) ); // Convert the coseKey to a public key. short len = KMCoseKey.cast(eekKey).getEcdsa256PublicKey(scratchPad, (short) 0); @@ -375,7 +409,7 @@ public void processUpdateEekChain(APDU apdu) throws Exception { Util.arrayCopyNonAtomic(scratchPad, (short) 0, data, dataEntryIndex, len); // Update the state updateState(UPDATE); - KMKeymasterApplet.sendError(apdu, KMError.OK); + KMAppletInst.sendError(apdu, KMError.OK); } catch (Exception e) { clearDataTable(); releaseOperation(); @@ -388,21 +422,21 @@ public void processUpdateChallenge(APDU apdu) throws Exception { // 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); + KMArray.add(arr, (short) 0, KMByteBlob.exp()); + arr = KMAppletInst.receiveIncoming(apdu, arr); // Store the challenge in the data table. - short challenge = KMArray.cast(arr).get((short) 0); - short dataEntryIndex = createEntry(CHALLENGE, KMByteBlob.cast(challenge).length()); + short challenge = KMArray.get(arr, (short) 0); + short dataEntryIndex = createEntry(CHALLENGE, KMByteBlob.length(challenge)); Util.arrayCopyNonAtomic( - KMByteBlob.cast(challenge).getBuffer(), - KMByteBlob.cast(challenge).getStartOff(), + KMByteBlob.getBuffer(challenge), + KMByteBlob.getStartOff(challenge), data, dataEntryIndex, - KMByteBlob.cast(challenge).length() + KMByteBlob.length(challenge) ); // Update the state updateState(UPDATE); - KMKeymasterApplet.sendError(apdu, KMError.OK); + KMAppletInst.sendError(apdu, KMError.OK); } catch (Exception e) { clearDataTable(); releaseOperation(); @@ -433,9 +467,9 @@ public void processFinishSendData(APDU apdu) throws Exception { short deviceInfo = createDeviceInfo(scratchPad); // Generate Nonce for AES-GCM seProvider.newRandomNumber(scratchPad, (short) 0, - KMKeymasterApplet.AES_GCM_NONCE_LENGTH); + KMKeymasterDevice.AES_GCM_NONCE_LENGTH); short nonce = KMByteBlob.instance(scratchPad, (short) 0, - KMKeymasterApplet.AES_GCM_NONCE_LENGTH); + KMKeymasterDevice.AES_GCM_NONCE_LENGTH); // Initializes cipher instance. initAesGcmOperation(scratchPad, nonce); // Encode Enc_Structure as additional data for AES-GCM. @@ -444,19 +478,19 @@ public void processFinishSendData(APDU apdu) throws Exception { 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); + len = KMKeymasterDevice.encodeToApduBuffer(deviceInfo, scratchPad, + (short) 0, KMKeymasterDevice.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); + KMArray.add(arr, (short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.add(arr, (short) 1, pubKeysToSignMac); + KMArray.add(arr, (short) 2, encodedDeviceInfo); + KMArray.add(arr, (short) 3, coseEncryptProtectedHeader); + KMArray.add(arr, (short) 4, coseEncryptUnProtectedHeader); + KMArray.add(arr, (short) 5, partialCipherText); + KMArray.add(arr, (short) 6, KMInteger.uint_8(MORE_DATA)); + KMKeymasterDevice.sendOutgoing(apdu, arr); } catch (Exception e) { clearDataTable(); releaseOperation(); @@ -496,12 +530,12 @@ public void processGetResponse(APDU apdu) throws Exception { } 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); + KMArray.add(arr,(short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.add(arr,(short) 1, data); + KMArray.add(arr,(short) 2, recipientStructure); // represents there is more output to retrieve - KMArray.cast(arr).add((short) 3, KMInteger.uint_8(moreData)); - KMKeymasterApplet.sendOutgoing(apdu, arr); + KMArray.add(arr, (short) 3, KMInteger.uint_8(moreData)); + KMKeymasterDevice.sendOutgoing(apdu, arr); } catch (Exception e) { clearDataTable(); releaseOperation(); @@ -511,28 +545,28 @@ public void processGetResponse(APDU apdu) throws Exception { public void process(short ins, APDU apdu) throws Exception { switch (ins) { - case KMKeymasterApplet.INS_GET_RKP_HARDWARE_INFO: + case KMKeymasterDevice.INS_GET_RKP_HARDWARE_INFO: processGetRkpHwInfoCmd(apdu); break; - case KMKeymasterApplet.INS_GENERATE_RKP_KEY_CMD: + case KMKeymasterDevice.INS_GENERATE_RKP_KEY_CMD: processGenerateRkpKey(apdu); break; - case KMKeymasterApplet.INS_BEGIN_SEND_DATA_CMD: + case KMKeymasterDevice.INS_BEGIN_SEND_DATA_CMD: processBeginSendData(apdu); break; - case KMKeymasterApplet.INS_UPDATE_KEY_CMD: + case KMKeymasterDevice.INS_UPDATE_KEY_CMD: processUpdateKey(apdu); break; - case KMKeymasterApplet.INS_UPDATE_EEK_CHAIN_CMD: + case KMKeymasterDevice.INS_UPDATE_EEK_CHAIN_CMD: processUpdateEekChain(apdu); break; - case KMKeymasterApplet.INS_UPDATE_CHALLENGE_CMD: + case KMKeymasterDevice.INS_UPDATE_CHALLENGE_CMD: processUpdateChallenge(apdu); break; - case KMKeymasterApplet.INS_FINISH_SEND_DATA_CMD: + case KMKeymasterDevice.INS_FINISH_SEND_DATA_CMD: processFinishSendData(apdu); break; - case KMKeymasterApplet.INS_GET_RESPONSE_CMD: + case KMKeymasterDevice.INS_GET_RESPONSE_CMD: processGetResponse(apdu); break; default: @@ -568,8 +602,8 @@ private void updateOutputProcessingState(byte state) { private short getHmacKey(boolean testMode, byte[] scratchPad) { short macKey = KMByteBlob.instance(MAC_KEY_SIZE); - Util.arrayFillNonAtomic(KMByteBlob.cast(macKey).getBuffer(), - KMByteBlob.cast(macKey).getStartOff(), MAC_KEY_SIZE, (byte) 0); + Util.arrayFillNonAtomic(KMByteBlob.getBuffer(macKey), + KMByteBlob.getStartOff(macKey), MAC_KEY_SIZE, (byte) 0); if (!testMode) { Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) (2 * MAC_KEY_SIZE), (byte) 0); short len = @@ -583,8 +617,8 @@ private short getHmacKey(boolean testMode, byte[] scratchPad) { KMCose.MAC_DERIVE_KEY_CTX, (short) 0, (short) KMCose.MAC_DERIVE_KEY_CTX.length, - KMByteBlob.cast(macKey).getBuffer(), - KMByteBlob.cast(macKey).getStartOff(), + KMByteBlob.getBuffer(macKey), + KMByteBlob.getStartOff(macKey), MAC_KEY_SIZE ); if (len != MAC_KEY_SIZE) { @@ -611,18 +645,18 @@ private short validateAndExtractPublicKey(short coseMacPtr, byte[] scratchPad) { short macKey = getHmacKey(testMode, scratchPad); // 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()); + short ptr = KMArray.get(coseMacPtr, KMCose.COSE_MAC0_PROTECTED_PARAMS_OFFSET); + ptr = decoder.decode(coseHeadersExp, KMByteBlob.getBuffer(ptr), + KMByteBlob.getStartOff(ptr), KMByteBlob.length(ptr)); if (!KMCoseHeaders.cast(ptr).isDataValid(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()); + ptr = KMArray.get(coseMacPtr, KMCose.COSE_MAC0_PAYLOAD_OFFSET); + ptr = decoder.decode(coseKeyExp, KMByteBlob.getBuffer(ptr), + KMByteBlob.getStartOff(ptr), KMByteBlob.length(ptr)); if (!KMCoseKey.cast(ptr).isDataValid(KMCose.COSE_KEY_TYPE_EC2, KMType.INVALID_VALUE, KMCose.COSE_ALG_ES256, KMType.INVALID_VALUE, KMCose.COSE_ECCURVE_256)) { @@ -638,25 +672,24 @@ private short validateAndExtractPublicKey(short coseMacPtr, byte[] scratchPad) { // Compute CoseMac Structure and compare the macs. short macStructure = - KMCose.constructCoseMacStructure(KMArray.cast(coseMacPtr).get( + kmCoseInst.constructCoseMacStructure(KMArray.get(coseMacPtr, 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); + KMArray.get(coseMacPtr, KMCose.COSE_MAC0_PAYLOAD_OFFSET)); + short encodedLen = KMKeymasterDevice.encodeToApduBuffer(macStructure, scratchPad, (short) 0, + KMKeymasterDevice.MAX_COSE_BUF_SIZE); - short hmacLen = seProvider.hmacSign(KMByteBlob.cast(macKey).getBuffer(), - KMByteBlob.cast(macKey).getStartOff(), + short hmacLen = seProvider.hmacSign(KMByteBlob.getBuffer(macKey), + KMByteBlob.getStartOff(macKey), (short) 32, scratchPad, (short) 0, encodedLen, scratchPad, encodedLen); - if (hmacLen != KMByteBlob.cast( - KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_TAG_OFFSET)).length()) { + if (hmacLen != KMByteBlob.length(KMArray.get(coseMacPtr, KMCose.COSE_MAC0_TAG_OFFSET))) { 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(), + KMByteBlob.getBuffer(KMArray.get(coseMacPtr, KMCose.COSE_MAC0_TAG_OFFSET)), + KMByteBlob.getStartOff(KMArray.get(coseMacPtr, KMCose.COSE_MAC0_TAG_OFFSET)), hmacLen)) { KMException.throwIt(KMError.STATUS_INVALID_MAC); } @@ -676,8 +709,7 @@ private short validateAndExtractPublicKey(short coseMacPtr, byte[] scratchPad) { private short validateAndExtractEekPub(short eekArr, byte[] scratchPad) { short leafPubKey = 0; try { - leafPubKey = - KMKeymasterApplet.validateCertChain( + leafPubKey = KMAppletInst.validateCertChain( (TRUE == data[getEntry(TEST_MODE)]) ? false : true, // validate EEK root KMCose.COSE_ALG_ES256, KMCose.COSE_ALG_ECDH_ES_HKDF_256, @@ -727,28 +759,28 @@ private void constructPartialPubKeysToSignMac(byte[] scratchPad, short arrayLeng short encodedCoseKeysLen) { short ptr; short len; - short headerPtr = KMCose.constructHeaders( + short headerPtr = kmCoseInst.constructHeaders( 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); + len = KMKeymasterDevice.encodeToApduBuffer(headerPtr, scratchPad, (short) 0, + KMKeymasterDevice.MAX_COSE_BUF_SIZE); short protectedHeader = KMByteBlob.instance(scratchPad, (short) 0, len); // create MAC_Structure ptr = - KMCose.constructCoseMacStructure(protectedHeader, + kmCoseInst.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); + len = KMKeymasterDevice.encodeToApduBuffer(ptr, scratchPad, (short) 0, + KMKeymasterDevice.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); + KMArray.add(arrPtr, i, KMType.INVALID_VALUE); } arrayLength = encoder.getEncodedLength(arrPtr); short bufIndex = repository.alloc((short) 6); @@ -763,33 +795,6 @@ private void constructPartialPubKeysToSignMac(byte[] scratchPad, short arrayLeng ((KMOperation) operation[0]).update(scratchPad, (short) 0, (short) (len + partialPayloadLen)); } - private static short getAttestKeyParameters() { - 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 short createSignedMac(KMDeviceUniqueKey deviceUniqueKey, byte[] scratchPad, short deviceMapPtr, short pubKeysToSign) { // Challenge @@ -802,28 +807,28 @@ private short createSignedMac(KMDeviceUniqueKey deviceUniqueKey, byte[] scratchP /* 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); + KMArray.add(aad, (short) 0, challengePtr); + KMArray.add(aad, (short) 1, deviceMapPtr); + KMArray.add(aad, (short) 2, pubKeysToSign); + aad = KMKeymasterDevice.encodeToApduBuffer(aad, scratchPad, + (short) 0, KMKeymasterDevice.MAX_COSE_BUF_SIZE); aad = KMByteBlob.instance(scratchPad, (short) 0, aad); /* construct protected header */ - short protectedHeaders = KMCose.constructHeaders( + short protectedHeaders = kmCoseInst.constructHeaders( 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 = KMKeymasterDevice.encodeToApduBuffer(protectedHeaders, scratchPad, + (short) 0, KMKeymasterDevice.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); + kmCoseInst.constructCoseSignStructure(protectedHeaders, aad, ephmeralMacKey); + signStructure = KMKeymasterDevice.encodeToApduBuffer(signStructure, scratchPad, + (short) 0, KMKeymasterDevice.MAX_COSE_BUF_SIZE); short len = seProvider.ecSign256( deviceUniqueKey, @@ -840,7 +845,7 @@ private short createSignedMac(KMDeviceUniqueKey deviceUniqueKey, byte[] scratchP unprotectedHeader = KMCoseHeaders.instance(unprotectedHeader); /* construct Cose_Sign1 */ - return KMCose.constructCoseSign1(protectedHeaders, unprotectedHeader, + return kmCoseInst.constructCoseSign1(protectedHeaders, unprotectedHeader, ephmeralMacKey, signStructure); } @@ -933,11 +938,11 @@ private short createDeviceInfo(byte[] scratchpad) { short index = 0; while (index < (short) deviceIds.length) { if (deviceIds[index] != KMType.INVALID_VALUE) { - KMMap.cast(map).add(mapIndex++, deviceIds[index], deviceIds[(short) (index + 1)]); + KMMap.add(map, mapIndex++, deviceIds[index], deviceIds[(short) (index + 1)]); } index += 2; } - KMMap.cast(map).canonicalize(); + KMMap.canonicalize(map); return map; } @@ -1025,8 +1030,8 @@ private short getBootParams(byte bootParam, byte[] scratchPad) { if (bootParam == OS_VERSION_ID) { value = KMTextString - .instance(KMInteger.cast(value).getBuffer(), KMInteger.cast(value).getStartOff(), - KMInteger.cast(value).length()); + .instance(KMInteger.getBuffer(value), KMInteger.getStartOff(value), + KMInteger.length(value)); } return value; } @@ -1044,23 +1049,23 @@ private short ecdhHkdfDeriveKey(byte[] privKeyA, short privKeyAOff, short privKe key = KMByteBlob.instance(scratchPad, (short) 0, key); short kdfContext = - KMCose.constructKdfContext(pubKeyA, pubKeyAOff, pubKeyALen, pubKeyB, pubKeyBOff, pubKeyBLen, + kmCoseInst.constructKdfContext(pubKeyA, pubKeyAOff, pubKeyALen, pubKeyB, pubKeyBOff, pubKeyBLen, true); - kdfContext = KMKeymasterApplet - .encodeToApduBuffer(kdfContext, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + kdfContext = KMKeymasterDevice + .encodeToApduBuffer(kdfContext, scratchPad, (short) 0, KMKeymasterDevice.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(), + KMByteBlob.getBuffer(key), + KMByteBlob.getStartOff(key), + KMByteBlob.length(key), scratchPad, (short) 0, (short) 32, - KMByteBlob.cast(kdfContext).getBuffer(), - KMByteBlob.cast(kdfContext).getStartOff(), - KMByteBlob.cast(kdfContext).length(), + KMByteBlob.getBuffer(kdfContext), + KMByteBlob.getStartOff(kdfContext), + KMByteBlob.length(kdfContext), scratchPad, (short) 32, // offset (short) 32 // Length of expected output. @@ -1089,8 +1094,8 @@ private short generateEphemeralEcKey(byte[] scratchPad) { Util.arrayCopyNonAtomic( scratchPad, (short) 0, - KMByteBlob.cast(ptr).getBuffer(), - KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.getBuffer(ptr), + KMByteBlob.getStartOff(ptr), lengths[0]); //Store ephemeral public key in data table for later usage. short dataEntryIndex = createEntry(EPHEMERAL_PUB_KEY, lengths[1]); @@ -1129,9 +1134,9 @@ private void initAesGcmOperation(byte[] scratchPad, short nonce) { // Generate session key short sessionKeyLen = ecdhHkdfDeriveKey( - KMByteBlob.cast(privKey).getBuffer(), /* Ephemeral Private Key */ - KMByteBlob.cast(privKey).getStartOff(), - KMByteBlob.cast(privKey).length(), + KMByteBlob.getBuffer(privKey), /* Ephemeral Private Key */ + KMByteBlob.getStartOff(privKey), + KMByteBlob.length(privKey), data, /* Ephemeral Public key */ pubKeyIndex, getEntryLength(EPHEMERAL_PUB_KEY), @@ -1151,10 +1156,10 @@ private void initAesGcmOperation(byte[] scratchPad, short nonce) { 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) + KMByteBlob.getBuffer(nonce), /* nonce */ + KMByteBlob.getStartOff(nonce), + KMByteBlob.length(nonce), + (short) (KMKeymasterDevice.AES_GCM_AUTH_TAG_LENGTH * 8) ); if (operation[0] == null) { KMException.throwIt(KMError.STATUS_FAILED); @@ -1162,22 +1167,22 @@ private void initAesGcmOperation(byte[] scratchPad, short nonce) { } private short processRecipientStructure(byte[] scratchPad) { - short protectedHeaderRecipient = KMCose.constructHeaders( + short protectedHeaderRecipient = kmCoseInst.constructHeaders( 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 + protectedHeaderRecipient = KMKeymasterDevice .encodeToApduBuffer(protectedHeaderRecipient, scratchPad, (short) 0, - KMKeymasterApplet.MAX_COSE_BUF_SIZE); + KMKeymasterDevice.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( + kmCoseInst.constructCoseKey( KMInteger.uint_8(KMCose.COSE_KEY_TYPE_EC2), KMType.INVALID_VALUE, KMNInteger.uint_8(KMCose.COSE_ALG_ES256), @@ -1192,11 +1197,11 @@ private short processRecipientStructure(byte[] scratchPad) { short keyIdentifierPtr = KMByteBlob .instance(data, getEntry(EEK_KEY_ID), getEntryLength(EEK_KEY_ID)); short unprotectedHeaderRecipient = - KMCose.constructHeaders(KMType.INVALID_VALUE, keyIdentifierPtr, KMType.INVALID_VALUE, + kmCoseInst.constructHeaders(KMType.INVALID_VALUE, keyIdentifierPtr, KMType.INVALID_VALUE, coseKey); // Construct recipients structure. - return KMCose.constructRecipientsStructure(protectedHeaderRecipient, unprotectedHeaderRecipient, + return kmCoseInst.constructRecipientsStructure(protectedHeaderRecipient, unprotectedHeaderRecipient, KMSimpleValue.instance(KMSimpleValue.NULL)); } @@ -1244,9 +1249,9 @@ private short processBcc(byte[] scratchPad) { 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); + short bcc = KMAppletInst.generateBcc(true, scratchPad); + len = KMKeymasterDevice + .encodeToApduBuffer(bcc, scratchPad, (short) 0, KMKeymasterDevice.MAX_COSE_BUF_SIZE); } else { byte[] bcc = seProvider.getBootCertificateChain(); len = Util.getShort(bcc, (short) 0); @@ -1266,19 +1271,19 @@ private short processBcc(byte[] scratchPad) { // AAD is the CoseEncrypt structure private void processAesGcmUpdateAad(byte[] scratchPad) { - short protectedHeader = KMCose.constructHeaders( + short protectedHeader = kmCoseInst.constructHeaders( 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 = KMKeymasterDevice.encodeToApduBuffer(protectedHeader, scratchPad, (short) 0, + KMKeymasterDevice.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); + kmCoseInst.constructCoseEncryptStructure(protectedHeader, KMByteBlob.instance((short) 0)); + coseEncryptStr = KMKeymasterDevice.encodeToApduBuffer(coseEncryptStr, scratchPad, (short) 0, + KMKeymasterDevice.MAX_COSE_BUF_SIZE); ((KMOperation) operation[0]).updateAAD(scratchPad, (short) 0, coseEncryptStr); } @@ -1291,13 +1296,13 @@ private short processSignedMac(byte[] scratchPad, short pubKeysToSignMac, short //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); + KMArray.add(arr, (short) 0, signedMac); + KMArray.add(arr, (short) 1, KMType.INVALID_VALUE); if (arrLength == 3) { - KMArray.cast(arr).add((short) 2, KMType.INVALID_VALUE); + KMArray.add(arr, (short) 2, KMType.INVALID_VALUE); } - short len = KMKeymasterApplet - .encodeToApduBuffer(arr, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + short len = KMKeymasterDevice + .encodeToApduBuffer(arr, scratchPad, (short) 0, KMKeymasterDevice.MAX_COSE_BUF_SIZE); short cipherTextLen = ((KMOperation) operation[0]) .update(scratchPad, (short) 0, len, scratchPad, len); Util.arrayCopyNonAtomic( @@ -1312,92 +1317,71 @@ private short processSignedMac(byte[] scratchPad, short pubKeysToSignMac, short private short getCoseEncryptProtectedHeader(byte[] scratchPad) { // CoseEncrypt protected headers. - short protectedHeader = KMCose.constructHeaders( + short protectedHeader = kmCoseInst.constructHeaders( 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 = KMKeymasterDevice.encodeToApduBuffer(protectedHeader, scratchPad, (short) 0, + KMKeymasterDevice.MAX_COSE_BUF_SIZE); return KMByteBlob.instance(scratchPad, (short) 0, protectedHeader); } private short getCoseEncryptUnprotectedHeader(byte[] scratchPad, short nonce) { /* CoseEncrypt unprotected headers */ - return KMCose + return kmCoseInst .constructHeaders(KMType.INVALID_VALUE, KMType.INVALID_VALUE, nonce, KMType.INVALID_VALUE); } - private short constructCoseEncryptHeaders(byte[] scratchPad, short nonce) { - // CoseEncrypt protected headers. - short protectedHeader = KMCose.constructHeaders( - 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); - /* CoseEncrypt unprotected headers */ - short unprotectedHeader = - KMCose.constructHeaders(KMType.INVALID_VALUE, KMType.INVALID_VALUE, nonce, - KMType.INVALID_VALUE); - // Construct partial CoseEncrypt - return KMCose.constructCoseEncrypt(protectedHeader, unprotectedHeader, KMType.INVALID_VALUE, - KMType.INVALID_VALUE); - } - - private short constructCoseMacForRkpKey(boolean testMode, byte[] scratchPad, short pubKey) { // prepare cosekey short coseKey = - KMCose.constructCoseKey( + kmCoseInst.constructCoseKey( 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(), + KMByteBlob.getBuffer(pubKey), + KMByteBlob.getStartOff(pubKey), + KMByteBlob.length(pubKey), 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 len = KMKeymasterDevice + .encodeToApduBuffer(coseKey, scratchPad, (short) 0, KMKeymasterDevice.MAX_COSE_BUF_SIZE); short payload = KMByteBlob.instance(scratchPad, (short) 0, len); // Get the mackey. short macKey = getHmacKey(testMode, scratchPad); // Prepare protected header, which is required to construct the COSE_MAC0 - short headerPtr = KMCose.constructHeaders( + short headerPtr = kmCoseInst.constructHeaders( 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); + len = KMKeymasterDevice + .encodeToApduBuffer(headerPtr, scratchPad, (short) 0, KMKeymasterDevice.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); + kmCoseInst.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); + len = KMKeymasterDevice.encodeToApduBuffer(macStructure, scratchPad, (short) 0, + KMKeymasterDevice.MAX_COSE_BUF_SIZE); // HMAC Sign. short hmacLen = seProvider - .hmacSign(KMByteBlob.cast(macKey).getBuffer(), KMByteBlob.cast(macKey).getStartOff(), + .hmacSign(KMByteBlob.getBuffer(macKey), KMByteBlob.getStartOff(macKey), (short) 32, scratchPad, (short) 0, len, scratchPad, len); // Create COSE_MAC0 object short coseMac0 = - KMCose + kmCoseInst .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); + len = KMKeymasterDevice + .encodeToApduBuffer(coseMac0, scratchPad, (short) 0, KMKeymasterDevice.MAX_COSE_BUF_SIZE); return KMByteBlob.instance(scratchPad, (short) 0, len); } @@ -1409,22 +1393,22 @@ private short getEcAttestKeyParameters() { .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); + KMByteBlob.add(byteBlob, (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); + KMByteBlob.add(byteBlob, (short) 0, KMType.ATTEST_KEY); short purpose = KMEnumArrayTag.instance(KMType.PURPOSE, byteBlob); - KMArray.cast(arrPtr).add(tagIndex++, purpose); + KMArray.add(arrPtr, 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); + KMArray.add(arrPtr, tagIndex++, KMEnumTag.instance(KMType.ALGORITHM, KMType.EC)); + KMArray.add(arrPtr, tagIndex++, keySize); + KMArray.add(arrPtr, tagIndex++, digest); // Curve - P256 - KMArray.cast(arrPtr).add(tagIndex++, KMEnumTag.instance(KMType.ECCURVE, KMType.P_256)); + KMArray.add(arrPtr, 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)); + KMArray.add(arrPtr, tagIndex, KMBoolTag.instance(KMType.NO_AUTH_REQUIRED)); return KMKeyParameters.instance(arrPtr); } } From fc001e9049475bc76923e2b68c83798d21dd79a2 Mon Sep 17 00:00:00 2001 From: "avinash.hedage" Date: Thu, 20 Jan 2022 18:42:51 +0000 Subject: [PATCH 2/5] merged cast optimization changes, ST comment changes and package restructure changes --- .../javacard/kmapplet/KMAndroidSEApplet.java | 23 +- .../android/javacard/seprovider/KMAESKey.java | 2 + .../seprovider/KMAndroidSEProvider.java | 16 +- .../seprovider/KMECDeviceUniqueKey.java | 2 + .../javacard/seprovider/KMECPrivateKey.java | 2 + .../javacard/seprovider/KMHmacKey.java | 3 + .../seprovider/KMKeymasterProvision.java | 30 +- .../seprovider/KMKeymintProvision.java | 3 + .../javacard/seprovider/KMOperationImpl.java | 3 +- .../javacard/seprovider/KMPoolManager.java | 3 + .../KMRsa2048NoDigestSignature.java | 2 + .../javacard/kmdevice}/KMAttestationCert.java | 2 +- .../kmdevice/KMAttestationCertImpl.java | 9 +- .../javacard/kmdevice}/KMAttestationKey.java | 2 +- .../android/javacard/kmdevice/KMBoolTag.java | 1 - .../android/javacard/kmdevice/KMByteBlob.java | 2 +- .../javacard/kmdevice}/KMComputedHmacKey.java | 2 +- .../android/javacard/kmdevice/KMDecoder.java | 1 - .../javacard/kmdevice}/KMDeviceUniqueKey.java | 2 +- .../android/javacard/kmdevice/KMEncoder.java | 1 - .../android/javacard/kmdevice/KMError.java | 1 + .../javacard/kmdevice}/KMException.java | 2 +- .../android/javacard/kmdevice/KMInteger.java | 1 - .../javacard/kmdevice/KMKeyParameters.java | 1 - .../javacard/kmdevice/KMKeymasterDevice.java | 391 +++++++++++------- .../javacard/kmdevice/KMKeymintDevice.java | 137 +++++- .../javacard/kmdevice}/KMMasterKey.java | 7 +- .../javacard/kmdevice}/KMOperation.java | 2 +- .../javacard/kmdevice/KMOperationState.java | 2 - .../javacard/kmdevice/KMPKCS8Decoder.java | 1 - .../javacard/kmdevice}/KMPreSharedKey.java | 2 +- .../javacard/kmdevice/KMRepository.java | 3 - .../javacard/kmdevice}/KMSEProvider.java | 5 +- .../com/android/javacard/kmdevice/KMTag.java | 1 - .../javacard/kmdevice}/KMUpgradable.java | 2 +- .../android/javacard/kmdevice/KMUtils.java | 2 - .../RemotelyProvisionedComponentDevice.java | 5 - 37 files changed, 454 insertions(+), 222 deletions(-) rename Applet/{AndroidSEProvider/src/com/android/javacard/seprovider => src/com/android/javacard/kmdevice}/KMAttestationCert.java (99%) rename Applet/{AndroidSEProvider/src/com/android/javacard/seprovider => src/com/android/javacard/kmdevice}/KMAttestationKey.java (95%) rename Applet/{AndroidSEProvider/src/com/android/javacard/seprovider => src/com/android/javacard/kmdevice}/KMComputedHmacKey.java (50%) rename Applet/{AndroidSEProvider/src/com/android/javacard/seprovider => src/com/android/javacard/kmdevice}/KMDeviceUniqueKey.java (94%) rename Applet/{AndroidSEProvider/src/com/android/javacard/seprovider => src/com/android/javacard/kmdevice}/KMException.java (97%) rename Applet/{AndroidSEProvider/src/com/android/javacard/seprovider => src/com/android/javacard/kmdevice}/KMMasterKey.java (87%) rename Applet/{AndroidSEProvider/src/com/android/javacard/seprovider => src/com/android/javacard/kmdevice}/KMOperation.java (98%) rename Applet/{AndroidSEProvider/src/com/android/javacard/seprovider => src/com/android/javacard/kmdevice}/KMPreSharedKey.java (95%) rename Applet/{AndroidSEProvider/src/com/android/javacard/seprovider => src/com/android/javacard/kmdevice}/KMSEProvider.java (99%) rename Applet/{AndroidSEProvider/src/com/android/javacard/seprovider => src/com/android/javacard/kmdevice}/KMUpgradable.java (95%) diff --git a/Applet/AndroidSEApplet/src/com/android/javacard/kmapplet/KMAndroidSEApplet.java b/Applet/AndroidSEApplet/src/com/android/javacard/kmapplet/KMAndroidSEApplet.java index b9625035..010b7813 100644 --- a/Applet/AndroidSEApplet/src/com/android/javacard/kmapplet/KMAndroidSEApplet.java +++ b/Applet/AndroidSEApplet/src/com/android/javacard/kmapplet/KMAndroidSEApplet.java @@ -38,12 +38,11 @@ import com.android.javacard.kmdevice.KMTag; import com.android.javacard.kmdevice.KMTextString; import com.android.javacard.seprovider.KMAndroidSEProvider; -import com.android.javacard.seprovider.KMDeviceUniqueKey; import com.android.javacard.seprovider.KMError; -import com.android.javacard.seprovider.KMException; +import com.android.javacard.kmdevice.KMException; import com.android.javacard.seprovider.KMKeymasterProvision; import com.android.javacard.seprovider.KMKeymintProvision; -import com.android.javacard.seprovider.KMSEProvider; +import com.android.javacard.kmdevice.KMSEProvider; import com.android.javacard.seprovider.KMType; import javacard.framework.APDU; @@ -83,8 +82,6 @@ public class KMAndroidSEApplet extends Applet implements AppletEvent, OnUpgradeL // public static final byte BOOT_HASH_MAX_SIZE = 32; // Provision reporting status - - public static final byte KM_40 = 0x00; public static final byte KM_41 = 0x01; @@ -100,7 +97,7 @@ public class KMAndroidSEApplet extends Applet implements AppletEvent, OnUpgradeL private static KMKeymasterDevice kmDeviceInst; KMAndroidSEApplet() { - seProvider = new KMAndroidSEProvider(); + seProvider = (KMSEProvider) new KMAndroidSEProvider(); repositoryInst = new KMRepository(seProvider.isUpgrading()); decoderInst = new KMDecoder(); if(kmDevice == KM_40 || kmDevice == KM_41) { @@ -276,23 +273,15 @@ private short validateApdu(APDU apdu) { // Read the apdu header and buffer. byte[] apduBuffer = apdu.getBuffer(); byte apduClass = apduBuffer[ISO7816.OFFSET_CLA]; - short P1P2 = Util.getShort(apduBuffer, ISO7816.OFFSET_P1); // Validate APDU Header. if ((apduClass != kmDeviceInst.CLA_ISO7816_NO_SM_NO_CHAN)) { kmDeviceInst.sendError(apdu, KMError.UNSUPPORTED_CLA); return KMType.INVALID_VALUE; } - - if (kmDevice == KMKeymasterDevice.KEYMASTER_SPECIFICATION && - P1P2 != KMKeymasterDevice.KEYMASTER_HAL_VERSION) { - kmDeviceInst.sendError(apdu, KMError.INVALID_P1P2); - return KMType.INVALID_VALUE; - } - // Validate P1P2. - if (kmDevice == KMKeymasterDevice.KEYMINT_SPECIFICATION && - P1P2 != KMKeymasterDevice.KM_HAL_VERSION) { - kmDeviceInst.sendError(apdu, KMError.INVALID_P1P2); + short err = kmDeviceInst.validateApduHeader(apdu); + if (err != KMError.OK) { + kmDeviceInst.sendError(apdu, err); return KMType.INVALID_VALUE; } return apduBuffer[ISO7816.OFFSET_INS]; diff --git a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMAESKey.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMAESKey.java index 7e34065e..d92e26ec 100644 --- a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMAESKey.java +++ b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMAESKey.java @@ -17,6 +17,8 @@ import org.globalplatform.upgrade.Element; +import com.android.javacard.kmdevice.KMMasterKey; + import javacard.security.AESKey; public class KMAESKey implements KMMasterKey { diff --git a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMAndroidSEProvider.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMAndroidSEProvider.java index 488222f9..b9a8ec57 100644 --- a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMAndroidSEProvider.java +++ b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMAndroidSEProvider.java @@ -18,6 +18,15 @@ import org.globalplatform.upgrade.Element; import org.globalplatform.upgrade.UpgradeManager; +import com.android.javacard.kmdevice.KMAttestationKey; +import com.android.javacard.kmdevice.KMComputedHmacKey; +import com.android.javacard.kmdevice.KMDeviceUniqueKey; +import com.android.javacard.kmdevice.KMException; +import com.android.javacard.kmdevice.KMMasterKey; +import com.android.javacard.kmdevice.KMOperation; +import com.android.javacard.kmdevice.KMPreSharedKey; +import com.android.javacard.kmdevice.KMSEProvider; + import javacard.framework.JCSystem; import javacard.framework.Util; import javacard.security.AESKey; @@ -1239,12 +1248,16 @@ public void deleteAttestationIds() { attIdModel = null; } + @Override public boolean isPowerReset(boolean isForStatusUpdate) { boolean flag = false; if(isForStatusUpdate == false) { if (resetFlag[0] == POWER_RESET_TRUE) { resetFlag[0] = POWER_RESET_FALSE; flag = true; + if (poolMgr != null) { + poolMgr.powerReset(); + } } } else { if (resetFlag[1] == POWER_RESET_TRUE) { @@ -1252,9 +1265,6 @@ public boolean isPowerReset(boolean isForStatusUpdate) { flag = true; } } - if ((flag ==true) && (poolMgr != null)) { - poolMgr.powerReset(); - } return flag; } diff --git a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMECDeviceUniqueKey.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMECDeviceUniqueKey.java index 0f68de61..cdf0b48b 100644 --- a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMECDeviceUniqueKey.java +++ b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMECDeviceUniqueKey.java @@ -14,6 +14,8 @@ * limitations under the License. */ package com.android.javacard.seprovider; +import com.android.javacard.kmdevice.KMDeviceUniqueKey; + import javacard.security.ECPrivateKey; import javacard.security.ECPublicKey; import javacard.security.KeyPair; diff --git a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMECPrivateKey.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMECPrivateKey.java index 33372f97..75825c01 100644 --- a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMECPrivateKey.java +++ b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMECPrivateKey.java @@ -17,6 +17,8 @@ import org.globalplatform.upgrade.Element; +import com.android.javacard.kmdevice.KMAttestationKey; + import javacard.security.AESKey; import javacard.security.ECPrivateKey; import javacard.security.KeyPair; diff --git a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMHmacKey.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMHmacKey.java index a863bf88..58829ea9 100644 --- a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMHmacKey.java +++ b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMHmacKey.java @@ -17,6 +17,9 @@ import org.globalplatform.upgrade.Element; +import com.android.javacard.kmdevice.KMComputedHmacKey; +import com.android.javacard.kmdevice.KMPreSharedKey; + import javacard.security.HMACKey; public class KMHmacKey implements KMPreSharedKey, KMComputedHmacKey { diff --git a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMKeymasterProvision.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMKeymasterProvision.java index 318bc952..bc73479e 100644 --- a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMKeymasterProvision.java +++ b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMKeymasterProvision.java @@ -7,6 +7,7 @@ import com.android.javacard.kmdevice.KMKeyParameters; import com.android.javacard.kmdevice.KMKeymasterDevice; import com.android.javacard.kmdevice.KMRepository; +import com.android.javacard.kmdevice.KMSEProvider; import com.android.javacard.kmdevice.KMTag; import com.android.javacard.kmdevice.KMByteBlob; import com.android.javacard.kmdevice.KMByteTag; @@ -14,6 +15,7 @@ import com.android.javacard.kmdevice.KMEnum; import com.android.javacard.kmdevice.KMEnumArrayTag; import com.android.javacard.kmdevice.KMEnumTag; +import com.android.javacard.kmdevice.KMException; import javacard.framework.APDU; import javacard.framework.Util; @@ -204,8 +206,30 @@ public void processProvisionAttestationKey(APDU apdu) { } public void processProvisionAttestationCertDataCmd(APDU apdu) { - // TODO Handle this function properly - kmDeviceInst.processAttestationCertDataCmd(apdu); + // Buffer holds the corresponding offsets and lengths of the certChain, certIssuer and certExpiry + // in the bufferRef[0] buffer. + short var = KMByteBlob.instance((short) 12); + // These variables point to the appropriate positions in the var buffer. + short certChainPos = KMByteBlob.getStartOff(var); + short certIssuerPos = (short) (KMByteBlob.getStartOff(var) + 4); + short certExpiryPos = (short) (KMByteBlob.getStartOff(var) + 8); + short bufferLength = apdu.getIncomingLength(); + short bufferStartOffset = kmRepositroyInst.allocReclaimableMemory(bufferLength); + byte[] buffer = kmRepositroyInst.getHeap(); + kmDeviceInst.receiveIncomingCertData(apdu, buffer, bufferLength, + bufferStartOffset, KMByteBlob.getBuffer(var), KMByteBlob.getStartOff(var)); + // persist data + seProvider.persistProvisionData( + (byte[]) buffer, + Util.getShort(KMByteBlob.getBuffer(var), certChainPos), // offset + Util.getShort(KMByteBlob.getBuffer(var), (short) (certChainPos + 2)), // length + Util.getShort(KMByteBlob.getBuffer(var), certIssuerPos), // offset + Util.getShort(KMByteBlob.getBuffer(var), (short) (certIssuerPos + 2)), // length + Util.getShort(KMByteBlob.getBuffer(var), certExpiryPos), // offset + Util.getShort(KMByteBlob.getBuffer(var), (short) (certExpiryPos + 2))); // length + + // reclaim memory + kmRepositroyInst.reclaimMemory(bufferLength); provisionStatus |= (PROVISION_STATUS_ATTESTATION_CERT_CHAIN | PROVISION_STATUS_ATTESTATION_CERT_PARAMS); kmDeviceInst.sendError(apdu, KMError.OK); @@ -248,7 +272,7 @@ public void processProvisionPreSharedSecretCmd(APDU apdu) { public void processGetProvisionStatusCmd(APDU apdu) { short resp = KMArray.instance((short) 2); - KMArray.add(resp, (short) 0, buildErrorStatus(KMError.OK)); + KMArray.add(resp, (short) 0, kmDeviceInst.buildErrorStatus(KMError.OK)); KMArray.add(resp, (short) 1, KMInteger.uint_16(provisionStatus)); kmDeviceInst.sendOutgoing(apdu, resp); } diff --git a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMKeymintProvision.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMKeymintProvision.java index da4f670c..8e46e1cc 100644 --- a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMKeymintProvision.java +++ b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMKeymintProvision.java @@ -6,10 +6,13 @@ import com.android.javacard.kmdevice.KMCoseHeaders; import com.android.javacard.kmdevice.KMCoseKey; import com.android.javacard.kmdevice.KMDecoder; +import com.android.javacard.kmdevice.KMDeviceUniqueKey; +import com.android.javacard.kmdevice.KMException; import com.android.javacard.kmdevice.KMInteger; import com.android.javacard.kmdevice.KMKeymasterDevice; import com.android.javacard.kmdevice.KMMap; import com.android.javacard.kmdevice.KMRepository; +import com.android.javacard.kmdevice.KMSEProvider; import com.android.javacard.kmdevice.KMTextString; import javacard.framework.APDU; diff --git a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMOperationImpl.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMOperationImpl.java index baa8a0f9..4a96131d 100644 --- a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMOperationImpl.java +++ b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMOperationImpl.java @@ -15,8 +15,9 @@ */ package com.android.javacard.seprovider; +import com.android.javacard.kmdevice.KMException; +import com.android.javacard.kmdevice.KMOperation; import com.android.javacard.seprovider.KMError; -import com.android.javacard.seprovider.KMException; import com.android.javacard.seprovider.KMType; import javacard.framework.JCSystem; import javacard.framework.Util; diff --git a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMPoolManager.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMPoolManager.java index e3baf610..6d162079 100644 --- a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMPoolManager.java +++ b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMPoolManager.java @@ -14,6 +14,9 @@ * limitations under the License. */ package com.android.javacard.seprovider; +import com.android.javacard.kmdevice.KMException; +import com.android.javacard.kmdevice.KMOperation; + import javacard.framework.JCSystem; import javacard.security.KeyAgreement; import javacard.security.Signature; diff --git a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMRsa2048NoDigestSignature.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMRsa2048NoDigestSignature.java index 303e2b98..89c390bd 100644 --- a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMRsa2048NoDigestSignature.java +++ b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMRsa2048NoDigestSignature.java @@ -15,6 +15,8 @@ */ package com.android.javacard.seprovider; +import com.android.javacard.kmdevice.KMException; + import javacard.framework.Util; import javacard.security.CryptoException; import javacard.security.Key; diff --git a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMAttestationCert.java b/Applet/src/com/android/javacard/kmdevice/KMAttestationCert.java similarity index 99% rename from Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMAttestationCert.java rename to Applet/src/com/android/javacard/kmdevice/KMAttestationCert.java index 78ac7123..029191ee 100644 --- a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMAttestationCert.java +++ b/Applet/src/com/android/javacard/kmdevice/KMAttestationCert.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.javacard.seprovider; +package com.android.javacard.kmdevice; /** * The KMAttestationCert interface represents a X509 compliant attestation certificate required to diff --git a/Applet/src/com/android/javacard/kmdevice/KMAttestationCertImpl.java b/Applet/src/com/android/javacard/kmdevice/KMAttestationCertImpl.java index dc3a0557..ae478e16 100644 --- a/Applet/src/com/android/javacard/kmdevice/KMAttestationCertImpl.java +++ b/Applet/src/com/android/javacard/kmdevice/KMAttestationCertImpl.java @@ -15,13 +15,6 @@ */ package com.android.javacard.kmdevice; -import com.android.javacard.seprovider.KMAESKey; -import com.android.javacard.seprovider.KMAttestationCert; -import com.android.javacard.seprovider.KMAttestationKey; -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; @@ -1017,7 +1010,7 @@ public KMAttestationCert makeUniqueId(byte[] scratchPad, short scratchPadOff, scratchPadOff++; //Get the key data from the master key - KMAESKey aesKey = (KMAESKey) masterKey; + KMMasterKey aesKey = masterKey; short mKeyData = KMByteBlob.instance((short) (aesKey.getKeySizeBits() / 8)); aesKey.getKey( KMByteBlob.getBuffer(mKeyData), /* Key */ diff --git a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMAttestationKey.java b/Applet/src/com/android/javacard/kmdevice/KMAttestationKey.java similarity index 95% rename from Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMAttestationKey.java rename to Applet/src/com/android/javacard/kmdevice/KMAttestationKey.java index 1b8e334e..76054a7b 100644 --- a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMAttestationKey.java +++ b/Applet/src/com/android/javacard/kmdevice/KMAttestationKey.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.javacard.seprovider; +package com.android.javacard.kmdevice; /** * KMAttestationKey is a marker interface and the SE Provider has to implement this interface. diff --git a/Applet/src/com/android/javacard/kmdevice/KMBoolTag.java b/Applet/src/com/android/javacard/kmdevice/KMBoolTag.java index eac2e8ce..2886c3ab 100644 --- a/Applet/src/com/android/javacard/kmdevice/KMBoolTag.java +++ b/Applet/src/com/android/javacard/kmdevice/KMBoolTag.java @@ -16,7 +16,6 @@ package com.android.javacard.kmdevice; -import com.android.javacard.seprovider.KMException; import javacard.framework.ISO7816; import javacard.framework.ISOException; import javacard.framework.Util; diff --git a/Applet/src/com/android/javacard/kmdevice/KMByteBlob.java b/Applet/src/com/android/javacard/kmdevice/KMByteBlob.java index abe9e589..9348c6b7 100644 --- a/Applet/src/com/android/javacard/kmdevice/KMByteBlob.java +++ b/Applet/src/com/android/javacard/kmdevice/KMByteBlob.java @@ -74,7 +74,7 @@ public static void validate(short ptr) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } } - + // Add the byte public void add(short index, byte val) { short len = length(); diff --git a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMComputedHmacKey.java b/Applet/src/com/android/javacard/kmdevice/KMComputedHmacKey.java similarity index 50% rename from Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMComputedHmacKey.java rename to Applet/src/com/android/javacard/kmdevice/KMComputedHmacKey.java index 3b0aa405..7988e592 100644 --- a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMComputedHmacKey.java +++ b/Applet/src/com/android/javacard/kmdevice/KMComputedHmacKey.java @@ -1,4 +1,4 @@ -package com.android.javacard.seprovider; +package com.android.javacard.kmdevice; public interface KMComputedHmacKey { diff --git a/Applet/src/com/android/javacard/kmdevice/KMDecoder.java b/Applet/src/com/android/javacard/kmdevice/KMDecoder.java index 3fc78d61..b37740e7 100644 --- a/Applet/src/com/android/javacard/kmdevice/KMDecoder.java +++ b/Applet/src/com/android/javacard/kmdevice/KMDecoder.java @@ -16,7 +16,6 @@ package com.android.javacard.kmdevice; -import com.android.javacard.seprovider.KMException; import javacard.framework.ISO7816; import javacard.framework.ISOException; import javacard.framework.JCSystem; diff --git a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMDeviceUniqueKey.java b/Applet/src/com/android/javacard/kmdevice/KMDeviceUniqueKey.java similarity index 94% rename from Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMDeviceUniqueKey.java rename to Applet/src/com/android/javacard/kmdevice/KMDeviceUniqueKey.java index 08e60a3f..517a5b06 100644 --- a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMDeviceUniqueKey.java +++ b/Applet/src/com/android/javacard/kmdevice/KMDeviceUniqueKey.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.javacard.seprovider; +package com.android.javacard.kmdevice; public interface KMDeviceUniqueKey { diff --git a/Applet/src/com/android/javacard/kmdevice/KMEncoder.java b/Applet/src/com/android/javacard/kmdevice/KMEncoder.java index 9ae15e7f..39f6fa7a 100644 --- a/Applet/src/com/android/javacard/kmdevice/KMEncoder.java +++ b/Applet/src/com/android/javacard/kmdevice/KMEncoder.java @@ -16,7 +16,6 @@ package com.android.javacard.kmdevice; -import com.android.javacard.seprovider.KMException; import javacard.framework.ISO7816; import javacard.framework.ISOException; import javacard.framework.JCSystem; diff --git a/Applet/src/com/android/javacard/kmdevice/KMError.java b/Applet/src/com/android/javacard/kmdevice/KMError.java index 649507b3..6351c377 100644 --- a/Applet/src/com/android/javacard/kmdevice/KMError.java +++ b/Applet/src/com/android/javacard/kmdevice/KMError.java @@ -102,6 +102,7 @@ public class KMError { public static final short CRYPTO_UNINITIALIZED_KEY = 10012; //Generic Unknown error. public static final short GENERIC_UNKNOWN_ERROR = 10013; + public static final short PUBLIC_KEY_OPERATION = 10014; // Remote key provisioning error codes. public static final short STATUS_FAILED = 32000; diff --git a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMException.java b/Applet/src/com/android/javacard/kmdevice/KMException.java similarity index 97% rename from Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMException.java rename to Applet/src/com/android/javacard/kmdevice/KMException.java index c0b2431f..9c466343 100644 --- a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMException.java +++ b/Applet/src/com/android/javacard/kmdevice/KMException.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.javacard.seprovider; +package com.android.javacard.kmdevice; import javacard.framework.JCSystem; diff --git a/Applet/src/com/android/javacard/kmdevice/KMInteger.java b/Applet/src/com/android/javacard/kmdevice/KMInteger.java index 838de8b7..7825ef81 100644 --- a/Applet/src/com/android/javacard/kmdevice/KMInteger.java +++ b/Applet/src/com/android/javacard/kmdevice/KMInteger.java @@ -16,7 +16,6 @@ package com.android.javacard.kmdevice; -import com.android.javacard.seprovider.KMException; import javacard.framework.ISO7816; import javacard.framework.ISOException; import javacard.framework.Util; diff --git a/Applet/src/com/android/javacard/kmdevice/KMKeyParameters.java b/Applet/src/com/android/javacard/kmdevice/KMKeyParameters.java index 5ed35a0e..74980cbd 100644 --- a/Applet/src/com/android/javacard/kmdevice/KMKeyParameters.java +++ b/Applet/src/com/android/javacard/kmdevice/KMKeyParameters.java @@ -16,7 +16,6 @@ package com.android.javacard.kmdevice; -import com.android.javacard.seprovider.KMException; import javacard.framework.ISO7816; import javacard.framework.ISOException; import javacard.framework.Util; diff --git a/Applet/src/com/android/javacard/kmdevice/KMKeymasterDevice.java b/Applet/src/com/android/javacard/kmdevice/KMKeymasterDevice.java index 5bf413b3..520b82e7 100644 --- a/Applet/src/com/android/javacard/kmdevice/KMKeymasterDevice.java +++ b/Applet/src/com/android/javacard/kmdevice/KMKeymasterDevice.java @@ -16,10 +16,6 @@ package com.android.javacard.kmdevice; -import com.android.javacard.seprovider.KMAttestationCert; -import com.android.javacard.seprovider.KMDeviceUniqueKey; -import com.android.javacard.seprovider.KMException; -import com.android.javacard.seprovider.KMSEProvider; import javacard.framework.APDU; import javacard.framework.ISO7816; import javacard.framework.ISOException; @@ -91,7 +87,7 @@ public class KMKeymasterDevice { 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 + protected 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 @@ -150,8 +146,8 @@ public class KMKeymasterDevice { 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 KEYMASTER_SPECIFICATION = 0x01; - public static final byte KEYMINT_SPECIFICATION = 0x02; + // public static final byte KEYMASTER_SPECIFICATION = 0x01; + // public static final byte KEYMINT_SPECIFICATION = 0x02; // Constant // AddRngEntropy @@ -170,6 +166,8 @@ public class KMKeymasterDevice { private static final short HMAC_SHARED_PARAM_MAX_SIZE = 64; protected static final short MAX_CERT_SIZE = 2048; + protected static final short POWER_RESET_MASK_FLAG = (short) 0x4000; + //getHardwareInfo constants. private static byte[] JAVACARD_KEYMASTER_DEVICE; private static byte[] GOOGLE; @@ -419,17 +417,6 @@ public short mapCryptoErrorToKMError(short reason) { } } - protected short validateApduHeader(APDU apdu) { - // Read the apdu header and buffer. - byte[] apduBuffer = apdu.getBuffer(); - byte apduIns = apduBuffer[ISO7816.OFFSET_INS]; - // Validate whether INS can be supported - if (!(apduIns > KEYMINT_CMD_APDU_START && apduIns < KEYMINT_CMD_APDU_END)) { - ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); - } - return apduIns; - } - /** * Processes an incoming APDU and handles it using command objects. * @param apdu the incoming APDU @@ -447,7 +434,7 @@ public void process(APDU apdu) { sendError(apdu, KMError.OK); return; case INS_GENERATE_KEY_CMD: - processGenerateKey1(apdu); + processGenerateKey(apdu); break; case INS_ATTEST_KEY_CMD: processAttestKeyCmd(apdu); @@ -683,6 +670,22 @@ public short receiveIncoming(APDU apdu, short reqExp, byte[] reclamBuf, short bL return decoder.decode(reqExp, reclamBuf, bStartOffset, bLen); } + public void receiveIncomingCertData(APDU apdu, byte[] reclamBuf, short bLen, short bStartOffset, byte[] outBuf, short outOff) { + byte[] srcBuffer = apdu.getBuffer(); + short recvLen = apdu.setIncomingAndReceive(); + short srcOffset = apdu.getOffsetCdata(); + short index = bStartOffset; + while (recvLen > 0 && ((short) (index - bStartOffset) < bLen)) { + Util.arrayCopyNonAtomic(srcBuffer, srcOffset, reclamBuf, index, recvLen); + index += recvLen; + recvLen = apdu.receiveBytes(srcOffset); + } + decoder.decodeCertificateData((short) 3, + reclamBuf, bStartOffset, bLen, + outBuf, outOff); + } + + private void processGetHwInfoCmd(APDU apdu) { // No arguments expected short respPtr = getHardwareInfo(); @@ -743,7 +746,7 @@ private void processGetKeyCharacteristicsCmd(APDU apdu) { } // make response. short resp = KMArray.instance((short) 2); - KMArray.add(resp, (short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.add(resp, (short) 0, buildErrorStatus(KMError.OK)); KMArray.add(resp, (short) 1, data[KEY_CHARACTERISTICS]); sendOutgoing(apdu, resp); } @@ -758,7 +761,7 @@ private void processGetHmacSharingParamCmd(APDU apdu) { KMHmacSharingParameters.setSeed(params, seed); // prepare the response short resp = KMArray.instance((short) 2); - KMArray.add(resp, (short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.add(resp, (short) 0, buildErrorStatus(KMError.OK)); KMArray.add(resp, (short) 1, params); sendOutgoing(apdu, resp); } @@ -927,7 +930,7 @@ private void processComputeSharedHmacCmd(APDU apdu) { short signature = KMByteBlob.instance(scratchPad, keyLen, signLen); // prepare the response short resp = KMArray.instance((short) 2); - KMArray.add(resp, (short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.add(resp, (short) 0, buildErrorStatus(KMError.OK)); KMArray.add(resp, (short) 1, signature); sendOutgoing(apdu, resp); } @@ -1007,7 +1010,7 @@ private void processUpgradeKeyCmd(APDU apdu) { } // prepare the response short resp = KMArray.instance((short) 2); - KMArray.add(resp, (short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.add(resp, (short) 0, buildErrorStatus(KMError.OK)); KMArray.add(resp, (short) 1, data[KEY_BLOB]); sendOutgoing(apdu, resp); } @@ -1468,30 +1471,14 @@ private void processAbortOperationCmd(APDU apdu) { } private short finishOperationCmd(APDU apdu){ - short cmd = KMArray.instance((short) 7); - KMArray.add(cmd, (short) 0, KMInteger.exp());//op handle - short keyParam = KMKeyParameters.exp(); - KMArray.add(cmd, (short) 1, keyParam);// Key Parameters - KMArray.add(cmd, (short) 2, KMByteBlob.exp());// input data - KMArray.add(cmd, (short) 3, KMByteBlob.exp()); // signature - short authToken = KMHardwareAuthToken.exp(); - KMArray.add(cmd, (short) 4, authToken); // auth token - short verToken = getKMVerificationTokenExp(); - KMArray.add(cmd, (short) 5, verToken); // time stamp token - KMArray.add(cmd, (short) 6, KMByteBlob.exp()); //confirmation token - return receiveIncoming(apdu, cmd); + return receiveIncoming(apdu, prepareFinishExp()); } private void processFinishOperationCmd(APDU apdu) { short cmd = finishOperationCmd(apdu); - byte[] scratchPad = apdu.getBuffer(); - data[OP_HANDLE] = KMArray.get(cmd, (short) 0); - data[KEY_PARAMETERS] = KMArray.get(cmd, (short) 1); - data[INPUT_DATA] = KMArray.get(cmd, (short) 2); - data[SIGNATURE] = KMArray.get(cmd, (short) 3); - data[HW_TOKEN] = KMArray.get(cmd, (short) 4); - data[VERIFICATION_TOKEN] = KMArray.get(cmd, (short) 5); - data[CONFIRMATION_TOKEN] = KMArray.get(cmd, (short) 6); + byte[] scratchPad = apdu.getBuffer(); + getFinishInputParameters(cmd, data, OP_HANDLE, KEY_PARAMETERS, INPUT_DATA, + SIGNATURE, HW_TOKEN, VERIFICATION_TOKEN, CONFIRMATION_TOKEN); // Check Operation Handle KMOperationState op = findOperation(data[OP_HANDLE]); @@ -1523,13 +1510,7 @@ private void processFinishOperationCmd(APDU apdu) { releaseOperation(op); // make response - short keyParam = KMArray.instance((short) 0); - keyParam = KMKeyParameters.instance(keyParam); - short resp = KMArray.instance((short) 3); - KMArray.add(resp, (short) 0, KMInteger.uint_16(KMError.OK)); - KMArray.add(resp, (short) 1, keyParam); - KMArray.add(resp, (short) 2, data[OUTPUT_DATA]); - sendOutgoing(apdu, resp); + sendOutgoing(apdu, prepareFinishResp(data[OUTPUT_DATA])); } private void finishEncryptOperation(KMOperationState op, byte[] scratchPad) { @@ -1580,7 +1561,7 @@ private void finishAesDesOperation(KMOperationState op){ KMException.throwIt(KMError.INVALID_INPUT_LENGTH); } // update aad if there is any - updateAAD(op, KMType.INVALID_VALUE, (byte) 0x01); + updateAAD(op, (byte) 0x01); if(op.isAesGcmUpdateAllowed()){ op.setAesGcmUpdateComplete(); } @@ -1850,27 +1831,14 @@ private void validateVerificationToken(short verToken, byte[] scratchPad) { } private short updateOperationCmd(APDU apdu){ - short cmd = KMArray.instance((short) 5); - // Arguments - short keyParams = KMKeyParameters.exp(); - KMArray.add(cmd, (short) 0, KMInteger.exp()); - KMArray.add(cmd, (short) 1, keyParams); - KMArray.add(cmd, (short) 2, KMByteBlob.exp()); - short authToken = KMHardwareAuthToken.exp(); - KMArray.add(cmd, (short) 3, authToken); - short verToken = getKMVerificationTokenExp(); - KMArray.add(cmd, (short) 4, verToken); - return receiveIncoming(apdu, cmd); + return receiveIncoming(apdu, prepareUpdateExp()); } private void processUpdateOperationCmd(APDU apdu) { short cmd = updateOperationCmd(apdu); byte[] scratchPad = apdu.getBuffer(); - data[OP_HANDLE] = KMArray.get(cmd, (short) 0); - data[KEY_PARAMETERS] = KMArray.get(cmd, (short) 1); //will be passed as INVALID_VALUE from keymint HAL - data[INPUT_DATA] = KMArray.get(cmd, (short) 2); - data[HW_TOKEN] = KMArray.get(cmd, (short) 3); - data[VERIFICATION_TOKEN] = KMArray.get(cmd, (short) 4); + getUpdateInputParameters(cmd, data, OP_HANDLE, KEY_PARAMETERS, + INPUT_DATA, HW_TOKEN, VERIFICATION_TOKEN); // Input data must be present even if it is zero length. if (data[INPUT_DATA] == KMType.INVALID_VALUE) { @@ -1910,7 +1878,7 @@ private void processUpdateOperationCmd(APDU apdu) { if (op.getBlockMode() == KMType.GCM) { // data[KEY_PARAMETERS] will be invalid for keymint if(data[KEY_PARAMETERS] != KMType.INVALID_VALUE) { - updateAAD(op, KMType.INVALID_VALUE, (byte) 0x00); + updateAAD(op, (byte) 0x00); } // if input data present if (len > 0) { @@ -1949,16 +1917,8 @@ private void processUpdateOperationCmd(APDU apdu) { data[OUTPUT_DATA] = KMByteBlob.instance((short) 0); } // Persist if there are any updates. - //op.persist(); // make response - short resp = KMArray.instance((short) 4); - KMArray.add(resp, (short) 0, KMInteger.uint_16(KMError.OK)); - KMArray.add(resp, (short) 1, data[OUTPUT_DATA]); - short keyParm = KMKeyParameters.instance(KMArray.instance((short) 0)); - KMArray.add(resp, (short) 2, keyParm); // will be ignored in keymint HAL - KMArray.add(resp, (short) 3, KMInteger.uint_16(inputConsumed)); // will be ignored in keymint HAL - - sendOutgoing(apdu, resp); + sendOutgoing(apdu, prepareUpdateResp(data[OUTPUT_DATA], KMInteger.uint_16(inputConsumed))); } private short updateAadOperationCmd(APDU apdu){ @@ -1974,44 +1934,36 @@ private short updateAadOperationCmd(APDU apdu){ //update operation should send 0x00 for finish variable, where as finish operation // should send 0x01 for finish variable. - private void updateAAD(KMOperationState op, short associatedData, byte finish) { - - short aData = associatedData; - if(associatedData == KMType.INVALID_VALUE) { - // Is input data absent - if (data[INPUT_DATA] == KMType.INVALID_VALUE) { - KMException.throwIt(KMError.INVALID_ARGUMENT); - } - // Update can be called either to update auth data, update input data or both. - // But if it is called for neither then return error. - short len = KMByteBlob.length(data[INPUT_DATA]); - short tag = - KMKeyParameters.findTag(data[KEY_PARAMETERS], KMType.BYTES_TAG, KMType.ASSOCIATED_DATA); - // For Finish operation the input data can be zero length and associated data can be - // INVALID_VALUE - // For update operation either input data or associated data should be present. - if (tag == KMType.INVALID_VALUE && len <= 0 && finish == 0x00) { - KMException.throwIt(KMError.INVALID_INPUT_LENGTH); - } - // Check if associated data is present and update aad still allowed by the operation. - if (tag != KMType.INVALID_VALUE) { - // If allowed the update the aad - if (!op.isAesGcmUpdateAllowed()) { - KMException.throwIt(KMError.INVALID_TAG); - } - aData = KMByteTag.getValue(tag); - } - } - try { - if(aData != KMType.INVALID_VALUE) { - op.getOperation() - .updateAAD( - KMByteBlob.getBuffer(aData), - KMByteBlob.getStartOff(aData), - KMByteBlob.length(aData)); + public void updateAAD(KMOperationState op, byte finish) { + // Is input data absent + if (data[INPUT_DATA] == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // Update can be called either to update auth data, update input data or both. + // But if it is called for neither then return error. + short len = KMByteBlob.length(data[INPUT_DATA]); + short tag = + KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.ASSOCIATED_DATA, data[KEY_PARAMETERS]); + // For Finish operation the input data can be zero length and associated data can be + // INVALID_VALUE + // For update operation either input data or associated data should be present. + if (tag == KMType.INVALID_VALUE && len <= 0 && finish == 0x00) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + // Check if associated data is present and update aad still allowed by the operation. + if (tag != KMType.INVALID_VALUE) { + // If allowed the update the aad + if (!op.isAesGcmUpdateAllowed()) { + KMException.throwIt(KMError.INVALID_TAG); } - } catch(CryptoException exp){ - KMException.throwIt(KMError.UNKNOWN_ERROR); + // If allowed the update the aad + short aData = KMByteTag.getValue(tag); + + op.getOperation() + .updateAAD( + KMByteBlob.getBuffer(aData), + KMByteBlob.getStartOff(aData), + KMByteBlob.length(aData)); } } @@ -2047,11 +1999,19 @@ private void processUpdateAadOperationCmd(APDU apdu) { } // authorize the update operation authorizeUpdateFinishOperation(op, scratchPad); - updateAAD(op, data[INPUT_DATA], (byte)0x00); + try { + op.getOperation() + .updateAAD( + KMByteBlob.getBuffer(data[INPUT_DATA]), + KMByteBlob.getStartOff(data[INPUT_DATA]), + KMByteBlob.length(data[INPUT_DATA])); + } catch(CryptoException exp){ + KMException.throwIt(KMError.UNKNOWN_ERROR); + } // make response short resp = KMArray.instance((short) 1); - KMArray.add(resp, (short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.add(resp, (short) 0, buildErrorStatus(KMError.OK)); sendOutgoing(apdu, resp); } @@ -2162,12 +2122,8 @@ private void processBeginOperationCmd(APDU apdu) { } short params = KMKeyParameters.instance(iv); - short resp = KMArray.instance((short) 5); - KMArray.add(resp, (short) 0, KMInteger.uint_16(KMError.OK)); - KMArray.add(resp, (short) 1, params); - KMArray.add(resp, (short) 2, data[OP_HANDLE]); - KMArray.add(resp, (short) 3, KMInteger.uint_8(op.getBufferingMode())); - KMArray.add(resp, (short) 4, KMInteger.uint_16((short) (op.getMacLength() / 8))); + short resp = prepareBeginResp(params, data[OP_HANDLE], KMInteger.uint_8(op.getBufferingMode()), + KMInteger.uint_16((short) (op.getMacLength() / 8))); sendOutgoing(apdu, resp); } @@ -2405,6 +2361,7 @@ private void authorizeAndBeginOperation(KMOperationState op, byte[] scratchPad) authorizeDigest(op); authorizePadding(op); authorizeBlockModeAndMacLength(op); + assertPrivateOperation(op.getPurpose(), op.getAlgorithm()); authorizeUserSecureIdAuthTimeout(op, scratchPad); authorizeDeviceUnlock(scratchPad); authorizeKeyUsageForCount(scratchPad); @@ -2826,15 +2783,12 @@ private boolean validateHwToken(short hwToken, byte[] scratchPad) { } private short importKeyCmd(APDU apdu){ - short cmd = KMArray.instance((short) 6); + short cmd = KMArray.instance((short) 3); // Arguments short params = KMKeyParameters.expAny(); KMArray.add(cmd, (short) 0, params); KMArray.add(cmd, (short) 1, KMEnum.instance(KMType.KEY_FORMAT)); KMArray.add(cmd, (short) 2, KMByteBlob.exp()); - KMArray.add(cmd, (short) 3, KMByteBlob.exp()); //attest key - KMArray.add(cmd, (short) 4, params); //attest key params - KMArray.add(cmd, (short) 5, KMByteBlob.exp()); //issuer return receiveIncoming(apdu, cmd); } @@ -2845,9 +2799,6 @@ private void processImportKeyCmd(APDU apdu) { data[KEY_PARAMETERS] = KMArray.get(cmd, (short) 0); short keyFmt = KMArray.get(cmd, (short) 1); data[IMPORTED_KEY_BLOB] = KMArray.get(cmd, (short) 2); - data[ATTEST_KEY_BLOB] = KMArray.get(cmd, (short) 3); - data[ATTEST_KEY_PARAMS] = KMArray.get(cmd, (short) 4); - data[ATTEST_KEY_ISSUER] = KMArray.get(cmd, (short) 5); keyFmt = KMEnum.getVal(keyFmt); data[CERTIFICATE] = KMArray.instance((short)0); //by default the cert is empty. @@ -3048,7 +2999,7 @@ private void importKey(APDU apdu, short keyFmt, byte[] scratchPad) { createEncryptedKeyBlob(scratchPad); // prepare the response short resp = KMArray.instance((short) 3); - KMArray.add(resp, (short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.add(resp, (short) 0, buildErrorStatus(KMError.OK)); KMArray.add(resp, (short) 1, data[KEY_BLOB]); KMArray.add(resp, (short) 2, data[KEY_CHARACTERISTICS]); sendOutgoing(apdu, resp); @@ -3430,7 +3381,7 @@ private short generateKeyCmd(APDU apdu){ return receiveIncoming(apdu, cmd); } - private void processGenerateKey1(APDU apdu) { + private void processGenerateKey(APDU apdu) { // Receive the incoming request fully from the master into buffer. short cmd = generateKeyCmd(apdu); // Re-purpose the apdu buffer as scratch pad. @@ -3474,7 +3425,7 @@ private void processGenerateKey1(APDU apdu) { createEncryptedKeyBlob(scratchPad); // prepare the response short resp = KMArray.instance((short) 3); - KMArray.add(resp, (short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.add(resp, (short) 0, buildErrorStatus(KMError.OK)); KMArray.add(resp, (short) 1, data[KEY_BLOB]); KMArray.add(resp, (short) 2, data[KEY_CHARACTERISTICS]); sendOutgoing(apdu, resp); @@ -3519,16 +3470,7 @@ public void processAttestationCertDataCmd(APDU apdu) { } private short generateAttestKeyCmd(APDU apdu) { - short params = KMKeyParameters.expAny(); - short blob = KMByteBlob.exp(); - // Array of expected arguments - short cmd = KMArray.instance((short) 5); - KMArray.add(cmd, (short) 0, blob); //key blob - KMArray.add(cmd, (short) 1, params); //keyparamters to be attested. - KMArray.add(cmd, (short) 2, blob); //attest key blob - KMArray.add(cmd, (short) 3, params); //attest key params - KMArray.add(cmd, (short) 4, blob); //attest issuer - return receiveIncoming(apdu, cmd); + return receiveIncoming(apdu, generateAttestKeyExp()); } private void processGetCertChainCmd(APDU apdu) { @@ -3570,11 +3512,8 @@ private void processAttestKeyCmd(APDU apdu) { short cmd = generateAttestKeyCmd(apdu); // Re-purpose the apdu buffer as scratch pad. byte[] scratchPad = apdu.getBuffer(); - data[KEY_BLOB] = KMArray.get(cmd, (short) 0); - data[KEY_PARAMETERS] = KMArray.get(cmd, (short) 1); - data[ATTEST_KEY_BLOB] = KMArray.get(cmd, (short) 2); - data[ATTEST_KEY_PARAMS] = KMArray.get(cmd, (short) 3); - data[ATTEST_KEY_ISSUER] = KMArray.get(cmd, (short) 4); + getAttestKeyInputParameters(cmd, data, KEY_BLOB, KEY_PARAMETERS, ATTEST_KEY_BLOB, + ATTEST_KEY_PARAMS, ATTEST_KEY_ISSUER); data[CERTIFICATE] = KMArray.instance((short)0); //by default the cert is empty. // Check for app id and app data. @@ -3599,7 +3538,7 @@ private void processAttestKeyCmd(APDU apdu) { generateAttestation(data[ATTEST_KEY_BLOB], data[ATTEST_KEY_PARAMS], scratchPad); short resp = KMArray.instance((short) 2); - KMArray.add(resp, (short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.add(resp, (short) 0, buildErrorStatus(KMError.OK)); KMArray.add(resp, (short) 1, data[CERTIFICATE]); sendOutgoing(apdu, resp); } @@ -4368,16 +4307,15 @@ private void finishTrustedConfirmationOperation(KMOperationState op) { } } - - public short getHardwareInfo() { - short respPtr = KMArray.instance((short) 3); - KMArray.add(respPtr, (short) 0, KMEnum.instance(KMType.HARDWARE_TYPE, KMType.STRONGBOX)); + short respPtr = KMArray.instance((short) 4); + KMArray.add(respPtr, (short) 0, buildErrorStatus(KMError.OK)); + KMArray.add(respPtr, (short) 1, KMEnum.instance(KMType.HARDWARE_TYPE, KMType.STRONGBOX)); KMArray.add(respPtr, - (short) 1, + (short) 2, KMByteBlob.instance( JAVACARD_KEYMASTER_DEVICE, (short) 0, (short) JAVACARD_KEYMASTER_DEVICE.length)); - KMArray.add(respPtr, (short) 2, KMByteBlob.instance(GOOGLE, (short) 0, (short) GOOGLE.length)); + KMArray.add(respPtr, (short) 3, KMByteBlob.instance(GOOGLE, (short) 0, (short) GOOGLE.length)); return respPtr; } @@ -4507,4 +4445,149 @@ public short getMacFromVerificationToken(short verToken) { public short getMgf1Digest(short keyParams, short hwParams) { return KMType.SHA1; } + +//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. + public short buildErrorStatus(short err) { + short int32Ptr = KMInteger.instance((short) 4); + short powerResetStatus = 0; + if (seProvider.isPowerReset(true)) { + powerResetStatus = POWER_RESET_MASK_FLAG; + } + Util.setShort(KMInteger.getBuffer(int32Ptr), + KMInteger.getStartOff(int32Ptr), + powerResetStatus); + + Util.setShort(KMInteger.getBuffer(int32Ptr), + (short) (KMInteger.getStartOff(int32Ptr) + 2), + err); + return int32Ptr; + } + + public short generateAttestKeyExp() { + // Arguments + short keyParams = KMKeyParameters.expAny(); + short keyBlob = KMByteBlob.exp(); + short argsProto = KMArray.instance((short) 2); + KMArray.add(argsProto, (short) 0, keyBlob); + KMArray.add(argsProto, (short) 1, keyParams); + return argsProto; + } + + public void getAttestKeyInputParameters(short arrPtr, short[] data, byte keyBlobOff, + byte keyParametersOff, + byte attestKeyBlobOff, byte attestKeyParamsOff, byte attestKeyIssuerOff) { + data[keyBlobOff] = KMArray.get(arrPtr, (short) 0); + data[keyParametersOff] = KMArray.get(arrPtr, (short) 1); + data[attestKeyBlobOff] = KMType.INVALID_VALUE; + data[attestKeyParamsOff] = KMType.INVALID_VALUE; + data[attestKeyIssuerOff] = KMType.INVALID_VALUE; + } + + public short prepareBeginResp(short paramsPtr, short opHandlePtr, short bufModPtr, + short macLengthPtr) { + short resp = KMArray.instance((short) 3); + KMArray.add(resp, (short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.add(resp, (short) 1, paramsPtr); + KMArray.add(resp, (short) 2, opHandlePtr); + return resp; + } + + public short prepareFinishExp() { + short byteBlob = KMByteBlob.exp(); + short cmd = KMArray.instance((short) 6); + KMArray.add(cmd, (short) 0, KMInteger.exp());//op handle + short keyParam = KMKeyParameters.exp(); + KMArray.add(cmd, (short) 1, keyParam);// Key Parameters + KMArray.add(cmd, (short) 2, byteBlob);// input data + KMArray.add(cmd, (short) 3, byteBlob); // signature + short authToken = KMHardwareAuthToken.exp(); + KMArray.add(cmd, (short) 4, authToken); // auth token + short verToken = getKMVerificationTokenExp(); + KMArray.add(cmd, (short) 5, verToken); // time stamp token + return cmd; + } + + public short prepareUpdateExp() { + short cmd = KMArray.instance((short) 5); + // Arguments + short keyParams = KMKeyParameters.exp(); + KMArray.add(cmd, (short) 0, KMInteger.exp()); + KMArray.add(cmd, (short) 1, keyParams); + KMArray.add(cmd, (short) 2, KMByteBlob.exp()); + short authToken = KMHardwareAuthToken.exp(); + KMArray.add(cmd, (short) 3, authToken); + short verToken = getKMVerificationTokenExp(); + KMArray.add(cmd, (short) 4, verToken); + return cmd; + } + + public void getUpdateInputParameters(short arrPtr, short[] data, byte opHandleOff, + byte keyParametersOff, byte inputDataOff, byte hwTokenOff, + byte verToken) { + data[opHandleOff] = KMArray.get(arrPtr, (short) 0); + data[keyParametersOff] = KMArray.get(arrPtr, (short) 1); + data[inputDataOff] = KMArray.get(arrPtr, (short) 2); + data[hwTokenOff] = KMArray.get(arrPtr, (short) 3); + data[verToken] = KMArray.get(arrPtr, (short) 4); + } + + public void getFinishInputParameters(short arrPtr, short[] data, byte opHandleOff, + byte keyParametersOff, byte inputDataOff, byte signDataOff, byte hwTokenOff, byte verToken, + byte confToken) { + data[opHandleOff] = KMArray.get(arrPtr, (short) 0); + data[keyParametersOff] = KMArray.get(arrPtr, (short) 1); + data[inputDataOff] = KMArray.get(arrPtr, (short) 2); + data[signDataOff] = KMArray.get(arrPtr, (short) 3); + data[hwTokenOff] = KMArray.get(arrPtr, (short) 4); + data[verToken] = KMArray.get(arrPtr, (short) 5); + data[confToken] = KMType.INVALID_VALUE; + } + + public short prepareFinishResp(short outputPtr) { + short keyParam = KMArray.instance((short) 0); + keyParam = KMKeyParameters.instance(keyParam); + short resp = KMArray.instance((short) 3); + KMArray.add(resp, (short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.add(resp, (short) 1, keyParam); + KMArray.add(resp, (short) 2, outputPtr); + return resp; + } + + public short prepareUpdateResp(short outputPtr, short inputConsumedPtr) { + short resp = KMArray.instance((short) 4); + KMArray.add(resp, (short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.add(resp, (short) 1, inputConsumedPtr); + short keyParm = KMKeyParameters.instance(KMArray.instance((short) 0)); + KMArray.add(resp, (short) 2, keyParm); + KMArray.add(resp, (short) 3, outputPtr); + return resp; + } + + public short validateApduHeader(APDU apdu) { + return KMError.OK; + } + + public boolean isAssociatedDataTagSupported() { + return true; + } + + void assertPrivateOperation(short purpose, short algorithm) { + switch(algorithm) { + case KMType.RSA: + if (purpose == KMType.ENCRYPT || purpose == KMType.VERIFY) { + KMException.throwIt(KMError.PUBLIC_KEY_OPERATION); + } + break; + case KMType.EC: + if (purpose == KMType.VERIFY) { + KMException.throwIt(KMError.PUBLIC_KEY_OPERATION); + } + break; + default: + break; + } + } } diff --git a/Applet/src/com/android/javacard/kmdevice/KMKeymintDevice.java b/Applet/src/com/android/javacard/kmdevice/KMKeymintDevice.java index 92722568..9715f617 100644 --- a/Applet/src/com/android/javacard/kmdevice/KMKeymintDevice.java +++ b/Applet/src/com/android/javacard/kmdevice/KMKeymintDevice.java @@ -1,9 +1,7 @@ package com.android.javacard.kmdevice; -import com.android.javacard.seprovider.KMAttestationCert; -import com.android.javacard.seprovider.KMException; -import com.android.javacard.seprovider.KMSEProvider; - +import javacard.framework.APDU; +import javacard.framework.ISO7816; import javacard.framework.Util; import javacard.security.CryptoException; @@ -47,7 +45,7 @@ public short getHardwareInfo() { final byte version = 1; // Make the response short respPtr = KMArray.instance((short) 6); - KMArray.add(respPtr, (short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.add(respPtr, (short) 0, buildErrorStatus(KMError.OK)); KMArray.add(respPtr, (short) 1, KMInteger.uint_8(version)); KMArray.add(respPtr, (short) 2, KMEnum.instance(KMType.HARDWARE_TYPE, KMType.STRONGBOX)); KMArray.add(respPtr, @@ -272,5 +270,134 @@ public void validateECKeys() { } } } + + @Override + public short buildErrorStatus(short err) { + return KMInteger.uint_16(err); + } + + @Override + public short generateAttestKeyExp() { + short params = KMKeyParameters.expAny(); + short blob = KMByteBlob.exp(); + // Array of expected arguments + short cmd = KMArray.instance((short) 5); + KMArray.add(cmd, (short) 0, blob); //key blob + KMArray.add(cmd, (short) 1, params); //keyparamters to be attested. + KMArray.add(cmd, (short) 2, blob); //attest key blob + KMArray.add(cmd, (short) 3, params); //attest key params + KMArray.add(cmd, (short) 4, blob); //attest issuer + return cmd; + } + + @Override + public void getAttestKeyInputParameters(short arrPtr, short[] data, byte keyBlobOff, + byte keyParametersOff, + byte attestKeyBlobOff, byte attestKeyParamsOff, byte attestKeyIssuerOff) { + data[keyBlobOff] = KMArray.get(arrPtr, (short) 0); + data[keyParametersOff] = KMArray.get(arrPtr, (short) 1); + data[attestKeyBlobOff] = KMArray.get(arrPtr, (short) 2); + data[attestKeyParamsOff] = KMArray.get(arrPtr, (short) 3); + data[attestKeyIssuerOff] = KMArray.get(arrPtr, (short) 4); + } + + @Override + public short prepareBeginResp(short paramsPtr, short opHandlePtr, short bufModePtr, + short macLengthPtr) { + short resp = KMArray.instance((short) 5); + KMArray.add(resp, (short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.add(resp, (short) 1, paramsPtr); + KMArray.add(resp, (short) 2, opHandlePtr); + KMArray.add(resp, (short) 3, bufModePtr); + KMArray.add(resp, (short) 4, macLengthPtr); + return resp; + } + + @Override + public short prepareFinishExp() { + short byteBlob = KMByteBlob.exp(); + short cmd = KMArray.instance((short) 6); + KMArray.add(cmd, (short) 0, KMInteger.exp());//op handle + KMArray.add(cmd, (short) 1, byteBlob);// input data + KMArray.add(cmd, (short) 2, byteBlob); // signature + short authToken = KMHardwareAuthToken.exp(); + KMArray.add(cmd, (short) 3, authToken); // auth token + short verToken = getKMVerificationTokenExp(); + KMArray.add(cmd, (short) 4, verToken); // time stamp token + KMArray.add(cmd, (short) 5, byteBlob); // confirmation token + return cmd; + } + + @Override + public short prepareUpdateExp() { + short cmd = KMArray.instance((short) 4); + // Arguments + KMArray.add(cmd, (short) 0, KMInteger.exp()); + KMArray.add(cmd,(short) 1, KMByteBlob.exp()); + short authToken = KMHardwareAuthToken.exp(); + KMArray.add(cmd,(short) 2, authToken); + short verToken = getKMVerificationTokenExp(); + KMArray.add(cmd,(short) 3, verToken); + return cmd; + } + + @Override + public void getUpdateInputParameters(short arrPtr, short[] data, byte opHandleOff, + byte keyParametersOff, byte inputDataOff, byte hwTokenOff, + byte verToken) { + data[opHandleOff] = KMArray.get(arrPtr, (short) 0); + data[inputDataOff] = KMArray.get(arrPtr, (short) 1); + data[hwTokenOff] = KMArray.get(arrPtr, (short) 2); + data[verToken] = KMArray.get(arrPtr, (short) 3); + } + + @Override + public void getFinishInputParameters(short arrPtr, short[] data, byte opHandleOff, + byte keyParametersOff, byte inputDataOff, byte signDataOff, byte hwTokenOff, byte verToken, + byte confToken) { + data[opHandleOff] = KMArray.get(arrPtr, (short) 0); + data[inputDataOff] = KMArray.get(arrPtr, (short) 1); + data[signDataOff] = KMArray.get(arrPtr, (short) 2); + data[hwTokenOff] = KMArray.get(arrPtr, (short) 3); + data[verToken] = KMArray.get(arrPtr, (short) 4); + data[confToken] = KMArray.get(arrPtr, (short) 5); + } + + @Override + public short prepareFinishResp(short outputPtr) { + short resp = KMArray.instance((short) 2); + KMArray.add(resp, (short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.add(resp, (short) 1, outputPtr); + return resp; + } + + @Override + public short prepareUpdateResp(short outputPtr, short inputConsumedPtr) { + short resp = KMArray.instance((short) 2); + KMArray.add(resp, (short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.add(resp, (short) 1, outputPtr); + return resp; + } + + + @Override + public short validateApduHeader(APDU apdu) { + byte[] apduBuffer = apdu.getBuffer(); + short P1P2 = Util.getShort(apduBuffer, ISO7816.OFFSET_P1); + byte ins = apduBuffer[ISO7816.OFFSET_INS]; + switch (ins) { + case INS_GET_CERT_CHAIN_CMD: + return KMError.UNSUPPORTED_INSTRUCTION; + } + if (P1P2 != KM_HAL_VERSION) { + return KMError.INVALID_P1P2; + } + return KMError.OK; + } + + @Override + public void updateAAD(KMOperationState op, byte finish) { + return; + } } diff --git a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMMasterKey.java b/Applet/src/com/android/javacard/kmdevice/KMMasterKey.java similarity index 87% rename from Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMMasterKey.java rename to Applet/src/com/android/javacard/kmdevice/KMMasterKey.java index 6eab5e56..1c5125e6 100644 --- a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMMasterKey.java +++ b/Applet/src/com/android/javacard/kmdevice/KMMasterKey.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.javacard.seprovider; +package com.android.javacard.kmdevice; /** * KMMasterKey is a marker interface and the SE Provider has to implement this interface. Internally @@ -21,5 +21,8 @@ * master key is maintained by the SEProvider. */ public interface KMMasterKey { - + + public byte getKey(byte[] keyData, short kOff); + + public short getKeySizeBits(); } diff --git a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMOperation.java b/Applet/src/com/android/javacard/kmdevice/KMOperation.java similarity index 98% rename from Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMOperation.java rename to Applet/src/com/android/javacard/kmdevice/KMOperation.java index b73d58d1..b3341231 100644 --- a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMOperation.java +++ b/Applet/src/com/android/javacard/kmdevice/KMOperation.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.javacard.seprovider; +package com.android.javacard.kmdevice; /** * KMOperation represents a persistent operation started by keymaster hal's beginOperation function. diff --git a/Applet/src/com/android/javacard/kmdevice/KMOperationState.java b/Applet/src/com/android/javacard/kmdevice/KMOperationState.java index e4a97345..70dcc834 100644 --- a/Applet/src/com/android/javacard/kmdevice/KMOperationState.java +++ b/Applet/src/com/android/javacard/kmdevice/KMOperationState.java @@ -16,8 +16,6 @@ package com.android.javacard.kmdevice; -import com.android.javacard.seprovider.KMException; -import com.android.javacard.seprovider.KMOperation; import javacard.framework.JCSystem; import javacard.framework.Util; diff --git a/Applet/src/com/android/javacard/kmdevice/KMPKCS8Decoder.java b/Applet/src/com/android/javacard/kmdevice/KMPKCS8Decoder.java index 6c5cb6a4..8afb1516 100644 --- a/Applet/src/com/android/javacard/kmdevice/KMPKCS8Decoder.java +++ b/Applet/src/com/android/javacard/kmdevice/KMPKCS8Decoder.java @@ -1,6 +1,5 @@ package com.android.javacard.kmdevice; -import com.android.javacard.seprovider.KMException; import javacard.framework.Util; public class KMPKCS8Decoder { diff --git a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMPreSharedKey.java b/Applet/src/com/android/javacard/kmdevice/KMPreSharedKey.java similarity index 95% rename from Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMPreSharedKey.java rename to Applet/src/com/android/javacard/kmdevice/KMPreSharedKey.java index 86bb1df9..268f9a8e 100644 --- a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMPreSharedKey.java +++ b/Applet/src/com/android/javacard/kmdevice/KMPreSharedKey.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.javacard.seprovider; +package com.android.javacard.kmdevice; /** * KMPreSharedKey is a marker interface and the SE Provider has to implement this interface. diff --git a/Applet/src/com/android/javacard/kmdevice/KMRepository.java b/Applet/src/com/android/javacard/kmdevice/KMRepository.java index eef060ad..364fe0d8 100644 --- a/Applet/src/com/android/javacard/kmdevice/KMRepository.java +++ b/Applet/src/com/android/javacard/kmdevice/KMRepository.java @@ -16,10 +16,7 @@ package com.android.javacard.kmdevice; -import com.android.javacard.seprovider.KMException; -import com.android.javacard.seprovider.KMUpgradable; import org.globalplatform.upgrade.Element; - import javacard.framework.ISO7816; import javacard.framework.ISOException; import javacard.framework.JCSystem; diff --git a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMSEProvider.java b/Applet/src/com/android/javacard/kmdevice/KMSEProvider.java similarity index 99% rename from Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMSEProvider.java rename to Applet/src/com/android/javacard/kmdevice/KMSEProvider.java index 8252ca7f..d1636eb0 100644 --- a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMSEProvider.java +++ b/Applet/src/com/android/javacard/kmdevice/KMSEProvider.java @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.javacard.seprovider; +package com.android.javacard.kmdevice; + /** * KMSEProvider is facade to use SE specific methods. The main intention of this interface is to @@ -774,6 +775,8 @@ void persistProvisionData(byte[] buf, short certChainOff, short certChainLen, * @return Instance of the KMAttestationKey. */ KMAttestationKey getAttestationKey(); + + public boolean isPowerReset(boolean isForStatusUpdate); } diff --git a/Applet/src/com/android/javacard/kmdevice/KMTag.java b/Applet/src/com/android/javacard/kmdevice/KMTag.java index ccb68c95..670ada1d 100644 --- a/Applet/src/com/android/javacard/kmdevice/KMTag.java +++ b/Applet/src/com/android/javacard/kmdevice/KMTag.java @@ -16,7 +16,6 @@ package com.android.javacard.kmdevice; -import com.android.javacard.seprovider.KMException; import javacard.framework.Util; /** diff --git a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMUpgradable.java b/Applet/src/com/android/javacard/kmdevice/KMUpgradable.java similarity index 95% rename from Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMUpgradable.java rename to Applet/src/com/android/javacard/kmdevice/KMUpgradable.java index 9bca1c8c..905c8bd0 100644 --- a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMUpgradable.java +++ b/Applet/src/com/android/javacard/kmdevice/KMUpgradable.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.javacard.seprovider; +package com.android.javacard.kmdevice; import org.globalplatform.upgrade.Element; diff --git a/Applet/src/com/android/javacard/kmdevice/KMUtils.java b/Applet/src/com/android/javacard/kmdevice/KMUtils.java index 108f2378..00c60ae0 100644 --- a/Applet/src/com/android/javacard/kmdevice/KMUtils.java +++ b/Applet/src/com/android/javacard/kmdevice/KMUtils.java @@ -15,8 +15,6 @@ */ package com.android.javacard.kmdevice; -import com.android.javacard.seprovider.KMException; - import javacard.framework.Util; public class KMUtils { diff --git a/Applet/src/com/android/javacard/kmdevice/RemotelyProvisionedComponentDevice.java b/Applet/src/com/android/javacard/kmdevice/RemotelyProvisionedComponentDevice.java index 655f939a..9026eadd 100644 --- a/Applet/src/com/android/javacard/kmdevice/RemotelyProvisionedComponentDevice.java +++ b/Applet/src/com/android/javacard/kmdevice/RemotelyProvisionedComponentDevice.java @@ -15,11 +15,6 @@ */ package com.android.javacard.kmdevice; -import com.android.javacard.seprovider.KMDeviceUniqueKey; -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; From c47214a3f7515f5324fc31ec9bb580aca5ee82aa Mon Sep 17 00:00:00 2001 From: "avinash.hedage" Date: Fri, 21 Jan 2022 08:03:28 +0000 Subject: [PATCH 3/5] fixed keymaster provision issue --- .../javacard/seprovider/KMKeymasterProvision.java | 6 ++++-- .../javacard/seprovider/KMKeymintProvision.java | 3 ++- .../android/javacard/kmdevice/KMKeymasterDevice.java | 12 +++++++----- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMKeymasterProvision.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMKeymasterProvision.java index bc73479e..c34cd24a 100644 --- a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMKeymasterProvision.java +++ b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMKeymasterProvision.java @@ -135,9 +135,10 @@ public void processProvisionAttestationKey(APDU apdu) { KMArray.add(argsProto, (short) 1, keyFormatPtr); KMArray.add(argsProto, (short) 2, blob); - short args = kmDeviceInst.receiveIncoming(apdu, argsProto); // Re-purpose the apdu buffer as scratch pad. byte[] scratchPad = apdu.getBuffer(); + + short args = kmDeviceInst.receiveIncoming(apdu, argsProto); // key params should have os patch, os version and verified root of trust short keyParams = KMArray.get(args, (short) 0); @@ -213,11 +214,12 @@ public void processProvisionAttestationCertDataCmd(APDU apdu) { short certChainPos = KMByteBlob.getStartOff(var); short certIssuerPos = (short) (KMByteBlob.getStartOff(var) + 4); short certExpiryPos = (short) (KMByteBlob.getStartOff(var) + 8); + short recvLen = apdu.setIncomingAndReceive(); short bufferLength = apdu.getIncomingLength(); short bufferStartOffset = kmRepositroyInst.allocReclaimableMemory(bufferLength); byte[] buffer = kmRepositroyInst.getHeap(); kmDeviceInst.receiveIncomingCertData(apdu, buffer, bufferLength, - bufferStartOffset, KMByteBlob.getBuffer(var), KMByteBlob.getStartOff(var)); + bufferStartOffset, recvLen, KMByteBlob.getBuffer(var), KMByteBlob.getStartOff(var)); // persist data seProvider.persistProvisionData( (byte[]) buffer, diff --git a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMKeymintProvision.java b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMKeymintProvision.java index 8e46e1cc..52cca58e 100644 --- a/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMKeymintProvision.java +++ b/Applet/AndroidSEProvider/src/com/android/javacard/seprovider/KMKeymintProvision.java @@ -70,10 +70,11 @@ public void processProvisionAdditionalCertChain(APDU apdu) { KMMap.add(map, (short) 0, KMTextString.exp(), coseSignArr); // receive incoming data and decode it. byte[] srcBuffer = apdu.getBuffer(); + short recvLen = apdu.setIncomingAndReceive(); short bufferLength = apdu.getIncomingLength(); short bufferStartOffset = kmRepositroyInst.allocReclaimableMemory(bufferLength); byte[] buffer = kmRepositroyInst.getHeap(); - map = kmDeviceInst.receiveIncoming(apdu, map, buffer, bufferLength, bufferStartOffset); + map = kmDeviceInst.receiveIncoming(apdu, map, buffer, bufferLength, bufferStartOffset, recvLen); arrInst = KMMap.getKeyValue(map, (short) 0); // Validate Additional certificate chain. short leafCoseKey = diff --git a/Applet/src/com/android/javacard/kmdevice/KMKeymasterDevice.java b/Applet/src/com/android/javacard/kmdevice/KMKeymasterDevice.java index 520b82e7..062c227d 100644 --- a/Applet/src/com/android/javacard/kmdevice/KMKeymasterDevice.java +++ b/Applet/src/com/android/javacard/kmdevice/KMKeymasterDevice.java @@ -298,6 +298,7 @@ private static void initKMDeviceStatics() { KMUtils.initStatics(); KMBoolTag.initStatics(); KMPKCS8Decoder.initStatics(); + KMEnumArrayTag.initStatics(); } @@ -648,16 +649,17 @@ public static void sendOutgoing(APDU apdu, short resp) { * Receives data, which can be extended data, as requested by the command instance. */ public short receiveIncoming(APDU apdu, short reqExp) { + short recvLen = apdu.setIncomingAndReceive(); short bufferLength = apdu.getIncomingLength(); short bufferStartOffset = repository.allocReclaimableMemory(bufferLength); - short req = receiveIncoming(apdu, reqExp, repository.getHeap(), bufferLength, bufferStartOffset); + short req = receiveIncoming(apdu, reqExp, repository.getHeap(), bufferLength, bufferStartOffset, recvLen); repository.reclaimMemory(bufferLength); return req; } - public short receiveIncoming(APDU apdu, short reqExp, byte[] reclamBuf, short bLen, short bStartOffset) { + public short receiveIncoming(APDU apdu, short reqExp, byte[] reclamBuf, short bLen, short bStartOffset, short incomingReceivedLen) { byte[] srcBuffer = apdu.getBuffer(); - short recvLen = apdu.setIncomingAndReceive(); + short recvLen = incomingReceivedLen; short srcOffset = apdu.getOffsetCdata(); // TODO add logic to handle the extended length buffer. In this case the memory can be reused // from extended buffer. @@ -670,9 +672,9 @@ public short receiveIncoming(APDU apdu, short reqExp, byte[] reclamBuf, short bL return decoder.decode(reqExp, reclamBuf, bStartOffset, bLen); } - public void receiveIncomingCertData(APDU apdu, byte[] reclamBuf, short bLen, short bStartOffset, byte[] outBuf, short outOff) { + public void receiveIncomingCertData(APDU apdu, byte[] reclamBuf, short bLen, short bStartOffset, short incomingReceivedLen, byte[] outBuf, short outOff) { byte[] srcBuffer = apdu.getBuffer(); - short recvLen = apdu.setIncomingAndReceive(); + short recvLen = incomingReceivedLen; short srcOffset = apdu.getOffsetCdata(); short index = bStartOffset; while (recvLen > 0 && ((short) (index - bStartOffset) < bLen)) { From feb7480f46c128b1dda01f0c03c2be630808f36c Mon Sep 17 00:00:00 2001 From: "avinash.hedage" Date: Fri, 21 Jan 2022 20:45:26 +0000 Subject: [PATCH 4/5] Keymint provision and VTS fixes --- .../javacard/kmapplet/KMAndroidSEApplet.java | 9 +-- .../com/android/javacard/kmdevice/KMCose.java | 2 +- .../javacard/kmdevice/KMKeymasterDevice.java | 27 ++++++-- .../javacard/kmdevice/KMKeymintDevice.java | 4 +- .../android/javacard/kmdevice/KMNInteger.java | 65 +++++++++++++++++-- .../javacard/kmdevice/KMTextString.java | 2 +- 6 files changed, 85 insertions(+), 24 deletions(-) diff --git a/Applet/AndroidSEApplet/src/com/android/javacard/kmapplet/KMAndroidSEApplet.java b/Applet/AndroidSEApplet/src/com/android/javacard/kmapplet/KMAndroidSEApplet.java index 010b7813..bdcf589f 100644 --- a/Applet/AndroidSEApplet/src/com/android/javacard/kmapplet/KMAndroidSEApplet.java +++ b/Applet/AndroidSEApplet/src/com/android/javacard/kmapplet/KMAndroidSEApplet.java @@ -272,16 +272,9 @@ private short computeObjectCount() { private short validateApdu(APDU apdu) { // Read the apdu header and buffer. byte[] apduBuffer = apdu.getBuffer(); - byte apduClass = apduBuffer[ISO7816.OFFSET_CLA]; - - // Validate APDU Header. - if ((apduClass != kmDeviceInst.CLA_ISO7816_NO_SM_NO_CHAN)) { - kmDeviceInst.sendError(apdu, KMError.UNSUPPORTED_CLA); - return KMType.INVALID_VALUE; - } short err = kmDeviceInst.validateApduHeader(apdu); if (err != KMError.OK) { - kmDeviceInst.sendError(apdu, err); + kmDeviceInst.sendError(apdu, err); return KMType.INVALID_VALUE; } return apduBuffer[ISO7816.OFFSET_INS]; diff --git a/Applet/src/com/android/javacard/kmdevice/KMCose.java b/Applet/src/com/android/javacard/kmdevice/KMCose.java index f0134771..494130ec 100644 --- a/Applet/src/com/android/javacard/kmdevice/KMCose.java +++ b/Applet/src/com/android/javacard/kmdevice/KMCose.java @@ -527,7 +527,7 @@ public short constructCoseKey(short keyType, short keyId, short keyAlg, short ke KMArray.add(arrPtr, (short) (KMArray.length(arrPtr) - 1), testKey); } arrPtr = KMCoseKey.instance(arrPtr); - KMCoseKey.canonicalize(arrPtr); + KMCoseKey.cast(arrPtr).canonicalize(); return arrPtr; } diff --git a/Applet/src/com/android/javacard/kmdevice/KMKeymasterDevice.java b/Applet/src/com/android/javacard/kmdevice/KMKeymasterDevice.java index 062c227d..f906bce6 100644 --- a/Applet/src/com/android/javacard/kmdevice/KMKeymasterDevice.java +++ b/Applet/src/com/android/javacard/kmdevice/KMKeymasterDevice.java @@ -43,7 +43,7 @@ public class KMKeymasterDevice { public static final short BOOT_PATCH_LVL_SIZE = 4; public static final byte CLA_ISO7816_NO_SM_NO_CHAN = (byte) 0x80; - public static final short KM_HAL_VERSION = (short) 0x5000; + public static final short KEYMINT_HAL_VERSION = (short) 0x5000; public static final short KEYMASTER_HAL_VERSION = (short) 0x4000; private static final short MAX_AUTH_DATA_SIZE = (short) 512; private static final short DERIVE_KEY_INPUT_SIZE = (short) 256; @@ -427,7 +427,8 @@ public void process(APDU apdu) { resetData(); repository.onProcess(); // Validate APDU Header. - short apduIns = validateApduHeader(apdu); + byte[] apduBuffer = apdu.getBuffer(); + byte apduIns = apduBuffer[ISO7816.OFFSET_INS]; switch (apduIns) { case INS_INIT_STRONGBOX_CMD: @@ -1234,7 +1235,7 @@ private KMAttestationCert makeAttestationCert(short attKeyBlob, short attKeyPara short attestParam = KMArray.get(keyBlob, KEY_BLOB_PARAMS); attestParam = KMKeyCharacteristics.getStrongboxEnforced(attestParam); short attKeyPurpose = - KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, attestParam); + KMKeyParameters.findTag(attestParam, KMType.ENUM_ARRAY_TAG, KMType.PURPOSE); // If the attest key's purpose is not "attest key" then error. if (!KMEnumArrayTag.contains(attKeyPurpose, KMType.ATTEST_KEY)) { KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); @@ -1243,7 +1244,7 @@ private KMAttestationCert makeAttestationCert(short attKeyBlob, short attKeyPara if (KMByteBlob.length(issuer) <= 0) { KMException.throwIt(KMError.MISSING_ISSUER_SUBJECT_NAME); } - short alg = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, attestParam); + short alg = KMKeyParameters.findTag(attestParam, KMType.ENUM_TAG, KMType.ALGORITHM); if(KMEnumTag.getValue(alg) == KMType.RSA) { short attestationKeyPublic = KMArray.get(keyBlob, KEY_BLOB_PUB_KEY); @@ -1259,7 +1260,7 @@ private KMAttestationCert makeAttestationCert(short attKeyBlob, short attKeyPara // Save attestation application id - must be present. short attAppId = - KMKeyParameters.findTag( KMType.BYTES_TAG, KMType.ATTESTATION_APPLICATION_ID, data[KEY_PARAMETERS]); + KMKeyParameters.findTag(data[KEY_PARAMETERS], KMType.BYTES_TAG, KMType.ATTESTATION_APPLICATION_ID); if (attAppId == KMType.INVALID_VALUE) { KMException.throwIt(KMError.ATTESTATION_APPLICATION_ID_MISSING); } @@ -1945,7 +1946,7 @@ public void updateAAD(KMOperationState op, byte finish) { // But if it is called for neither then return error. short len = KMByteBlob.length(data[INPUT_DATA]); short tag = - KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.ASSOCIATED_DATA, data[KEY_PARAMETERS]); + KMKeyParameters.findTag(data[KEY_PARAMETERS], KMType.BYTES_TAG, KMType.ASSOCIATED_DATA); // For Finish operation the input data can be zero length and associated data can be // INVALID_VALUE // For update operation either input data or associated data should be present. @@ -2663,7 +2664,7 @@ private void authorizeUserSecureIdAuthTimeout(KMOperationState op, byte[] scratc } authTimeoutTagPtr = - KMKeyParameters.findTag(KMType.ULONG_TAG, KMType.AUTH_TIMEOUT_MILLIS, data[HW_PARAMETERS]); + KMKeyParameters.findTag(data[HW_PARAMETERS], KMType.ULONG_TAG, KMType.AUTH_TIMEOUT_MILLIS); if (authTimeoutTagPtr == KMType.INVALID_VALUE) { KMException.throwIt(KMError.INVALID_KEY_BLOB); } @@ -4569,6 +4570,18 @@ public short prepareUpdateResp(short outputPtr, short inputConsumedPtr) { } public short validateApduHeader(APDU apdu) { + byte[] apduBuffer = apdu.getBuffer(); + byte apduClass = apduBuffer[ISO7816.OFFSET_CLA]; + short P1P2 = Util.getShort(apduBuffer, ISO7816.OFFSET_P1); + + // Validate APDU Header. + if ((apduClass != CLA_ISO7816_NO_SM_NO_CHAN)) { + return ISO7816.SW_CLA_NOT_SUPPORTED; + } + // Validate P1P2. + if (P1P2 != KEYMASTER_HAL_VERSION) { + return ISO7816.SW_INCORRECT_P1P2; + } return KMError.OK; } diff --git a/Applet/src/com/android/javacard/kmdevice/KMKeymintDevice.java b/Applet/src/com/android/javacard/kmdevice/KMKeymintDevice.java index 9715f617..1b7dd6d2 100644 --- a/Applet/src/com/android/javacard/kmdevice/KMKeymintDevice.java +++ b/Applet/src/com/android/javacard/kmdevice/KMKeymintDevice.java @@ -162,7 +162,7 @@ public KMAttestationCert makeCommonCert(short swParams, short hwParams, short ke } // Serial number short serialNum = - KMKeyParameters.findTag(KMType.BIGNUM_TAG, KMType.CERTIFICATE_SERIAL_NUM, keyParams); + KMKeyParameters.findTag(keyParams, KMType.BIGNUM_TAG, KMType.CERTIFICATE_SERIAL_NUM); if (serialNum != KMType.INVALID_VALUE) { serialNum = KMBignumTag.getValue(serialNum); } else { @@ -389,7 +389,7 @@ public short validateApduHeader(APDU apdu) { case INS_GET_CERT_CHAIN_CMD: return KMError.UNSUPPORTED_INSTRUCTION; } - if (P1P2 != KM_HAL_VERSION) { + if (P1P2 != KEYMINT_HAL_VERSION) { return KMError.INVALID_P1P2; } return KMError.OK; diff --git a/Applet/src/com/android/javacard/kmdevice/KMNInteger.java b/Applet/src/com/android/javacard/kmdevice/KMNInteger.java index e062e6e8..ef6dc8b6 100644 --- a/Applet/src/com/android/javacard/kmdevice/KMNInteger.java +++ b/Applet/src/com/android/javacard/kmdevice/KMNInteger.java @@ -20,7 +20,7 @@ import javacard.framework.ISOException; import javacard.framework.Util; -public class KMNInteger extends KMInteger { +public class KMNInteger extends KMType { private static KMNInteger prototype; public static final byte SIGNED_MASK = (byte) 0x80; @@ -111,14 +111,69 @@ public static short uint_64(byte[] num, short offset) { Util.arrayCopy(num, offset, heap, (short) (ptr + TLV_HEADER_SIZE), KMInteger.UINT_64); return ptr; } - - @Override - protected short getBaseOffset() { + + private short getStartOff() { + return (short) (getBaseOffset() + TLV_HEADER_SIZE); + } + + private short getShort() { + return Util.getShort(heap, (short) (getStartOff() + 2)); + } + + private short getBaseOffset() { return instanceTable[KM_NEG_INTEGER_OFFSET]; } - + + private short length() { + return Util.getShort(heap, (short) (getBaseOffset() + 1)); + } + + private short getSignificantShort() { + return Util.getShort(heap, getStartOff()); + } + + private 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); + } + + private byte[] getBuffer() { + return heap; + } + public static boolean isSignedInteger(byte[] num, short offset) { byte val = num[offset]; return SIGNED_MASK == (val & SIGNED_MASK); } + + public static short getShort(short bPtr) { + return KMNInteger.cast(bPtr).getShort(); + } + + public static short getStartOff(short bPtr) { + return KMNInteger.cast(bPtr).getStartOff(); + } + + public static short getSignificantShort(short bPtr) { + return KMNInteger.cast(bPtr).getSignificantShort(); + } + + public static void getValue(short bPtr, byte[] dest, short destOff, short length) { + KMNInteger.cast(bPtr).getValue(dest, destOff, length); + } + + public static short length(short bPtr) { + return KMNInteger.cast(bPtr).length(); + } + + public static byte[] getBuffer(short bPtr) { + return KMNInteger.cast(bPtr).getBuffer(); + } + } diff --git a/Applet/src/com/android/javacard/kmdevice/KMTextString.java b/Applet/src/com/android/javacard/kmdevice/KMTextString.java index 68f6da3d..8f4e332b 100644 --- a/Applet/src/com/android/javacard/kmdevice/KMTextString.java +++ b/Applet/src/com/android/javacard/kmdevice/KMTextString.java @@ -86,7 +86,7 @@ private byte[] getBuffer() { // Get the start of blob public short getStartOff() { - return Util.getShort(heap, (short)(getBaseOffset() + TLV_HEADER_SIZE)); + return (short)(getBaseOffset() + TLV_HEADER_SIZE); } public static short length(short bPtr) { From f539e4cf3e8028a05dc250e883cc6bce4239a250 Mon Sep 17 00:00:00 2001 From: "avinash.hedage" Date: Sat, 22 Jan 2022 17:29:39 +0000 Subject: [PATCH 5/5] Keymint VTS fixes --- .../javacard/kmdevice/KMKeymasterDevice.java | 21 ++++++++++--------- .../javacard/kmdevice/KMKeymintDevice.java | 14 ++++++++++++- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/Applet/src/com/android/javacard/kmdevice/KMKeymasterDevice.java b/Applet/src/com/android/javacard/kmdevice/KMKeymasterDevice.java index f906bce6..a6d4b00f 100644 --- a/Applet/src/com/android/javacard/kmdevice/KMKeymasterDevice.java +++ b/Applet/src/com/android/javacard/kmdevice/KMKeymasterDevice.java @@ -2956,6 +2956,7 @@ private void processImportWrappedKeyCmd(APDU apdu) { } private void validateImportKey(short params, short keyFmt){ + validatePurpose(params); // 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 @@ -2974,6 +2975,10 @@ private void validateImportKey(short params, short keyFmt){ } } + public void validatePurpose(short params) { + return; + } + private void importKey(APDU apdu, short keyFmt, byte[] scratchPad) { validateImportKey(data[KEY_PARAMETERS], keyFmt); // Check algorithm and dispatch to appropriate handler. @@ -3033,20 +3038,18 @@ private void importECKeys(byte[] scratchPad) { // check whether the key size tag is present in key parameters. short keySize = KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + short SecretLen = (short) (KMByteBlob.length(data[SECRET]) * 8); if (keySize != KMType.INVALID_VALUE) { // As per NIST.SP.800-186 page 9, secret for 256 curve should be between // 256-383 - if (((256 <= (short) (KMByteBlob.length(data[SECRET]) * 8)) - && (383 >= (short) (KMByteBlob.length(data[SECRET]) * 8))) - ^ keySize == 256) { + 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 <= (short) (KMByteBlob.length(data[SECRET]) * 8)) - && (383 >= (short) (KMByteBlob.length(data[SECRET]) * 8))){ + if ((256 > SecretLen) || (383 < SecretLen)){ KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); } // add the key size to scratchPad @@ -3060,17 +3063,14 @@ private void importECKeys(byte[] scratchPad) { 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 <= (short) (KMByteBlob.length(data[SECRET]) * 8)) - && (383 >= (short) (KMByteBlob.length(data[SECRET]) * 8))) - ^ curve == KMType.P_256) { + 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 <= (short) (KMByteBlob.length(data[SECRET]) * 8)) - && (383 >= (short) (KMByteBlob.length(data[SECRET]) * 8))){ + if ((256 > SecretLen) || (383 < SecretLen)){ KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); } // add the curve to scratchPad @@ -3398,6 +3398,7 @@ private void processGenerateKey(APDU apdu) { // As per specification Early boot keys may be created after early boot ended. validateEarlyBoot(); // Algorithm must be present + validatePurpose(data[KEY_PARAMETERS]); KMTag.assertPresence(data[KEY_PARAMETERS], KMType.ENUM_TAG, KMType.ALGORITHM, KMError.INVALID_ARGUMENT); short alg = KMEnumTag.getValue(KMType.ALGORITHM, data[KEY_PARAMETERS]); // Check algorithm and dispatch to appropriate handler. diff --git a/Applet/src/com/android/javacard/kmdevice/KMKeymintDevice.java b/Applet/src/com/android/javacard/kmdevice/KMKeymintDevice.java index 1b7dd6d2..3b1c8f6f 100644 --- a/Applet/src/com/android/javacard/kmdevice/KMKeymintDevice.java +++ b/Applet/src/com/android/javacard/kmdevice/KMKeymintDevice.java @@ -266,7 +266,7 @@ public void validateECKeys() { KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); } else { if (eccurve != KMType.P_256) { - KMException.throwIt(KMError.UNSUPPORTED_EC_CURVE); + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); } } } @@ -399,5 +399,17 @@ public short validateApduHeader(APDU apdu) { public void updateAAD(KMOperationState op, byte finish) { return; } + + @Override + public void validatePurpose(short params) { + short attKeyPurpose = + KMKeyParameters.findTag(params, KMType.ENUM_ARRAY_TAG, KMType.PURPOSE); + // ATTEST_KEY cannot be combined with any other purpose. + if (attKeyPurpose != KMType.INVALID_VALUE + && KMEnumArrayTag.contains(attKeyPurpose, KMType.ATTEST_KEY) + && KMEnumArrayTag.length(attKeyPurpose) > 1) { + KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); + } + } }

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 #{qZgEay+N%?~{QnOOVQN{F? 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