diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-07-07 05:12:16 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-07-07 05:12:16 +0000 |
commit | 0b125613bce94daff1d77b0748a3088160b01843 (patch) | |
tree | 4ab55fda2f6ca57c7f124f95b46330ca4c7b4626 | |
parent | 6fe42ca21f3dee3acd60a9ab62c06a107038e8dc (diff) | |
parent | 7d5f689cd2c1ea2dc579933678e7c622dc21ba1e (diff) | |
download | libese-android14-mainline-uwb-release.tar.gz |
Snap for 10453563 from 7d5f689cd2c1ea2dc579933678e7c622dc21ba1e to mainline-uwb-releaseaml_uwb_341513070aml_uwb_341511050aml_uwb_341310300aml_uwb_341310030aml_uwb_341111010aml_uwb_341011000android14-mainline-uwb-release
Change-Id: I0e040345afee15cf8ce675d6559ed702452485b0
190 files changed, 53563 insertions, 1 deletions
diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java new file mode 100644 index 0000000..7d84099 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java @@ -0,0 +1,619 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.keymaster; + +import com.android.javacard.seprovider.KMAndroidSEProvider; +import com.android.javacard.seprovider.KMException; +import javacard.framework.APDU; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; +import javacard.security.CryptoException; +import org.globalplatform.upgrade.Element; +import org.globalplatform.upgrade.OnUpgradeListener; +import org.globalplatform.upgrade.UpgradeManager; + +/** + * This class extends from KMKeymasterApplet which is main entry point to receive apdu commands. All + * the provision commands are processed here and later the data is handed over to the KMDataStore + * class which stores the data in the flash memory. + */ +public class KMAndroidSEApplet extends KMKeymasterApplet implements OnUpgradeListener { + // Magic number version stored along with provisioned data. This is used to differentiate + // between data before and after the magic number is used. + private static final byte KM_MAGIC_NUMBER = (byte) 0x82; + // MSB byte is for Major version and LSB byte is for Minor version. + public static final short KM_APPLET_PACKAGE_VERSION = 0x0301; + // This flag is used to know if card reset happened. + private static final short POWER_RESET_MASK_FLAG = (short) 0x4000; + + // Provider specific Commands + private static final byte INS_KEYMINT_PROVIDER_APDU_START = 0x00; + private static final byte INS_PROVISION_ATTEST_IDS_CMD = INS_KEYMINT_PROVIDER_APDU_START + 3; + // Commands 4, 5 and 6 are reserved for vendor usage. + private static final byte INS_GET_PROVISION_STATUS_CMD = INS_KEYMINT_PROVIDER_APDU_START + 7; + // 0x08 was reserved for INS_INIT_STRONGBOX_CMD + // 0x09 was reserved for INS_SET_BOOT_ENDED_CMD earlier. it is unused now. + private static final byte INS_SE_FACTORY_PROVISIONING_LOCK_CMD = + INS_KEYMINT_PROVIDER_APDU_START + 10; + private static final byte INS_PROVISION_OEM_ROOT_PUBLIC_KEY_CMD = + INS_KEYMINT_PROVIDER_APDU_START + 11; + private static final byte INS_OEM_UNLOCK_PROVISIONING_CMD = INS_KEYMINT_PROVIDER_APDU_START + 12; + private static final byte INS_PROVISION_RKP_DEVICE_UNIQUE_KEYPAIR_CMD = + INS_KEYMINT_PROVIDER_APDU_START + 13; + private static final byte INS_PROVISION_RKP_ADDITIONAL_CERT_CHAIN_CMD = + INS_KEYMINT_PROVIDER_APDU_START + 14; + private static final byte INS_PROVISION_PRESHARED_SECRET_CMD = + INS_KEYMINT_PROVIDER_APDU_START + 15; + private static final byte INS_SET_BOOT_PARAMS_CMD = + INS_KEYMINT_PROVIDER_APDU_START + 16; // Unused + private static final byte INS_OEM_LOCK_PROVISIONING_CMD = INS_KEYMINT_PROVIDER_APDU_START + 17; + private static final byte INS_PROVISION_SECURE_BOOT_MODE_CMD = + INS_KEYMINT_PROVIDER_APDU_START + 18; + + private static final byte INS_KEYMINT_PROVIDER_APDU_END = 0x1F; + // The length of the provisioned pre shared key. + public static final byte SHARED_SECRET_KEY_SIZE = 32; + + // Version of the database which is used to differentiate between different version of the + // database. + protected short packageVersion; + + KMAndroidSEApplet() { + super(new KMAndroidSEProvider()); + packageVersion = KM_APPLET_PACKAGE_VERSION; + } + + /** + * Installs this applet. + * + * @param bArray the array containing installation parameters + * @param bOffset the starting offset in bArray + * @param bLength the length in bytes of the parameter data in bArray + */ + public static void install(byte[] bArray, short bOffset, byte bLength) { + new KMAndroidSEApplet().register(bArray, (short) (bOffset + 1), bArray[bOffset]); + } + + public void handleDeviceBooted() { + if (seProvider.isBootSignalEventSupported() && seProvider.isDeviceRebooted()) { + kmDataStore.clearDeviceBootStatus(); + super.reboot(); + seProvider.clearDeviceBooted(true); + } + } + + @Override + public void updateApduStatusFlags(short apduIns) { + apduStatusFlags[APDU_INCOMING_AND_RECEIVE_STATUS_INDEX] = 0; + apduStatusFlags[APDU_CASE4_COMMAND_STATUS_INDEX] = 1; + switch (apduIns) { + case INS_GET_PROVISION_STATUS_CMD: + case INS_SE_FACTORY_PROVISIONING_LOCK_CMD: + apduStatusFlags[APDU_CASE4_COMMAND_STATUS_INDEX] = 0; + break; + default: + super.updateApduStatusFlags(apduIns); + } + } + + @Override + public void process(APDU apdu) { + try { + handleDeviceBooted(); + // If this is select applet apdu which is selecting this applet then return + if (apdu.isISOInterindustryCLA()) { + if (selectingApplet()) { + return; + } + } + short apduIns = validateApdu(apdu); + if (apduIns == KMType.INVALID_VALUE) { + return; + } + updateApduStatusFlags(apduIns); + if (((KMAndroidSEProvider) seProvider).isPowerReset()) { + super.powerReset(); + } + + if (isCommandAllowed(apduIns)) { + switch (apduIns) { + case INS_PROVISION_ATTEST_IDS_CMD: + processProvisionAttestIdsCmd(apdu); + kmDataStore.setProvisionStatus(PROVISION_STATUS_ATTEST_IDS); + sendResponse(apdu, KMError.OK); + break; + + case INS_PROVISION_PRESHARED_SECRET_CMD: + processProvisionPreSharedSecretCmd(apdu); + kmDataStore.setProvisionStatus(PROVISION_STATUS_PRESHARED_SECRET); + sendResponse(apdu, KMError.OK); + break; + + case INS_GET_PROVISION_STATUS_CMD: + processGetProvisionStatusCmd(apdu); + break; + + case INS_PROVISION_RKP_DEVICE_UNIQUE_KEYPAIR_CMD: + processProvisionRkpDeviceUniqueKeyPair(apdu); + break; + + case INS_PROVISION_RKP_ADDITIONAL_CERT_CHAIN_CMD: + processProvisionRkpAdditionalCertChain(apdu); + break; + + case INS_SE_FACTORY_PROVISIONING_LOCK_CMD: + kmDataStore.setProvisionStatus(PROVISION_STATUS_SE_LOCKED); + sendResponse(apdu, KMError.OK); + break; + + case INS_PROVISION_OEM_ROOT_PUBLIC_KEY_CMD: + processProvisionOEMRootPublicKeyCmd(apdu); + kmDataStore.setProvisionStatus(PROVISION_STATUS_OEM_PUBLIC_KEY); + sendResponse(apdu, KMError.OK); + break; + + case INS_OEM_LOCK_PROVISIONING_CMD: + processOEMLockProvisionCmd(apdu); + break; + + case INS_OEM_UNLOCK_PROVISIONING_CMD: + processOEMUnlockProvisionCmd(apdu); + break; + + case INS_PROVISION_SECURE_BOOT_MODE_CMD: + processSecureBootCmd(apdu); + break; + + default: + super.process(apdu); + break; + } + } else { + ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED); + } + } catch (KMException exception) { + sendResponse(apdu, KMException.reason()); + } catch (ISOException exp) { + sendResponse(apdu, mapISOErrorToKMError(exp.getReason())); + } catch (CryptoException e) { + sendResponse(apdu, mapCryptoErrorToKMError(e.getReason())); + } catch (Exception e) { + sendResponse(apdu, KMError.GENERIC_UNKNOWN_ERROR); + } finally { + repository.clean(); + } + } + + private boolean isCommandAllowed(short apduIns) { + boolean result = true; + switch (apduIns) { + case INS_PROVISION_ATTEST_IDS_CMD: + case INS_PROVISION_PRESHARED_SECRET_CMD: + case INS_PROVISION_SECURE_BOOT_MODE_CMD: + case INS_PROVISION_OEM_ROOT_PUBLIC_KEY_CMD: + if (kmDataStore.isProvisionLocked()) { + result = false; + } + break; + + case INS_OEM_UNLOCK_PROVISIONING_CMD: + if (!kmDataStore.isProvisionLocked()) { + result = false; + } + break; + + case INS_SE_FACTORY_PROVISIONING_LOCK_CMD: + if (isSeFactoryProvisioningLocked() || !isSeFactoryProvisioningComplete()) { + result = false; + } + break; + + case INS_OEM_LOCK_PROVISIONING_CMD: + // Allow lock only when + // 1. All the necessary provisioning commands are succcessfully executed + // 2. SE provision is locked + // 3. OEM Root Public is provisioned. + if (kmDataStore.isProvisionLocked() + || !(isProvisioningComplete() && isSeFactoryProvisioningLocked())) { + result = false; + } + break; + + case INS_PROVISION_RKP_DEVICE_UNIQUE_KEYPAIR_CMD: + case INS_PROVISION_RKP_ADDITIONAL_CERT_CHAIN_CMD: + if (isSeFactoryProvisioningLocked()) { + result = false; + } + break; + + case INS_GET_PROVISION_STATUS_CMD: + break; + + default: + // Allow other commands only if provision is completed. + if (!isProvisioningComplete()) { + result = false; + } + } + return result; + } + + private boolean isSeFactoryProvisioningLocked() { + short pStatus = kmDataStore.getProvisionStatus(); + boolean result = false; + if ((0 != (pStatus & PROVISION_STATUS_SE_LOCKED))) { + result = true; + } + return result; + } + + private boolean isSeFactoryProvisioningComplete() { + short pStatus = kmDataStore.getProvisionStatus(); + if (PROVISION_STATUS_DEVICE_UNIQUE_KEYPAIR + == (pStatus & PROVISION_STATUS_DEVICE_UNIQUE_KEYPAIR)) { + return true; + } + return false; + } + + private void processSecureBootCmd(APDU apdu) { + short argsProto = KMArray.instance((short) 1); + KMArray.cast(argsProto).add((short) 0, KMInteger.exp()); + short args = receiveIncoming(apdu, argsProto); + short val = KMInteger.cast(KMArray.cast(args).get((short) 0)).getShort(); + if (val != 1 && val != 0) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // Store secure boot mode value. + JCSystem.beginTransaction(); + kmDataStore.secureBootMode = (byte) val; + JCSystem.commitTransaction(); + kmDataStore.setProvisionStatus(PROVISION_STATUS_SECURE_BOOT_MODE); + sendResponse(apdu, KMError.OK); + } + + private void processOEMUnlockProvisionCmd(APDU apdu) { + authenticateOEM(OEM_UNLOCK_PROVISION_VERIFICATION_LABEL, apdu); + kmDataStore.unlockProvision(); + sendResponse(apdu, KMError.OK); + } + + private void processOEMLockProvisionCmd(APDU apdu) { + authenticateOEM(OEM_LOCK_PROVISION_VERIFICATION_LABEL, apdu); + // Enable the lock bit in provision status. + kmDataStore.setProvisionStatus(PROVISION_STATUS_PROVISIONING_LOCKED); + sendResponse(apdu, KMError.OK); + } + + private void authenticateOEM(byte[] plainMsg, APDU apdu) { + + tmpVariables[0] = KMArray.instance((short) 1); + KMArray.cast(tmpVariables[0]).add((short) 0, KMByteBlob.exp()); + short args = receiveIncoming(apdu, tmpVariables[0]); + // Get the signature input. + short signature = KMArray.cast(args).get((short) 0); + byte[] oemPublicKey = kmDataStore.getOEMRootPublicKey(); + + if (!seProvider.ecVerify256( + oemPublicKey, + (short) 0, + (short) oemPublicKey.length, + plainMsg, + (short) 0, + (short) plainMsg.length, + KMByteBlob.cast(signature).getBuffer(), + KMByteBlob.cast(signature).getStartOff(), + KMByteBlob.cast(signature).length())) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + } + + private void processProvisionOEMRootPublicKeyCmd(APDU apdu) { + // Arguments + short keyparams = KMKeyParameters.exp(); + short keyFormatPtr = KMEnum.instance(KMType.KEY_FORMAT); + short blob = KMByteBlob.exp(); + short argsProto = KMArray.instance((short) 3); + KMArray.cast(argsProto).add((short) 0, keyparams); + KMArray.cast(argsProto).add((short) 1, keyFormatPtr); + KMArray.cast(argsProto).add((short) 2, blob); + short args = receiveIncoming(apdu, argsProto); + + // key params should have os patch, os version and verified root of trust + data[KEY_PARAMETERS] = KMArray.cast(args).get((short) 0); + tmpVariables[0] = KMArray.cast(args).get((short) 1); + // Key format must be RAW format + byte keyFormat = KMEnum.cast(tmpVariables[0]).getVal(); + if (keyFormat != KMType.RAW) { + KMException.throwIt(KMError.UNIMPLEMENTED); + } + + // get algorithm - only EC keys expected + tmpVariables[0] = KMEnumTag.getValue(KMType.ALGORITHM, data[KEY_PARAMETERS]); + if (tmpVariables[0] != KMType.EC) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // get digest - only SHA256 supported + tmpVariables[0] = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.DIGEST, data[KEY_PARAMETERS]); + if (tmpVariables[0] != KMType.INVALID_VALUE) { + if (KMEnumArrayTag.cast(tmpVariables[0]).length() != 1) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + tmpVariables[0] = KMEnumArrayTag.cast(tmpVariables[0]).get((short) 0); + if (tmpVariables[0] != KMType.SHA2_256) { + KMException.throwIt(KMError.INCOMPATIBLE_DIGEST); + } + } else { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // Purpose should be VERIFY + tmpVariables[0] = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, data[KEY_PARAMETERS]); + if (tmpVariables[0] != KMType.INVALID_VALUE) { + if (KMEnumArrayTag.cast(tmpVariables[0]).length() != 1) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + tmpVariables[0] = KMEnumArrayTag.cast(tmpVariables[0]).get((short) 0); + if (tmpVariables[0] != KMType.VERIFY) { + KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); + } + } else { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + + tmpVariables[0] = KMArray.cast(args).get((short) 2); + // persist OEM Root Public Key. + kmDataStore.persistOEMRootPublicKey( + KMByteBlob.cast(tmpVariables[0]).getBuffer(), + KMByteBlob.cast(tmpVariables[0]).getStartOff(), + KMByteBlob.cast(tmpVariables[0]).length()); + } + + private static void processProvisionRkpDeviceUniqueKeyPair(APDU apdu) { + // Re-purpose the apdu buffer as scratch pad. + byte[] scratchPad = apdu.getBuffer(); + short arr = KMArray.instance((short) 1); + short coseKeyExp = KMCoseKey.exp(); + KMArray.cast(arr).add((short) 0, coseKeyExp); // [ CoseKey ] + arr = receiveIncoming(apdu, arr); + // Get cose key. + short coseKey = KMArray.cast(arr).get((short) 0); + short pubKeyLen = KMCoseKey.cast(coseKey).getEcdsa256PublicKey(scratchPad, (short) 0); + short privKeyLen = KMCoseKey.cast(coseKey).getPrivateKey(scratchPad, pubKeyLen); + // Store the Device unique Key. + kmDataStore.createRkpDeviceUniqueKeyPair( + scratchPad, (short) 0, pubKeyLen, scratchPad, pubKeyLen, privKeyLen); + short bcc = generateBcc(false, scratchPad); + short len = KMKeymasterApplet.encodeToApduBuffer(bcc, scratchPad, (short) 0, MAX_COSE_BUF_SIZE); + kmDataStore.persistBootCertificateChain(scratchPad, (short) 0, len); + kmDataStore.setProvisionStatus(PROVISION_STATUS_DEVICE_UNIQUE_KEYPAIR); + sendResponse(apdu, KMError.OK); + } + + private void processProvisionRkpAdditionalCertChain(APDU apdu) { + // X509 certificate chain is received as shown below: + /** + * x509CertChain = bstr .cbor UdsCerts + * + * <p>AdditionalDKSignatures = { * SignerName => DKCertChain } ; SignerName is a string + * identifier that indicates both the signing authority as ; well as the format of the + * DKCertChain SignerName = tstr + * + * <p>DKCertChain = [ 2* X509Certificate ; Root -> ... -> Leaf. "Root" is the vendor self-signed + * ; cert, "Leaf" contains DK_pub. There may also be ; intermediate certificates between Root + * and Leaf. ] ; A bstr containing a DER-encoded X.509 certificate (RSA, NIST P-curve, or edDSA) + * X509Certificate = bstr + */ + // Store the cbor encoded UdsCerts as it is in the persistent memory so cbor decoding is + // required here. + byte[] srcBuffer = apdu.getBuffer(); + short recvLen = apdu.setIncomingAndReceive(); + apduStatusFlags[APDU_INCOMING_AND_RECEIVE_STATUS_INDEX] = 1; + short srcOffset = apdu.getOffsetCdata(); + short bufferLength = apdu.getIncomingLength(); + short bufferStartOffset = repository.allocReclaimableMemory(bufferLength); + short index = bufferStartOffset; + byte[] buffer = repository.getHeap(); + while (recvLen > 0 && ((short) (index - bufferStartOffset) < bufferLength)) { + Util.arrayCopyNonAtomic(srcBuffer, srcOffset, buffer, index, recvLen); + index += recvLen; + recvLen = apdu.receiveBytes(srcOffset); + } + short byteHeaderLen = + decoder.readCertificateChainHeaderLen(buffer, bufferStartOffset, bufferLength); + kmDataStore.persistAdditionalCertChain( + buffer, + (short) (bufferStartOffset + byteHeaderLen), + (short) (bufferLength - byteHeaderLen)); + kmDataStore.setProvisionStatus(PROVISION_STATUS_ADDITIONAL_CERT_CHAIN); + // reclaim memory + repository.reclaimMemory(bufferLength); + sendResponse(apdu, KMError.OK); + } + + private void processProvisionAttestIdsCmd(APDU apdu) { + short keyparams = KMKeyParameters.exp(); + short cmd = KMArray.instance((short) 1); + KMArray.cast(cmd).add((short) 0, keyparams); + short args = receiveIncoming(apdu, cmd); + + short attData = KMArray.cast(args).get((short) 0); + // persist attestation Ids - if any is missing then exception occurs + setAttestationIds(attData); + } + + public void setAttestationIds(short attIdVals) { + KMKeyParameters instParam = KMKeyParameters.cast(attIdVals); + KMArray vals = KMArray.cast(instParam.getVals()); + short index = 0; + short length = vals.length(); + short key; + short type; + short obj; + while (index < length) { + obj = vals.get(index); + key = KMTag.getKey(obj); + type = KMTag.getTagType(obj); + + if (KMType.BYTES_TAG != type) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + obj = KMByteTag.cast(obj).getValue(); + if (KMByteBlob.cast(obj).length() > KMConfigurations.MAX_ATTESTATION_IDS_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + kmDataStore.setAttestationId( + key, + KMByteBlob.cast(obj).getBuffer(), + KMByteBlob.cast(obj).getStartOff(), + KMByteBlob.cast(obj).length()); + index++; + } + } + + private void processProvisionPreSharedSecretCmd(APDU apdu) { + short blob = KMByteBlob.exp(); + short argsProto = KMArray.instance((short) 1); + KMArray.cast(argsProto).add((short) 0, blob); + short args = receiveIncoming(apdu, argsProto); + + short val = KMArray.cast(args).get((short) 0); + + if (val != KMType.INVALID_VALUE && KMByteBlob.cast(val).length() != SHARED_SECRET_KEY_SIZE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // Persist shared Hmac. + kmDataStore.createPresharedKey( + KMByteBlob.cast(val).getBuffer(), + KMByteBlob.cast(val).getStartOff(), + KMByteBlob.cast(val).length()); + } + + // This function masks the error code with POWER_RESET_MASK_FLAG + // in case if card reset event occurred. The clients of the Applet + // has to extract the power reset status from the error code and + // process accordingly. + private static short buildErrorStatus(short err) { + short int32Ptr = KMInteger.instance((short) 4); + short powerResetStatus = 0; + if (((KMAndroidSEProvider) seProvider).isPowerReset()) { + powerResetStatus = POWER_RESET_MASK_FLAG; + } + + Util.setShort( + KMInteger.cast(int32Ptr).getBuffer(), + KMInteger.cast(int32Ptr).getStartOff(), + powerResetStatus); + + Util.setShort( + KMInteger.cast(int32Ptr).getBuffer(), + (short) (KMInteger.cast(int32Ptr).getStartOff() + 2), + err); + // reset power reset status flag to its default value. + // repository.restorePowerResetStatus(); //TODO + return int32Ptr; + } + + private void processGetProvisionStatusCmd(APDU apdu) { + byte[] scratchpad = apdu.getBuffer(); + short pStatus = kmDataStore.getProvisionStatus(); + Util.setShort(scratchpad, (short) 0, pStatus); + short resp = KMArray.instance((short) 2); + KMArray.cast(resp).add((short) 0, buildErrorStatus(KMError.OK)); + KMArray.cast(resp).add((short) 1, KMInteger.instance(scratchpad, (short) 0, (short) 2)); + sendOutgoing(apdu, resp); + } + + private boolean isProvisioningComplete() { + short pStatus = kmDataStore.getProvisionStatus(); + short pCompleteStatus = + PROVISION_STATUS_DEVICE_UNIQUE_KEYPAIR + | PROVISION_STATUS_PRESHARED_SECRET + | PROVISION_STATUS_ATTEST_IDS + | PROVISION_STATUS_OEM_PUBLIC_KEY + | PROVISION_STATUS_SECURE_BOOT_MODE; + if (kmDataStore.isProvisionLocked() || (pCompleteStatus == (pStatus & pCompleteStatus))) { + return true; + } + return false; + } + + @Override + public void onCleanup() {} + + @Override + public void onConsolidate() {} + + private boolean isUpgradeAllowed(short oldVersion) { + boolean upgradeAllowed = false; + // Downgrade of the Applet is not allowed. + if (KM_APPLET_PACKAGE_VERSION >= oldVersion) { + upgradeAllowed = true; + } + return upgradeAllowed; + } + + @Override + public void onRestore(Element element) { + element.initRead(); + byte magicNumber = element.readByte(); + if (magicNumber != KM_MAGIC_NUMBER) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short oldPackageVersion = element.readShort(); + // Validate version. + if (!isUpgradeAllowed(oldPackageVersion)) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + kmDataStore.onRestore(element, oldPackageVersion, KM_APPLET_PACKAGE_VERSION); + } + + @Override + public Element onSave() { + short primitiveCount = 3; + primitiveCount += kmDataStore.getBackupPrimitiveByteCount(); + short objectCount = kmDataStore.getBackupObjectCount(); + // Create element. + Element element = + UpgradeManager.createElement(Element.TYPE_SIMPLE, primitiveCount, objectCount); + + element.write(KM_MAGIC_NUMBER); + element.write(packageVersion); + kmDataStore.onSave(element); + return element; + } + + private short validateApdu(APDU apdu) { + // Read the apdu header and buffer. + byte[] apduBuffer = apdu.getBuffer(); + short P1P2 = Util.getShort(apduBuffer, ISO7816.OFFSET_P1); + + // Validate CLA + if (!apdu.isValidCLA()) { + ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED); + } + + // Validate P1P2. + if (P1P2 != KMKeymasterApplet.KM_HAL_VERSION) { + sendResponse(apdu, KMError.INVALID_P1P2); + return KMType.INVALID_VALUE; + } + return apduBuffer[ISO7816.OFFSET_INS]; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java new file mode 100644 index 0000000..e7d8252 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java @@ -0,0 +1,1055 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.keymaster; + +import com.android.javacard.seprovider.KMAESKey; +import com.android.javacard.seprovider.KMAttestationCert; +import com.android.javacard.seprovider.KMException; +import com.android.javacard.seprovider.KMKey; +import com.android.javacard.seprovider.KMSEProvider; +import javacard.framework.JCSystem; +import javacard.framework.Util; + +/** + * The class encodes strongbox generated and signed attestation certificates. It only encodes the + * required fields of the certificates. This class is not meant to be a generic X509 cert encoder. + * Any fields that are fixed are added as byte arrays. Extensions are encoded as per the values. The + * certificate is assembled with leafs first and then the sequences. + */ +public class KMAttestationCertImpl implements KMAttestationCert { + + // The maximum size of the either software or hardware parameters. + private static final byte MAX_PARAMS = 30; + // DER encoded object identifiers required by the cert. + // rsaEncryption - 1.2.840.113549.1.1.1 + private static final byte[] rsaEncryption = { + 0x06, 0x09, 0x2A, (byte) 0x86, 0x48, (byte) 0x86, (byte) 0xF7, 0x0D, 0x01, 0x01, 0x01 + }; + // ecPublicKey - 1.2.840.10045.2.1 + private static final byte[] eccPubKey = { + 0x06, 0x07, 0x2A, (byte) 0x86, 0x48, (byte) 0xCE, 0x3D, 0x02, 0x01 + }; + // prime256v1 curve - 1.2.840.10045.3.1.7 + private static final byte[] prime256v1 = { + 0x06, 0x08, 0x2A, (byte) 0x86, 0x48, (byte) 0xCE, 0x3D, 0x03, 0x01, 0x07 + }; + // Key Usage Extn - 2.5.29.15 + private static final byte[] keyUsageExtn = {0x06, 0x03, 0x55, 0x1D, 0x0F}; + // Android Extn - 1.3.6.1.4.1.11129.2.1.17 + private static final byte[] androidExtn = { + 0x06, 0x0A, 0X2B, 0X06, 0X01, 0X04, 0X01, (byte) 0XD6, 0X79, 0X02, 0X01, 0X11 + }; + // The length of the RSA signature. + private static final short RSA_SIG_LEN = 256; + // The maximum length of the ECDSA signature. + private static final byte ECDSA_MAX_SIG_LEN = 72; + // Signature algorithm identifier - ecdsaWithSha256 - 1.2.840.10045.4.3.2 + // SEQUENCE of alg OBJ ID and parameters = NULL. + private static final byte[] X509EcdsaSignAlgIdentifier = { + 0x30, 0x0A, 0x06, 0x08, 0x2A, (byte) 0x86, 0x48, (byte) 0xCE, (byte) 0x3D, 0x04, 0x03, 0x02 + }; + // Signature algorithm identifier - sha256WithRSAEncryption - 1.2.840.113549.1.1.11 + // SEQUENCE of alg OBJ ID and parameters = NULL. + private static final byte[] X509RsaSignAlgIdentifier = { + 0x30, + 0x0D, + 0x06, + 0x09, + 0x2A, + (byte) 0x86, + 0x48, + (byte) 0x86, + (byte) 0xF7, + 0x0D, + 0x01, + 0x01, + 0x0B, + 0x05, + 0x00 + }; + + // Below are the allowed softwareEnforced Authorization tags inside the attestation certificate's + // extension. + private static final short[] swTagIds = { + KMType.ATTESTATION_APPLICATION_ID, + KMType.CREATION_DATETIME, + KMType.ALLOW_WHILE_ON_BODY, + KMType.USAGE_COUNT_LIMIT, + KMType.USAGE_EXPIRE_DATETIME, + KMType.ORIGINATION_EXPIRE_DATETIME, + KMType.ACTIVE_DATETIME, + }; + + // Below are the allowed hardwareEnforced Authorization tags inside the attestation certificate's + // extension. + private static final short[] hwTagIds = { + KMType.BOOT_PATCH_LEVEL, + KMType.VENDOR_PATCH_LEVEL, + KMType.ATTESTATION_ID_MODEL, + KMType.ATTESTATION_ID_MANUFACTURER, + KMType.ATTESTATION_ID_MEID, + KMType.ATTESTATION_ID_IMEI, + KMType.ATTESTATION_ID_SERIAL, + KMType.ATTESTATION_ID_PRODUCT, + KMType.ATTESTATION_ID_DEVICE, + KMType.ATTESTATION_ID_BRAND, + KMType.OS_PATCH_LEVEL, + KMType.OS_VERSION, + KMType.ROOT_OF_TRUST, + KMType.ORIGIN, + KMType.UNLOCKED_DEVICE_REQUIRED, + KMType.TRUSTED_CONFIRMATION_REQUIRED, + KMType.AUTH_TIMEOUT, + KMType.USER_AUTH_TYPE, + KMType.NO_AUTH_REQUIRED, + KMType.EARLY_BOOT_ONLY, + KMType.ROLLBACK_RESISTANCE, + KMType.RSA_OAEP_MGF_DIGEST, + KMType.RSA_PUBLIC_EXPONENT, + KMType.ECCURVE, + KMType.PADDING, + KMType.DIGEST, + KMType.KEYSIZE, + KMType.ALGORITHM, + KMType.PURPOSE + }; + // Below are the constants for the key usage extension. + private static final byte keyUsageSign = (byte) 0x80; // 0 bit + private static final byte keyUsageKeyEncipher = (byte) 0x20; // 2nd- bit + private static final byte keyUsageDataEncipher = (byte) 0x10; // 3rd- bit + private static final byte keyUsageKeyAgreement = (byte) 0x08; // 4th- bit + private static final byte keyUsageCertSign = (byte) 0x04; // 5th- bit + // KeyMint HAL Version constant. + private static final short KEYMINT_VERSION = 200; + // Attestation version constant. + private static final short ATTESTATION_VERSION = 200; + // The X.509 version as per rfc5280#section-4.1.2.1 + private static final byte X509_VERSION = (byte) 0x02; + + // Buffer indexes in transient array + private static final byte NUM_INDEX_ENTRIES = 21; + private static final byte CERT_START = (byte) 0; + private static final byte CERT_LENGTH = (byte) 1; + private static final byte TBS_START = (byte) 2; + private static final byte TBS_LENGTH = (byte) 3; + private static final byte BUF_START = (byte) 4; + private static final byte BUF_LENGTH = (byte) 5; + private static final byte SW_PARAM_INDEX = (byte) 6; + private static final byte HW_PARAM_INDEX = (byte) 7; + // Data indexes in transient array + private static final byte STACK_PTR = (byte) 8; + private static final byte UNIQUE_ID = (byte) 9; + private static final byte ATT_CHALLENGE = (byte) 10; + private static final byte NOT_BEFORE = (byte) 11; + private static final byte NOT_AFTER = (byte) 12; + private static final byte PUB_KEY = (byte) 13; + private static final byte VERIFIED_BOOT_KEY = (byte) 14; + private static final byte VERIFIED_HASH = (byte) 15; + private static final byte ISSUER = (byte) 16; + private static final byte SUBJECT_NAME = (byte) 17; + private static final byte SERIAL_NUMBER = (byte) 18; + private static final byte CERT_ATT_KEY_SECRET = (byte) 19; + private static final byte CERT_ATT_KEY_RSA_PUB_MOD = (byte) 20; + // State indexes in transient array + private static final byte NUM_STATE_ENTRIES = 7; + private static final byte KEY_USAGE = (byte) 0; + private static final byte UNUSED_BITS = (byte) 1; + private static final byte DEVICE_LOCKED = (byte) 2; + private static final byte VERIFIED_STATE = (byte) 3; + private static final byte CERT_MODE = (byte) 4; + private static final byte RSA_CERT = (byte) 5; + private static final byte CERT_RSA_SIGN = (byte) 6; + + private static KMAttestationCert inst; + private static KMSEProvider seProvider; + + private static short[] indexes; + private static byte[] states; + + private static byte[] stack; + private static short[] swParams; + private static short[] hwParams; + // The maximum size of the serial number. + private static final byte SERIAL_NUM_MAX_LEN = 20; + + private KMAttestationCertImpl() {} + + public static KMAttestationCert instance(boolean rsaCert, KMSEProvider provider) { + if (inst == null) { + inst = new KMAttestationCertImpl(); + seProvider = provider; + + // Allocate transient memory + indexes = JCSystem.makeTransientShortArray(NUM_INDEX_ENTRIES, JCSystem.CLEAR_ON_RESET); + states = JCSystem.makeTransientByteArray(NUM_STATE_ENTRIES, JCSystem.CLEAR_ON_RESET); + swParams = JCSystem.makeTransientShortArray(MAX_PARAMS, JCSystem.CLEAR_ON_RESET); + hwParams = JCSystem.makeTransientShortArray(MAX_PARAMS, JCSystem.CLEAR_ON_RESET); + } + init(rsaCert); + return inst; + } + + private static void init(boolean rsaCert) { + for (short i = 0; i < NUM_INDEX_ENTRIES; i++) { + indexes[i] = 0; + } + Util.arrayFillNonAtomic(states, (short) 0, NUM_STATE_ENTRIES, (byte) 0); + stack = null; + states[CERT_MODE] = KMType.NO_CERT; + states[UNUSED_BITS] = 8; + states[RSA_CERT] = rsaCert ? (byte) 1 : (byte) 0; + states[CERT_RSA_SIGN] = 1; + indexes[CERT_ATT_KEY_SECRET] = KMType.INVALID_VALUE; + indexes[CERT_ATT_KEY_RSA_PUB_MOD] = KMType.INVALID_VALUE; + indexes[ISSUER] = KMType.INVALID_VALUE; + indexes[SUBJECT_NAME] = KMType.INVALID_VALUE; + indexes[SERIAL_NUMBER] = KMType.INVALID_VALUE; + } + + @Override + public KMAttestationCert verifiedBootHash(short obj) { + indexes[VERIFIED_HASH] = obj; + return this; + } + + @Override + public KMAttestationCert verifiedBootKey(short obj) { + indexes[VERIFIED_BOOT_KEY] = obj; + return this; + } + + @Override + public KMAttestationCert verifiedBootState(byte val) { + states[VERIFIED_STATE] = val; + return this; + } + + private KMAttestationCert uniqueId(short obj) { + indexes[UNIQUE_ID] = obj; + return this; + } + + @Override + public KMAttestationCert notBefore(short obj, boolean derEncoded, byte[] scratchpad) { + if (!derEncoded) { + // convert milliseconds to UTC date + indexes[NOT_BEFORE] = KMUtils.convertToDate(obj, scratchpad, true); + } else { + indexes[NOT_BEFORE] = + KMByteBlob.instance( + KMByteBlob.cast(obj).getBuffer(), + KMByteBlob.cast(obj).getStartOff(), + KMByteBlob.cast(obj).length()); + } + return this; + } + + @Override + public KMAttestationCert notAfter( + short usageExpiryTimeObj, boolean derEncoded, byte[] scratchPad) { + if (!derEncoded) { + if (usageExpiryTimeObj != KMType.INVALID_VALUE) { + // compare if the expiry time is greater then 2050 then use generalized + // time format else use utc time format. + short tmpVar = KMInteger.uint_64(KMUtils.firstJan2050, (short) 0); + if (KMInteger.compare(usageExpiryTimeObj, tmpVar) >= 0) { + usageExpiryTimeObj = KMUtils.convertToDate(usageExpiryTimeObj, scratchPad, false); + } else { + usageExpiryTimeObj = KMUtils.convertToDate(usageExpiryTimeObj, scratchPad, true); + } + indexes[NOT_AFTER] = usageExpiryTimeObj; + } else { + // notAfter = certExpirtyTimeObj; + } + } else { + indexes[NOT_AFTER] = usageExpiryTimeObj; + } + return this; + } + + @Override + public KMAttestationCert deviceLocked(boolean val) { + if (val) { + states[DEVICE_LOCKED] = (byte) 0xFF; + } else { + states[DEVICE_LOCKED] = 0; + } + return this; + } + + @Override + public KMAttestationCert publicKey(short obj) { + indexes[PUB_KEY] = obj; + return this; + } + + @Override + public KMAttestationCert attestationChallenge(short obj) { + indexes[ATT_CHALLENGE] = obj; + return this; + } + + @Override + public KMAttestationCert extensionTag(short tag, boolean hwEnforced) { + if (hwEnforced) { + hwParams[indexes[HW_PARAM_INDEX]] = tag; + indexes[HW_PARAM_INDEX]++; + } else { + swParams[indexes[SW_PARAM_INDEX]] = tag; + indexes[SW_PARAM_INDEX]++; + } + if (KMTag.getKey(tag) == KMType.PURPOSE) { + createKeyUsage(tag); + } + return this; + } + + @Override + public KMAttestationCert issuer(short obj) { + indexes[ISSUER] = obj; + return this; + } + + private void createKeyUsage(short tag) { + short len = KMEnumArrayTag.cast(tag).length(); + byte index = 0; + while (index < len) { + if (KMEnumArrayTag.cast(tag).get(index) == KMType.SIGN) { + states[KEY_USAGE] = (byte) (states[KEY_USAGE] | keyUsageSign); + } else if (KMEnumArrayTag.cast(tag).get(index) == KMType.WRAP_KEY) { + states[KEY_USAGE] = (byte) (states[KEY_USAGE] | keyUsageKeyEncipher); + } else if (KMEnumArrayTag.cast(tag).get(index) == KMType.DECRYPT) { + states[KEY_USAGE] = (byte) (states[KEY_USAGE] | keyUsageDataEncipher); + } else if (KMEnumArrayTag.cast(tag).get(index) == KMType.AGREE_KEY) { + states[KEY_USAGE] = (byte) (states[KEY_USAGE] | keyUsageKeyAgreement); + } else if (KMEnumArrayTag.cast(tag).get(index) == KMType.ATTEST_KEY) { + states[KEY_USAGE] = (byte) (states[KEY_USAGE] | keyUsageCertSign); + } + index++; + } + index = states[KEY_USAGE]; + while (index != 0) { + index = (byte) (index << 1); + states[UNUSED_BITS]--; + } + } + + private static void pushTbsCert(boolean rsaCert, boolean rsa) { + short last = indexes[STACK_PTR]; + pushExtensions(); + // subject public key info + if (rsaCert) { + pushRsaSubjectKeyInfo(); + } else { + pushEccSubjectKeyInfo(); + } + // subject + pushBytes( + KMByteBlob.cast(indexes[SUBJECT_NAME]).getBuffer(), + KMByteBlob.cast(indexes[SUBJECT_NAME]).getStartOff(), + KMByteBlob.cast(indexes[SUBJECT_NAME]).length()); + pushValidity(); + // issuer - der encoded + pushBytes( + KMByteBlob.cast(indexes[ISSUER]).getBuffer(), + KMByteBlob.cast(indexes[ISSUER]).getStartOff(), + KMByteBlob.cast(indexes[ISSUER]).length()); + // Algorithm Id + if (rsa) { + pushAlgorithmId(X509RsaSignAlgIdentifier); + } else { + pushAlgorithmId(X509EcdsaSignAlgIdentifier); + } + // Serial Number + pushBytes( + KMByteBlob.cast(indexes[SERIAL_NUMBER]).getBuffer(), + KMByteBlob.cast(indexes[SERIAL_NUMBER]).getStartOff(), + KMByteBlob.cast(indexes[SERIAL_NUMBER]).length()); + pushIntegerHeader(KMByteBlob.cast(indexes[SERIAL_NUMBER]).length()); + // Version + pushByte(X509_VERSION); + pushIntegerHeader((short) 1); + pushByte((byte) 0x03); + pushByte((byte) 0xA0); + // Finally sequence header. + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + private static void pushExtensions() { + short last = indexes[STACK_PTR]; + // Push KeyUsage extension + if (states[KEY_USAGE] != 0) { + pushKeyUsage(states[KEY_USAGE], states[UNUSED_BITS]); + } + if (states[CERT_MODE] == KMType.ATTESTATION_CERT) { + pushKeyDescription(); + } + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + // Extensions have explicit tag of [3] + pushLength((short) (last - indexes[STACK_PTR])); + pushByte((byte) 0xA3); + } + + // Time SEQUENCE{UTCTime, UTC or Generalized Time) + private static void pushValidity() { + short last = indexes[STACK_PTR]; + if (indexes[NOT_AFTER] != 0) { + pushBytes( + KMByteBlob.cast(indexes[NOT_AFTER]).getBuffer(), + KMByteBlob.cast(indexes[NOT_AFTER]).getStartOff(), + KMByteBlob.cast(indexes[NOT_AFTER]).length()); + } else { + KMException.throwIt(KMError.INVALID_DATA); + } + pushTimeHeader(KMByteBlob.cast(indexes[NOT_AFTER]).length()); + pushBytes( + KMByteBlob.cast(indexes[NOT_BEFORE]).getBuffer(), + KMByteBlob.cast(indexes[NOT_BEFORE]).getStartOff(), + KMByteBlob.cast(indexes[NOT_BEFORE]).length()); + pushTimeHeader(KMByteBlob.cast(indexes[NOT_BEFORE]).length()); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + private static void pushTimeHeader(short len) { + if (len == 13) { // UTC Time + pushLength((short) 0x0D); + pushByte((byte) 0x17); + } else if (len == 15) { // Generalized Time + pushLength((short) 0x0F); + pushByte((byte) 0x18); + } else { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + } + + // SEQUENCE{SEQUENCE{algId, NULL}, bitString{SEQUENCE{ modulus as positive integer, public + // exponent + // as positive integer} + private static void pushRsaSubjectKeyInfo() { + short last = indexes[STACK_PTR]; + pushBytes(KMKeymasterApplet.F4, (short) 0, (short) KMKeymasterApplet.F4.length); + pushIntegerHeader((short) KMKeymasterApplet.F4.length); + pushBytes( + KMByteBlob.cast(indexes[PUB_KEY]).getBuffer(), + KMByteBlob.cast(indexes[PUB_KEY]).getStartOff(), + KMByteBlob.cast(indexes[PUB_KEY]).length()); + + // encode modulus as positive if the MSB is 1. + if (KMByteBlob.cast(indexes[PUB_KEY]).get((short) 0) < 0) { + pushByte((byte) 0x00); + pushIntegerHeader((short) (KMByteBlob.cast(indexes[PUB_KEY]).length() + 1)); + } else { + pushIntegerHeader(KMByteBlob.cast(indexes[PUB_KEY]).length()); + } + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + pushBitStringHeader((byte) 0x00, (short) (last - indexes[STACK_PTR])); + pushRsaEncryption(); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + // SEQUENCE{SEQUENCE{ecPubKey, prime256v1}, bitString{pubKey}} + private static void pushEccSubjectKeyInfo() { + short last = indexes[STACK_PTR]; + pushBytes( + KMByteBlob.cast(indexes[PUB_KEY]).getBuffer(), + KMByteBlob.cast(indexes[PUB_KEY]).getStartOff(), + KMByteBlob.cast(indexes[PUB_KEY]).length()); + pushBitStringHeader((byte) 0x00, KMByteBlob.cast(indexes[PUB_KEY]).length()); + pushEcDsa(); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + private static void pushEcDsa() { + short last = indexes[STACK_PTR]; + pushBytes(prime256v1, (short) 0, (short) prime256v1.length); + pushBytes(eccPubKey, (short) 0, (short) eccPubKey.length); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + private static void pushRsaEncryption() { + short last = indexes[STACK_PTR]; + pushNullHeader(); + pushBytes(rsaEncryption, (short) 0, (short) rsaEncryption.length); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + // KeyDescription ::= SEQUENCE { + // attestationVersion INTEGER, # Value 200 + // attestationSecurityLevel SecurityLevel, # See below + // keymasterVersion INTEGER, # Value 200 + // keymasterSecurityLevel SecurityLevel, # See below + // attestationChallenge OCTET_STRING, # Tag::ATTESTATION_CHALLENGE from attestParams + // uniqueId OCTET_STRING, # Empty unless key has Tag::INCLUDE_UNIQUE_ID + // softwareEnforced AuthorizationList, # See below + // hardwareEnforced AuthorizationList, # See below + // } + private static void pushKeyDescription() { + short last = indexes[STACK_PTR]; + pushHWParams(); + pushSWParams(); + if (indexes[UNIQUE_ID] != 0) { + pushOctetString( + KMByteBlob.cast(indexes[UNIQUE_ID]).getBuffer(), + KMByteBlob.cast(indexes[UNIQUE_ID]).getStartOff(), + KMByteBlob.cast(indexes[UNIQUE_ID]).length()); + } else { + pushOctetStringHeader((short) 0); + } + pushOctetString( + KMByteBlob.cast(indexes[ATT_CHALLENGE]).getBuffer(), + KMByteBlob.cast(indexes[ATT_CHALLENGE]).getStartOff(), + KMByteBlob.cast(indexes[ATT_CHALLENGE]).length()); + pushEnumerated(KMType.STRONGBOX); + pushShort(KEYMINT_VERSION); + pushIntegerHeader((short) 2); + pushEnumerated(KMType.STRONGBOX); + pushShort(ATTESTATION_VERSION); + pushIntegerHeader((short) 2); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + pushOctetStringHeader((short) (last - indexes[STACK_PTR])); + pushBytes(androidExtn, (short) 0, (short) androidExtn.length); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + private static void pushSWParams() { + short last = indexes[STACK_PTR]; + byte index = 0; + short length = (short) swTagIds.length; + do { + pushParams(swParams, indexes[SW_PARAM_INDEX], swTagIds[index]); + } while (++index < length); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + private static void pushHWParams() { + short last = indexes[STACK_PTR]; + byte index = 0; + short length = (short) hwTagIds.length; + do { + if (hwTagIds[index] == KMType.ROOT_OF_TRUST) { + pushRoT(); + continue; + } + if (pushParams(hwParams, indexes[HW_PARAM_INDEX], hwTagIds[index])) { + continue; + } + } while (++index < length); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + private static boolean pushParams(short[] params, short len, short tagId) { + short index = 0; + while (index < len) { + if (tagId == KMTag.getKey(params[index])) { + pushTag(params[index]); + return true; + } + index++; + } + return false; + } + + private static void pushTag(short tag) { + short type = KMTag.getTagType(tag); + short tagId = KMTag.getKey(tag); + short val; + switch (type) { + case KMType.BYTES_TAG: + val = KMByteTag.cast(tag).getValue(); + pushBytesTag( + tagId, + KMByteBlob.cast(val).getBuffer(), + KMByteBlob.cast(val).getStartOff(), + KMByteBlob.cast(val).length()); + break; + case KMType.ENUM_TAG: + val = KMEnumTag.cast(tag).getValue(); + pushEnumTag(tagId, (byte) val); + break; + case KMType.ENUM_ARRAY_TAG: + val = KMEnumArrayTag.cast(tag).getValues(); + pushEnumArrayTag( + tagId, + KMByteBlob.cast(val).getBuffer(), + KMByteBlob.cast(val).getStartOff(), + KMByteBlob.cast(val).length()); + break; + case KMType.UINT_TAG: + case KMType.ULONG_TAG: + case KMType.DATE_TAG: + val = KMIntegerTag.cast(tag).getValue(); + pushIntegerTag( + tagId, + KMInteger.cast(val).getBuffer(), + KMInteger.cast(val).getStartOff(), + KMInteger.cast(val).length()); + break; + case KMType.UINT_ARRAY_TAG: + case KMType.ULONG_ARRAY_TAG: + // According to KeyMint hal only one user secure id is used but this conflicts with + // tag type which is ULONG-REP. Currently this is encoded as SET OF INTEGERS + val = KMIntegerArrayTag.cast(tag).getValues(); + pushIntegerArrayTag(tagId, val); + break; + case KMType.BOOL_TAG: + val = KMBoolTag.cast(tag).getVal(); + pushBoolTag(tagId); + break; + default: + KMException.throwIt(KMError.INVALID_TAG); + break; + } + } + + // RootOfTrust ::= SEQUENCE { + // verifiedBootKey OCTET_STRING, + // deviceLocked BOOLEAN, + // verifiedBootState VerifiedBootState, + // verifiedBootHash OCTET_STRING, + // } + // VerifiedBootState ::= ENUMERATED { + // Verified (0), + // SelfSigned (1), + // Unverified (2), + // Failed (3), + // } + private static void pushRoT() { + short last = indexes[STACK_PTR]; + // verified boot hash + pushOctetString( + KMByteBlob.cast(indexes[VERIFIED_HASH]).getBuffer(), + KMByteBlob.cast(indexes[VERIFIED_HASH]).getStartOff(), + KMByteBlob.cast(indexes[VERIFIED_HASH]).length()); + + pushEnumerated(states[VERIFIED_STATE]); + + pushBoolean(states[DEVICE_LOCKED]); + // verified boot Key + pushOctetString( + KMByteBlob.cast(indexes[VERIFIED_BOOT_KEY]).getBuffer(), + KMByteBlob.cast(indexes[VERIFIED_BOOT_KEY]).getStartOff(), + KMByteBlob.cast(indexes[VERIFIED_BOOT_KEY]).length()); + + // Finally sequence header + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + // ... and tag Id + pushTagIdHeader(KMType.ROOT_OF_TRUST, (short) (last - indexes[STACK_PTR])); + } + + private static void pushOctetString(byte[] buf, short start, short len) { + pushBytes(buf, start, len); + pushOctetStringHeader(len); + } + + private static void pushBoolean(byte val) { + pushByte(val); + pushBooleanHeader((short) 1); + } + + private static void pushBooleanHeader(short len) { + pushLength(len); + pushByte((byte) 0x01); + } + + // Only SET of INTEGERS supported are padding, digest, purpose and blockmode + // All of these are enum array tags i.e. byte long values + private static void pushEnumArrayTag(short tagId, byte[] buf, short start, short len) { + short last = indexes[STACK_PTR]; + short index = 0; + while (index < len) { + pushByte(buf[(short) (start + index)]); + pushIntegerHeader((short) 1); + index++; + } + pushSetHeader((short) (last - indexes[STACK_PTR])); + pushTagIdHeader(tagId, (short) (last - indexes[STACK_PTR])); + } + + // Only SET of INTEGERS supported are padding, digest, purpose and blockmode + // All of these are enum array tags i.e. byte long values + private static void pushIntegerArrayTag(short tagId, short arr) { + short last = indexes[STACK_PTR]; + short index = 0; + short len = KMArray.cast(arr).length(); + short ptr; + while (index < len) { + ptr = KMArray.cast(arr).get(index); + pushInteger( + KMInteger.cast(ptr).getBuffer(), + KMInteger.cast(ptr).getStartOff(), + KMInteger.cast(ptr).length()); + index++; + } + pushSetHeader((short) (last - indexes[STACK_PTR])); + pushTagIdHeader(tagId, (short) (last - indexes[STACK_PTR])); + } + + private static void pushSetHeader(short len) { + pushLength(len); + pushByte((byte) 0x31); + } + + private static void pushEnumerated(byte val) { + short last = indexes[STACK_PTR]; + pushByte(val); + pushEnumeratedHeader((short) (last - indexes[STACK_PTR])); + } + + private static void pushEnumeratedHeader(short len) { + pushLength(len); + pushByte((byte) 0x0A); + } + + private static void pushBoolTag(short tagId) { + short last = indexes[STACK_PTR]; + pushNullHeader(); + pushTagIdHeader(tagId, (short) (last - indexes[STACK_PTR])); + } + + private static void pushNullHeader() { + pushByte((byte) 0); + pushByte((byte) 0x05); + } + + private static void pushEnumTag(short tagId, byte val) { + short last = indexes[STACK_PTR]; + pushByte(val); + pushIntegerHeader((short) (last - indexes[STACK_PTR])); + pushTagIdHeader(tagId, (short) (last - indexes[STACK_PTR])); + } + + private static void pushIntegerTag(short tagId, byte[] buf, short start, short len) { + short last = indexes[STACK_PTR]; + pushInteger(buf, start, len); + pushTagIdHeader(tagId, (short) (last - indexes[STACK_PTR])); + } + + // Ignore leading zeros. Only Unsigned Integers are required hence if MSB is set then add 0x00 + // as most significant byte. + private static void pushInteger(byte[] buf, short start, short len) { + short last = indexes[STACK_PTR]; + byte index = 0; + while (index < (byte) len) { + if (buf[(short) (start + index)] != 0) { + break; + } + index++; + } + if (index == (byte) len) { + pushByte((byte) 0x00); + } else { + pushBytes(buf, (short) (start + index), (short) (len - index)); + if (buf[(short) (start + index)] < 0) { // MSB is 1 + pushByte((byte) 0x00); // always unsigned int + } + } + pushIntegerHeader((short) (last - indexes[STACK_PTR])); + } + + // Bytes Tag is a octet string and tag id is added explicitly + private static void pushBytesTag(short tagId, byte[] buf, short start, short len) { + short last = indexes[STACK_PTR]; + pushBytes(buf, start, len); + pushOctetStringHeader((short) (last - indexes[STACK_PTR])); + pushTagIdHeader(tagId, (short) (last - indexes[STACK_PTR])); + } + + // tag id <= 30 ---> 0xA0 | {tagId} + // 30 < tagId < 128 ---> 0xBF 0x{tagId} + // tagId >= 128 ---> 0xBF 0x80+(tagId/128) 0x{tagId - (128*(tagId/128))} + private static void pushTagIdHeader(short tagId, short len) { + pushLength(len); + short count = (short) (tagId / 128); + if (count > 0) { + pushByte((byte) (tagId - (128 * count))); + pushByte((byte) (0x80 + count)); + pushByte((byte) 0xBF); + } else if (tagId > 30) { + pushByte((byte) tagId); + pushByte((byte) 0xBF); + } else { + pushByte((byte) (0xA0 | (byte) tagId)); + } + } + + // SEQUENCE {ObjId, OCTET STRING{BIT STRING{keyUsage}}} + private static void pushKeyUsage(byte keyUsage, byte unusedBits) { + short last = indexes[STACK_PTR]; + pushByte(keyUsage); + pushBitStringHeader(unusedBits, (short) (last - indexes[STACK_PTR])); + pushOctetStringHeader((short) (last - indexes[STACK_PTR])); + pushBytes(keyUsageExtn, (short) 0, (short) keyUsageExtn.length); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + private static void pushAlgorithmId(byte[] algId) { + pushBytes(algId, (short) 0, (short) algId.length); + } + + private static void pushIntegerHeader(short len) { + pushLength(len); + pushByte((byte) 0x02); + } + + private static void pushOctetStringHeader(short len) { + pushLength(len); + pushByte((byte) 0x04); + } + + private static void pushSequenceHeader(short len) { + pushLength(len); + pushByte((byte) 0x30); + } + + private static void pushBitStringHeader(byte unusedBits, short len) { + pushByte(unusedBits); + pushLength((short) (len + 1)); // 1 extra byte for unused bits byte + pushByte((byte) 0x03); + } + + private static void pushLength(short len) { + if (len < 128) { + pushByte((byte) len); + } else if (len < 256) { + pushByte((byte) len); + pushByte((byte) 0x81); + } else { + pushShort(len); + pushByte((byte) 0x82); + } + } + + private static void pushShort(short val) { + decrementStackPtr((short) 2); + Util.setShort(stack, indexes[STACK_PTR], val); + } + + private static void pushByte(byte val) { + decrementStackPtr((short) 1); + stack[indexes[STACK_PTR]] = val; + } + + private static void pushBytes(byte[] buf, short start, short len) { + decrementStackPtr(len); + if (buf != null) { + Util.arrayCopyNonAtomic(buf, start, stack, indexes[STACK_PTR], len); + } + } + + private static void decrementStackPtr(short cnt) { + indexes[STACK_PTR] = (short) (indexes[STACK_PTR] - cnt); + if (indexes[BUF_START] > indexes[STACK_PTR]) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + } + + @Override + public KMAttestationCert buffer(byte[] buf, short start, short maxLen) { + stack = buf; + indexes[BUF_START] = start; + indexes[BUF_LENGTH] = maxLen; + indexes[STACK_PTR] = (short) (indexes[BUF_START] + indexes[BUF_LENGTH]); + return this; + } + + @Override + public short getCertStart() { + return indexes[CERT_START]; + } + + @Override + public short getCertLength() { + return indexes[CERT_LENGTH]; + } + + public void build(short attSecret, short attMod, boolean rsaSign, boolean fakeCert) { + indexes[STACK_PTR] = (short) (indexes[BUF_START] + indexes[BUF_LENGTH]); + short last = indexes[STACK_PTR]; + short sigLen = 0; + if (fakeCert) { + rsaSign = true; + pushByte((byte) 0); + sigLen = 1; + } + // Push placeholder signature Bit string header + // This will potentially change at the end + else if (rsaSign) { + decrementStackPtr(RSA_SIG_LEN); + } else { + decrementStackPtr(ECDSA_MAX_SIG_LEN); + } + short signatureOffset = indexes[STACK_PTR]; + pushBitStringHeader((byte) 0, (short) (last - indexes[STACK_PTR])); + if (rsaSign) { + pushAlgorithmId(X509RsaSignAlgIdentifier); + } else { + pushAlgorithmId(X509EcdsaSignAlgIdentifier); + } + indexes[TBS_LENGTH] = indexes[STACK_PTR]; + pushTbsCert((states[RSA_CERT] == 0 ? false : true), rsaSign); + indexes[TBS_START] = indexes[STACK_PTR]; + indexes[TBS_LENGTH] = (short) (indexes[TBS_LENGTH] - indexes[TBS_START]); + if (attSecret != KMType.INVALID_VALUE) { + // Sign with the attestation key + // The pubKey is the modulus. + if (rsaSign) { + sigLen = + seProvider.rsaSign256Pkcs1( + KMByteBlob.cast(attSecret).getBuffer(), + KMByteBlob.cast(attSecret).getStartOff(), + KMByteBlob.cast(attSecret).length(), + KMByteBlob.cast(attMod).getBuffer(), + KMByteBlob.cast(attMod).getStartOff(), + KMByteBlob.cast(attMod).length(), + stack, + indexes[TBS_START], + indexes[TBS_LENGTH], + stack, + signatureOffset); + if (sigLen > RSA_SIG_LEN) KMException.throwIt(KMError.UNKNOWN_ERROR); + } else { + sigLen = + seProvider.ecSign256( + KMByteBlob.cast(attSecret).getBuffer(), + KMByteBlob.cast(attSecret).getStartOff(), + KMByteBlob.cast(attSecret).length(), + stack, + indexes[TBS_START], + indexes[TBS_LENGTH], + stack, + signatureOffset); + if (sigLen > ECDSA_MAX_SIG_LEN) KMException.throwIt(KMError.UNKNOWN_ERROR); + } + // Adjust signature length + indexes[STACK_PTR] = signatureOffset; + pushBitStringHeader((byte) 0, sigLen); + } else if (!fakeCert) { // No attestation key provisioned in the factory + KMException.throwIt(KMError.ATTESTATION_KEYS_NOT_PROVISIONED); + } + last = (short) (signatureOffset + sigLen); + // Add certificate sequence header + indexes[STACK_PTR] = indexes[TBS_START]; + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + indexes[CERT_START] = indexes[STACK_PTR]; + indexes[CERT_LENGTH] = (short) (last - indexes[CERT_START]); + } + + @Override + public void build() { + if (states[CERT_MODE] == KMType.FAKE_CERT) { + build(KMType.INVALID_VALUE, KMType.INVALID_VALUE, true, true); + } else { + build( + indexes[CERT_ATT_KEY_SECRET], + indexes[CERT_ATT_KEY_RSA_PUB_MOD], + (states[CERT_RSA_SIGN] == 0 ? false : true), + false); + } + } + + @Override + public KMAttestationCert makeUniqueId( + byte[] scratchPad, + short scratchPadOff, + byte[] creationTime, + short timeOffset, + short creationTimeLen, + byte[] attestAppId, + short appIdOff, + short attestAppIdLen, + byte resetSinceIdRotation, + KMKey masterKey) { + // Concatenate T||C||R + // temporal count T + short temp = + KMUtils.countTemporalCount( + creationTime, timeOffset, creationTimeLen, scratchPad, scratchPadOff); + Util.setShort(scratchPad, (short) scratchPadOff, temp); + temp = scratchPadOff; + scratchPadOff += 2; + + // Application Id C + Util.arrayCopyNonAtomic(attestAppId, appIdOff, scratchPad, scratchPadOff, attestAppIdLen); + scratchPadOff += attestAppIdLen; + + // Reset After Rotation R + scratchPad[scratchPadOff] = resetSinceIdRotation; + scratchPadOff++; + + // Get the key data from the master key + KMAESKey aesKey = (KMAESKey) masterKey; + short mKeyData = KMByteBlob.instance((short) (aesKey.aesKey.getSize() / 8)); + aesKey.aesKey.getKey( + KMByteBlob.cast(mKeyData).getBuffer(), /* Key */ + KMByteBlob.cast(mKeyData).getStartOff()); /* Key start*/ + timeOffset = KMByteBlob.instance((short) 32); + appIdOff = + seProvider.hmacSign( + KMByteBlob.cast(mKeyData).getBuffer(), /* Key */ + KMByteBlob.cast(mKeyData).getStartOff(), /* Key start*/ + KMByteBlob.cast(mKeyData).length(), /* Key length*/ + scratchPad, /* data */ + temp, /* data start */ + scratchPadOff, /* data length */ + KMByteBlob.cast(timeOffset).getBuffer(), /* signature buffer */ + KMByteBlob.cast(timeOffset).getStartOff()); /* signature start */ + if (appIdOff != 32) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + return uniqueId(timeOffset); + } + + @Override + public boolean serialNumber(short number) { + // https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.2 + short length = KMByteBlob.cast(number).length(); + if (length > SERIAL_NUM_MAX_LEN) { + return false; + } + // The serial number Must be a positive integer. + byte msb = KMByteBlob.cast(number).get((short) 0); + if (msb < 0 && length > (SERIAL_NUM_MAX_LEN - 1)) { + return false; + } + indexes[SERIAL_NUMBER] = number; + return true; + } + + @Override + public boolean subjectName(short sub) { + if (sub == KMType.INVALID_VALUE || KMByteBlob.cast(sub).length() == 0) return false; + indexes[SUBJECT_NAME] = sub; + return true; + } + + @Override + public KMAttestationCert ecAttestKey(short attestKey, byte mode) { + states[CERT_MODE] = mode; + indexes[CERT_ATT_KEY_SECRET] = attestKey; + indexes[CERT_ATT_KEY_RSA_PUB_MOD] = KMType.INVALID_VALUE; + states[CERT_RSA_SIGN] = 0; + return this; + } + + @Override + public KMAttestationCert rsaAttestKey(short attestPrivExp, short attestMod, byte mode) { + states[CERT_MODE] = mode; + indexes[CERT_ATT_KEY_SECRET] = attestPrivExp; + indexes[CERT_ATT_KEY_RSA_PUB_MOD] = attestMod; + states[CERT_RSA_SIGN] = 1; + return this; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMConfigurations.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMConfigurations.java new file mode 100644 index 0000000..738326b --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMConfigurations.java @@ -0,0 +1,31 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.keymaster; + +/** + * This class contains all the configuration values. Vendors can modify these values accordingly + * based on their environment. + */ +public class KMConfigurations { + // Machine types + public static final byte LITTLE_ENDIAN = 0x00; + public static final byte BIG_ENDIAN = 0x01; + public static final byte TEE_MACHINE_TYPE = LITTLE_ENDIAN; + // If the size of the attestation ids is known and lesser than 64 + // then reduce the size here. It reduces the heap memory usage. + public static final byte MAX_ATTESTATION_IDS_SIZE = 64; + public static final short MAX_SUBJECT_DER_LEN = 1095; +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMUtils.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMUtils.java new file mode 100644 index 0000000..95ee67f --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMUtils.java @@ -0,0 +1,440 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.keymaster; + +import com.android.javacard.seprovider.KMException; +import javacard.framework.Util; + +/** + * This is a utility class which helps in converting date to UTC format and doing some arithmetic + * Operations. + */ +public class KMUtils { + + // 64 bit unsigned calculations for time + public static final byte[] oneSecMsec = {0, 0, 0, 0, 0, 0, 0x03, (byte) 0xE8}; // 1000 msec + public static final byte[] oneMinMsec = {0, 0, 0, 0, 0, 0, (byte) 0xEA, 0x60}; // 60000 msec + public static final byte[] oneHourMsec = { + 0, 0, 0, 0, 0, 0x36, (byte) 0xEE, (byte) 0x80 + }; // 3600000 msec + public static final byte[] oneDayMsec = {0, 0, 0, 0, 0x05, 0x26, 0x5C, 0x00}; // 86400000 msec + public static final byte[] oneMonthMsec = { + 0, 0, 0, 0, (byte) 0x9C, (byte) 0xBE, (byte) 0xBD, 0x50 + }; // 2629746000 msec + public static final byte[] leapYearMsec = { + 0, 0, 0, 0x07, (byte) 0x5C, (byte) 0xD7, (byte) 0x88, 0x00 + }; // 31622400000; + public static final byte[] yearMsec = { + 0, 0, 0, 0x07, 0x57, (byte) 0xB1, 0x2C, 0x00 + }; // 31536000000 + // Leap year(366) + 3 * 365 + public static final byte[] fourYrsMsec = { + 0, 0, 0, 0x1D, 0x63, (byte) 0xEB, 0x0C, 0x00 + }; // 126230400000 + public static final byte[] firstJan2020 = { + 0, 0, 0x01, 0x6F, 0x5E, 0x66, (byte) 0xE8, 0x00 + }; // 1577836800000 msec + public static final byte[] firstJan2050 = { + 0, 0, 0x02, 0x4b, (byte) 0xCE, 0x5C, (byte) 0xF0, 0x00 + }; // 2524608000000 + // msec + public static final byte[] febMonthLeapMSec = { + 0, 0, 0, 0, (byte) 0x95, 0x58, 0x6C, 0x00 + }; // 2505600000 + public static final byte[] febMonthMsec = { + 0, 0, 0, 0, (byte) 0x90, 0x32, 0x10, 0x00 + }; // 2419200000 + public static final byte[] ThirtyOneDaysMonthMsec = { + 0, 0, 0, 0, (byte) 0x9F, (byte) 0xA5, 0x24, 0x00 + }; // 2678400000 + public static final byte[] ThirtDaysMonthMsec = { + 0, 0, 0, 0, (byte) 0x9A, 0x7E, (byte) 0xC8, 0x00 + }; // 2592000000 + public static final short year2051 = 2051; + public static final short year2020 = 2020; + // Convert to milliseconds constants + public static final byte[] SEC_TO_MILLIS_SHIFT_POS = {9, 8, 7, 6, 5, 3}; + + // -------------------------------------- + public static short convertToDate(short time, byte[] scratchPad, boolean utcFlag) { + + short yrsCount = 0; + short monthCount = 1; + short dayCount = 1; + short hhCount = 0; + short mmCount = 0; + short ssCount = 0; + byte Z = 0x5A; + boolean from2020 = true; + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + Util.arrayCopyNonAtomic( + KMInteger.cast(time).getBuffer(), + KMInteger.cast(time).getStartOff(), + scratchPad, + (short) (8 - KMInteger.cast(time).length()), + KMInteger.cast(time).length()); + // If the time is less then 1 Jan 2020 then it is an error + if (KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, firstJan2020, (short) 0, (short) 8) + < 0) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + if (utcFlag + && KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, firstJan2050, (short) 0, (short) 8) + >= 0) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + + if (KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, firstJan2050, (short) 0, (short) 8) + < 0) { + Util.arrayCopyNonAtomic(firstJan2020, (short) 0, scratchPad, (short) 8, (short) 8); + subtract(scratchPad, (short) 0, (short) 8, (short) 16, (byte) 8); + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + } else { + from2020 = false; + Util.arrayCopyNonAtomic(firstJan2050, (short) 0, scratchPad, (short) 8, (short) 8); + subtract(scratchPad, (short) 0, (short) 8, (short) 16, (byte) 8); + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + } + // divide the given time with four yrs msec count + if (KMInteger.unsignedByteArrayCompare(scratchPad, (short) 0, fourYrsMsec, (short) 0, (short) 8) + >= 0) { + Util.arrayCopyNonAtomic(fourYrsMsec, (short) 0, scratchPad, (short) 8, (short) 8); + // quotient is multiple of 4 + yrsCount = divide(scratchPad, (short) 0, (short) 8, (short) 16); + yrsCount = (short) (yrsCount * 4); // number of yrs. + // copy reminder as new dividend + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + } + + // Get the leap year index starting from the (base Year + yrsCount) Year. + short leapYrIdx = getLeapYrIndex(from2020, yrsCount); + + // if leap year index is 0, then the number of days for the 1st year will be 366 days. + // if leap year index is not 0, then the number of days for the 1st year will be 365 days. + if (((leapYrIdx == 0) + && (KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, leapYearMsec, (short) 0, (short) 8) + >= 0)) + || ((leapYrIdx != 0) + && (KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, yearMsec, (short) 0, (short) 8) + >= 0))) { + for (short i = 0; i < 4; i++) { + yrsCount++; + if (i == leapYrIdx) { + Util.arrayCopyNonAtomic(leapYearMsec, (short) 0, scratchPad, (short) 8, (short) 8); + } else { + Util.arrayCopyNonAtomic(yearMsec, (short) 0, scratchPad, (short) 8, (short) 8); + } + subtract(scratchPad, (short) 0, (short) 8, (short) 16, (byte) 8); + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + if (((short) (i + 1) == leapYrIdx)) { + if (KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, leapYearMsec, (short) 0, (short) 8) + < 0) { + break; + } + } else { + if (KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, yearMsec, (short) 0, (short) 8) + < 0) { + break; + } + } + } + } + + // total yrs from 1970 + if (from2020) { + yrsCount = (short) (year2020 + yrsCount); + } else { + yrsCount = (short) (year2051 + yrsCount); + } + + // divide the given time with one month msec count + if (KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, oneMonthMsec, (short) 0, (short) 8) + >= 0) { + for (short i = 0; i < 12; i++) { + if (i == 1) { + // Feb month + if (isLeapYear(yrsCount)) { + // Leap year 29 days + Util.arrayCopyNonAtomic(febMonthLeapMSec, (short) 0, scratchPad, (short) 8, (short) 8); + } else { + // 28 days + Util.arrayCopyNonAtomic(febMonthMsec, (short) 0, scratchPad, (short) 8, (short) 8); + } + } else if (((i <= 6) && ((i % 2 == 0))) || ((i > 6) && ((i % 2 == 1)))) { + Util.arrayCopyNonAtomic( + ThirtyOneDaysMonthMsec, (short) 0, scratchPad, (short) 8, (short) 8); + } else { + // 30 Days + Util.arrayCopyNonAtomic(ThirtDaysMonthMsec, (short) 0, scratchPad, (short) 8, (short) 8); + } + + if (KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, scratchPad, (short) 8, (short) 8) + >= 0) { + subtract(scratchPad, (short) 0, (short) 8, (short) 16, (byte) 8); + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + } else { + break; + } + monthCount++; + } + } + + // divide the given time with one day msec count + if (KMInteger.unsignedByteArrayCompare(scratchPad, (short) 0, oneDayMsec, (short) 0, (short) 8) + >= 0) { + Util.arrayCopyNonAtomic(oneDayMsec, (short) 0, scratchPad, (short) 8, (short) 8); + dayCount = divide(scratchPad, (short) 0, (short) 8, (short) 16); + dayCount++; + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + } + + // divide the given time with one hour msec count + if (KMInteger.unsignedByteArrayCompare(scratchPad, (short) 0, oneHourMsec, (short) 0, (short) 8) + >= 0) { + Util.arrayCopyNonAtomic(oneHourMsec, (short) 0, scratchPad, (short) 8, (short) 8); + hhCount = divide(scratchPad, (short) 0, (short) 8, (short) 16); + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + } + + // divide the given time with one minute msec count + if (KMInteger.unsignedByteArrayCompare(scratchPad, (short) 0, oneMinMsec, (short) 0, (short) 8) + >= 0) { + Util.arrayCopyNonAtomic(oneMinMsec, (short) 0, scratchPad, (short) 8, (short) 8); + mmCount = divide(scratchPad, (short) 0, (short) 8, (short) 16); + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + } + + // divide the given time with one second msec count + if (KMInteger.unsignedByteArrayCompare(scratchPad, (short) 0, oneSecMsec, (short) 0, (short) 8) + >= 0) { + Util.arrayCopyNonAtomic(oneSecMsec, (short) 0, scratchPad, (short) 8, (short) 8); + ssCount = divide(scratchPad, (short) 0, (short) 8, (short) 16); + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + } + + // Now convert to ascii string YYMMDDhhmmssZ or YYYYMMDDhhmmssZ + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + short len = numberToString(yrsCount, scratchPad, (short) 0); // returns YYYY + len += numberToString(monthCount, scratchPad, len); + len += numberToString(dayCount, scratchPad, len); + len += numberToString(hhCount, scratchPad, len); + len += numberToString(mmCount, scratchPad, len); + len += numberToString(ssCount, scratchPad, len); + scratchPad[len] = Z; + len++; + if (utcFlag) { + return KMByteBlob.instance(scratchPad, (short) 2, (short) (len - 2)); // YY + } else { + return KMByteBlob.instance(scratchPad, (short) 0, len); // YYYY + } + } + + public static short numberToString(short number, byte[] scratchPad, short offset) { + byte zero = 0x30; + byte len = 2; + byte digit; + if (number > 999) { + len = 4; + } + byte index = len; + while (index > 0) { + digit = (byte) (number % 10); + number = (short) (number / 10); + scratchPad[(short) (offset + index - 1)] = (byte) (digit + zero); + index--; + } + return len; + } + + // Use Euclid's formula: dividend = quotient*divisor + remainder + // i.e. dividend - quotient*divisor = remainder where remainder < divisor. + // so this is division by subtraction until remainder remains. + public static short divide(byte[] buf, short dividend, short divisor, short remainder) { + short expCnt = 1; + short q = 0; + // first increase divisor so that it becomes greater then dividend. + while (compare(buf, divisor, dividend) < 0) { + shiftLeft(buf, divisor); + expCnt = (short) (expCnt << 1); + } + // Now subtract divisor from dividend if dividend is greater then divisor. + // Copy remainder in the dividend and repeat. + while (expCnt != 0) { + if (compare(buf, dividend, divisor) >= 0) { + subtract(buf, dividend, divisor, remainder, (byte) 8); + copy(buf, remainder, dividend); + q = (short) (q + expCnt); + } + expCnt = (short) (expCnt >> 1); + shiftRight(buf, divisor); + } + return q; + } + + public static void copy(byte[] buf, short from, short to) { + Util.arrayCopyNonAtomic(buf, from, buf, to, (short) 8); + } + + public static byte compare(byte[] buf, short lhs, short rhs) { + return KMInteger.unsignedByteArrayCompare(buf, lhs, buf, rhs, (short) 8); + } + + public static void shiftLeft(byte[] buf, short start, short count) { + short index = 0; + while (index < count) { + shiftLeft(buf, start); + index++; + } + } + + public static void shiftLeft(byte[] buf, short start) { + byte index = 7; + byte carry = 0; + byte tmp; + while (index >= 0) { + tmp = buf[(short) (start + index)]; + buf[(short) (start + index)] = (byte) (buf[(short) (start + index)] << 1); + buf[(short) (start + index)] = (byte) (buf[(short) (start + index)] + carry); + if (tmp < 0) { + carry = 1; + } else { + carry = 0; + } + index--; + } + } + + public static void shiftRight(byte[] buf, short start) { + byte index = 0; + byte carry = 0; + byte tmp; + while (index < 8) { + tmp = (byte) (buf[(short) (start + index)] & 0x01); + buf[(short) (start + index)] = (byte) (buf[(short) (start + index)] >> 1); + buf[(short) (start + index)] = (byte) (buf[(short) (start + index)] & 0x7F); + buf[(short) (start + index)] = (byte) (buf[(short) (start + index)] | carry); + if (tmp == 1) { + carry = (byte) 0x80; + } else { + carry = 0; + } + index++; + } + } + + public static void add(byte[] buf, short op1, short op2, short result) { + byte index = 7; + byte carry = 0; + short tmp; + short val1 = 0; + short val2 = 0; + while (index >= 0) { + val1 = (short) (buf[(short) (op1 + index)] & 0x00FF); + val2 = (short) (buf[(short) (op2 + index)] & 0x00FF); + tmp = (short) (val1 + val2 + carry); + carry = 0; + if (tmp > 255) { + carry = 1; // max unsigned byte value is 255 + } + buf[(short) (result + index)] = (byte) (tmp & (byte) 0xFF); + index--; + } + } + + // subtraction by borrowing. + public static void subtract(byte[] buf, short op1, short op2, short result, byte sizeBytes) { + byte borrow = 0; + byte index = (byte) (sizeBytes - 1); + short r; + short x; + short y; + while (index >= 0) { + x = (short) (buf[(short) (op1 + index)] & 0xFF); + y = (short) (buf[(short) (op2 + index)] & 0xFF); + r = (short) (x - y - borrow); + borrow = 0; + if (r < 0) { + borrow = 1; + r = (short) (r + 256); // max unsigned byte value is 255 + } + buf[(short) (result + index)] = (byte) (r & 0xFF); + index--; + } + } + + public static short countTemporalCount( + byte[] bufTime, short timeOff, short timeLen, byte[] scratchPad, short offset) { + Util.arrayFillNonAtomic(scratchPad, (short) offset, (short) 24, (byte) 0); + Util.arrayCopyNonAtomic(bufTime, timeOff, scratchPad, (short) (offset + 8 - timeLen), timeLen); + Util.arrayCopyNonAtomic( + ThirtDaysMonthMsec, (short) 0, scratchPad, (short) (offset + 8), (short) 8); + return divide(scratchPad, (short) 0, (short) 8, (short) 16); + } + + public static boolean isLeapYear(short year) { + if ((short) (year % 4) == (short) 0) { + if (((short) (year % 100) == (short) 0) && ((short) (year % 400)) != (short) 0) { + return false; + } + return true; + } + return false; + } + + public static short getLeapYrIndex(boolean from2020, short yrsCount) { + short newBaseYr = (short) (from2020 ? (year2020 + yrsCount) : (year2051 + yrsCount)); + for (short i = 0; i < 4; i++) { + if (isLeapYear((short) (newBaseYr + i))) { + return i; + } + } + return -1; + } + + public static void computeOnesCompliment(byte[] buf, short offset, short len) { + short index = offset; + // Compute 1s compliment + while (index < (short) (len + offset)) { + buf[index] = (byte) ~buf[index]; + index++; + } + } + + // i * 1000 = (i << 9) + (i << 8) + (i << 7) + (i << 6) + (i << 5) + ( i << 3) + public static void convertToMilliseconds( + byte[] buf, short inputOff, short outputOff, short scratchPadOff) { + short index = 0; + short length = (short) SEC_TO_MILLIS_SHIFT_POS.length; + while (index < length) { + Util.arrayCopyNonAtomic(buf, inputOff, buf, scratchPadOff, (short) 8); + shiftLeft(buf, scratchPadOff, SEC_TO_MILLIS_SHIFT_POS[index]); + Util.arrayCopyNonAtomic(buf, outputOff, buf, (short) (scratchPadOff + 8), (short) 8); + add(buf, scratchPadOff, (short) (8 + scratchPadOff), (short) (16 + scratchPadOff)); + Util.arrayCopyNonAtomic(buf, (short) (scratchPadOff + 16), buf, outputOff, (short) 8); + Util.arrayFillNonAtomic(buf, scratchPadOff, (short) 24, (byte) 0); + index++; + } + } +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAESKey.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAESKey.java new file mode 100644 index 0000000..41059bb --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAESKey.java @@ -0,0 +1,53 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.security.AESKey; +import org.globalplatform.upgrade.Element; + +/** This is a wrapper class for AESKey. */ +public class KMAESKey implements KMKey { + + public AESKey aesKey; + + public KMAESKey(AESKey key) { + aesKey = key; + } + + public static void onSave(Element element, KMAESKey kmKey) { + element.write(kmKey.aesKey); + } + + public static KMAESKey onRestore(AESKey aesKey) { + if (aesKey == null) { + return null; + } + return new KMAESKey(aesKey); + } + + public static short getBackupPrimitiveByteCount() { + return (short) 0; + } + + public static short getBackupObjectCount() { + return (short) 1; + } + + @Override + public short getPublicKey(byte[] buf, short offset) { + return (short) 0; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAndroidSEProvider.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAndroidSEProvider.java new file mode 100644 index 0000000..cb33272 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAndroidSEProvider.java @@ -0,0 +1,1578 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; +import javacard.security.AESKey; +import javacard.security.CryptoException; +import javacard.security.DESKey; +import javacard.security.ECPrivateKey; +import javacard.security.ECPublicKey; +import javacard.security.HMACKey; +import javacard.security.Key; +import javacard.security.KeyAgreement; +import javacard.security.KeyBuilder; +import javacard.security.KeyPair; +import javacard.security.MessageDigest; +import javacard.security.RSAPrivateKey; +import javacard.security.RandomData; +import javacard.security.Signature; +import javacardx.crypto.AEADCipher; +import javacardx.crypto.Cipher; +import org.globalplatform.upgrade.Element; +import org.globalplatform.upgrade.UpgradeManager; + +/** + * This class implements KMSEProvider and provides all the necessary crypto operations required to + * support the KeyMint specification. This class supports AES, 3DES, HMAC, RSA, ECDSA, ECDH + * algorithms additionally it also supports ECDSA_NO_DIGEST, RSA_NO_DIGEST and RSA_OAEP_MGF1_SHA1 + * and RSA_OAEP_MGF1_SHA256 algorithms. This class follows the pattern of Init-Update-Final for the + * crypto operations. + */ +public class KMAndroidSEProvider implements KMSEProvider { + + // The tag length for AES GCM algorithm. + public static final byte AES_GCM_TAG_LENGTH = 16; + // The nonce length for AES GCM algorithm. + public static final byte AES_GCM_NONCE_LENGTH = 12; + // AES keysize offsets in aesKeys[] for 128 and 256 sizes respectively. + public static final byte KEYSIZE_128_OFFSET = 0x00; + public static final byte KEYSIZE_256_OFFSET = 0x01; + // The size of the temporary buffer. + public static final short TMP_ARRAY_SIZE = 300; + // The length of the rsa key in bytes. + private static final short RSA_KEY_SIZE = 256; + // Below are the flag to denote device reset events + public static final byte POWER_RESET_FALSE = (byte) 0xAA; + public static final byte POWER_RESET_TRUE = (byte) 0x00; + // The computed HMAC key size. + private static final byte COMPUTED_HMAC_KEY_SIZE = 32; + // The constant 'L' as defiend in + // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-108.pdf, page 12. + private static byte[] CMAC_KDF_CONSTANT_L; + // Constant to represent 0. + private static byte[] CMAC_KDF_CONSTANT_ZERO; + // KeyAgreement instance. + private static KeyAgreement keyAgreement; + + // AESKey + private AESKey aesKeys[]; + // DES3Key + private DESKey triDesKey; + // HMACKey + private HMACKey hmacKey; + // RSA Key Pair + private KeyPair rsaKeyPair; + // EC Key Pair. + private KeyPair ecKeyPair; + // Temporary array. + public byte[] tmpArray; + // This is used for internal encryption/decryption operations. + private static AEADCipher aesGcmCipher; + // Instance of Signature algorithm used in KDF. + private Signature kdf; + // Flag used to denote the power reset event. + public static byte[] resetFlag; + // Instance of HMAC Signature algorithm. + private Signature hmacSignature; + // For ImportwrappedKey operations. + private KMRsaOAEPEncoding rsaOaepDecipher; + // Instance of pool manager. + private KMPoolManager poolMgr; + // Instance of KMOperationImpl used only to encrypt/decrypt the KeyBlobs. + private KMOperationImpl globalOperation; + // Entropy + private RandomData rng; + // Singleton instance. + private static KMAndroidSEProvider androidSEProvider = null; + + public static KMAndroidSEProvider getInstance() { + return androidSEProvider; + } + + public KMAndroidSEProvider() { + initStatics(); + // Re-usable AES,DES and HMAC keys in persisted memory. + aesKeys = new AESKey[2]; + aesKeys[KEYSIZE_128_OFFSET] = + (AESKey) + KeyBuilder.buildKey( + KeyBuilder.TYPE_AES_TRANSIENT_RESET, KeyBuilder.LENGTH_AES_128, false); + aesKeys[KEYSIZE_256_OFFSET] = + (AESKey) + KeyBuilder.buildKey( + KeyBuilder.TYPE_AES_TRANSIENT_RESET, KeyBuilder.LENGTH_AES_256, false); + triDesKey = + (DESKey) + KeyBuilder.buildKey( + KeyBuilder.TYPE_DES_TRANSIENT_RESET, KeyBuilder.LENGTH_DES3_3KEY, false); + hmacKey = + (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC_TRANSIENT_RESET, (short) 512, false); + rsaKeyPair = new KeyPair(KeyPair.ALG_RSA, KeyBuilder.LENGTH_RSA_2048); + ecKeyPair = new KeyPair(KeyPair.ALG_EC_FP, KeyBuilder.LENGTH_EC_FP_256); + keyAgreement = KeyAgreement.getInstance(KeyAgreement.ALG_EC_SVDP_DH_PLAIN, false); + poolMgr = KMPoolManager.getInstance(); + poolMgr.initECKey(ecKeyPair); + // RsaOAEP Decipher + rsaOaepDecipher = new KMRsaOAEPEncoding(KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA1); + + kdf = Signature.getInstance(Signature.ALG_AES_CMAC_128, false); + hmacSignature = Signature.getInstance(Signature.ALG_HMAC_SHA_256, false); + + globalOperation = new KMOperationImpl(); + + // Temporary transient array created to use locally inside functions. + tmpArray = JCSystem.makeTransientByteArray(TMP_ARRAY_SIZE, JCSystem.CLEAR_ON_DESELECT); + Util.arrayFillNonAtomic(tmpArray, (short) 0, TMP_ARRAY_SIZE, (byte) 0); + // Random number generator initialisation. + rng = RandomData.getInstance(RandomData.ALG_KEYGENERATION); + androidSEProvider = this; + resetFlag = JCSystem.makeTransientByteArray((short) 1, JCSystem.CLEAR_ON_RESET); + resetFlag[0] = (byte) POWER_RESET_FALSE; + } + + void initStatics() { + CMAC_KDF_CONSTANT_L = new byte[] {0x00, 0x00, 0x01, 0x00}; + CMAC_KDF_CONSTANT_ZERO = new byte[] {0x00}; + } + + public void clean() { + Util.arrayFillNonAtomic(tmpArray, (short) 0, TMP_ARRAY_SIZE, (byte) 0); + } + + public AESKey createAESKey(short keysize) { + try { + if (keysize > TMP_ARRAY_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + newRandomNumber(tmpArray, (short) 0, (short) (keysize / 8)); + return createAESKey(tmpArray, (short) 0, (short) (keysize / 8)); + } finally { + clean(); + } + } + + public AESKey createAESKey(byte[] buf, short startOff, short length) { + AESKey key = null; + short keysize = (short) (length * 8); + if (keysize == 128) { + key = (AESKey) aesKeys[KEYSIZE_128_OFFSET]; + key.setKey(buf, (short) startOff); + } else if (keysize == 256) { + key = (AESKey) aesKeys[KEYSIZE_256_OFFSET]; + key.setKey(buf, (short) startOff); + } else { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + return key; + } + + public DESKey createTDESKey() { + try { + newRandomNumber(tmpArray, (short) 0, (short) (KeyBuilder.LENGTH_DES3_3KEY / 8)); + return createTDESKey(tmpArray, (short) 0, (short) (KeyBuilder.LENGTH_DES3_3KEY / 8)); + } finally { + clean(); + } + } + + public DESKey createTDESKey(byte[] secretBuffer, short secretOff, short secretLength) { + triDesKey.setKey(secretBuffer, secretOff); + return triDesKey; + } + + public HMACKey createHMACKey(short keysize) { + // As per the KeyMint2.0 specification + // The minimum supported HMAC key size is 64 bits + // The maximum supported HMAC key size is 512 bits + // The keysize should be a multiple of 8. + if ((keysize % 8 != 0) || !(keysize >= 64 && keysize <= 512)) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + try { + newRandomNumber(tmpArray, (short) 0, (short) (keysize / 8)); + return createHMACKey(tmpArray, (short) 0, (short) (keysize / 8)); + } finally { + clean(); + } + } + + public HMACKey createHMACKey(byte[] secretBuffer, short secretOff, short secretLength) { + hmacKey.setKey(secretBuffer, secretOff, secretLength); + return hmacKey; + } + + public KeyPair createRsaKeyPair() { + rsaKeyPair.genKeyPair(); + return rsaKeyPair; + } + + public RSAPrivateKey createRsaKey( + byte[] modBuffer, + short modOff, + short modLength, + byte[] privBuffer, + short privOff, + short privLength) { + RSAPrivateKey privKey = (RSAPrivateKey) rsaKeyPair.getPrivate(); + privKey.setExponent(privBuffer, privOff, privLength); + privKey.setModulus(modBuffer, modOff, modLength); + return privKey; + } + + public KeyPair createECKeyPair() { + ecKeyPair.genKeyPair(); + return ecKeyPair; + } + + public ECPrivateKey createEcKey(byte[] privBuffer, short privOff, short privLength) { + ECPrivateKey privKey = (ECPrivateKey) ecKeyPair.getPrivate(); + privKey.setS(privBuffer, privOff, privLength); + return privKey; + } + + @Override + public short createSymmetricKey(byte alg, short keysize, byte[] buf, short startOff) { + switch (alg) { + case KMType.AES: + AESKey aesKey = createAESKey(keysize); + return aesKey.getKey(buf, startOff); + case KMType.DES: + DESKey desKey = createTDESKey(); + return desKey.getKey(buf, startOff); + case KMType.HMAC: + HMACKey hmacKey = createHMACKey(keysize); + return hmacKey.getKey(buf, startOff); + default: + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + return 0; + } + + @Override + public void createAsymmetricKey( + byte alg, + byte[] privKeyBuf, + short privKeyStart, + short privKeyLength, + byte[] pubModBuf, + short pubModStart, + short pubModLength, + short[] lengths) { + switch (alg) { + case KMType.RSA: + if (RSA_KEY_SIZE != privKeyLength || RSA_KEY_SIZE != pubModLength) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + KeyPair rsaKey = createRsaKeyPair(); + RSAPrivateKey privKey = (RSAPrivateKey) rsaKey.getPrivate(); + // Copy exponent. + Util.arrayFillNonAtomic(tmpArray, (short) 0, RSA_KEY_SIZE, (byte) 0); + lengths[0] = privKey.getExponent(tmpArray, (short) 0); + if (lengths[0] > privKeyLength) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + Util.arrayFillNonAtomic(privKeyBuf, privKeyStart, privKeyLength, (byte) 0); + Util.arrayCopyNonAtomic( + tmpArray, + (short) 0, + privKeyBuf, + (short) (privKeyStart + privKeyLength - lengths[0]), + lengths[0]); + // Copy modulus + Util.arrayFillNonAtomic(tmpArray, (short) 0, RSA_KEY_SIZE, (byte) 0); + lengths[1] = privKey.getModulus(tmpArray, (short) 0); + if (lengths[1] > pubModLength) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + Util.arrayFillNonAtomic(pubModBuf, pubModStart, pubModLength, (byte) 0); + Util.arrayCopyNonAtomic( + tmpArray, + (short) 0, + pubModBuf, + (short) (pubModStart + pubModLength - lengths[1]), + lengths[1]); + break; + case KMType.EC: + KeyPair ecKey = createECKeyPair(); + ECPublicKey ecPubKey = (ECPublicKey) ecKey.getPublic(); + ECPrivateKey ecPrivKey = (ECPrivateKey) ecKey.getPrivate(); + lengths[0] = ecPrivKey.getS(privKeyBuf, privKeyStart); + lengths[1] = ecPubKey.getW(pubModBuf, pubModStart); + if (lengths[0] > privKeyLength || lengths[1] > pubModLength) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + break; + default: + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + } + + @Override + public boolean importSymmetricKey( + byte alg, short keysize, byte[] buf, short startOff, short length) { + switch (alg) { + case KMType.AES: + createAESKey(buf, startOff, length); + break; + case KMType.DES: + createTDESKey(buf, startOff, length); + break; + case KMType.HMAC: + createHMACKey(buf, startOff, length); + break; + default: + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + return true; + } + + @Override + public boolean importAsymmetricKey( + byte alg, + byte[] privKeyBuf, + short privKeyStart, + short privKeyLength, + byte[] pubModBuf, + short pubModStart, + short pubModLength) { + switch (alg) { + case KMType.RSA: + createRsaKey(pubModBuf, pubModStart, pubModLength, privKeyBuf, privKeyStart, privKeyLength); + break; + case KMType.EC: + createEcKey(privKeyBuf, privKeyStart, privKeyLength); + break; + default: + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + return true; + } + + @Override + public void getTrueRandomNumber(byte[] buf, short start, short length) { + newRandomNumber(buf, start, length); + } + + @Override + public void newRandomNumber(byte[] num, short startOff, short length) { + rng.nextBytes(num, startOff, length); + } + + @Override + public void addRngEntropy(byte[] num, short offset, short length) { + rng.setSeed(num, offset, length); + } + + public short aesGCMEncrypt( + AESKey key, + byte[] secret, + short secretStart, + short secretLen, + byte[] encSecret, + short encSecretStart, + byte[] nonce, + short nonceStart, + short nonceLen, + byte[] authData, + short authDataStart, + short authDataLen, + byte[] authTag, + short authTagStart, + short authTagLen) { + if (authTagLen != AES_GCM_TAG_LENGTH) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + if (nonceLen != AES_GCM_NONCE_LENGTH) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + if (aesGcmCipher == null) { + aesGcmCipher = (AEADCipher) Cipher.getInstance(AEADCipher.ALG_AES_GCM, false); + } + aesGcmCipher.init(key, Cipher.MODE_ENCRYPT, nonce, nonceStart, nonceLen); + if (authDataLen != 0) { + aesGcmCipher.updateAAD(authData, authDataStart, authDataLen); + } + short ciphLen = aesGcmCipher.doFinal(secret, secretStart, secretLen, encSecret, encSecretStart); + aesGcmCipher.retrieveTag(authTag, authTagStart, authTagLen); + return ciphLen; + } + + @Override + public short aesGCMEncrypt( + byte[] aesKey, + short aesKeyStart, + short aesKeyLen, + byte[] secret, + short secretStart, + short secretLen, + byte[] encSecret, + short encSecretStart, + byte[] nonce, + short nonceStart, + short nonceLen, + byte[] authData, + short authDataStart, + short authDataLen, + byte[] authTag, + short authTagStart, + short authTagLen) { + + AESKey key = createAESKey(aesKey, aesKeyStart, aesKeyLen); + return aesGCMEncrypt( + key, + secret, + secretStart, + secretLen, + encSecret, + encSecretStart, + nonce, + nonceStart, + nonceLen, + authData, + authDataStart, + authDataLen, + authTag, + authTagStart, + authTagLen); + } + + @Override + public boolean aesGCMDecrypt( + byte[] aesKey, + short aesKeyStart, + short aesKeyLen, + byte[] encSecret, + short encSecretStart, + short encSecretLen, + byte[] secret, + short secretStart, + byte[] nonce, + short nonceStart, + short nonceLen, + byte[] authData, + short authDataStart, + short authDataLen, + byte[] authTag, + short authTagStart, + short authTagLen) { + if (aesGcmCipher == null) { + aesGcmCipher = (AEADCipher) Cipher.getInstance(AEADCipher.ALG_AES_GCM, false); + } + boolean verification = false; + AESKey key = createAESKey(aesKey, aesKeyStart, aesKeyLen); + aesGcmCipher.init(key, Cipher.MODE_DECRYPT, nonce, nonceStart, nonceLen); + if (authDataLen != 0) { + aesGcmCipher.updateAAD(authData, authDataStart, authDataLen); + } + // encrypt the secret + aesGcmCipher.doFinal(encSecret, encSecretStart, encSecretLen, secret, secretStart); + verification = + aesGcmCipher.verifyTag( + authTag, authTagStart, (short) authTagLen, (short) AES_GCM_TAG_LENGTH); + return verification; + } + + public HMACKey cmacKdf( + KMKey preSharedKey, + byte[] label, + short labelStart, + short labelLen, + byte[] context, + short contextStart, + short contextLength) { + // Note: the variables i and L correspond to i and L in the standard. See page 12 of + // http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-108.pdf. + try { + // This is hardcoded to requirement - 32 byte output with two concatenated + // 16 bytes K1 and K2. + final byte n = 2; // hardcoded + + // [i] counter - 32 bits + short iBufLen = 4; + short keyOutLen = n * 16; + // Convert Hmackey to AES Key as the algorithm is ALG_AES_CMAC_128. + KMHmacKey hmacKey = ((KMHmacKey) preSharedKey); + hmacKey.hmacKey.getKey(tmpArray, (short) 0); + aesKeys[KEYSIZE_256_OFFSET].setKey(tmpArray, (short) 0); + // Initialize the key derivation function. + kdf.init(aesKeys[KEYSIZE_256_OFFSET], Signature.MODE_SIGN); + // Clear the tmpArray buffer. + Util.arrayFillNonAtomic(tmpArray, (short) 0, (short) 256, (byte) 0); + + Util.arrayFillNonAtomic(tmpArray, (short) 0, iBufLen, (byte) 0); + Util.arrayFillNonAtomic(tmpArray, (short) iBufLen, keyOutLen, (byte) 0); + + byte i = 1; + short pos = 0; + while (i <= n) { + tmpArray[3] = i; + // 4 bytes of iBuf with counter in it + kdf.update(tmpArray, (short) 0, (short) iBufLen); + kdf.update(label, labelStart, (short) labelLen); // label + kdf.update( + CMAC_KDF_CONSTANT_ZERO, + (short) 0, + (short) CMAC_KDF_CONSTANT_ZERO.length); // 1 byte of 0x00 + kdf.update(context, contextStart, contextLength); // context + // 4 bytes of L - signature of 16 bytes + pos = + kdf.sign( + CMAC_KDF_CONSTANT_L, + (short) 0, + (short) CMAC_KDF_CONSTANT_L.length, + tmpArray, + (short) (iBufLen + pos)); + i++; + } + return createHMACKey(tmpArray, (short) iBufLen, (short) keyOutLen); + } finally { + clean(); + } + } + + public short hmacSign( + HMACKey key, byte[] data, short dataStart, short dataLength, byte[] mac, short macStart) { + hmacSignature.init(key, Signature.MODE_SIGN); + return hmacSignature.sign(data, dataStart, dataLength, mac, macStart); + } + + @Override + public short hmacSign( + byte[] keyBuf, + short keyStart, + short keyLength, + byte[] data, + short dataStart, + short dataLength, + byte[] mac, + short macStart) { + HMACKey key = createHMACKey(keyBuf, keyStart, keyLength); + return hmacSign(key, data, dataStart, dataLength, mac, macStart); + } + + @Override + public short hmacSign( + Object key, byte[] data, short dataStart, short dataLength, byte[] mac, short macStart) { + if (!(key instanceof KMHmacKey)) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + KMHmacKey hmacKey = (KMHmacKey) key; + return hmacSign(hmacKey.hmacKey, data, dataStart, dataLength, mac, macStart); + } + + @Override + public short hmacKDF( + KMKey masterkey, + byte[] data, + short dataStart, + short dataLength, + byte[] signature, + short signatureStart) { + try { + KMAESKey aesKey = (KMAESKey) masterkey; + short keyLen = (short) (aesKey.aesKey.getSize() / 8); + aesKey.aesKey.getKey(tmpArray, (short) 0); + return hmacSign( + tmpArray, (short) 0, keyLen, data, dataStart, dataLength, signature, signatureStart); + } finally { + clean(); + } + } + + @Override + public boolean hmacVerify( + KMKey key, + byte[] data, + short dataStart, + short dataLength, + byte[] mac, + short macStart, + short macLength) { + KMHmacKey hmacKey = (KMHmacKey) key; + hmacSignature.init(hmacKey.hmacKey, Signature.MODE_VERIFY); + return hmacSignature.verify(data, dataStart, dataLength, mac, macStart, macLength); + } + + @Override + public short rsaDecipherOAEP256( + byte[] secret, + short secretStart, + short secretLength, + byte[] modBuffer, + short modOff, + short modLength, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart) { + RSAPrivateKey key = (RSAPrivateKey) rsaKeyPair.getPrivate(); + key.setExponent(secret, (short) secretStart, (short) secretLength); + key.setModulus(modBuffer, (short) modOff, (short) modLength); + rsaOaepDecipher.init(key, Cipher.MODE_DECRYPT); + return rsaOaepDecipher.doFinal( + inputDataBuf, + (short) inputDataStart, + (short) inputDataLength, + outputDataBuf, + (short) outputDataStart); + } + + private byte mapSignature256Alg(byte alg, byte padding, byte digest) { + switch (alg) { + case KMType.RSA: + switch (padding) { + case KMType.RSA_PKCS1_1_5_SIGN: + { + if (digest == KMType.DIGEST_NONE) { + return KMRsa2048NoDigestSignature.ALG_RSA_PKCS1_NODIGEST; + } else { + return Signature.ALG_RSA_SHA_256_PKCS1; + } + } + case KMType.RSA_PSS: + return Signature.ALG_RSA_SHA_256_PKCS1_PSS; + case KMType.PADDING_NONE: + return KMRsa2048NoDigestSignature.ALG_RSA_SIGN_NOPAD; + } + break; + case KMType.EC: + if (digest == KMType.DIGEST_NONE) { + return KMEcdsa256NoDigestSignature.ALG_ECDSA_NODIGEST; + } else { + return Signature.ALG_ECDSA_SHA_256; + } + case KMType.HMAC: + return Signature.ALG_HMAC_SHA_256; + } + return -1; + } + + private byte mapCipherAlg(byte alg, byte padding, byte blockmode, byte digest) { + switch (alg) { + case KMType.AES: + switch (blockmode) { + case KMType.ECB: + return Cipher.ALG_AES_BLOCK_128_ECB_NOPAD; + case KMType.CBC: + return Cipher.ALG_AES_BLOCK_128_CBC_NOPAD; + case KMType.CTR: + return Cipher.ALG_AES_CTR; + case KMType.GCM: + return AEADCipher.ALG_AES_GCM; + } + break; + case KMType.DES: + switch (blockmode) { + case KMType.ECB: + return Cipher.ALG_DES_ECB_NOPAD; + case KMType.CBC: + return Cipher.ALG_DES_CBC_NOPAD; + } + break; + case KMType.RSA: + switch (padding) { + case KMType.PADDING_NONE: + return Cipher.ALG_RSA_NOPAD; + case KMType.RSA_PKCS1_1_5_ENCRYPT: + return Cipher.ALG_RSA_PKCS1; + case KMType.RSA_OAEP: + { + if (digest == KMType.SHA1) { + /* MGF Digest is SHA1 */ + return KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA1; + } else if (digest == KMType.SHA2_256) { + /* MGF Digest is SHA256 */ + return KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA256; + } else { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + } + } + break; + } + return -1; + } + + public KMOperation createSymmetricCipher( + short alg, + short purpose, + short macLength, + short blockMode, + short padding, + byte[] secret, + short secretStart, + short secretLength, + byte[] ivBuffer, + short ivStart, + short ivLength, + boolean isRkp) { + + short cipherAlg = mapCipherAlg((byte) alg, (byte) padding, (byte) blockMode, (byte) 0); + KMOperation operation = null; + if (isRkp) { + operation = poolMgr.getRKpOperation(purpose, cipherAlg, alg, padding, blockMode, macLength); + } else { + operation = + poolMgr.getOperationImpl( + purpose, cipherAlg, alg, padding, blockMode, macLength, secretLength, false); + } + // Get the KeyObject from the operation and update the key with the secret key material. + KMKeyObject keyObj = operation.getKeyObject(); + Key key = (Key) keyObj.keyObjectInst; + switch (secretLength) { + case 32: + case 16: + ((AESKey) key).setKey(secret, secretStart); + break; + case 24: + ((DESKey) key).setKey(secret, secretStart); + break; + default: + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + break; + } + ((KMOperationImpl) operation).init(key, KMType.INVALID_VALUE, ivBuffer, ivStart, ivLength); + return operation; + } + + public KMOperation createHmacSignerVerifier( + short purpose, + short digest, + byte[] secret, + short secretStart, + short secretLength, + boolean isRkp) { + KMOperation operation = null; + if (digest != KMType.SHA2_256) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + if (isRkp) { + operation = + poolMgr.getRKpOperation( + purpose, + Signature.ALG_HMAC_SHA_256, + KMType.HMAC, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE); + } else { + operation = + poolMgr.getOperationImpl( + purpose, + Signature.ALG_HMAC_SHA_256, + KMType.HMAC, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + (short) 0, + false); + } + // Get the KeyObject from the operation and update the key with the secret key material. + KMKeyObject keyObj = operation.getKeyObject(); + HMACKey key = (HMACKey) keyObj.keyObjectInst; + key.setKey(secret, secretStart, secretLength); + ((KMOperationImpl) operation).init(key, digest, null, (short) 0, (short) 0); + return operation; + } + + private KMOperation createHmacSignerVerifier( + short purpose, short digest, HMACKey hmacKey, boolean isTrustedConf) { + if (digest != KMType.SHA2_256) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + KMOperation operation = + poolMgr.getOperationImpl( + purpose, + Signature.ALG_HMAC_SHA_256, + KMType.HMAC, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + (short) 0, + isTrustedConf); + // Get the KeyObject from the operation and update the key with the secret key material. + KMKeyObject keyObj = operation.getKeyObject(); + HMACKey key = (HMACKey) keyObj.keyObjectInst; + short len = hmacKey.getKey(tmpArray, (short) 0); + key.setKey(tmpArray, (short) 0, len); + ((KMOperationImpl) operation).init(key, digest, null, (short) 0, (short) 0); + return operation; + } + + @Override + public KMOperation getRkpOperation( + byte purpose, + byte alg, + byte digest, + byte padding, + byte blockMode, + byte[] keyBuf, + short keyStart, + short keyLength, + byte[] ivBuf, + short ivStart, + short ivLength, + short macLength) { + KMOperation opr = null; + switch (alg) { + case KMType.AES: + // Convert macLength to bytes + macLength = (short) (macLength / 8); + opr = + createSymmetricCipher( + alg, + purpose, + macLength, + blockMode, + padding, + keyBuf, + keyStart, + keyLength, + ivBuf, + ivStart, + ivLength, + true /* isRKP */); + break; + case KMType.HMAC: + opr = + createHmacSignerVerifier( + purpose, digest, keyBuf, keyStart, keyLength, true /* isRKP */); + break; + default: + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + return opr; + } + + @Override + public KMOperation initSymmetricOperation( + byte purpose, + byte alg, + byte digest, + byte padding, + byte blockMode, + byte[] keyBuf, + short keyStart, + short keyLength, + byte[] ivBuf, + short ivStart, + short ivLength, + short macLength) { + KMOperation opr = null; + switch (alg) { + case KMType.AES: + case KMType.DES: + // Convert macLength to bytes + macLength = (short) (macLength / 8); + opr = + createSymmetricCipher( + alg, + purpose, + macLength, + blockMode, + padding, + keyBuf, + keyStart, + keyLength, + ivBuf, + ivStart, + ivLength, + false /* isRKP */); + break; + case KMType.HMAC: + opr = + createHmacSignerVerifier( + purpose, digest, keyBuf, keyStart, keyLength, false /* isRKP */); + break; + default: + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + return opr; + } + + @Override + public KMOperation initSymmetricOperation( + byte purpose, + byte alg, + byte digest, + byte padding, + byte blockMode, + Object key, + byte interfaceType, + byte[] ivBuf, + short ivStart, + short ivLength, + short macLength, + boolean oneShot) { + short keyLen = 0; + globalOperation.setPurpose(purpose); + globalOperation.setAlgorithmType(alg); + globalOperation.setPaddingAlgorithm(padding); + globalOperation.setBlockMode(blockMode); + try { + switch (interfaceType) { + case KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY: + KMAESKey aesKey = (KMAESKey) key; + keyLen = (short) (aesKey.aesKey.getSize() / 8); + aesKey.aesKey.getKey(tmpArray, (short) 0); + break; + + default: + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + + switch (alg) { + case KMType.HMAC: + HMACKey hmackey = createHMACKey(tmpArray, (short) 0, keyLen); + globalOperation.setSignature(hmacSignature); + globalOperation.init(hmackey, digest, null, (short) 0, (short) 0); + break; + + default: + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + } finally { + clean(); + } + return globalOperation; + } + + @Override + public KMOperation initTrustedConfirmationSymmetricOperation(KMKey computedHmacKey) { + KMHmacKey key = (KMHmacKey) computedHmacKey; + return createHmacSignerVerifier(KMType.VERIFY, KMType.SHA2_256, key.hmacKey, true); + } + + public KMOperation createRsaSigner( + short digest, + short padding, + byte[] secret, + short secretStart, + short secretLength, + byte[] modBuffer, + short modOff, + short modLength) { + byte alg = mapSignature256Alg(KMType.RSA, (byte) padding, (byte) digest); + KMOperation operation = + poolMgr.getOperationImpl( + KMType.SIGN, + alg, + KMType.RSA, + padding, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + secretLength, + false); + // Get the KeyObject from the operation and update the key with the secret key material. + KMKeyObject keyObj = operation.getKeyObject(); + RSAPrivateKey key = (RSAPrivateKey) ((KeyPair) (keyObj.keyObjectInst)).getPrivate(); + key.setExponent(secret, secretStart, secretLength); + key.setModulus(modBuffer, modOff, modLength); + ((KMOperationImpl) operation).init(key, digest, null, (short) 0, (short) 0); + return operation; + } + + public KMOperation createRsaDecipher( + short padding, + short mgfDigest, + byte[] secret, + short secretStart, + short secretLength, + byte[] modBuffer, + short modOff, + short modLength) { + byte cipherAlg = mapCipherAlg(KMType.RSA, (byte) padding, (byte) 0, (byte) mgfDigest); + KMOperation operation = + poolMgr.getOperationImpl( + KMType.DECRYPT, + cipherAlg, + KMType.RSA, + padding, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + secretLength, + false); + // Get the KeyObject from the operation and update the key with the secret key material. + KMKeyObject keyObj = operation.getKeyObject(); + RSAPrivateKey key = (RSAPrivateKey) ((KeyPair) (keyObj.keyObjectInst)).getPrivate(); + key.setExponent(secret, secretStart, secretLength); + key.setModulus(modBuffer, modOff, modLength); + ((KMOperationImpl) operation).init(key, KMType.INVALID_VALUE, null, (short) 0, (short) 0); + return operation; + } + + public KMOperation createEcSigner( + short digest, byte[] secret, short secretStart, short secretLength) { + byte alg = mapSignature256Alg(KMType.EC, (byte) 0, (byte) digest); + KMOperation operation = + poolMgr.getOperationImpl( + KMType.SIGN, + alg, + KMType.EC, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + secretLength, + false); + KMKeyObject keyObj = operation.getKeyObject(); + ECPrivateKey key = (ECPrivateKey) ((KeyPair) (keyObj.keyObjectInst)).getPrivate(); + key.setS(secret, secretStart, secretLength); + ((KMOperationImpl) operation).init(key, digest, null, (short) 0, (short) 0); + return operation; + } + + public KMOperation createKeyAgreement(byte[] secret, short secretStart, short secretLength) { + KMOperation operation = + poolMgr.getOperationImpl( + KMType.AGREE_KEY, + KeyAgreement.ALG_EC_SVDP_DH_PLAIN, + KMType.EC, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + (short) 0, + false); + KMKeyObject keyObj = operation.getKeyObject(); + ECPrivateKey key = (ECPrivateKey) ((KeyPair) (keyObj.keyObjectInst)).getPrivate(); + key.setS(secret, secretStart, secretLength); + ((KMOperationImpl) operation).init(key, KMType.INVALID_VALUE, null, (short) 0, (short) 0); + return operation; + } + + @Override + public KMOperation initAsymmetricOperation( + byte purpose, + byte alg, + byte padding, + byte digest, + byte mgfDigest, + byte[] privKeyBuf, + short privKeyStart, + short privKeyLength, + byte[] pubModBuf, + short pubModStart, + short pubModLength) { + KMOperation opr = null; + if (alg == KMType.RSA) { + switch (purpose) { + case KMType.SIGN: + opr = + createRsaSigner( + digest, + padding, + privKeyBuf, + privKeyStart, + privKeyLength, + pubModBuf, + pubModStart, + pubModLength); + break; + case KMType.DECRYPT: + opr = + createRsaDecipher( + padding, + mgfDigest, + privKeyBuf, + privKeyStart, + privKeyLength, + pubModBuf, + pubModStart, + pubModLength); + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + break; + } + } else if (alg == KMType.EC) { + switch (purpose) { + case KMType.SIGN: + opr = createEcSigner(digest, privKeyBuf, privKeyStart, privKeyLength); + break; + + case KMType.AGREE_KEY: + opr = createKeyAgreement(privKeyBuf, privKeyStart, privKeyLength); + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + break; + } + } else { + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + } + return opr; + } + + @Override + public short cmacKDF( + KMKey pSharedKey, + byte[] label, + short labelStart, + short labelLen, + byte[] context, + short contextStart, + short contextLength, + byte[] keyBuf, + short keyStart) { + HMACKey key = + cmacKdf(pSharedKey, label, labelStart, labelLen, context, contextStart, contextLength); + return key.getKey(keyBuf, keyStart); + } + + @Override + public boolean isUpgrading() { + return UpgradeManager.isUpgrading(); + } + + @Override + public KMKey createMasterKey(KMKey masterKey, short keySizeBits) { + try { + if (masterKey == null) { + AESKey key = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, keySizeBits, false); + masterKey = new KMAESKey(key); + } + short keyLen = (short) (keySizeBits / 8); + Util.arrayFillNonAtomic(tmpArray, (short) 0, keyLen, (byte) 0); + getTrueRandomNumber(tmpArray, (short) 0, keyLen); + ((KMAESKey) masterKey).aesKey.setKey(tmpArray, (short) 0); + return (KMKey) masterKey; + } finally { + clean(); + } + } + + @Override + public KMKey createPreSharedKey(KMKey preSharedKey, byte[] keyData, short offset, short length) { + short lengthInBits = (short) (length * 8); + if ((lengthInBits % 8 != 0) || !(lengthInBits >= 64 && lengthInBits <= 512)) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + if (preSharedKey == null) { + HMACKey key = (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC, lengthInBits, false); + preSharedKey = new KMHmacKey(key); + } + ((KMHmacKey) preSharedKey).hmacKey.setKey(keyData, offset, length); + return (KMKey) preSharedKey; + } + + @Override + public KMKey createComputedHmacKey( + KMKey computedHmacKey, byte[] keyData, short offset, short length) { + if (length != COMPUTED_HMAC_KEY_SIZE) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + if (computedHmacKey == null) { + HMACKey key = + (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC, (short) (length * 8), false); + computedHmacKey = new KMHmacKey(key); + } + ((KMHmacKey) computedHmacKey).hmacKey.setKey(keyData, offset, length); + return (KMKey) computedHmacKey; + } + + @Override + public short ecSign256( + byte[] secret, + short secretStart, + short secretLength, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart) { + + ECPrivateKey key = (ECPrivateKey) ecKeyPair.getPrivate(); + key.setS(secret, secretStart, secretLength); + + Signature.OneShot signer = null; + try { + + signer = + Signature.OneShot.open( + MessageDigest.ALG_SHA_256, Signature.SIG_CIPHER_ECDSA, Cipher.PAD_NULL); + signer.init(key, Signature.MODE_SIGN); + return signer.sign( + inputDataBuf, inputDataStart, inputDataLength, outputDataBuf, outputDataStart); + } finally { + if (signer != null) { + signer.close(); + } + } + } + + @Override + public short rsaSign256Pkcs1( + byte[] secret, + short secretStart, + short secretLength, + byte[] modBuf, + short modStart, + short modLength, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart) { + + Signature.OneShot signer = null; + try { + + signer = + Signature.OneShot.open( + MessageDigest.ALG_SHA_256, Signature.SIG_CIPHER_RSA, Cipher.PAD_PKCS1); + + RSAPrivateKey key = (RSAPrivateKey) rsaKeyPair.getPrivate(); + ; + key.setExponent(secret, secretStart, secretLength); + key.setModulus(modBuf, modStart, modLength); + + signer.init(key, Signature.MODE_SIGN); + return signer.sign( + inputDataBuf, inputDataStart, inputDataLength, outputDataBuf, outputDataStart); + } finally { + if (signer != null) { + signer.close(); + } + } + } + + @Override + public boolean isAttestationKeyProvisioned() { + return false; + } + + @Override + public short getAttestationKeyAlgorithm() { + return KMType.INVALID_VALUE; + } + + @Override + public short hkdf( + byte[] ikm, + short ikmOff, + short ikmLen, + byte[] salt, + short saltOff, + short saltLen, + byte[] info, + short infoOff, + short infoLen, + byte[] out, + short outOff, + short outLen) { + // HMAC_extract + hkdfExtract(ikm, ikmOff, ikmLen, salt, saltOff, saltLen, tmpArray, (short) 0); + // HMAC_expand + return hkdfExpand(tmpArray, (short) 0, (short) 32, info, infoOff, infoLen, out, outOff, outLen); + } + + private short hkdfExtract( + byte[] ikm, + short ikmOff, + short ikmLen, + byte[] salt, + short saltOff, + short saltLen, + byte[] out, + short off) { + // https://tools.ietf.org/html/rfc5869#section-2.2 + HMACKey hmacKey = createHMACKey(salt, saltOff, saltLen); + hmacSignature.init(hmacKey, Signature.MODE_SIGN); + return hmacSignature.sign(ikm, ikmOff, ikmLen, out, off); + } + + private short hkdfExpand( + byte[] prk, + short prkOff, + short prkLen, + byte[] info, + short infoOff, + short infoLen, + byte[] out, + short outOff, + short outLen) { + // https://tools.ietf.org/html/rfc5869#section-2.3 + short digestLen = (short) 32; // SHA256 digest length. + // Calculate no of iterations N. + short n = (short) ((short) (outLen + digestLen - 1) / digestLen); + if (n > 255) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + HMACKey hmacKey = createHMACKey(prk, prkOff, prkLen); + Util.arrayFill(tmpArray, (short) 0, (short) 33, (byte) 0); + short bytesCopied = 0; + short len = 0; + for (short i = 0; i < n; i++) { + tmpArray[0]++; + hmacSignature.init(hmacKey, Signature.MODE_SIGN); + if (i != 0) { + hmacSignature.update(tmpArray, (short) 1, (short) 32); + } + hmacSignature.update(info, infoOff, infoLen); + len = hmacSignature.sign(tmpArray, (short) 0, (short) 1, tmpArray, (short) 1); + if ((short) (bytesCopied + len) > outLen) { + len = (short) (outLen - bytesCopied); + } + Util.arrayCopyNonAtomic(tmpArray, (short) 1, out, (short) (outOff + bytesCopied), len); + bytesCopied += len; + } + return outLen; + } + + @Override + public short ecdhKeyAgreement( + byte[] privKey, + short privKeyOff, + short privKeyLen, + byte[] publicKey, + short publicKeyOff, + short publicKeyLen, + byte[] secret, + short secretOff) { + keyAgreement.init(createEcKey(privKey, privKeyOff, privKeyLen)); + return keyAgreement.generateSecret(publicKey, publicKeyOff, publicKeyLen, secret, secretOff); + } + + @Override + public boolean ecVerify256( + byte[] pubKey, + short pubKeyOffset, + short pubKeyLen, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] signatureDataBuf, + short signatureDataStart, + short signatureDataLen) { + Signature.OneShot signer = null; + try { + signer = + Signature.OneShot.open( + MessageDigest.ALG_SHA_256, Signature.SIG_CIPHER_ECDSA, Cipher.PAD_NULL); + ECPublicKey key = (ECPublicKey) ecKeyPair.getPublic(); + key.setW(pubKey, pubKeyOffset, pubKeyLen); + signer.init(key, Signature.MODE_VERIFY); + return signer.verify( + inputDataBuf, + inputDataStart, + inputDataLength, + signatureDataBuf, + signatureDataStart, + (short) (signatureDataBuf[(short) (signatureDataStart + 1)] + 2)); + } finally { + if (signer != null) { + signer.close(); + } + } + } + + @Override + public short signWithDeviceUniqueKey( + KMKey ecPrivKey, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart) { + Signature.OneShot signer = null; + try { + signer = + Signature.OneShot.open( + MessageDigest.ALG_SHA_256, Signature.SIG_CIPHER_ECDSA, Cipher.PAD_NULL); + signer.init( + ((KMECDeviceUniqueKeyPair) ecPrivKey).ecKeyPair.getPrivate(), Signature.MODE_SIGN); + return signer.sign( + inputDataBuf, inputDataStart, inputDataLength, outputDataBuf, outputDataStart); + } finally { + if (signer != null) { + signer.close(); + } + } + } + + @Override + public KMKey createRkpDeviceUniqueKeyPair( + KMKey key, + byte[] pubKey, + short pubKeyOff, + short pubKeyLen, + byte[] privKey, + short privKeyOff, + short privKeyLen) { + if (key == null) { + KeyPair ecKeyPair = new KeyPair(KeyPair.ALG_EC_FP, KeyBuilder.LENGTH_EC_FP_256); + poolMgr.initECKey(ecKeyPair); + key = new KMECDeviceUniqueKeyPair(ecKeyPair); + } + ECPrivateKey ecKeyPair = (ECPrivateKey) ((KMECDeviceUniqueKeyPair) key).ecKeyPair.getPrivate(); + ECPublicKey ecPublicKey = (ECPublicKey) ((KMECDeviceUniqueKeyPair) key).ecKeyPair.getPublic(); + ecKeyPair.setS(privKey, privKeyOff, privKeyLen); + ecPublicKey.setW(pubKey, pubKeyOff, pubKeyLen); + return (KMKey) key; + } + + @Override + public KMKey createRkpMacKey(KMKey rkpMacKey, byte[] keyData, short offset, short length) { + if (rkpMacKey == null) { + HMACKey key = + (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC, (short) (length * 8), false); + rkpMacKey = new KMHmacKey(key); + } + ((KMHmacKey) rkpMacKey).hmacKey.setKey(keyData, offset, length); + return rkpMacKey; + } + + @Override + public short messageDigest256( + byte[] inBuff, short inOffset, short inLength, byte[] outBuff, short outOffset) { + MessageDigest.OneShot mDigest = null; + short len = 0; + try { + mDigest = MessageDigest.OneShot.open(MessageDigest.ALG_SHA_256); + len = mDigest.doFinal(inBuff, inOffset, inLength, outBuff, outOffset); + } finally { + if (mDigest != null) { + mDigest.close(); + mDigest = null; + } + } + return len; + } + + public boolean isPowerReset() { + boolean flag = false; + if (resetFlag[0] == POWER_RESET_TRUE) { + resetFlag[0] = POWER_RESET_FALSE; + flag = true; + if (poolMgr != null) { + poolMgr.powerReset(); + } + } + return flag; + } + + @Override + public void onSave(Element element, byte interfaceType, Object object) { + element.write(interfaceType); + if (object == null) { + element.write(null); + return; + } + switch (interfaceType) { + case KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY: + KMAESKey.onSave(element, (KMAESKey) object); + break; + case KMDataStoreConstants.INTERFACE_TYPE_PRE_SHARED_KEY: + KMHmacKey.onSave(element, (KMHmacKey) object); + break; + case KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR: + KMECDeviceUniqueKeyPair.onSave(element, (KMECDeviceUniqueKeyPair) object); + break; + case KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY: + KMHmacKey.onSave(element, (KMHmacKey) object); + break; + default: + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + } + + @Override + public Object onRestore(Element element) { + if (element == null) { + return null; + } + byte interfaceType = element.readByte(); + switch (interfaceType) { + case KMDataStoreConstants.INTERFACE_TYPE_COMPUTED_HMAC_KEY: + return KMHmacKey.onRestore((HMACKey) element.readObject()); + case KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY: + return KMAESKey.onRestore((AESKey) element.readObject()); + case KMDataStoreConstants.INTERFACE_TYPE_PRE_SHARED_KEY: + return KMHmacKey.onRestore((HMACKey) element.readObject()); + case KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR: + return KMECDeviceUniqueKeyPair.onRestore((KeyPair) element.readObject()); + case KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY: + return KMHmacKey.onRestore((HMACKey) element.readObject()); + default: + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return null; + } + + @Override + public short getBackupPrimitiveByteCount(byte interfaceType) { + short primitiveCount = 1; // interface type + switch (interfaceType) { + case KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY: + primitiveCount += KMAESKey.getBackupPrimitiveByteCount(); + break; + case KMDataStoreConstants.INTERFACE_TYPE_PRE_SHARED_KEY: + primitiveCount += KMHmacKey.getBackupPrimitiveByteCount(); + break; + case KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR: + primitiveCount += KMECDeviceUniqueKeyPair.getBackupPrimitiveByteCount(); + break; + case KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY: + primitiveCount += KMHmacKey.getBackupPrimitiveByteCount(); + break; + default: + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return primitiveCount; + } + + @Override + public short getBackupObjectCount(byte interfaceType) { + switch (interfaceType) { + case KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY: + return KMAESKey.getBackupObjectCount(); + case KMDataStoreConstants.INTERFACE_TYPE_PRE_SHARED_KEY: + return KMHmacKey.getBackupObjectCount(); + case KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR: + return KMECDeviceUniqueKeyPair.getBackupObjectCount(); + case KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY: + return KMHmacKey.getBackupObjectCount(); + default: + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return 0; + } + + @Override + public boolean isBootSignalEventSupported() { + return false; + } + + @Override + public boolean isDeviceRebooted() { + return false; + } + + @Override + public void clearDeviceBooted(boolean resetBootFlag) { + // To be filled + } +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAttestationCert.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAttestationCert.java new file mode 100644 index 0000000..05801b0 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAttestationCert.java @@ -0,0 +1,198 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +/** + * The KMAttestationCert interface represents a X509 compliant attestation certificate required to + * support keymaster's attestKey function. This cert will be created according to the specifications + * given in android keymaster hal documentation. KMSeProvider has to provide the instance of this + * certificate. This interface is designed based on builder pattern and hence each method returns + * instance of cert. + */ +public interface KMAttestationCert { + + /** + * Set verified boot hash. + * + * @param obj This is a KMByteBlob containing hash + * @return instance of KMAttestationCert + */ + KMAttestationCert verifiedBootHash(short obj); + + /** + * Set verified boot key received during booting up. + * + * @param obj This is a KMByteBlob containing verified boot key. + * @return instance of KMAttestationCert + */ + KMAttestationCert verifiedBootKey(short obj); + + /** + * Set verified boot state received during booting up. + * + * @param val This is a byte containing verified boot state value. + * @return instance of KMAttestationCert + */ + KMAttestationCert verifiedBootState(byte val); + + /** + * Set uniqueId received from CA certificate during provisioning. + * + * @param scratchpad Buffer to store intermediate results. + * @param scratchPadOff Start offset of the scratchpad buffer. + * @param creationTime This buffer contains the CREATION_TIME value. + * @param creationTimeOff Start offset of creattionTime buffer. + * @param creationTimeLen Length of the creationTime buffer. + * @param attestAppId This buffer contains the ATTESTATION_APPLICATION_ID value. + * @param attestAppIdOff Start offset of the attestAppId buffer. + * @param attestAppIdLen Length of the attestAppId buffer. + * @param resetSinceIdRotation This holds the information of RESET_SINCE_ID_ROTATION. + * @param masterKey + * @return instance of KMAttestationCert. + */ + KMAttestationCert makeUniqueId( + byte[] scratchpad, + short scratchPadOff, + byte[] creationTime, + short creationTimeOff, + short creationTimeLen, + byte[] attestAppId, + short attestAppIdOff, + short attestAppIdLen, + byte resetSinceIdRotation, + KMKey masterKey); + + /** + * Set start time received from creation/activation time tag. Used for certificate's valid period. + * + * @param obj This is a KMByteBlob object containing start time. + * @param scratchpad Buffer to store intermediate results. + * @return instance of KMAttestationCert. + */ + KMAttestationCert notBefore(short obj, boolean derEncoded, byte[] scratchpad); + + /** + * Set expiry time received from expiry time tag or ca certificates expiry time. Used for + * certificate's valid period. + * + * @param usageExpiryTimeObj This is a KMByteBlob containing expiry time. certificate. + * @param scratchPad Buffer to store intermediate results. + * @return instance of KMAttestationCert + */ + KMAttestationCert notAfter(short usageExpiryTimeObj, boolean derEncoded, byte[] scratchPad); + + /** + * Set device lock status received during booting time or due to device lock command. + * + * @param val This is true if device is locked. + * @return instance of KMAttestationCert + */ + KMAttestationCert deviceLocked(boolean val); + + /** + * Set public key to be attested received from attestKey command. + * + * @param obj This is KMByteBlob containing the public key. + * @return instance of KMAttestationCert + */ + KMAttestationCert publicKey(short obj); + + /** + * Set attestation challenge received from attestKey command. + * + * @param obj This is KMByteBlob containing the attestation challenge. + * @return instance of KMAttestationCert + */ + KMAttestationCert attestationChallenge(short obj); + + /** + * Set extension tag received from key characteristics which needs to be added to android + * extension. This method will called once for each tag. + * + * @param tag is the KMByteBlob containing KMTag. + * @param hwEnforced is true if the tag has to be added to hw enforced list or else added to sw + * enforced list. + * @return instance of KMAttestationCert + */ + KMAttestationCert extensionTag(short tag, boolean hwEnforced); + + /** + * Set ASN.1 encoded X509 issuer field received from attestation key CA cert. + * + * @param obj This is KMByteBlob containing the issuer. + * @return instance of KMAttestationCert + */ + KMAttestationCert issuer(short obj); + + /** + * Set byte buffer to be used to generate certificate. + * + * @param buf This is byte[] buffer. + * @param bufStart This is short start offset. + * @param maxLen This is short length of the buffer. + * @return instance of KMAttestationCert + */ + KMAttestationCert buffer(byte[] buf, short bufStart, short maxLen); + + /** + * Get the start of the certificate + * + * @return start of the attestation cert. + */ + short getCertStart(); + + /** + * Get the length of the certificate + * + * @return length of the attestation cert. + */ + short getCertLength(); + + /** + * Build a fake signed certificate. After this method executes the certificate is ready with the + * signature equal to 1 byte which is 0 and with rsa signature algorithm. + */ + void build(); + + /** + * Set the Serial number in the certificate. If no serial number is set then serial number is 1. + * + * @param serialNumber + */ + boolean serialNumber(short serialNumber); + + /** + * Set the Subject Name in the certificate. + * + * @param subject + */ + boolean subjectName(short subject); + + /** + * Set attestation key and mode. + * + * @param attestKey KMByteBlob of the key + * @param mode + */ + KMAttestationCert ecAttestKey(short attestKey, byte mode); + /** + * Set attestation key and mode. + * + * @param attestKey KMByteBlob of the key + * @param mode + */ + KMAttestationCert rsaAttestKey(short attestPrivExp, short attestMod, byte mode); +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMDataStoreConstants.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMDataStoreConstants.java new file mode 100644 index 0000000..61ddb36 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMDataStoreConstants.java @@ -0,0 +1,17 @@ +package com.android.javacard.seprovider; + +/** + * This class holds different interface type constants to differentiate between the instances of + * Computed Hmac key, device unique key pair, RKP Mac key, and master key when passed as generic + * objects. These constants are used in upgrade flow to retrieve the size of the object and + * primitive types saved and restored for respective key types. + */ +public class KMDataStoreConstants { + // INTERFACE Types + public static final byte INTERFACE_TYPE_COMPUTED_HMAC_KEY = 0x01; + // 0x02 reserved for INTERFACE_TYPE_ATTESTATION_KEY + public static final byte INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR = 0x03; + public static final byte INTERFACE_TYPE_MASTER_KEY = 0x04; + public static final byte INTERFACE_TYPE_PRE_SHARED_KEY = 0x05; + public static final byte INTERFACE_TYPE_RKP_MAC_KEY = 0x06; +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMECDeviceUniqueKeyPair.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMECDeviceUniqueKeyPair.java new file mode 100644 index 0000000..0e430a3 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMECDeviceUniqueKeyPair.java @@ -0,0 +1,55 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.security.ECPublicKey; +import javacard.security.KeyPair; +import org.globalplatform.upgrade.Element; + +/** This is a wrapper class for KeyPair. */ +public class KMECDeviceUniqueKeyPair implements KMKey { + + public KeyPair ecKeyPair; + + @Override + public short getPublicKey(byte[] buf, short offset) { + ECPublicKey publicKey = (ECPublicKey) ecKeyPair.getPublic(); + return publicKey.getW(buf, offset); + } + + public KMECDeviceUniqueKeyPair(KeyPair ecPair) { + ecKeyPair = ecPair; + } + + public static void onSave(Element element, KMECDeviceUniqueKeyPair kmKey) { + element.write(kmKey.ecKeyPair); + } + + public static KMECDeviceUniqueKeyPair onRestore(KeyPair ecKey) { + if (ecKey == null) { + return null; + } + return new KMECDeviceUniqueKeyPair(ecKey); + } + + public static short getBackupPrimitiveByteCount() { + return (short) 0; + } + + public static short getBackupObjectCount() { + return (short) 1; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMEcdsa256NoDigestSignature.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMEcdsa256NoDigestSignature.java new file mode 100644 index 0000000..83774ab --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMEcdsa256NoDigestSignature.java @@ -0,0 +1,138 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.framework.Util; +import javacard.security.CryptoException; +import javacard.security.Key; +import javacard.security.MessageDigest; +import javacard.security.Signature; +import javacardx.crypto.Cipher; + +/** + * This class provides support for ECDSA_NO_DIGEST signature algorithm. Added this because javacard + * 3.0.5 does not support this + */ +public class KMEcdsa256NoDigestSignature extends Signature { + + public static final byte ALG_ECDSA_NODIGEST = (byte) 0x67; + public static final byte MAX_NO_DIGEST_MSG_LEN = 32; + private byte algorithm; + private Signature inst; + + public KMEcdsa256NoDigestSignature(byte alg) { + algorithm = alg; + // There is no constant for no digest so ALG_ECDSA_SHA_256 is used. However, + // signPreComputedHash is used for signing which is equivalent to no digest sign. + inst = Signature.getInstance(Signature.ALG_ECDSA_SHA_256, false); + } + + @Override + public void init(Key key, byte b) throws CryptoException { + inst.init(key, b); + } + + @Override + public void init(Key key, byte b, byte[] bytes, short i, short i1) throws CryptoException { + inst.init(key, b, bytes, i, i1); + } + + @Override + public void setInitialDigest(byte[] bytes, short i, short i1, byte[] bytes1, short i2, short i3) + throws CryptoException {} + + @Override + public byte getAlgorithm() { + return algorithm; + } + + @Override + public byte getMessageDigestAlgorithm() { + return MessageDigest.ALG_NULL; + } + + @Override + public byte getCipherAlgorithm() { + return 0; + } + + @Override + public byte getPaddingAlgorithm() { + return Cipher.PAD_NULL; + } + + @Override + public short getLength() throws CryptoException { + return inst.getLength(); + } + + @Override + public void update(byte[] message, short msgStart, short messageLength) throws CryptoException { + // HAL accumulates the data and send it at finish operation. + } + + @Override + public short sign(byte[] bytes, short i, short i1, byte[] bytes1, short i2) + throws CryptoException { + try { + if (i1 > MAX_NO_DIGEST_MSG_LEN) { + CryptoException.throwIt(CryptoException.ILLEGAL_USE); + } + // add zeros to the left + if (i1 < MAX_NO_DIGEST_MSG_LEN) { + Util.arrayFillNonAtomic( + KMAndroidSEProvider.getInstance().tmpArray, + (short) 0, + (short) MAX_NO_DIGEST_MSG_LEN, + (byte) 0); + } + Util.arrayCopyNonAtomic( + bytes, + i, + KMAndroidSEProvider.getInstance().tmpArray, + (short) (MAX_NO_DIGEST_MSG_LEN - i1), + i1); + return inst.signPreComputedHash( + KMAndroidSEProvider.getInstance().tmpArray, + (short) 0, + (short) MAX_NO_DIGEST_MSG_LEN, + bytes1, + i2); + } finally { + KMAndroidSEProvider.getInstance().clean(); + } + } + + @Override + public short signPreComputedHash(byte[] bytes, short i, short i1, byte[] bytes1, short i2) + throws CryptoException { + return inst.sign(bytes, i, i1, bytes1, i2); + } + + @Override + public boolean verify(byte[] bytes, short i, short i1, byte[] bytes1, short i2, short i3) + throws CryptoException { + // Verification is handled inside HAL + return false; + } + + @Override + public boolean verifyPreComputedHash( + byte[] bytes, short i, short i1, byte[] bytes1, short i2, short i3) throws CryptoException { + // Verification is handled inside HAL + return false; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMError.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMError.java new file mode 100644 index 0000000..69cb069 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMError.java @@ -0,0 +1,32 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +/** + * KMError includes all the error codes from android keymaster hal specifications. The values are + * positive unlike negative values in keymaster hal. + */ +public class KMError { + + public static final short OK = 0; + public static final short UNSUPPORTED_PURPOSE = 2; + public static final short UNSUPPORTED_ALGORITHM = 4; + public static final short INVALID_INPUT_LENGTH = 21; + public static final short VERIFICATION_FAILED = 30; + public static final short TOO_MANY_OPERATIONS = 31; + public static final short INVALID_ARGUMENT = 38; + public static final short UNKNOWN_ERROR = 1000; +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMException.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMException.java new file mode 100644 index 0000000..79983a2 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMException.java @@ -0,0 +1,46 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.seprovider; + +import javacard.framework.JCSystem; + +/** + * KMException is shared instance of exception used for all exceptions in the applet. It is used to + * throw EMError errors. + */ +public class KMException extends RuntimeException { + + private static short[] reason; + private static KMException exception; + + private KMException() {} + + public static short reason() { + return reason[0]; + } + + public static void throwIt(short e) { + if (reason == null) { + reason = JCSystem.makeTransientShortArray((short) 1, JCSystem.CLEAR_ON_DESELECT); + } + if (exception == null) { + exception = new KMException(); + } + reason[0] = e; + throw exception; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMHmacKey.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMHmacKey.java new file mode 100644 index 0000000..e938a2b --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMHmacKey.java @@ -0,0 +1,53 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.security.HMACKey; +import org.globalplatform.upgrade.Element; + +/** This is a wrapper class for HMACKey. */ +public class KMHmacKey implements KMKey { + + public HMACKey hmacKey; + + public KMHmacKey(HMACKey key) { + hmacKey = key; + } + + public static void onSave(Element element, KMHmacKey kmKey) { + element.write(kmKey.hmacKey); + } + + public static KMHmacKey onRestore(HMACKey hmacKey) { + if (hmacKey == null) { + return null; + } + return new KMHmacKey(hmacKey); + } + + public static short getBackupPrimitiveByteCount() { + return (short) 0; + } + + public static short getBackupObjectCount() { + return (short) 1; + } + + @Override + public short getPublicKey(byte[] buf, short offset) { + return (short) 0; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMKey.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMKey.java new file mode 100644 index 0000000..9894382 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMKey.java @@ -0,0 +1,10 @@ +package com.android.javacard.seprovider; + +/** + * This interface helps to decouple Javacard internal key objects from the keymaster package. Using + * Javacard key objects provides security by providing protection against side channel attacks. + * KMAESKey, KMECDeviceUniqueKey and KMHmacKey implements this interface. + */ +public interface KMKey { + short getPublicKey(byte[] buf, short offset); +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMKeyObject.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMKeyObject.java new file mode 100644 index 0000000..a37da08 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMKeyObject.java @@ -0,0 +1,10 @@ +package com.android.javacard.seprovider; + +/** + * This class holds the KeyObject and its associated algorithm value. Each KMKeyObject is tied to + * one of the crypto operations. + */ +public class KMKeyObject { + public byte algorithm; + public Object keyObjectInst; +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperation.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperation.java new file mode 100644 index 0000000..12e691e --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperation.java @@ -0,0 +1,75 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +/** + * KMOperation represents a persistent operation started by keymaster hal's beginOperation function. + * This operation is persistent i.e. it will be stored in non volatile memory of se card. It will be + * returned back to KMSEProvider for the reuse when the operation is finished. + */ +public interface KMOperation { + + // Used for cipher operations + short update( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart); + + // Used for signature operations + short update(byte[] inputDataBuf, short inputDataStart, short inputDataLength); + + // Used for finishing cipher operations or ecdh keyAgreement. + short finish( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart); + + // Used for finishing signing operations. + short sign( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] signBuf, + short signStart); + + // Used for finishing verifying operations. + boolean verify( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] signBuf, + short signStart, + short signLength); + + // Used for aborting the ongoing operations. + void abort(); + + // Used for AES GCM cipher operation. + void updateAAD(byte[] dataBuf, short dataStart, short dataLength); + + // Used for getting output size before finishing a AES GCM cipher operation. For encryption this + // will + // include the auth tag which is appended at the end of the encrypted data. For decryption this + // will be + // size of the decrypted data only. + short getAESGCMOutputSize(short dataSize, short macLength); + + KMKeyObject getKeyObject(); +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperationImpl.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperationImpl.java new file mode 100644 index 0000000..8059e44 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperationImpl.java @@ -0,0 +1,415 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.framework.JCSystem; +import javacard.framework.Util; +import javacard.security.CryptoException; +import javacard.security.Key; +import javacard.security.KeyAgreement; +import javacard.security.PrivateKey; +import javacard.security.Signature; +import javacardx.crypto.AEADCipher; +import javacardx.crypto.Cipher; + +/** + * This class contains the actual implementation of all the crypto operations. It internally uses + * the Javacard crypto library to perform the operations. + */ +public class KMOperationImpl implements KMOperation { + + private static final byte ALG_TYPE_OFFSET = 0x00; + private static final byte PADDING_OFFSET = 0x01; + private static final byte PURPOSE_OFFSET = 0x02; + private static final byte BLOCK_MODE_OFFSET = 0x03; + private static final byte MAC_LENGTH_OFFSET = 0x04; + private final byte[] EMPTY = {}; + // This will hold the length of the buffer stored inside the + // Java Card after the GCM update operation. + private static final byte AES_GCM_UPDATE_LEN_OFFSET = 0x05; + private static final byte PARAMETERS_LENGTH = 6; + private short[] parameters; + // Either one of Cipher/Signature instance is stored. + private Object[] operationInst; + + public KMOperationImpl() { + parameters = JCSystem.makeTransientShortArray(PARAMETERS_LENGTH, JCSystem.CLEAR_ON_RESET); + operationInst = JCSystem.makeTransientObjectArray((short) 2, JCSystem.CLEAR_ON_RESET); + reset(); + } + + public short getPurpose() { + return parameters[PURPOSE_OFFSET]; + } + + public void setPurpose(short mode) { + parameters[PURPOSE_OFFSET] = mode; + } + + public short getMacLength() { + return parameters[MAC_LENGTH_OFFSET]; + } + + public void setMacLength(short macLength) { + parameters[MAC_LENGTH_OFFSET] = macLength; + } + + public short getPaddingAlgorithm() { + return parameters[PADDING_OFFSET]; + } + + public void setPaddingAlgorithm(short alg) { + parameters[PADDING_OFFSET] = alg; + } + + public void setBlockMode(short mode) { + parameters[BLOCK_MODE_OFFSET] = mode; + } + + public short getBlockMode() { + return parameters[BLOCK_MODE_OFFSET]; + } + + public short getAlgorithmType() { + return parameters[ALG_TYPE_OFFSET]; + } + + public void setAlgorithmType(short cipherAlg) { + parameters[ALG_TYPE_OFFSET] = cipherAlg; + } + + public void setCipher(Cipher cipher) { + operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO] = cipher; + } + + public void setSignature(Signature signer) { + operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO] = signer; + } + + public void setKeyAgreement(KeyAgreement keyAgreement) { + operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO] = keyAgreement; + } + + public boolean isResourceMatches(Object object, byte resourceType) { + return operationInst[resourceType] == object; + } + + public void setKeyObject(KMKeyObject keyObject) { + operationInst[KMPoolManager.RESOURCE_TYPE_KEY] = keyObject; + } + + public KMKeyObject getKeyObject() { + return (KMKeyObject) operationInst[KMPoolManager.RESOURCE_TYPE_KEY]; + } + + private void reset() { + operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO] = null; + operationInst[KMPoolManager.RESOURCE_TYPE_KEY] = null; + parameters[MAC_LENGTH_OFFSET] = KMType.INVALID_VALUE; + parameters[AES_GCM_UPDATE_LEN_OFFSET] = 0; + parameters[BLOCK_MODE_OFFSET] = KMType.INVALID_VALUE; + parameters[PURPOSE_OFFSET] = KMType.INVALID_VALUE; + parameters[ALG_TYPE_OFFSET] = KMType.INVALID_VALUE; + parameters[PADDING_OFFSET] = KMType.INVALID_VALUE; + } + + private byte mapPurpose(short purpose) { + switch (purpose) { + case KMType.ENCRYPT: + return Cipher.MODE_ENCRYPT; + case KMType.DECRYPT: + return Cipher.MODE_DECRYPT; + case KMType.SIGN: + return Signature.MODE_SIGN; + case KMType.VERIFY: + return Signature.MODE_VERIFY; + } + return -1; + } + + private void initSymmetricCipher(Key key, byte[] ivBuffer, short ivStart, short ivLength) { + Cipher symmCipher = (Cipher) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]; + byte cipherAlg = symmCipher.getAlgorithm(); + switch (cipherAlg) { + case Cipher.ALG_AES_BLOCK_128_CBC_NOPAD: + case Cipher.ALG_AES_CTR: + symmCipher.init(key, mapPurpose(getPurpose()), ivBuffer, ivStart, ivLength); + break; + case Cipher.ALG_AES_BLOCK_128_ECB_NOPAD: + case Cipher.ALG_DES_ECB_NOPAD: + symmCipher.init(key, mapPurpose(getPurpose())); + break; + case Cipher.ALG_DES_CBC_NOPAD: + // Consume only 8 bytes of iv. the random number for iv is of 16 bytes. + // While sending back the iv, send only 8 bytes. + symmCipher.init(key, mapPurpose(getPurpose()), ivBuffer, ivStart, (short) 8); + break; + case AEADCipher.ALG_AES_GCM: + ((AEADCipher) symmCipher).init(key, mapPurpose(getPurpose()), ivBuffer, ivStart, ivLength); + break; + default: // This should never happen + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + } + + private void initRsa(Key key, short digest) { + if (KMType.SIGN == getPurpose()) { + byte mode; + if (getPaddingAlgorithm() == KMType.PADDING_NONE + || (getPaddingAlgorithm() == KMType.RSA_PKCS1_1_5_SIGN && digest == KMType.DIGEST_NONE)) { + mode = Cipher.MODE_DECRYPT; + } else { + mode = Signature.MODE_SIGN; + } + ((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]).init((PrivateKey) key, mode); + } else { // RSA Cipher + ((Cipher) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .init((PrivateKey) key, mapPurpose(getPurpose())); + } + } + + private void initEc(Key key) { + if (KMType.AGREE_KEY == getPurpose()) { + ((KeyAgreement) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]).init((PrivateKey) key); + } else { + ((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .init((PrivateKey) key, mapPurpose(getPurpose())); + } + } + + public void init(Key key, short digest, byte[] buf, short start, short length) { + switch (getAlgorithmType()) { + case KMType.AES: + case KMType.DES: + initSymmetricCipher(key, buf, start, length); + break; + case KMType.HMAC: + ((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .init(key, mapPurpose(getPurpose())); + break; + case KMType.RSA: + initRsa(key, digest); + break; + case KMType.EC: + initEc(key); + break; + default: // This should never happen + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + } + + @Override + public short update( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart) { + short len = + ((Cipher) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .update(inputDataBuf, inputDataStart, inputDataLength, outputDataBuf, outputDataStart); + if (parameters[ALG_TYPE_OFFSET] == KMType.AES && parameters[BLOCK_MODE_OFFSET] == KMType.GCM) { + // Every time Block size data is stored as intermediate result. + parameters[AES_GCM_UPDATE_LEN_OFFSET] += (short) (inputDataLength - len); + } + return len; + } + + @Override + public short update(byte[] inputDataBuf, short inputDataStart, short inputDataLength) { + ((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .update(inputDataBuf, inputDataStart, inputDataLength); + return 0; + } + + private short finishKeyAgreement( + byte[] publicKey, short start, short len, byte[] output, short outputStart) { + return ((KeyAgreement) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .generateSecret(publicKey, start, len, output, outputStart); + } + + private short finishCipher( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLen, + byte[] outputDataBuf, + short outputDataStart) { + short len = 0; + try { + byte[] tmpArray = KMAndroidSEProvider.getInstance().tmpArray; + Cipher cipher = (Cipher) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]; + short cipherAlg = parameters[ALG_TYPE_OFFSET]; + short blockMode = parameters[BLOCK_MODE_OFFSET]; + short mode = parameters[PURPOSE_OFFSET]; + short macLength = parameters[MAC_LENGTH_OFFSET]; + short padding = parameters[PADDING_OFFSET]; + + if (cipherAlg == KMType.AES && blockMode == KMType.GCM) { + if (mode == KMType.DECRYPT) { + inputDataLen = (short) (inputDataLen - macLength); + } + } else if ((cipherAlg == KMType.DES || cipherAlg == KMType.AES) + && padding == KMType.PKCS7 + && mode == KMType.ENCRYPT) { + byte blkSize = 16; + byte paddingBytes; + short inputlen = inputDataLen; + if (cipherAlg == KMType.DES) { + blkSize = 8; + } + // padding bytes + if (inputlen % blkSize == 0) { + paddingBytes = blkSize; + } else { + paddingBytes = (byte) (blkSize - (inputlen % blkSize)); + } + // final len with padding + inputlen = (short) (inputlen + paddingBytes); + // intermediate buffer to copy input data+padding + // fill in the padding + Util.arrayFillNonAtomic(tmpArray, (short) 0, inputlen, paddingBytes); + // copy the input data + Util.arrayCopyNonAtomic(inputDataBuf, inputDataStart, tmpArray, (short) 0, inputDataLen); + inputDataBuf = tmpArray; + inputDataLen = inputlen; + inputDataStart = 0; + } + len = + cipher.doFinal( + inputDataBuf, inputDataStart, inputDataLen, outputDataBuf, outputDataStart); + if ((cipherAlg == KMType.AES || cipherAlg == KMType.DES) + && padding == KMType.PKCS7 + && mode == KMType.DECRYPT) { + byte blkSize = 16; + if (cipherAlg == KMType.DES) { + blkSize = 8; + } + if (len > 0) { + // verify if padding is corrupted. + byte paddingByte = outputDataBuf[(short) (outputDataStart + len - 1)]; + // padding byte always should be <= block size + if ((short) paddingByte > blkSize || (short) paddingByte <= 0) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + + for (short j = 1; j <= paddingByte; ++j) { + if (outputDataBuf[(short) (outputDataStart + len - j)] != paddingByte) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + } + len = (short) (len - (short) paddingByte); // remove the padding bytes + } + } else if (cipherAlg == KMType.AES && blockMode == KMType.GCM) { + if (mode == KMType.ENCRYPT) { + len += + ((AEADCipher) cipher) + .retrieveTag(outputDataBuf, (short) (outputDataStart + len), macLength); + } else { + boolean verified = + ((AEADCipher) cipher) + .verifyTag( + inputDataBuf, (short) (inputDataStart + inputDataLen), macLength, macLength); + if (!verified) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + } + } + } finally { + KMAndroidSEProvider.getInstance().clean(); + } + return len; + } + + @Override + public short finish( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLen, + byte[] outputDataBuf, + short outputDataStart) { + if (parameters[PURPOSE_OFFSET] == KMType.AGREE_KEY) { + return finishKeyAgreement( + inputDataBuf, inputDataStart, inputDataLen, outputDataBuf, outputDataStart); + } else { + return finishCipher( + inputDataBuf, inputDataStart, inputDataLen, outputDataBuf, outputDataStart); + } + } + + @Override + public short sign( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] signBuf, + short signStart) { + return ((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .sign(inputDataBuf, inputDataStart, inputDataLength, signBuf, signStart); + } + + @Override + public boolean verify( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] signBuf, + short signStart, + short signLength) { + return ((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .verify(inputDataBuf, inputDataStart, inputDataLength, signBuf, signStart, signLength); + } + + @Override + public void abort() { + // Few simulators does not reset the Hmac signer instance on init so as + // a workaround to reset the hmac signer instance in case of abort/failure of the operation + // the corresponding sign / verify function is called. + if (operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO] != null) { + if ((parameters[PURPOSE_OFFSET] == KMType.SIGN || parameters[PURPOSE_OFFSET] == KMType.VERIFY) + && (((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]).getAlgorithm() + == Signature.ALG_HMAC_SHA_256)) { + Signature signer = (Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]; + try { + if (parameters[PURPOSE_OFFSET] == KMType.SIGN) { + signer.sign(EMPTY, (short) 0, (short) 0, EMPTY, (short) 0); + } else { + signer.verify(EMPTY, (short) 0, (short) 0, EMPTY, (short) 0, (short) 0); + } + } catch (Exception e) { + // Ignore. + } + } + } + reset(); + } + + @Override + public void updateAAD(byte[] dataBuf, short dataStart, short dataLength) { + ((AEADCipher) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .updateAAD(dataBuf, dataStart, dataLength); + } + + @Override + public short getAESGCMOutputSize(short dataSize, short macLength) { + if (parameters[PURPOSE_OFFSET] == KMType.ENCRYPT) { + return (short) (parameters[AES_GCM_UPDATE_LEN_OFFSET] + dataSize + macLength); + } else { + return (short) (parameters[AES_GCM_UPDATE_LEN_OFFSET] + dataSize - macLength); + } + } +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMPoolManager.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMPoolManager.java new file mode 100644 index 0000000..8ca2664 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMPoolManager.java @@ -0,0 +1,665 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.framework.JCSystem; +import javacard.security.AESKey; +import javacard.security.CryptoException; +import javacard.security.DESKey; +import javacard.security.ECPrivateKey; +import javacard.security.ECPublicKey; +import javacard.security.HMACKey; +import javacard.security.KeyAgreement; +import javacard.security.KeyBuilder; +import javacard.security.KeyPair; +import javacard.security.Signature; +import javacardx.crypto.AEADCipher; +import javacardx.crypto.Cipher; + +/** + * This class creates and manages all the cipher, signer, key agreement, operation and trusted + * confirmation pool instances. Each cipher or signer pool can hold a maximum of 4 instances per + * algorithm; however, only one instance of each algorithm is created initially and if required more + * instances are created dynamically. A maximum of four operations can be performed simultaneously. + * Upon reaching the maximum limit of 4, further operations or crypto instances will throw a + * TOO_MANY_OPERATIONS error. TrustedConfirmation pool is to support any operation which has the + * TRUSTED_CONFIRMATION tag in its key parameters. + */ +public class KMPoolManager { + + public static final byte MAX_OPERATION_INSTANCES = 4; + private static final byte HMAC_MAX_OPERATION_INSTANCES = 8; + public static final byte AES_128 = 0x04; + public static final byte AES_256 = 0x05; + // Resource type constants + public static final byte RESOURCE_TYPE_CRYPTO = 0x00; + public static final byte RESOURCE_TYPE_KEY = 0x01; + // static final variables + // -------------------------------------------------------------- + // P-256 Curve Parameters + static byte[] secp256r1_P; + static byte[] secp256r1_A; + + static byte[] secp256r1_B; + static byte[] secp256r1_S; + + // Uncompressed form + static byte[] secp256r1_UCG; + static byte[] secp256r1_N; + static final short secp256r1_H = 1; + // -------------------------------------------------------------- + + // Cipher pool + private Object[] cipherPool; + // Signature pool + private Object[] signerPool; + // Keyagreement pool + private Object[] keyAgreementPool; + // KMOperationImpl pool + private Object[] operationPool; + // Hmac signer pool which is used to support TRUSTED_CONFIRMATION_REQUIRED tag. + private Object[] hmacSignOperationPool; + + private Object[] keysPool; + // RKP uses AESGCM and HMAC in generateCSR flow. + KMOperation rkpOPeration; + Cipher rkpAesGcm; + Signature rkpHmac; + KMKeyObject rkpHmacKey; + KMKeyObject rkpAesKey; + + final byte[] KEY_ALGS = { + AES_128, AES_256, KMType.DES, KMType.RSA, KMType.EC, KMType.HMAC, + }; + + final byte[] CIPHER_ALGS = { + Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, + Cipher.ALG_AES_BLOCK_128_ECB_NOPAD, + Cipher.ALG_DES_CBC_NOPAD, + Cipher.ALG_DES_ECB_NOPAD, + Cipher.ALG_AES_CTR, + Cipher.ALG_RSA_PKCS1, + KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA1, + KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA256, + Cipher.ALG_RSA_NOPAD, + AEADCipher.ALG_AES_GCM + }; + + final byte[] SIG_ALGS = { + Signature.ALG_RSA_SHA_256_PKCS1, + Signature.ALG_RSA_SHA_256_PKCS1_PSS, + Signature.ALG_ECDSA_SHA_256, + Signature.ALG_HMAC_SHA_256, + KMRsa2048NoDigestSignature.ALG_RSA_SIGN_NOPAD, + KMRsa2048NoDigestSignature.ALG_RSA_PKCS1_NODIGEST, + KMEcdsa256NoDigestSignature.ALG_ECDSA_NODIGEST + }; + + final byte[] KEY_AGREE_ALGS = {KeyAgreement.ALG_EC_SVDP_DH_PLAIN}; + + private static KMPoolManager poolManager; + + public static KMPoolManager getInstance() { + if (poolManager == null) { + poolManager = new KMPoolManager(); + } + return poolManager; + } + + public static void initStatics() { + secp256r1_P = + new byte[] { + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF + }; + + secp256r1_A = + new byte[] { + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFC + }; + + secp256r1_B = + new byte[] { + (byte) 0x5A, (byte) 0xC6, (byte) 0x35, (byte) 0xD8, (byte) 0xAA, (byte) 0x3A, + (byte) 0x93, (byte) 0xE7, (byte) 0xB3, (byte) 0xEB, (byte) 0xBD, (byte) 0x55, + (byte) 0x76, (byte) 0x98, (byte) 0x86, (byte) 0xBC, (byte) 0x65, (byte) 0x1D, + (byte) 0x06, (byte) 0xB0, (byte) 0xCC, (byte) 0x53, (byte) 0xB0, (byte) 0xF6, + (byte) 0x3B, (byte) 0xCE, (byte) 0x3C, (byte) 0x3E, (byte) 0x27, (byte) 0xD2, + (byte) 0x60, (byte) 0x4B + }; + + secp256r1_S = + new byte[] { + (byte) 0xC4, (byte) 0x9D, (byte) 0x36, (byte) 0x08, (byte) 0x86, (byte) 0xE7, + (byte) 0x04, (byte) 0x93, (byte) 0x6A, (byte) 0x66, (byte) 0x78, (byte) 0xE1, + (byte) 0x13, (byte) 0x9D, (byte) 0x26, (byte) 0xB7, (byte) 0x81, (byte) 0x9F, + (byte) 0x7E, (byte) 0x90 + }; + + // Uncompressed form + secp256r1_UCG = + new byte[] { + (byte) 0x04, (byte) 0x6B, (byte) 0x17, (byte) 0xD1, (byte) 0xF2, (byte) 0xE1, + (byte) 0x2C, (byte) 0x42, (byte) 0x47, (byte) 0xF8, (byte) 0xBC, (byte) 0xE6, + (byte) 0xE5, (byte) 0x63, (byte) 0xA4, (byte) 0x40, (byte) 0xF2, (byte) 0x77, + (byte) 0x03, (byte) 0x7D, (byte) 0x81, (byte) 0x2D, (byte) 0xEB, (byte) 0x33, + (byte) 0xA0, (byte) 0xF4, (byte) 0xA1, (byte) 0x39, (byte) 0x45, (byte) 0xD8, + (byte) 0x98, (byte) 0xC2, (byte) 0x96, (byte) 0x4F, (byte) 0xE3, (byte) 0x42, + (byte) 0xE2, (byte) 0xFE, (byte) 0x1A, (byte) 0x7F, (byte) 0x9B, (byte) 0x8E, + (byte) 0xE7, (byte) 0xEB, (byte) 0x4A, (byte) 0x7C, (byte) 0x0F, (byte) 0x9E, + (byte) 0x16, (byte) 0x2B, (byte) 0xCE, (byte) 0x33, (byte) 0x57, (byte) 0x6B, + (byte) 0x31, (byte) 0x5E, (byte) 0xCE, (byte) 0xCB, (byte) 0xB6, (byte) 0x40, + (byte) 0x68, (byte) 0x37, (byte) 0xBF, (byte) 0x51, (byte) 0xF5 + }; + + secp256r1_N = + new byte[] { + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xBC, (byte) 0xE6, + (byte) 0xFA, (byte) 0xAD, (byte) 0xA7, (byte) 0x17, (byte) 0x9E, (byte) 0x84, + (byte) 0xF3, (byte) 0xB9, (byte) 0xCA, (byte) 0xC2, (byte) 0xFC, (byte) 0x63, + (byte) 0x25, (byte) 0x51 + }; + } + + private KMPoolManager() { + initStatics(); + cipherPool = new Object[(short) (CIPHER_ALGS.length * MAX_OPERATION_INSTANCES)]; + // Extra 4 algorithms are used to support TRUSTED_CONFIRMATION_REQUIRED feature. + signerPool = + new Object[(short) ((SIG_ALGS.length * MAX_OPERATION_INSTANCES) + MAX_OPERATION_INSTANCES)]; + keyAgreementPool = new Object[(short) (KEY_AGREE_ALGS.length * MAX_OPERATION_INSTANCES)]; + + keysPool = + new Object[(short) ((KEY_ALGS.length * MAX_OPERATION_INSTANCES) + MAX_OPERATION_INSTANCES)]; + operationPool = new Object[MAX_OPERATION_INSTANCES]; + hmacSignOperationPool = new Object[MAX_OPERATION_INSTANCES]; + /* Initialize pools */ + initializeOperationPool(); + initializeHmacSignOperationPool(); + initializeSignerPool(); + initializeCipherPool(); + initializeKeyAgreementPool(); + initializeKeysPool(); + // Initialize the Crypto and Key objects required for RKP flow. + initializeRKpObjects(); + } + + private void initializeRKpObjects() { + rkpOPeration = new KMOperationImpl(); + rkpAesGcm = Cipher.getInstance(AEADCipher.ALG_AES_GCM, false); + rkpHmac = Signature.getInstance(Signature.ALG_HMAC_SHA_256, false); + rkpAesKey = createKeyObjectInstance(AES_256); + rkpHmacKey = createKeyObjectInstance(KMType.HMAC); + } + + private void initializeKeysPool() { + for (short index = 0; index < KEY_ALGS.length; index++) { + keysPool[index] = createKeyObjectInstance(KEY_ALGS[index]); + } + } + + private void initializeOperationPool() { + for (short index = 0; index < MAX_OPERATION_INSTANCES; index++) { + operationPool[index] = new KMOperationImpl(); + } + } + + private void initializeHmacSignOperationPool() { + for (short index = 0; index < MAX_OPERATION_INSTANCES; index++) { + hmacSignOperationPool[index] = new KMOperationImpl(); + } + } + + // Create a signature instance of each algorithm once. + private void initializeSignerPool() { + short index; + for (index = 0; index < SIG_ALGS.length; index++) { + signerPool[index] = getSignatureInstance(SIG_ALGS[index]); + } + + // Allocate extra 4 HMAC signer instances required for trusted confirmation + for (short len = (short) (index + 4); index < len; index++) { + signerPool[index] = getSignatureInstance(Signature.ALG_HMAC_SHA_256); + } + } + + // Create a cipher instance of each algorithm once. + private void initializeCipherPool() { + for (short index = 0; index < CIPHER_ALGS.length; index++) { + cipherPool[index] = getCipherInstance(CIPHER_ALGS[index]); + } + } + + private void initializeKeyAgreementPool() { + for (short index = 0; index < KEY_AGREE_ALGS.length; index++) { + keyAgreementPool[index] = getKeyAgreementInstance(KEY_AGREE_ALGS[index]); + } + } + + private Object[] getCryptoPoolInstance(short purpose) { + switch (purpose) { + case KMType.AGREE_KEY: + return keyAgreementPool; + + case KMType.ENCRYPT: + case KMType.DECRYPT: + return cipherPool; + + case KMType.SIGN: + case KMType.VERIFY: + return signerPool; + + default: + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + return null; + } + + private Object createInstance(short purpose, short alg) { + switch (purpose) { + case KMType.AGREE_KEY: + return getKeyAgreementInstance((byte) alg); + + case KMType.ENCRYPT: + case KMType.DECRYPT: + return getCipherInstance((byte) alg); + + case KMType.SIGN: + case KMType.VERIFY: + return getSignatureInstance((byte) alg); + + default: + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + return null; + } + + private KeyAgreement getKeyAgreementInstance(byte alg) { + return KeyAgreement.getInstance(alg, false); + } + + private Signature getSignatureInstance(byte alg) { + if (KMRsa2048NoDigestSignature.ALG_RSA_SIGN_NOPAD == alg + || KMRsa2048NoDigestSignature.ALG_RSA_PKCS1_NODIGEST == alg) { + return new KMRsa2048NoDigestSignature(alg); + } else if (KMEcdsa256NoDigestSignature.ALG_ECDSA_NODIGEST == alg) { + return new KMEcdsa256NoDigestSignature(alg); + } else { + return Signature.getInstance(alg, false); + } + } + + private KMKeyObject createKeyObjectInstance(byte alg) { + Object keyObject = null; + switch (alg) { + case AES_128: + keyObject = + (AESKey) + KeyBuilder.buildKey( + KeyBuilder.TYPE_AES_TRANSIENT_RESET, KeyBuilder.LENGTH_AES_128, false); + break; + case AES_256: + keyObject = + (AESKey) + KeyBuilder.buildKey( + KeyBuilder.TYPE_AES_TRANSIENT_RESET, KeyBuilder.LENGTH_AES_256, false); + break; + case KMType.DES: + keyObject = + (DESKey) + KeyBuilder.buildKey( + KeyBuilder.TYPE_DES_TRANSIENT_RESET, KeyBuilder.LENGTH_DES3_3KEY, false); + break; + case KMType.RSA: + keyObject = new KeyPair(KeyPair.ALG_RSA, KeyBuilder.LENGTH_RSA_2048); + break; + case KMType.EC: + keyObject = new KeyPair(KeyPair.ALG_EC_FP, KeyBuilder.LENGTH_EC_FP_256); + initECKey((KeyPair) keyObject); + break; + case KMType.HMAC: + keyObject = + (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC_TRANSIENT_RESET, (short) 512, false); + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + KMKeyObject ptr = new KMKeyObject(); + ptr.algorithm = alg; + ptr.keyObjectInst = keyObject; + return ptr; + } + + private Cipher getCipherInstance(byte alg) { + if ((KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA1 == alg) + || (KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA256 == alg)) { + return new KMRsaOAEPEncoding(alg); + } else { + return Cipher.getInstance(alg, false); + } + } + + /** + * Returns the first available resource from operation pool. + * + * @return instance of the available resource or null if no resource is available. + */ + public KMOperation getResourceFromOperationPool(boolean isTrustedConfOpr) { + short index = 0; + KMOperationImpl impl; + Object[] oprPool; + if (isTrustedConfOpr) { + oprPool = hmacSignOperationPool; + } else { + oprPool = operationPool; + } + while (index < oprPool.length) { + impl = (KMOperationImpl) oprPool[index]; + // Mode is always set. so compare using mode value. + if (impl.getPurpose() == KMType.INVALID_VALUE) { + return impl; + } + index++; + } + return null; + } + + private byte getAlgorithm(short purpose, Object object) { + switch (purpose) { + case KMType.AGREE_KEY: + return ((KeyAgreement) object).getAlgorithm(); + + case KMType.ENCRYPT: + case KMType.DECRYPT: + return ((Cipher) object).getAlgorithm(); + + case KMType.SIGN: + case KMType.VERIFY: + return ((Signature) object).getAlgorithm(); + + default: + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + return 0; + } + + private boolean isResourceBusy(Object obj, byte resourceType) { + short index = 0; + while (index < MAX_OPERATION_INSTANCES) { + if (((KMOperationImpl) operationPool[index]).isResourceMatches(obj, resourceType) + || ((KMOperationImpl) hmacSignOperationPool[index]) + .isResourceMatches(obj, resourceType)) { + return true; + } + index++; + } + return false; + } + + private void setObject(short purpose, KMOperation operation, Object obj) { + switch (purpose) { + case KMType.AGREE_KEY: + ((KMOperationImpl) operation).setKeyAgreement((KeyAgreement) obj); + break; + case KMType.ENCRYPT: + case KMType.DECRYPT: + ((KMOperationImpl) operation).setCipher((Cipher) obj); + break; + case KMType.SIGN: + case KMType.VERIFY: + ((KMOperationImpl) operation).setSignature((Signature) obj); + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + } + + private void reserveOperation( + KMOperation operation, + short purpose, + short strongboxAlgType, + short padding, + short blockMode, + short macLength, + Object obj, + KMKeyObject keyObject) { + ((KMOperationImpl) operation).setPurpose(purpose); + ((KMOperationImpl) operation).setAlgorithmType(strongboxAlgType); + ((KMOperationImpl) operation).setPaddingAlgorithm(padding); + ((KMOperationImpl) operation).setBlockMode(blockMode); + ((KMOperationImpl) operation).setMacLength(macLength); + ((KMOperationImpl) operation).setKeyObject(keyObject); + setObject(purpose, operation, obj); + } + + public KMOperation getRKpOperation( + short purpose, + short alg, + short strongboxAlgType, + short padding, + short blockMode, + short macLength) { + if (((KMOperationImpl) rkpOPeration).getPurpose() != KMType.INVALID_VALUE) { + // Should not come here. + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + Object cryptoObj = null; + KMKeyObject keyObject = null; + + switch (alg) { + case AEADCipher.ALG_AES_GCM: + cryptoObj = rkpAesGcm; + keyObject = rkpAesKey; + break; + case Signature.ALG_HMAC_SHA_256: + cryptoObj = rkpHmac; + keyObject = rkpHmacKey; + break; + default: + // Should not come here. + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + break; + } + reserveOperation( + rkpOPeration, + purpose, + strongboxAlgType, + padding, + blockMode, + macLength, + cryptoObj, + keyObject); + return rkpOPeration; + } + + public KMOperation getOperationImpl( + short purpose, + short alg, + short strongboxAlgType, + short padding, + short blockMode, + short macLength, + short secretLength, + boolean isTrustedConfOpr) { + KMOperation operation; + // Throw exception if no resource from operation pool is available. + if (null == (operation = getResourceFromOperationPool(isTrustedConfOpr))) { + KMException.throwIt(KMError.TOO_MANY_OPERATIONS); + } + // Get one of the pool instances (cipher / signer / keyAgreement) based on purpose. + Object[] pool = getCryptoPoolInstance(purpose); + short index = 0; + short usageCount = 0; + short maxOperations = MAX_OPERATION_INSTANCES; + if (Signature.ALG_HMAC_SHA_256 == alg) { + maxOperations = HMAC_MAX_OPERATION_INSTANCES; + } + + KMKeyObject keyObject = getKeyObjectFromPool(alg, secretLength, maxOperations); + while (index < pool.length) { + if (usageCount >= maxOperations) { + KMException.throwIt(KMError.TOO_MANY_OPERATIONS); + } + if (pool[index] == null) { + // Create one of the instance (Cipher / Signer / KeyAgreement] based on purpose. + Object cipherObject = createInstance(purpose, alg); + JCSystem.beginTransaction(); + pool[index] = cipherObject; + JCSystem.commitTransaction(); + reserveOperation( + operation, + purpose, + strongboxAlgType, + padding, + blockMode, + macLength, + pool[index], + keyObject); + break; + } + if (alg == getAlgorithm(purpose, pool[index])) { + // Check if the crypto instance is not busy and free to use. + if (!isResourceBusy(pool[index], RESOURCE_TYPE_CRYPTO)) { + reserveOperation( + operation, + purpose, + strongboxAlgType, + padding, + blockMode, + macLength, + pool[index], + keyObject); + break; + } + usageCount++; + } + index++; + } + return operation; + } + + public KMKeyObject getKeyObjectFromPool(short alg, short secretLength, short maxOperations) { + KMKeyObject keyObject = null; + byte algo = mapAlgorithm(alg, secretLength); + short index = 0; + short usageCount = 0; + while (index < keysPool.length) { + if (usageCount >= maxOperations) { + KMException.throwIt(KMError.TOO_MANY_OPERATIONS); + } + if (keysPool[index] == null) { + keyObject = createKeyObjectInstance(algo); + JCSystem.beginTransaction(); + keysPool[index] = keyObject; + JCSystem.commitTransaction(); + break; + } + keyObject = (KMKeyObject) keysPool[index]; + if (algo == keyObject.algorithm) { + // Check if the Object instance is not busy and free to use. + if (!isResourceBusy(keyObject, RESOURCE_TYPE_KEY)) { + break; + } + usageCount++; + } + index++; + } + return keyObject; + } + + private byte mapAlgorithm(short alg, short secretLength) { + byte algo = 0; + switch (alg) { + case Cipher.ALG_AES_BLOCK_128_CBC_NOPAD: + case Cipher.ALG_AES_BLOCK_128_ECB_NOPAD: + case Cipher.ALG_AES_CTR: + case AEADCipher.ALG_AES_GCM: + if (secretLength == 16) { + algo = AES_128; + } else if (secretLength == 32) { + algo = AES_256; + } else { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + break; + case Cipher.ALG_DES_CBC_NOPAD: + case Cipher.ALG_DES_ECB_NOPAD: + algo = KMType.DES; + break; + case Cipher.ALG_RSA_PKCS1: + case KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA1: + case KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA256: + case Cipher.ALG_RSA_NOPAD: + case Signature.ALG_RSA_SHA_256_PKCS1: + case Signature.ALG_RSA_SHA_256_PKCS1_PSS: + case KMRsa2048NoDigestSignature.ALG_RSA_SIGN_NOPAD: + case KMRsa2048NoDigestSignature.ALG_RSA_PKCS1_NODIGEST: + algo = KMType.RSA; + break; + case Signature.ALG_ECDSA_SHA_256: + case KMEcdsa256NoDigestSignature.ALG_ECDSA_NODIGEST: + case KeyAgreement.ALG_EC_SVDP_DH_PLAIN: + algo = KMType.EC; + break; + case Signature.ALG_HMAC_SHA_256: + algo = KMType.HMAC; + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + return algo; + } + + public void initECKey(KeyPair ecKeyPair) { + ECPrivateKey privKey = (ECPrivateKey) ecKeyPair.getPrivate(); + ECPublicKey pubkey = (ECPublicKey) ecKeyPair.getPublic(); + pubkey.setFieldFP(secp256r1_P, (short) 0, (short) secp256r1_P.length); + pubkey.setA(secp256r1_A, (short) 0, (short) secp256r1_A.length); + pubkey.setB(secp256r1_B, (short) 0, (short) secp256r1_B.length); + pubkey.setG(secp256r1_UCG, (short) 0, (short) secp256r1_UCG.length); + pubkey.setK(secp256r1_H); + pubkey.setR(secp256r1_N, (short) 0, (short) secp256r1_N.length); + + privKey.setFieldFP(secp256r1_P, (short) 0, (short) secp256r1_P.length); + privKey.setA(secp256r1_A, (short) 0, (short) secp256r1_A.length); + privKey.setB(secp256r1_B, (short) 0, (short) secp256r1_B.length); + privKey.setG(secp256r1_UCG, (short) 0, (short) secp256r1_UCG.length); + privKey.setK(secp256r1_H); + privKey.setR(secp256r1_N, (short) 0, (short) secp256r1_N.length); + } + + public void powerReset() { + short index = 0; + while (index < operationPool.length) { + ((KMOperationImpl) operationPool[index]).abort(); + ((KMOperationImpl) hmacSignOperationPool[index]).abort(); + index++; + } + // release rkp operation + rkpOPeration.abort(); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsa2048NoDigestSignature.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsa2048NoDigestSignature.java new file mode 100644 index 0000000..4890ce2 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsa2048NoDigestSignature.java @@ -0,0 +1,140 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.framework.Util; +import javacard.security.CryptoException; +import javacard.security.Key; +import javacard.security.MessageDigest; +import javacard.security.Signature; +import javacardx.crypto.Cipher; + +/** This class provides support for RSA_NO_DIGEST signature algorithm. */ +public class KMRsa2048NoDigestSignature extends Signature { + + public static final byte ALG_RSA_SIGN_NOPAD = (byte) 0x65; + public static final byte ALG_RSA_PKCS1_NODIGEST = (byte) 0x66; + private byte algorithm; + private Cipher inst; + + public KMRsa2048NoDigestSignature(byte alg) { + algorithm = alg; + inst = Cipher.getInstance(Cipher.ALG_RSA_NOPAD, false); + } + + @Override + public void init(Key key, byte b) throws CryptoException { + inst.init(key, b); + } + + @Override + public void init(Key key, byte b, byte[] bytes, short i, short i1) throws CryptoException { + inst.init(key, b, bytes, i, i1); + } + + @Override + public void setInitialDigest(byte[] bytes, short i, short i1, byte[] bytes1, short i2, short i3) + throws CryptoException {} + + @Override + public byte getAlgorithm() { + return algorithm; + } + + @Override + public byte getMessageDigestAlgorithm() { + return MessageDigest.ALG_NULL; + } + + @Override + public byte getCipherAlgorithm() { + return algorithm; + } + + @Override + public byte getPaddingAlgorithm() { + return Cipher.PAD_NULL; + } + + @Override + public short getLength() throws CryptoException { + return 0; + } + + @Override + public void update(byte[] bytes, short i, short i1) throws CryptoException { + // HAL accumulates the data and send it at finish operation. + } + + @Override + public short sign(byte[] bytes, short i, short i1, byte[] bytes1, short i2) + throws CryptoException { + padData(bytes, i, i1, KMAndroidSEProvider.getInstance().tmpArray, (short) 0); + return inst.doFinal( + KMAndroidSEProvider.getInstance().tmpArray, (short) 0, (short) 256, bytes1, i2); + } + + @Override + public short signPreComputedHash(byte[] bytes, short i, short i1, byte[] bytes1, short i2) + throws CryptoException { + return 0; + } + + @Override + public boolean verify(byte[] bytes, short i, short i1, byte[] bytes1, short i2, short i3) + throws CryptoException { + // Verification is handled inside HAL + return false; + } + + @Override + public boolean verifyPreComputedHash( + byte[] bytes, short i, short i1, byte[] bytes1, short i2, short i3) throws CryptoException { + // Verification is handled inside HAL + return false; + } + + private void padData(byte[] buf, short start, short len, byte[] outBuf, short outBufStart) { + if (!isValidData(buf, start, len)) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + Util.arrayFillNonAtomic(outBuf, (short) outBufStart, (short) 256, (byte) 0x00); + if (algorithm == ALG_RSA_SIGN_NOPAD) { // add zero to right + } else if (algorithm == ALG_RSA_PKCS1_NODIGEST) { // 0x00||0x01||PS||0x00 + outBuf[0] = 0x00; + outBuf[1] = 0x01; + Util.arrayFillNonAtomic(outBuf, (short) 2, (short) (256 - len - 3), (byte) 0xFF); + outBuf[(short) (256 - len - 1)] = 0x00; + } else { + CryptoException.throwIt(CryptoException.ILLEGAL_USE); + } + Util.arrayCopyNonAtomic(buf, start, outBuf, (short) (256 - len), len); + } + + private boolean isValidData(byte[] buf, short start, short len) { + if (algorithm == ALG_RSA_SIGN_NOPAD) { + if (len > 256) { + return false; + } + } else { // ALG_RSA_PKCS1_NODIGEST + if (len > 245) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + return false; + } + } + return true; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsaOAEPEncoding.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsaOAEPEncoding.java new file mode 100644 index 0000000..bf34ea1 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsaOAEPEncoding.java @@ -0,0 +1,289 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.framework.JCSystem; +import javacard.framework.Util; +import javacard.security.CryptoException; +import javacard.security.Key; +import javacard.security.MessageDigest; +import javacardx.crypto.Cipher; + +/** This class has the implementation for RSA_OAEP decoding algorithm. */ +public class KMRsaOAEPEncoding extends Cipher { + + public static final byte ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA1 = (byte) 0x1E; + public static final byte ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA256 = (byte) 0x1F; + + final short MGF1_BUF_SIZE = 256; + static byte[] mgf1Buf; + private Cipher cipher; + private byte hash; + private byte mgf1Hash; + private byte algorithm; + + public KMRsaOAEPEncoding(byte alg) { + setDigests(alg); + cipher = Cipher.getInstance(Cipher.ALG_RSA_NOPAD, false); + algorithm = alg; + if (null == mgf1Buf) { + mgf1Buf = + JCSystem.makeTransientByteArray(MGF1_BUF_SIZE, JCSystem.MEMORY_TYPE_TRANSIENT_DESELECT); + } + } + + private void setDigests(byte alg) { + switch (alg) { + case ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA1: + hash = MessageDigest.ALG_SHA_256; + mgf1Hash = MessageDigest.ALG_SHA; + break; + case ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA256: + hash = MessageDigest.ALG_SHA_256; + mgf1Hash = MessageDigest.ALG_SHA_256; + break; + default: + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + } + } + + private short getDigestLength() { + switch (hash) { + case MessageDigest.ALG_SHA: + return MessageDigest.LENGTH_SHA; + case MessageDigest.ALG_SHA_224: + return MessageDigest.LENGTH_SHA_224; + case MessageDigest.ALG_SHA_256: + return MessageDigest.LENGTH_SHA_256; + case MessageDigest.ALG_SHA_384: + return MessageDigest.LENGTH_SHA_384; + case MessageDigest.ALG_SHA_512: + return MessageDigest.LENGTH_SHA_512; + default: + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + } + return 0; + } + + @Override + public void init(Key theKey, byte theMode) throws CryptoException { + cipher.init(theKey, theMode); + } + + @Override + public void init(Key theKey, byte theMode, byte[] bArray, short bOff, short bLen) + throws CryptoException { + cipher.init(theKey, theMode, bArray, bOff, bLen); + } + + @Override + public byte getAlgorithm() { + return algorithm; + } + + @Override + public byte getCipherAlgorithm() { + return 0; + } + + @Override + public byte getPaddingAlgorithm() { + return 0; + } + + @Override + public short doFinal( + byte[] inBuff, short inOffset, short inLength, byte[] outBuff, short outOffset) + throws CryptoException { + short len = cipher.doFinal(inBuff, inOffset, inLength, outBuff, outOffset); + + // https://tools.ietf.org/html/rfc8017#section-7.1 + // https://www.inf.pucrs.br/~calazans/graduate/TPVLSI_I/RSA-oaep_spec.pdf + // RSA OAEP Encoding and Decoding Mechanism for a 2048 bit RSA Key. + // Msg -> RSA-OAEP-ENCODE -> RSAEncryption -> RSADecryption -> + // RSA-OAEP-DECODE -> Msg + // RSA-OAEP-ENCODE generates an output length of 255, but RSAEncryption + // requires and input of length 256 so we pad 0 to the left of the input + // message and make the length equal to 256 and pass to RSAEncryption. + // RSADecryption takes input length equal to 256 and generates an + // output of length 256. After decryption the first byte of the output + // should be 0(left padding we did in encryption). + // RSA-OAEP-DECODE takes input of length 255 so remove the left padding of 1 + // byte. + if (len != 256 || outBuff[0] != 0) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + Util.arrayCopyNonAtomic( + outBuff, (short) (outOffset + 1), outBuff, (short) 0, (short) (len - 1)); + return rsaOAEPDecode(outBuff, (short) 0, (short) (len - 1)); + } + + @Override + public short update( + byte[] inBuff, short inOffset, short inLength, byte[] outBuff, short outOffset) + throws CryptoException { + return cipher.update(inBuff, inOffset, inLength, outBuff, outOffset); + } + + private void maskGenerationFunction1( + byte[] input, + short inputOffset, + short inputLen, + short expectedOutLen, + byte[] outBuf, + short outOffset) { + short counter = 0; + MessageDigest.OneShot md = null; + try { + md = MessageDigest.OneShot.open(mgf1Hash); + short digestLen = md.getLength(); + + Util.arrayCopyNonAtomic(input, inputOffset, mgf1Buf, (short) 0, inputLen); + while (counter < (short) (expectedOutLen / digestLen)) { + I2OS(counter, mgf1Buf, (short) inputLen); + md.doFinal( + mgf1Buf, + (short) 0, + (short) (4 + inputLen), + outBuf, + (short) (outOffset + (counter * digestLen))); + counter++; + } + + if ((short) (counter * digestLen) < expectedOutLen) { + I2OS(counter, mgf1Buf, (short) inputLen); + md.doFinal( + mgf1Buf, + (short) 0, + (short) (4 + inputLen), + outBuf, + (short) (outOffset + (counter * digestLen))); + } + + } finally { + if (md != null) { + md.close(); + } + Util.arrayFillNonAtomic(mgf1Buf, (short) 0, (short) MGF1_BUF_SIZE, (byte) 0); + } + } + + // Integer to Octet String conversion. + private void I2OS(short i, byte[] out, short offset) { + Util.arrayFillNonAtomic(out, (short) offset, (short) 4, (byte) 0); + out[(short) (offset + 3)] = (byte) (i >>> 0); + out[(short) (offset + 2)] = (byte) (i >>> 8); + } + + private short rsaOAEPDecode(byte[] encodedMsg, short encodedMsgOff, short encodedMsgLen) { + MessageDigest.OneShot md = null; + byte[] tmpArray = KMAndroidSEProvider.getInstance().tmpArray; + + try { + short hLen = getDigestLength(); + + if (encodedMsgLen < (short) (2 * hLen + 1)) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + // encodedMsg will be in the format of maskedSeed||maskedDB. + // maskedSeed length is hLen and maskedDB length is (encodedMsgLen - hLen) + // Now retrieve the seedMask by calling MGF(maskedDB, hLen). The length + // of the seedMask is hLen. + // seedMask = MGF(maskedDB, hLen) + maskGenerationFunction1( + encodedMsg, + (short) (encodedMsgOff + hLen), + (short) (encodedMsgLen - hLen), + hLen, + tmpArray, + (short) 0); + + // Get the seed by doing XOR of (maskedSeed ^ seedMask). + // seed = (maskedSeed ^ seedMask) + for (short i = 0; i < hLen; i++) { + // Store the seed in encodeMsg itself. + encodedMsg[(short) (encodedMsgOff + i)] ^= tmpArray[i]; + } + + // Now get the dbMask by calling MGF(seed , (emLen-hLen)). + // dbMask = MGF(seed , (emLen-hLen)). + maskGenerationFunction1( + encodedMsg, + (short) encodedMsgOff, + hLen, + (short) (encodedMsgLen - hLen), + tmpArray, + (short) 0); + + // Get the DB value. DB = (maskedDB ^ dbMask) + // DB = Hash(P)||00||01||Msg, where P is encoding parameters. (P = NULL) + for (short i = 0; i < (short) (encodedMsgLen - hLen); i++) { + // Store the DB inside encodeMsg itself. + encodedMsg[(short) (encodedMsgOff + i + hLen)] ^= tmpArray[i]; + } + + // Verify Hash. + md = MessageDigest.OneShot.open(hash); + Util.arrayFillNonAtomic(tmpArray, (short) 0, (short) 256, (byte) 0); + md.doFinal(tmpArray, (short) 0, (short) 0, tmpArray, (short) 0); + if (0 + != Util.arrayCompare( + encodedMsg, (short) (encodedMsgOff + hLen), tmpArray, (short) 0, hLen)) { + // Verification failed. + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + + // Find the Message block in DB. + // DB = Hash(P)||00||01||Msg, where P is encoding parameters. (P = NULL) + // The message will be located at the end of the Data block (DB). + // The DB block is first constructed by keeping the message at the end and + // to the message 0x01 byte is prepended. The hash of the + // encoding parameters is calculated and then copied from the + // starting of the block and a variable length of 0's are + // appended to the end of the hash till the 0x01 byte. + short start = (short) (encodedMsgOff + encodedMsgLen); + for (short i = (short) (encodedMsgOff + 2 * hLen); + i < (short) (encodedMsgOff + encodedMsgLen); + i++) { + if ((encodedMsg[i] != 0)) { + start = i; + break; + } + } + if ((start >= (short) (encodedMsgOff + encodedMsgLen)) || (encodedMsg[start] != 0x01)) { + // Bad Padding. + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + start++; // Message starting pos. + if (start < (short) (encodedMsgOff + encodedMsgLen)) { + // Copy the message + Util.arrayCopyNonAtomic( + encodedMsg, + start, + encodedMsg, + encodedMsgOff, + (short) (encodedMsgLen - (start - encodedMsgOff))); + } + return (short) (encodedMsgLen - (start - encodedMsgOff)); + + } finally { + if (md != null) { + md.close(); + } + Util.arrayFillNonAtomic(tmpArray, (short) 0, KMAndroidSEProvider.TMP_ARRAY_SIZE, (byte) 0); + } + } +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMSEProvider.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMSEProvider.java new file mode 100644 index 0000000..9313c04 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMSEProvider.java @@ -0,0 +1,782 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import org.globalplatform.upgrade.Element; + +/** + * KMSEProvider is facade to use SE specific methods. The main intention of this interface is to + * abstract the cipher, signature and backup and restore related functions. The instance of this + * interface is created by the singleton KMSEProviderImpl class for each provider. At a time there + * can be only one provider in the applet package. + */ +public interface KMSEProvider { + + /** + * This function tells if boot signal event is supported or not. + * + * @return true if supported, false otherwise. + */ + boolean isBootSignalEventSupported(); + + /** + * This function tells if the device is booted or not. + * + * @return true if device booted, false otherwise. + */ + boolean isDeviceRebooted(); + + /** + * This function is supposed to be used to reset the device booted stated after set boot param is + * handled + * + * @param resetBootFlag is false if event has been handled + */ + void clearDeviceBooted(boolean resetBootFlag); + + /** + * Create a symmetric key instance. If the algorithm and/or keysize are not supported then it + * should throw a CryptoException. + * + * @param alg will be KMType.AES, KMType.DES or KMType.HMAC. + * @param keysize will be 128 or 256 for AES or DES. It can be 64 to 512 (multiple of 8) for HMAC. + * @param buf is the buffer in which key has to be returned + * @param startOff is the start offset. + * @return length of the data in the buf. This should match the keysize (in bytes). + */ + short createSymmetricKey(byte alg, short keysize, byte[] buf, short startOff); + + /** + * Create a asymmetric key pair. If the algorithms are not supported then it should throw a + * CryptoException. For RSA the public key exponent must always be 0x010001. The key size of RSA + * key pair must be 2048 bits and key size of EC key pair must be for p256 curve. + * + * @param alg will be KMType.RSA or KMType.EC. + * @param privKeyBuf is the buffer to return the private key exponent in case of RSA or private + * key in case of EC. + * @param privKeyStart is the start offset. + * @param privKeyMaxLength is the maximum length of this private key buffer. + * @param pubModBuf is the buffer to return the modulus in case of RSA or public key in case of + * EC. + * @param pubModStart is the start of offset. + * @param pubModMaxLength is the maximum length of this public key buffer. + * @param lengths is the actual length of the key pair - lengths[0] should be private key and + * lengths[1] should be public key. + */ + void createAsymmetricKey( + byte alg, + byte[] privKeyBuf, + short privKeyStart, + short privKeyMaxLength, + byte[] pubModBuf, + short pubModStart, + short pubModMaxLength, + short[] lengths); + + /** + * Initializes the trusted confirmation operation. + * + * @param computedHmacKey Instance of the computed Hmac key. + * @return instance of KMOperation. + */ + KMOperation initTrustedConfirmationSymmetricOperation(KMKey computedHmacKey); + + /** + * Verify that the imported key is valid. If the algorithm and/or keysize are not supported then + * it should throw a CryptoException. + * + * @param alg will be KMType.AES, KMType.DES or KMType.HMAC. + * @param keysize will be 128 or 256 for AES or DES. It can be 64 to 512 (multiple of 8) for HMAC. + * @param buf is the buffer that contains the symmetric key. + * @param startOff is the start offset. + * @param length of the data in the buf. This should match the keysize (in bytes). + * @return true if the symmetric key is supported and valid. + */ + boolean importSymmetricKey(byte alg, short keysize, byte[] buf, short startOff, short length); + + /** + * Validate that the imported asymmetric key pair is valid. For RSA the public key exponent must + * always be 0x010001. The key size of RSA key pair must be 2048 bits and key size of EC key pair + * must be for p256 curve. If the algorithms are not supported then it should throw a + * CryptoException. + * + * @param alg will be KMType.RSA or KMType.EC. + * @param privKeyBuf is the buffer that contains the private key exponent in case of RSA or + * private key in case of EC. + * @param privKeyStart is the start offset. + * @param privKeyLength is the length of this private key buffer. + * @param pubModBuf is the buffer that contains the modulus in case of RSA or public key in case + * of EC. + * @param pubModStart is the start of offset. + * @param pubModLength is the length of this public key buffer. + * @return true if the key pair is supported and valid. + */ + boolean importAsymmetricKey( + byte alg, + byte[] privKeyBuf, + short privKeyStart, + short privKeyLength, + byte[] pubModBuf, + short pubModStart, + short pubModLength); + + /** + * This is a oneshot operation that generates random number of desired length. + * + * @param num is the buffer in which random number is returned to the applet. + * @param offset is start of the buffer. + * @param length indicates the size of buffer and desired length of random number in bytes. + */ + void newRandomNumber(byte[] num, short offset, short length); + + /** + * This is a oneshot operation that adds the entropy to the entropy pool. This operation + * corresponds to addRndEntropy command. This method may ignore the added entropy value if the SE + * provider does not support it. + * + * @param num is the buffer in which entropy value is given. + * @param offset is start of the buffer. + * @param length length of the buffer. + */ + void addRngEntropy(byte[] num, short offset, short length); + + /** + * This is a oneshot operation that generates and returns back a true random number. + * + * @param num is the buffer in which entropy value is returned. + * @param offset is start of the buffer. + * @param length length of the buffer. + */ + void getTrueRandomNumber(byte[] num, short offset, short length); + + /** + * This is a oneshot operation that performs encryption operation using AES GCM algorithm. It + * throws CryptoException if algorithm is not supported or if tag length is not equal to 16 or + * nonce length is not equal to 12. + * + * @param aesKey is the buffer that contains 128 bit or 256 bit aes key used to encrypt. + * @param aesKeyStart is the start in aes key buffer. + * @param aesKeyLen is the length of aes key buffer in bytes (16 or 32 bytes). + * @param data is the buffer that contains data to encrypt. + * @param dataStart is the start of the data buffer. + * @param dataLen is the length of the data buffer. + * @param encData is the buffer of the output encrypted data. + * @param encDataStart is the start of the encrypted data buffer. + * @param nonce is the buffer of nonce. + * @param nonceStart is the start of the nonce buffer. + * @param nonceLen is the length of the nonce buffer. + * @param authData is the authentication data buffer. + * @param authDataStart is the start of the authentication buffer. + * @param authDataLen is the length of the authentication buffer. + * @param authTag is the buffer to output authentication tag. + * @param authTagStart is the start of the buffer. + * @param authTagLen is the length of the buffer. + * @return length of the encrypted data. + */ + short aesGCMEncrypt( + byte[] aesKey, + short aesKeyStart, + short aesKeyLen, + byte[] data, + short dataStart, + short dataLen, + byte[] encData, + short encDataStart, + byte[] nonce, + short nonceStart, + short nonceLen, + byte[] authData, + short authDataStart, + short authDataLen, + byte[] authTag, + short authTagStart, + short authTagLen); + + /** + * This is a oneshot operation that performs decryption operation using AES GCM algorithm. It + * throws CryptoException if algorithm is not supported. + * + * @param aesKey is the buffer that contains 128 bit or 256 bit aes key used to encrypt. + * @param aesKeyStart is the start in aes key buffer. + * @param aesKeyLen is the length of aes key buffer in bytes (16 or 32 bytes). + * @param encData is the buffer of the input encrypted data. + * @param encDataStart is the start of the encrypted data buffer. + * @param encDataLen is the length of the data buffer. + * @param data is the buffer that contains output decrypted data. + * @param dataStart is the start of the data buffer. + * @param nonce is the buffer of nonce. + * @param nonceStart is the start of the nonce buffer. + * @param nonceLen is the length of the nonce buffer. + * @param authData is the authentication data buffer. + * @param authDataStart is the start of the authentication buffer. + * @param authDataLen is the length of the authentication buffer. + * @param authTag is the buffer to output authentication tag. + * @param authTagStart is the start of the buffer. + * @param authTagLen is the length of the buffer. + * @return true if the authentication is valid. + */ + boolean aesGCMDecrypt( + byte[] aesKey, + short aesKeyStart, + short aesKeyLen, + byte[] encData, + short encDataStart, + short encDataLen, + byte[] data, + short dataStart, + byte[] nonce, + short nonceStart, + short nonceLen, + byte[] authData, + short authDataStart, + short authDataLen, + byte[] authTag, + short authTagStart, + short authTagLen); + + /** + * This is a oneshot operation that performs key derivation function using cmac kdf (CKDF) as + * defined in android keymaster hal definition. + * + * @param hmacKey of pre-shared key. + * @param label is the label to be used for ckdf. + * @param labelStart is the start of label. + * @param labelLen is the length of the label. + * @param context is the context to be used for ckdf. + * @param contextStart is the start of the context + * @param contextLength is the length of the context + * @param key is the output buffer to return the derived key + * @param keyStart is the start of the output buffer. + * @return length of the derived key buffer in bytes. + */ + short cmacKDF( + KMKey hmacKey, + byte[] label, + short labelStart, + short labelLen, + byte[] context, + short contextStart, + short contextLength, + byte[] key, + short keyStart); + + /** + * This is a oneshot operation that signs the data using hmac algorithm. + * + * @param keyBuf is the buffer with hmac key. + * @param keyStart is the start of the buffer. + * @param keyLength is the length of the buffer which will be in bytes from 8 to 64. + * @param data is the buffer containing data to be signed. + * @param dataStart is the start of the data. + * @param dataLength is the length of the data. + * @param signature is the output signature buffer + * @param signatureStart is the start of the signature + * @return length of the signature buffer in bytes. + */ + short hmacSign( + byte[] keyBuf, + short keyStart, + short keyLength, + byte[] data, + short dataStart, + short dataLength, + byte[] signature, + short signatureStart); + + /** + * This is a oneshot operation that signs the data using hmac algorithm. + * + * @param hmacKey is the KMHmacKey. + * @param data is the buffer containing data to be signed. + * @param dataStart is the start of the data. + * @param dataLength is the length of the data. + * @param signature is the output signature buffer + * @param signatureStart is the start of the signature + * @return length of the signature buffer in bytes. + */ + short hmacSign( + Object hmacKey, + byte[] data, + short dataStart, + short dataLength, + byte[] signature, + short signatureStart); + + /** + * This is a oneshot operation that signs the data using hmac algorithm. This is used to derive + * the key, which is used to encrypt the keyblob. + * + * @param masterkey of masterkey. + * @param data is the buffer containing data to be signed. + * @param dataStart is the start of the data. + * @param dataLength is the length of the data. + * @param signature is the output signature buffer + * @param signatureStart is the start of the signature + * @return length of the signature buffer in bytes. + */ + short hmacKDF( + KMKey masterkey, + byte[] data, + short dataStart, + short dataLength, + byte[] signature, + short signatureStart); + + /** + * This is a oneshot operation that verifies the signature using hmac algorithm. + * + * @param keyBuf is the buffer with hmac key. + * @param keyStart is the start of the buffer. + * @param keyLength is the length of the buffer which will be in bytes from 8 to 64. + * @param data is the buffer containing data. + * @param dataStart is the start of the data. + * @param dataLength is the length of the data. + * @param signature is the signature buffer. + * @param signatureStart is the start of the signature buffer. + * @param signatureLen is the length of the signature buffer in bytes. + * @return true if the signature matches. + */ + boolean hmacVerify( + KMKey hmacKey, + byte[] data, + short dataStart, + short dataLength, + byte[] signature, + short signatureStart, + short signatureLen); + + /** + * This is a oneshot operation that decrypts the data using RSA algorithm with oaep256 padding. + * The public exponent is always 0x010001. It throws CryptoException if OAEP encoding validation + * fails. + * + * @param privExp is the private exponent (2048 bit) buffer. + * @param privExpStart is the start of the private exponent buffer. + * @param privExpLength is the length of the private exponent buffer in bytes. + * @param modBuffer is the modulus (2048 bit) buffer. + * @param modOff is the start of the modulus buffer. + * @param modLength is the length of the modulus buffer in bytes. + * @param inputDataBuf is the buffer of the input data. + * @param inputDataStart is the start of the input data buffer. + * @param inputDataLength is the length of the input data buffer in bytes. + * @param outputDataBuf is the output buffer that contains the decrypted data. + * @param outputDataStart is the start of the output data buffer. + * @return length of the decrypted data. + */ + short rsaDecipherOAEP256( + byte[] privExp, + short privExpStart, + short privExpLength, + byte[] modBuffer, + short modOff, + short modLength, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart); + + /** + * Implementation of HKDF as per RFC5869 https://datatracker.ietf.org/doc/html/rfc5869#section-2 + * + * @param ikm is the buffer containing input key material. + * @param ikmOff is the start of the input key. + * @param ikmLen is the length of the input key. + * @param salt is the buffer containing the salt. + * @param saltOff is the start of the salt buffer. + * @param saltLen is the length of the salt buffer. + * @param info is the buffer containing the application specific information + * @param infoOff is the start of the info buffer. + * @param infoLen is the length of the info buffer. + * @param out is the output buffer. + * @param outOff is the start of the output buffer. + * @param outLen is the length of the expected out buffer. + * @return Length of the out buffer which is outLen. + */ + short hkdf( + byte[] ikm, + short ikmOff, + short ikmLen, + byte[] salt, + short saltOff, + short saltLen, + byte[] info, + short infoOff, + short infoLen, + byte[] out, + short outOff, + short outLen); + + /** + * This function performs ECDH key agreement and generates a secret. + * + * @param privKey is the buffer containing the private key from first party. + * @param privKeyOff is the offset of the private key buffer. + * @param privKeyLen is the length of the private key buffer. + * @param publicKey is the buffer containing the public key from second party. + * @param publicKeyOff is the offset of the public key buffer. + * @param publicKeyLen is the length of the public key buffer. + * @param secret is the output buffer. + * @param secretOff is the offset of the output buffer. + * @return The length of the secret. + */ + short ecdhKeyAgreement( + byte[] privKey, + short privKeyOff, + short privKeyLen, + byte[] publicKey, + short publicKeyOff, + short publicKeyLen, + byte[] secret, + short secretOff); + + /** + * This is a oneshort operation that verifies the data using EC public key + * + * @param pubKey is the public key buffer. + * @param pubKeyOffset is the start of the public key buffer. + * @param pubKeyLen is the length of the public key. + * @param inputDataBuf is the buffer of the input data. + * @param inputDataStart is the start of the input data buffer. + * @param inputDataLength is the length of the input data buffer in bytes. + * @param signatureDataBuf is the buffer the signature input data. + * @param signatureDataStart is the start of the signature input data. + * @param signatureDataLen is the length of the signature input data. + * @return true if verification is successful, otherwise false. + */ + boolean ecVerify256( + byte[] pubKey, + short pubKeyOffset, + short pubKeyLen, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] signatureDataBuf, + short signatureDataStart, + short signatureDataLen); + + /** + * This is a oneshot operation that signs the data using device unique key. + * + * @param ecPrivKey instance of KMECDeviceUniqueKey to sign the input data. + * @param inputDataBuf is the buffer of the input data. + * @param inputDataStart is the start of the input data buffer. + * @param inputDataLength is the length of the input data buffer in bytes. + * @param outputDataBuf is the output buffer that contains the signature. + * @param outputDataStart is the start of the output data buffer. + * @return length of the decrypted data. + */ + short signWithDeviceUniqueKey( + KMKey deviceUniqueKey, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart); + + short ecSign256( + byte[] secret, + short secretStart, + short secretLength, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart); + + short rsaSign256Pkcs1( + byte[] secret, + short secretStart, + short secretLength, + byte[] modBuf, + short modStart, + short modLength, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart); + + /** + * This creates a persistent operation for signing, verify, encryption and decryption using HMAC, + * AES and DES algorithms when keymaster hal's beginOperation function is executed. The + * KMOperation instance can be reclaimed by the seProvider when KMOperation is finished or + * aborted. It throws CryptoException if algorithm is not supported. + * + * @param purpose is KMType.ENCRYPT or KMType.DECRYPT for AES and DES algorithm. It will be + * KMType.SIGN and KMType.VERIFY for HMAC algorithm + * @param alg is KMType.HMAC, KMType.AES or KMType.DES. + * @param digest is KMType.SHA2_256 in case of HMAC else it will be KMType.DIGEST_NONE. + * @param padding is KMType.PADDING_NONE or KMType.PKCS7 (in case of AES and DES). + * @param blockMode is KMType.CTR, KMType.GCM. KMType.CBC or KMType.ECB for AES or DES else it is + * 0. + * @param keyBuf is aes, des or hmac key buffer. + * @param keyStart is the start of the key buffer. + * @param keyLength is the length of the key buffer. + * @param ivBuf is the iv buffer (in case on AES and DES algorithm without ECB mode) + * @param ivStart is the start of the iv buffer. + * @param ivLength is the length of the iv buffer. It will be zero in case of HMAC and AES/DES + * with ECB mode. + * @param macLength is the mac length in case of signing operation for hmac algorithm. + * @return KMOperation instance. + */ + KMOperation initSymmetricOperation( + byte purpose, + byte alg, + byte digest, + byte padding, + byte blockMode, + byte[] keyBuf, + short keyStart, + short keyLength, + byte[] ivBuf, + short ivStart, + short ivLength, + short macLength); + + /** + * This creates a persistent operation for signing, verify, encryption and decryption using HMAC, + * AES and DES algorithms when keymaster hal's beginOperation function is executed. The + * KMOperation instance can be reclaimed by the seProvider when KMOperation is finished or + * aborted. It throws CryptoException if algorithm is not supported. + * + * @param purpose is KMType.ENCRYPT or KMType.DECRYPT for AES and DES algorithm. It will be + * KMType.SIGN and KMType.VERIFY for HMAC algorithm + * @param alg is KMType.HMAC, KMType.AES or KMType.DES. + * @param digest is KMType.SHA2_256 in case of HMAC else it will be KMType.DIGEST_NONE. + * @param padding is KMType.PADDING_NONE or KMType.PKCS7 (in case of AES and DES). + * @param blockMode is KMType.CTR, KMType.GCM. KMType.CBC or KMType.ECB for AES or DES else it is + * 0. + * @param key is a key object. + * @param interfaceType defines the type of key in the key object. + * @param ivBuf is the iv buffer (in case on AES and DES algorithm without ECB mode) + * @param ivStart is the start of the iv buffer. + * @param ivLength is the length of the iv buffer. It will be zero in case of HMAC and AES/DES + * with ECB mode. + * @param macLength is the mac length in case of signing operation for hmac algorithm. + * @param oneShot if true, creates oneshot operation. + * @return KMOperation instance. + */ + KMOperation initSymmetricOperation( + byte purpose, + byte alg, + byte digest, + byte padding, + byte blockMode, + Object key, + byte interfaceType, + byte[] ivBuf, + short ivStart, + short ivLength, + short macLength, + boolean oneShot); + + /** + * This function creates an Operation instance only for RKP module. + * + * @param purpose is KMType.ENCRYPT or KMType.DECRYPT for AES and DES algorithm. It will be + * KMType.SIGN and KMType.VERIFY for HMAC algorithm + * @param alg is KMType.HMAC, KMType.AES or KMType.DES. + * @param digest is KMType.SHA2_256 in case of HMAC else it will be KMType.DIGEST_NONE. + * @param padding is KMType.PADDING_NONE or KMType.PKCS7 (in case of AES and DES). + * @param blockMode is KMType.CTR, KMType.GCM. KMType.CBC or KMType.ECB for AES or DES else it is + * 0. + * @param keyBuf is aes, des or hmac key buffer. + * @param keyStart is the start of the key buffer. + * @param keyLength is the length of the key buffer. + * @param ivBuf is the iv buffer (in case on AES and DES algorithm without ECB mode) + * @param ivStart is the start of the iv buffer. + * @param ivLength is the length of the iv buffer. It will be zero in case of HMAC and AES/DES + * with ECB mode. + * @param macLength is the mac length in case of signing operation for hmac algorithm. + * @return KMOperation instance. + */ + KMOperation getRkpOperation( + byte purpose, + byte alg, + byte digest, + byte padding, + byte blockMode, + byte[] keyBuf, + short keyStart, + short keyLength, + byte[] ivBuf, + short ivStart, + short ivLength, + short macLength); + + /** + * This creates a persistent operation for signing, verify, encryption and decryption using RSA + * and EC algorithms when keymaster hal's beginOperation function is executed. For RSA the public + * exponent is always 0x0100101. For EC the curve is always p256. The KMOperation instance can be + * reclaimed by the seProvider when KMOperation is finished or aborted. It throws CryptoException + * if algorithm is not supported. + * + * @param purpose is KMType.ENCRYPT or KMType.DECRYPT for RSA. It will be * KMType.SIGN and + * KMType.VERIFY for RSA and EC algorithms. + * @param alg is KMType.RSA or KMType.EC algorithms. + * @param padding is KMType.PADDING_NONE or KMType.RSA_OAEP, KMType.RSA_PKCS1_1_5_ENCRYPT, + * KMType.RSA_PKCS1_1_5_SIGN or KMType.RSA_PSS. + * @param digest is KMType.DIGEST_NONE or KMType.SHA2_256. + * @param mgfDigest is the MGF digest. + * @param privKeyBuf is the private key in case of EC or private key exponent is case of RSA. + * @param privKeyStart is the start of the private key. + * @param privKeyLength is the length of the private key. + * @param pubModBuf is the modulus (in case of RSA) or public key (in case of EC). + * @param pubModStart is the start of the modulus. + * @param pubModLength is the length of the modulus. + * @return KMOperation instance that can be executed. + */ + KMOperation initAsymmetricOperation( + byte purpose, + byte alg, + byte padding, + byte digest, + byte mgfDigest, + byte[] privKeyBuf, + short privKeyStart, + short privKeyLength, + byte[] pubModBuf, + short pubModStart, + short pubModLength); + + /** + * This function tells if applet is upgrading or not. + * + * @return true if upgrading, otherwise false. + */ + boolean isUpgrading(); + + /** + * This function generates an AES Key of keySizeBits, which is used as an master key. This + * generated key is maintained by the SEProvider. This function should be called only once at the + * time of installation. + * + * @param instance of the masterkey. + * @param keySizeBits key size in bits. + * @return An instance of KMMasterKey. + */ + KMKey createMasterKey(KMKey masterKey, short keySizeBits); + + /** + * This function creates an HMACKey and initializes the key with the provided input key data. + * + * @param keyData buffer containing the key data. + * @param offset start of the buffer. + * @param length length of the buffer. + * @return An instance of the KMComputedHmacKey. + */ + KMKey createComputedHmacKey(KMKey computedHmacKey, byte[] keyData, short offset, short length); + + /** Returns true if factory provisioned attestation key is supported. */ + boolean isAttestationKeyProvisioned(); + + /** + * Returns algorithm type of the attestation key. It can be KMType.EC or KMType.RSA if the + * attestation key is provisioned in the factory. + */ + short getAttestationKeyAlgorithm(); + + /** + * Creates an ECKey instance and sets the public and private keys to it. + * + * @param testMode to indicate if current execution is for test or production. + * @param pubKey buffer containing the public key. + * @param pubKeyOff public key buffer start offset. + * @param pubKeyLen public key buffer length. + * @param privKey buffer containing the private key. + * @param privKeyOff private key buffer start offset. + * @param privKeyLen private key buffer length. + * @return instance of KMDeviceUniqueKey. + */ + KMKey createRkpDeviceUniqueKeyPair( + KMKey key, + byte[] pubKey, + short pubKeyOff, + short pubKeyLen, + byte[] privKey, + short privKeyOff, + short privKeyLen); + + /** + * This is a one-shot operation the does digest of the input mesage. + * + * @param inBuff input buffer to be digested. + * @param inOffset start offset of the input buffer. + * @param inLength length of the input buffer. + * @param outBuff is the output buffer that contains the digested data. + * @param outOffset start offset of the digested output buffer. + * @return length of the digested data. + */ + short messageDigest256( + byte[] inBuff, short inOffset, short inLength, byte[] outBuff, short outOffset); + + /** + * This function generates a HMAC key from the provided key buffers. + * + * @param presharedKey instance of the presharedkey. + * @param key buffer containing the key data. + * @param offset start offset of the buffer. + * @param length is the length of the key. + * @return instance of KMPresharedKey. + */ + KMKey createPreSharedKey(KMKey presharedKey, byte[] key, short offset, short length); + + /** + * This function saves the key objects while upgrade. + * + * @param element instance of the Element class where the objects to be stored. + * @param interfaceType the type interface of the parent object. + * @param object instance of the object to be saved. + */ + void onSave(Element element, byte interfaceType, Object object); + + /** + * This function restores the the object from element instance. + * + * @param element instance of the Element class. + * @return restored object. + */ + Object onRestore(Element element); + + /** + * This function returns the count of the primitive bytes required to be stored by the + * implementation of the interface type. + * + * @param interfaceType type interface of the parent object. + * @return count of the primitive bytes. + */ + short getBackupPrimitiveByteCount(byte interfaceType); + + /** + * This function returns the object count required to be stored by the implementation of the + * interface type. + * + * @param interfaceType type interface of the parent object. + * @return count of the objects. + */ + short getBackupObjectCount(byte interfaceType); + + /** + * This function creates an HMACKey and initializes the key with the provided input key data. + * + * @param keyData buffer containing the key data. + * @param offset start of the buffer. + * @param length length of the buffer. + * @return An instance of the KMRkpMacKey. + */ + KMKey createRkpMacKey(KMKey createComputedHmacKey, byte[] keyData, short offset, short length); +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMType.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMType.java new file mode 100644 index 0000000..82cbe26 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMType.java @@ -0,0 +1,79 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.seprovider; + +/** + * This class declares all types, tag types, and tag keys. It also establishes basic structure of + * any KMType i.e. struct{byte type, short length, value} where value can any of the KMType. Also, + * KMType refers to transient memory heap in the repository. Finally KMType's subtypes are singleton + * prototype objects which just cast the structure over contiguous memory buffer. + */ +public abstract class KMType { + + public static final short INVALID_VALUE = (short) 0x8000; + + // Algorithm Enum Tag key and values + public static final short ALGORITHM = 0x0002; + public static final byte RSA = 0x01; + public static final byte DES = 0x21; + public static final byte EC = 0x03; + public static final byte AES = 0x20; + public static final byte HMAC = (byte) 0x80; + + // EcCurve Enum Tag key and values. + public static final short ECCURVE = 0x000A; + public static final byte P_224 = 0x00; + public static final byte P_256 = 0x01; + public static final byte P_384 = 0x02; + public static final byte P_521 = 0x03; + + // Purpose + public static final short PURPOSE = 0x0001; + public static final byte ENCRYPT = 0x00; + public static final byte DECRYPT = 0x01; + public static final byte SIGN = 0x02; + public static final byte VERIFY = 0x03; + public static final byte DERIVE_KEY = 0x04; + public static final byte WRAP_KEY = 0x05; + public static final byte AGREE_KEY = 0x06; + public static final byte ATTEST_KEY = (byte) 0x07; + // Block mode + public static final short BLOCK_MODE = 0x0004; + public static final byte ECB = 0x01; + public static final byte CBC = 0x02; + public static final byte CTR = 0x03; + public static final byte GCM = 0x20; + + // Digest + public static final short DIGEST = 0x0005; + public static final byte DIGEST_NONE = 0x00; + public static final byte MD5 = 0x01; + public static final byte SHA1 = 0x02; + public static final byte SHA2_224 = 0x03; + public static final byte SHA2_256 = 0x04; + public static final byte SHA2_384 = 0x05; + public static final byte SHA2_512 = 0x06; + + // Padding mode + public static final short PADDING = 0x0006; + public static final byte PADDING_NONE = 0x01; + public static final byte RSA_OAEP = 0x02; + public static final byte RSA_PSS = 0x03; + public static final byte RSA_PKCS1_1_5_ENCRYPT = 0x04; + public static final byte RSA_PKCS1_1_5_SIGN = 0x05; + public static final byte PKCS7 = 0x40; +} diff --git a/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMUpgradable.java b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMUpgradable.java new file mode 100644 index 0000000..420b2c7 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMUpgradable.java @@ -0,0 +1,30 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import org.globalplatform.upgrade.Element; + +/** This interface helps in storing and restoring the applet data during the applet upgrades. */ +public interface KMUpgradable { + + void onSave(Element ele); + + void onRestore(Element ele, short oldVersion, short currentVersion); + + short getBackupPrimitiveByteCount(); + + short getBackupObjectCount(); +} diff --git a/ready_se/google/keymint/KM200/Applet/README.md b/ready_se/google/keymint/KM200/Applet/README.md new file mode 100644 index 0000000..c14a369 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/README.md @@ -0,0 +1,16 @@ +# JavaCardKeymaster Applet
+
+This directory contains the implementation of the KeyMint 2.0
+interface, in the form of a JavaCard 3.0.5 applet which runs in a secure
+element. It must be deployed in conjunction with the associated HAL,
+which mediates between Android Keystore and this applet.
+
+# Supported Features!
+
+ - KeyMint 2.0 supported functions for required VTS compliance.
+ - SharedSecret 1.0 supported functions for required VTS compliance.
+ - RemotelyProvisionedComponent 2.0 supported functions for required VTS compliance.
+
+# Not supported features
+ - Factory provisioned attestation key will not be supported in this applet.
+ - Limited usage keys will not be supported in this applet.
diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMArray.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMArray.java new file mode 100644 index 0000000..aa54d54 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMArray.java @@ -0,0 +1,165 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMArray represents an array of KMTypes. Array is the sequence of elements of one or more sub + * types of KMType. It also acts as a vector of one subtype of KMTypes on the lines of class KMArray + * <subType>, where subType is subclass of KMType. Vector is the sequence of elements of one sub + * type e.g. KMType.BYTE_BLOB_TYPE. The KMArray instance maps to the CBOR type array. KMArray is a + * KMType and it further extends the value field in TLV_HEADER as ARRAY_HEADER struct{short subType; + * short length;} followed by sequence of short pointers to KMType instances. The subType can be 0 + * if this is an array or subType is short KMType value e.g. KMType.BYTE_BLOB_TYPE if this is a + * vector of that sub type. + */ +public class KMArray extends KMType { + + public static final short ANY_ARRAY_LENGTH = 0x1000; + private static final byte ARRAY_HEADER_SIZE = 4; + private static KMArray prototype; + + private KMArray() {} + + private static KMArray proto(short ptr) { + if (prototype == null) { + prototype = new KMArray(); + } + KMType.instanceTable[KM_ARRAY_OFFSET] = ptr; + return prototype; + } + + public static short exp() { + short ptr = instance(ARRAY_TYPE, (short) ARRAY_HEADER_SIZE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.INVALID_VALUE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), ANY_ARRAY_LENGTH); + return ptr; + } + + public static short exp(short type) { + short ptr = instance(ARRAY_TYPE, (short) ARRAY_HEADER_SIZE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), type); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), ANY_ARRAY_LENGTH); + return ptr; + } + + public static short instance(short length) { + short ptr = KMType.instance(ARRAY_TYPE, (short) (ARRAY_HEADER_SIZE + (length * 2))); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.INVALID_VALUE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), length); + return ptr; + } + + public static short instance(short length, byte type) { + short ptr = instance(length); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), type); + return ptr; + } + + public static KMArray cast(short ptr) { + if (heap[ptr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public void add(short index, short objPtr) { + short len = length(); + if (index >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + Util.setShort(heap, (short) (getStartOff() + (short) (index * 2)), objPtr); + } + + public short get(short index) { + short len = length(); + if (index >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + return Util.getShort(heap, (short) (getStartOff() + (short) (index * 2))); + } + + public void swap(short index1, short index2) { + short len = length(); + if (index1 >= len || index2 >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + short indexPtr1 = + Util.getShort( + heap, + (short) + (instanceTable[KM_ARRAY_OFFSET] + + TLV_HEADER_SIZE + + ARRAY_HEADER_SIZE + + (short) (index1 * 2))); + short indexPtr2 = + Util.getShort( + heap, + (short) + (instanceTable[KM_ARRAY_OFFSET] + + TLV_HEADER_SIZE + + ARRAY_HEADER_SIZE + + (short) (index2 * 2))); + Util.setShort( + heap, + (short) + (instanceTable[KM_ARRAY_OFFSET] + + TLV_HEADER_SIZE + + ARRAY_HEADER_SIZE + + (short) (index1 * 2)), + indexPtr2); + Util.setShort( + heap, + (short) + (instanceTable[KM_ARRAY_OFFSET] + + TLV_HEADER_SIZE + + ARRAY_HEADER_SIZE + + (short) (index2 * 2)), + indexPtr1); + } + + public short containedType() { + return Util.getShort(heap, (short) (KMType.instanceTable[KM_ARRAY_OFFSET] + TLV_HEADER_SIZE)); + } + + public short getStartOff() { + return (short) (KMType.instanceTable[KM_ARRAY_OFFSET] + TLV_HEADER_SIZE + ARRAY_HEADER_SIZE); + } + + public short length() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_ARRAY_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short setLength(short len) { + return Util.setShort( + heap, (short) (KMType.instanceTable[KM_ARRAY_OFFSET] + TLV_HEADER_SIZE + 2), len); + } + + public byte[] getBuffer() { + return heap; + } + + public void deleteLastEntry() { + short len = length(); + Util.setShort( + heap, (short) (instanceTable[KM_ARRAY_OFFSET] + TLV_HEADER_SIZE + 2), (short) (len - 1)); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMAsn1Parser.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMAsn1Parser.java new file mode 100644 index 0000000..22a16a3 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMAsn1Parser.java @@ -0,0 +1,471 @@ +package com.android.javacard.keymaster; + +import com.android.javacard.seprovider.KMException; +import javacard.framework.JCSystem; +import javacard.framework.Util; + +/** + * This is a utility class that helps in parsing the PKCS8 encoded RSA and EC keys, certificate + * subject, subjectPublicKey info and ECDSA signatures. + */ +public class KMAsn1Parser { + + // Below are the ASN.1 tag types + public static final byte ASN1_OCTET_STRING = 0x04; + public static final byte ASN1_SEQUENCE = 0x30; + public static final byte ASN1_SET = 0x31; + public static final byte ASN1_INTEGER = 0x02; + public static final byte OBJECT_IDENTIFIER = 0x06; + public static final byte ASN1_A0_TAG = (byte) 0xA0; + public static final byte ASN1_A1_TAG = (byte) 0xA1; + public static final byte ASN1_BIT_STRING = 0x03; + + public static final byte ASN1_UTF8_STRING = 0x0C; + public static final byte ASN1_TELETEX_STRING = 0x14; + public static final byte ASN1_PRINTABLE_STRING = 0x13; + public static final byte ASN1_UNIVERSAL_STRING = 0x1C; + public static final byte ASN1_BMP_STRING = 0x1E; + public static final byte IA5_STRING = 0x16; + // OID of the EC P256 curve 1.2.840.10045.3.1.7 + public static final byte[] EC_CURVE = { + 0x06, 0x08, 0x2a, (byte) 0x86, 0x48, (byte) 0xce, 0x3d, 0x03, 0x01, 0x07 + }; + // Constant for rsaEncryption pkcs#1 (1.2.840.113549.1.1.1) and NULL + public static final byte[] RSA_ALGORITHM = { + 0x06, + 0x09, + 0x2A, + (byte) 0x86, + 0x48, + (byte) 0x86, + (byte) 0xF7, + 0x0D, + 0x01, + 0x01, + 0x01, + 0x05, + 0x00 + }; + // Constant for ecPublicKey (1.2.840.10045.2.1) and prime256v1 (1.2.840.10045.3.1.7) + public static final byte[] EC_ALGORITHM = { + 0x06, + 0x07, + 0x2a, + (byte) 0x86, + 0x48, + (byte) 0xce, + 0x3d, + 0x02, + 0x01, + 0x06, + 0x08, + 0x2a, + (byte) 0x86, + 0x48, + (byte) 0xce, + 0x3d, + 0x03, + 0x01, + 0x07 + }; + // The maximum length of email id attribute. + public static final short MAX_EMAIL_ADD_LEN = 255; + // Datatable offsets. + private static final byte DATA_START_OFFSET = 0; + private static final byte DATA_LENGTH_OFFSET = 1; + private static final byte DATA_CURSOR_OFFSET = 2; + // This array contains the last byte of OID for each oid type. + // The first 4 bytes are common as shown above in COMMON_OID + private static final byte[] attributeOIds = { + 0x03, /* commonName COMMON_OID.3 */ 0x04, /* surName COMMON_OID.4*/ + 0x05, /* serialNumber COMMON_OID.5 */ 0x06, /* countryName COMMON_OID.6 */ + 0x07, /* locality COMMON_OID.7 */ 0x08, /* stateOrProviince COMMON_OID.8 */ + 0x0A, /* organizationName COMMON_OID.10 */ 0x0B, /* organizationalUnitName COMMON_OID.11 */ + 0x0C, /* title COMMON_OID.10 */ 0x29, /* name COMMON_OID.41 */ + 0x2A, /* givenName COMMON_OID.42 */ 0x2B, /* initials COMMON_OID.43 */ + 0x2C, /* generationQualifier COMMON_OID.44 */ 0x2E, /* dnQualifer COMMON_OID.46 */ + 0x41, /* pseudonym COMMON_OID.65 */ + }; + // https://datatracker.ietf.org/doc/html/rfc5280, RFC 5280, Page 124 + // TODO Specification does not mention about the DN_QUALIFIER_OID max length. + // So the max limit is set at 64. + // For name the RFC 5280 supports up to 32768, as Javacard doesn't support + // that much length, the max limit for name is set to 128. + private static final byte[] attributeValueMaxLen = { + 0x40, /* 1-64 commonName */ + 0x28, /* 1-40 surname */ + 0x40, /* 1-64 serial */ + 0x02, /* 1-2 country */ + (byte) 0x80, /* 1-128 locality */ + (byte) 0x80, /* 1-128 state */ + 0x40, /* 1-64 organization */ + 0x40, /* 1-64 organization unit*/ + 0x40, /* 1-64 title */ + 0x29, /* 1-128 name */ + 0x10, /* 1-16 givenName */ + 0x05, /* 1-5 initials */ + 0x03, /* 1-3 gen qualifier */ + 0x40, /* 1-64 dn-qualifier */ + (byte) 0x80 /* 1-128 pseudonym */ + }; + private static KMAsn1Parser inst; + // https://datatracker.ietf.org/doc/html/rfc5280, RFC 5280, Page 21 + // 2.5.4 + public byte[] COMMON_OID = new byte[] {0x06, 0x03, 0x55, 0x04}; + public byte[] EMAIL_ADDRESS_OID = + new byte[] { + 0x06, 0x09, 0x2A, (byte) 0x86, 0x48, (byte) 0x86, (byte) 0xF7, 0x0D, 0x01, 0x09, 0x01 + }; + private byte[] data; + private short[] dataInfo; + + private KMAsn1Parser() { + dataInfo = JCSystem.makeTransientShortArray((short) 3, JCSystem.CLEAR_ON_RESET); + dataInfo[DATA_START_OFFSET] = 0; + dataInfo[DATA_LENGTH_OFFSET] = 0; + dataInfo[DATA_CURSOR_OFFSET] = 0; + } + + public static KMAsn1Parser instance() { + if (inst == null) { + inst = new KMAsn1Parser(); + } + return inst; + } + + public short decodeRsa(short blob) { + init(blob); + decodeCommon((short) 0, RSA_ALGORITHM); + return decodeRsaPrivateKey((short) 0); + } + + public short decodeEc(short blob) { + init(blob); + decodeCommon((short) 0, EC_ALGORITHM); + return decodeEcPrivateKey((short) 1); + } + + /* + Name ::= CHOICE { -- only one possibility for now -- + rdnSequence RDNSequence } + RDNSequence ::= SEQUENCE OF RelativeDistinguishedName + RelativeDistinguishedName ::= + SET SIZE (1..MAX) OF AttributeTypeAndValue + AttributeTypeAndValue ::= SEQUENCE { + type AttributeType, + value AttributeValue } + AttributeType ::= OBJECT IDENTIFIER + AttributeValue ::= ANY -- DEFINED BY AttributeType + */ + public void validateDerSubject(short blob) { + init(blob); + header(ASN1_SEQUENCE); + while (dataInfo[DATA_CURSOR_OFFSET] + < ((short) (dataInfo[DATA_START_OFFSET] + dataInfo[DATA_LENGTH_OFFSET]))) { + header(ASN1_SET); + header(ASN1_SEQUENCE); + // Parse and validate OBJECT-IDENTIFIER and Value fields + // Cursor is incremented in validateAttributeTypeAndValue. + validateAttributeTypeAndValue(); + } + } + + public short decodeEcSubjectPublicKeyInfo(short blob) { + init(blob); + header(ASN1_SEQUENCE); + short len = header(ASN1_SEQUENCE); + short ecPublicInfo = KMByteBlob.instance(len); + getBytes(ecPublicInfo); + if (Util.arrayCompare( + KMByteBlob.cast(ecPublicInfo).getBuffer(), + KMByteBlob.cast(ecPublicInfo).getStartOff(), + EC_ALGORITHM, + (short) 0, + KMByteBlob.cast(ecPublicInfo).length()) + != 0) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + len = header(ASN1_BIT_STRING); + if (len < 1) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + // TODO need to handle if unused bits are not zero + byte unusedBits = getByte(); + if (unusedBits != 0) { + KMException.throwIt(KMError.UNIMPLEMENTED); + } + short pubKey = KMByteBlob.instance((short) (len - 1)); + getBytes(pubKey); + return pubKey; + } + + // Seq[Int,Int,Int,Int,<ignore rest>] + public short decodeRsaPrivateKey(short version) { + short resp = KMArray.instance((short) 3); + header(ASN1_OCTET_STRING); + header(ASN1_SEQUENCE); + short len = header(ASN1_INTEGER); + if (len != 1) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + short ver = getByte(); + if (ver != version) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + len = header(ASN1_INTEGER); + short modulus = KMByteBlob.instance(len); + getBytes(modulus); + updateRsaKeyBuffer(modulus); + len = header(ASN1_INTEGER); + short pubKey = KMByteBlob.instance(len); + getBytes(pubKey); + len = header(ASN1_INTEGER); + short privKey = KMByteBlob.instance(len); + getBytes(privKey); + updateRsaKeyBuffer(privKey); + KMArray.cast(resp).add((short) 0, modulus); + KMArray.cast(resp).add((short) 1, pubKey); + KMArray.cast(resp).add((short) 2, privKey); + return resp; + } + + private void updateRsaKeyBuffer(short blob) { + byte[] buffer = KMByteBlob.cast(blob).getBuffer(); + short startOff = KMByteBlob.cast(blob).getStartOff(); + short len = KMByteBlob.cast(blob).length(); + if (0 == buffer[startOff] && len > 256) { + KMByteBlob.cast(blob).setStartOff(++startOff); + KMByteBlob.cast(blob).setLength(--len); + } + } + + private short readEcdsa256SigIntegerHeader() { + short len = header(ASN1_INTEGER); + if (len == 33) { + if (0 != getByte()) { + KMException.throwIt(KMError.INVALID_DATA); + } + len--; + } else if (len > 33) { + KMException.throwIt(KMError.INVALID_DATA); + } + return len; + } + + // Seq [Int, Int] + public short decodeEcdsa256Signature(short blob, byte[] scratchPad, short scratchPadOff) { + init(blob); + short len = header(ASN1_SEQUENCE); + len = readEcdsa256SigIntegerHeader(); + // concatenate r and s in the buffer (r||s) + Util.arrayFillNonAtomic(scratchPad, scratchPadOff, (short) 64, (byte) 0); + // read r + getBytes(scratchPad, (short) (scratchPadOff + 32 - len), len); + len = readEcdsa256SigIntegerHeader(); + // read s + getBytes(scratchPad, (short) (scratchPadOff + 64 - len), len); + return (short) 64; + } + + // Seq [Int, Blob] + public void decodeCommon(short version, byte[] alg) { + short len = header(ASN1_SEQUENCE); + len = header(ASN1_INTEGER); + if (len != 1) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + short ver = getByte(); + if (ver != version) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + len = header(ASN1_SEQUENCE); + short blob = KMByteBlob.instance(len); + getBytes(blob); + if (Util.arrayCompare( + KMByteBlob.cast(blob).getBuffer(), + KMByteBlob.cast(blob).getStartOff(), + alg, + (short) 0, + KMByteBlob.cast(blob).length()) + != 0) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + } + + // Seq[Int,blob,blob] + public short decodeEcPrivateKey(short version) { + short resp = KMArray.instance((short) 2); + header(ASN1_OCTET_STRING); + header(ASN1_SEQUENCE); + short len = header(ASN1_INTEGER); + if (len != 1) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + short ver = getByte(); + if (ver != version) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + len = header(ASN1_OCTET_STRING); + short privKey = KMByteBlob.instance(len); + getBytes(privKey); + validateTag0IfPresent(); + header(ASN1_A1_TAG); + len = header(ASN1_BIT_STRING); + if (len < 1) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + // TODO need to handle if unused bits are not zero + byte unusedBits = getByte(); + if (unusedBits != 0) { + KMException.throwIt(KMError.UNIMPLEMENTED); + } + short pubKey = KMByteBlob.instance((short) (len - 1)); + getBytes(pubKey); + KMArray.cast(resp).add((short) 0, pubKey); + KMArray.cast(resp).add((short) 1, privKey); + return resp; + } + + private void validateTag0IfPresent() { + if (data[dataInfo[DATA_CURSOR_OFFSET]] != ASN1_A0_TAG) { + return; + } + ; + short len = header(ASN1_A0_TAG); + if (len != EC_CURVE.length) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + if (Util.arrayCompare(data, dataInfo[DATA_CURSOR_OFFSET], EC_CURVE, (short) 0, len) != 0) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + incrementCursor(len); + } + + private void validateAttributeTypeAndValue() { + // First byte should be OBJECT_IDENTIFIER, otherwise it is not well-formed DER Subject. + if (data[dataInfo[DATA_CURSOR_OFFSET]] != OBJECT_IDENTIFIER) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + // Check if the OID matches the email address + if ((Util.arrayCompare( + data, + dataInfo[DATA_CURSOR_OFFSET], + EMAIL_ADDRESS_OID, + (short) 0, + (short) EMAIL_ADDRESS_OID.length) + == 0)) { + incrementCursor((short) EMAIL_ADDRESS_OID.length); + // Validate the length of the attribute value. + if (getByte() != IA5_STRING) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + short emailLength = getLength(); + if (emailLength <= 0 && emailLength > MAX_EMAIL_ADD_LEN) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + incrementCursor(emailLength); + return; + } + // Check other OIDs. + for (short i = 0; i < (short) attributeOIds.length; i++) { + if ((Util.arrayCompare( + data, + dataInfo[DATA_CURSOR_OFFSET], + COMMON_OID, + (short) 0, + (short) COMMON_OID.length) + == 0) + && (attributeOIds[i] + == data[(short) (dataInfo[DATA_CURSOR_OFFSET] + COMMON_OID.length)])) { + incrementCursor((short) (COMMON_OID.length + 1)); + // Validate the length of the attribute value. + short tag = getByte(); + if (tag != ASN1_UTF8_STRING + && tag != ASN1_TELETEX_STRING + && tag != ASN1_PRINTABLE_STRING + && tag != ASN1_UNIVERSAL_STRING + && tag != ASN1_BMP_STRING) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + short attrValueLength = getLength(); + if (attrValueLength <= 0 && attrValueLength > attributeValueMaxLen[i]) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + incrementCursor(attrValueLength); + return; + } + } + // If no match is found above then move the cursor to next element. + getByte(); // Move Cursor by one byte (OID) + incrementCursor(getLength()); // Move cursor to AtrributeTag + getByte(); // Move cursor to AttributeValue + incrementCursor(getLength()); // Move cursor to next SET element + } + + private short header(short tag) { + short t = getByte(); + if (t != tag) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + return getLength(); + } + + private byte getByte() { + byte d = data[dataInfo[DATA_CURSOR_OFFSET]]; + incrementCursor((short) 1); + return d; + } + + private short getShort() { + short d = Util.getShort(data, dataInfo[DATA_CURSOR_OFFSET]); + incrementCursor((short) 2); + return d; + } + + private void getBytes(short blob) { + short len = KMByteBlob.cast(blob).length(); + Util.arrayCopyNonAtomic( + data, + dataInfo[DATA_CURSOR_OFFSET], + KMByteBlob.cast(blob).getBuffer(), + KMByteBlob.cast(blob).getStartOff(), + len); + incrementCursor(len); + } + + private void getBytes(byte[] buffer, short offset, short len) { + Util.arrayCopyNonAtomic(data, dataInfo[DATA_CURSOR_OFFSET], buffer, offset, len); + incrementCursor(len); + } + + private short getLength() { + byte len = getByte(); + if (len >= 0) { + return len; + } + len = (byte) (len & 0x7F); + if (len == 1) { + return (short) (getByte() & 0xFF); + } else if (len == 2) { + return getShort(); + } else { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + return KMType.INVALID_VALUE; // should not come here + } + + public void init(short blob) { + data = KMByteBlob.cast(blob).getBuffer(); + dataInfo[DATA_START_OFFSET] = KMByteBlob.cast(blob).getStartOff(); + dataInfo[DATA_LENGTH_OFFSET] = KMByteBlob.cast(blob).length(); + dataInfo[DATA_CURSOR_OFFSET] = dataInfo[DATA_START_OFFSET]; + } + + public void incrementCursor(short n) { + dataInfo[DATA_CURSOR_OFFSET] += n; + if (dataInfo[DATA_CURSOR_OFFSET] + > ((short) (dataInfo[DATA_START_OFFSET] + dataInfo[DATA_LENGTH_OFFSET]))) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMBignumTag.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMBignumTag.java new file mode 100644 index 0000000..5eb7eae --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMBignumTag.java @@ -0,0 +1,110 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMBignumTag represents BIGNUM Tag Type from android keymaster hal specifications. The tag value + * of this tag is the KMByteBlob pointer i.e. offset of KMByteBlob in memory heap. struct{byte + * TAG_TYPE; short length; struct{short BIGNUM_TAG; short tagKey; short blobPtr}} + */ +public class KMBignumTag extends KMTag { + + private static KMBignumTag prototype; + + private KMBignumTag() {} + + private static KMBignumTag proto(short ptr) { + if (prototype == null) { + prototype = new KMBignumTag(); + } + KMType.instanceTable[KM_BIGNUM_TAG_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short blobPtr = KMByteBlob.exp(); + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), BIGNUM_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), INVALID_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), blobPtr); + return ptr; + } + + public static short instance(short key, short byteBlob) { + if (!validateKey(key, byteBlob)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (heap[byteBlob] != BYTE_BLOB_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), BIGNUM_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), key); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), byteBlob); + return ptr; + } + + public static KMBignumTag cast(short ptr) { + if (heap[ptr] != TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)) != BIGNUM_TAG) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + private static boolean validateKey(short key, short byteBlob) { + short valueLen = KMByteBlob.cast(byteBlob).length(); + switch (key) { + case CERTIFICATE_SERIAL_NUM: + if (valueLen > MAX_CERTIFICATE_SERIAL_SIZE) { + return false; + } + break; + default: + return false; + } + return true; + } + + public short getKey() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_BIGNUM_TAG_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short getTagType() { + return KMType.BIGNUM_TAG; + } + + public short getValue() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_BIGNUM_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + } + + public short length() { + short blobPtr = + Util.getShort( + heap, (short) (KMType.instanceTable[KM_BIGNUM_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + return KMByteBlob.cast(blobPtr).length(); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMBoolTag.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMBoolTag.java new file mode 100644 index 0000000..27730a5 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMBoolTag.java @@ -0,0 +1,115 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import com.android.javacard.seprovider.KMException; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMBoolTag represents BOOL TAG type from the android keymaster hal specifications. If it is + * present in the key parameter list then its value is always true. A KMTag always requires a value + * because it is a key value pair. The bool tag always has 0x01 as its value. struct{byte TAG_TYPE; + * short length; struct{short BOOL_TAG; short tagKey; byte value 1}} + */ +public class KMBoolTag extends KMTag { + + // The allowed tag keys of type bool tag. + private static final short[] tags = { + CALLER_NONCE, + INCLUDE_UNIQUE_ID, + BOOTLOADER_ONLY, + ROLLBACK_RESISTANCE, + NO_AUTH_REQUIRED, + ALLOW_WHILE_ON_BODY, + TRUSTED_USER_PRESENCE_REQUIRED, + TRUSTED_CONFIRMATION_REQUIRED, + UNLOCKED_DEVICE_REQUIRED, + RESET_SINCE_ID_ROTATION, + EARLY_BOOT_ONLY, + DEVICE_UNIQUE_ATTESTATION + }; + private static KMBoolTag prototype; + + private KMBoolTag() {} + + private static KMBoolTag proto(short ptr) { + if (prototype == null) { + prototype = new KMBoolTag(); + } + KMType.instanceTable[KM_BOOL_TAG_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short ptr = instance(TAG_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), BOOL_TAG); + return ptr; + } + + public static short instance(short key) { + if (!validateKey(key)) { + KMException.throwIt(KMError.INVALID_TAG); + } + short ptr = KMType.instance(TAG_TYPE, (short) 5); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), BOOL_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), key); + // Value is always 1. + heap[(short) (ptr + TLV_HEADER_SIZE + 4)] = 0x01; + return ptr; + } + + public static KMBoolTag cast(short ptr) { + if (heap[ptr] != TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)) != BOOL_TAG) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + // validate the tag key. + private static boolean validateKey(short key) { + short index = (short) tags.length; + while (--index >= 0) { + if (tags[index] == key) { + return true; + } + } + return false; + } + + public static short[] getTags() { + return tags; + } + + public short getKey() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_BOOL_TAG_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short getTagType() { + return KMType.BOOL_TAG; + } + + public byte getVal() { + return heap[(short) (KMType.instanceTable[KM_BOOL_TAG_OFFSET] + TLV_HEADER_SIZE + 4)]; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMByteBlob.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMByteBlob.java new file mode 100644 index 0000000..98d81fc --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMByteBlob.java @@ -0,0 +1,142 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMByteBlob represents contiguous block of bytes. It corresponds to CBOR type of Byte String. It + * extends KMType by specifying value field as zero or more sequence of bytes. struct{byte + * BYTE_BLOB_TYPE; short length; sequence of bytes} + */ +public class KMByteBlob extends KMType { + + private static byte OFFSET_SIZE = 2; + private static KMByteBlob prototype; + + protected KMByteBlob() {} + + private static KMByteBlob proto(short ptr) { + if (prototype == null) { + prototype = new KMByteBlob(); + } + KMType.instanceTable[KM_BYTE_BLOB_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + return KMType.exp(BYTE_BLOB_TYPE); + } + + // return an empty byte blob instance + public static short instance(short length) { + short ptr = KMType.instance(BYTE_BLOB_TYPE, (short) (length + 2)); + Util.setShort( + heap, (short) (ptr + TLV_HEADER_SIZE), (short) (ptr + TLV_HEADER_SIZE + OFFSET_SIZE)); + Util.setShort(heap, (short) (ptr + 1), length); + return ptr; + } + + // byte blob from existing buf + public static short instance(byte[] buf, short startOff, short length) { + short ptr = instance(length); + Util.arrayCopyNonAtomic( + buf, startOff, heap, (short) (ptr + TLV_HEADER_SIZE + OFFSET_SIZE), length); + return ptr; + } + + // cast the ptr to KMByteBlob + public static KMByteBlob cast(short ptr) { + if (heap[ptr] != BYTE_BLOB_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + 1)) == INVALID_VALUE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + // Add the byte + public void add(short index, byte val) { + short len = length(); + if (index >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + heap[(short) (getStartOff() + index)] = val; + } + + // Get the byte + public byte get(short index) { + short len = length(); + if (index >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + return heap[(short) (getStartOff() + index)]; + } + + // Get the start of blob + public short getStartOff() { + return Util.getShort(heap, (short) (getBaseOffset() + TLV_HEADER_SIZE)); + } + + public void setStartOff(short offset) { + Util.setShort(heap, (short) (getBaseOffset() + TLV_HEADER_SIZE), offset); + } + + // Get the length of the blob + public short length() { + return Util.getShort(heap, (short) (getBaseOffset() + 1)); + } + + // Get the buffer pointer in which blob is contained. + public byte[] getBuffer() { + return heap; + } + + public void getValue(byte[] destBuf, short destStart, short destLength) { + Util.arrayCopyNonAtomic(heap, getStartOff(), destBuf, destStart, destLength); + } + + public short getValues(byte[] destBuf, short destStart) { + short destLength = length(); + Util.arrayCopyNonAtomic(heap, getStartOff(), destBuf, destStart, destLength); + return destLength; + } + + public void setValue(byte[] srcBuf, short srcStart, short srcLength) { + if (length() < srcLength) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + Util.arrayCopyNonAtomic(srcBuf, srcStart, heap, getStartOff(), srcLength); + setLength(srcLength); + } + + public boolean isValid() { + return (length() != 0); + } + + protected short getBaseOffset() { + return instanceTable[KM_BYTE_BLOB_OFFSET]; + } + + public void setLength(short len) { + Util.setShort(heap, (short) (getBaseOffset() + 1), len); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMByteTag.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMByteTag.java new file mode 100644 index 0000000..ac49c0e --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMByteTag.java @@ -0,0 +1,145 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMByteTag represents BYTES Tag Type from android keymaster hal specifications. The tag value of + * this tag is the KMByteBlob pointer i.e. offset of KMByteBlob in memory heap. struct{byte + * TAG_TYPE; short length; struct{short BYTES_TAG; short tagKey; short blobPtr}} + */ +public class KMByteTag extends KMTag { + + private static KMByteTag prototype; + + private KMByteTag() {} + + private static KMByteTag proto(short ptr) { + if (prototype == null) { + prototype = new KMByteTag(); + } + KMType.instanceTable[KM_BYTE_TAG_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short blobPtr = KMByteBlob.exp(); + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), BYTES_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), INVALID_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), blobPtr); + return ptr; + } + + public static short instance(short key, short byteBlob) { + if (!validateKey(key, byteBlob)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (heap[byteBlob] != BYTE_BLOB_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), BYTES_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), key); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), byteBlob); + return ptr; + } + + public static KMByteTag cast(short ptr) { + if (heap[ptr] != TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)) != BYTES_TAG) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + private static boolean validateKey(short key, short byteBlob) { + short valueLen = KMByteBlob.cast(byteBlob).length(); + switch (key) { + case ATTESTATION_APPLICATION_ID: + if (valueLen > MAX_ATTESTATION_APP_ID_SIZE) { + return false; + } + break; + case CERTIFICATE_SUBJECT_NAME: + { + if (valueLen > KMConfigurations.MAX_SUBJECT_DER_LEN) { + return false; + } + KMAsn1Parser asn1Decoder = KMAsn1Parser.instance(); + asn1Decoder.validateDerSubject(byteBlob); + } + break; + case APPLICATION_ID: + case APPLICATION_DATA: + if (valueLen > MAX_APP_ID_APP_DATA_SIZE) { + return false; + } + break; + case ATTESTATION_CHALLENGE: + if (valueLen > MAX_ATTESTATION_CHALLENGE_SIZE) { + return false; + } + break; + case ATTESTATION_ID_BRAND: + case ATTESTATION_ID_DEVICE: + case ATTESTATION_ID_PRODUCT: + case ATTESTATION_ID_SERIAL: + case ATTESTATION_ID_IMEI: + case ATTESTATION_ID_MEID: + case ATTESTATION_ID_MANUFACTURER: + case ATTESTATION_ID_MODEL: + if (valueLen > KMConfigurations.MAX_ATTESTATION_IDS_SIZE) { + return false; + } + break; + case ROOT_OF_TRUST: + case NONCE: + break; + default: + return false; + } + return true; + } + + public short getKey() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_BYTE_TAG_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short getTagType() { + return KMType.BYTES_TAG; + } + + public short getValue() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_BYTE_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + } + + public short length() { + short blobPtr = + Util.getShort( + heap, (short) (KMType.instanceTable[KM_BYTE_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + return KMByteBlob.cast(blobPtr).length(); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCose.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCose.java new file mode 100644 index 0000000..0c2244c --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCose.java @@ -0,0 +1,602 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; + +/** + * This class constructs the Cose messages like CoseKey, CoseMac0, MacStructure, CoseSign1, + * SignStructure, CoseEncrypt, EncryptStructure and ReceipientStructures. + */ +public class KMCose { + + // COSE SIGN1 + public static final byte COSE_SIGN1_ENTRY_COUNT = 4; + public static final byte COSE_SIGN1_PROTECTED_PARAMS_OFFSET = 0; + public static final byte COSE_SIGN1_PAYLOAD_OFFSET = 2; + public static final byte COSE_SIGN1_SIGNATURE_OFFSET = 3; + // COSE MAC0 + public static final byte COSE_MAC0_ENTRY_COUNT = 4; + public static final byte COSE_MAC0_PROTECTED_PARAMS_OFFSET = 0; + public static final byte COSE_MAC0_PAYLOAD_OFFSET = 2; + public static final byte COSE_MAC0_TAG_OFFSET = 3; + // COSE ENCRYPT + public static final byte COSE_ENCRYPT_ENTRY_COUNT = 4; + public static final byte COSE_ENCRYPT_STRUCTURE_ENTRY_COUNT = 3; + public static final byte COSE_ENCRYPT_RECIPIENT_ENTRY_COUNT = 3; + + // COSE Labels + public static final byte COSE_LABEL_ALGORITHM = 1; + public static final byte COSE_LABEL_KEYID = 4; + public static final byte COSE_LABEL_IV = 5; + public static final byte COSE_LABEL_COSE_KEY = (byte) 0xFF; // -1 + + // COSE Algorithms + public static final byte COSE_ALG_AES_GCM_256 = 3; // AES-GCM mode w/ 256-bit key, 128-bit tag. + public static final byte COSE_ALG_HMAC_256 = 5; // HMAC w/ SHA-256 + public static final byte COSE_ALG_ES256 = (byte) 0xF9; // ECDSA w/ SHA-256; -7 + public static final byte COSE_ALG_ECDH_ES_HKDF_256 = (byte) 0xE7; // ECDH-EC+HKDF-256; -25 + + // COSE P256 EC Curve + public static final byte COSE_ECCURVE_256 = 1; + + // COSE key types + public static final byte COSE_KEY_TYPE_EC2 = 2; + public static final byte COSE_KEY_TYPE_SYMMETRIC_KEY = 4; + + // COSE Key Operations + public static final byte COSE_KEY_OP_SIGN = 1; + public static final byte COSE_KEY_OP_VERIFY = 2; + public static final byte COSE_KEY_OP_ENCRYPT = 3; + public static final byte COSE_KEY_OP_DECRYPT = 4; + + // AES GCM + public static final short AES_GCM_KEY_SIZE_BITS = 256; + // Cose key parameters. + public static final short COSE_KEY_KEY_TYPE = 1; + public static final short COSE_KEY_KEY_ID = 2; + public static final short COSE_KEY_ALGORITHM = 3; + public static final short COSE_KEY_CURVE = -1; + public static final short COSE_KEY_PUBKEY_X = -2; + public static final short COSE_KEY_PUBKEY_Y = -3; + public static final short COSE_KEY_PRIV_KEY = -4; + public static final byte[] COSE_TEST_KEY = { + (byte) 0xFF, (byte) 0xFE, (byte) 0xEE, (byte) 0x90 + }; // -70000 + public static final byte COSE_KEY_MAX_SIZE = 4; + + // kdfcontext strings + public static final byte[] client = {0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74}; + public static final byte[] server = {0x73, 0x65, 0x72, 0x76, 0x65, 0x72}; + // Context strings + public static final byte[] MAC_CONTEXT = {0x4d, 0x41, 0x43, 0x30}; // MAC0 + public static final byte[] SIGNATURE1_CONTEXT = { + 0x53, 0x69, 0x67, 0x6E, 0x61, 0x74, 0x75, 0x72, 0x65, 0x31 + }; // Signature1 + public static final byte[] ENCRYPT_CONTEXT = { + 0x45, 0x6E, 0x63, 0x72, 0x79, 0x70, 0x74 + }; // Encrypt + // Certificate payload supported keys + public static final byte ISSUER = (byte) 0x01; + public static final byte SUBJECT = (byte) 0x02; + public static final byte[] SUBJECT_PUBLIC_KEY = { + (byte) 0xFF, (byte) 0xB8, (byte) 0xBB, (byte) 0xA8 + }; + public static final byte[] KEY_USAGE = {(byte) 0xFF, (byte) 0xB8, (byte) 0xBB, (byte) 0xA7}; + // text strings + public static final byte[] TEST_ISSUER_NAME = { + (byte) 0x49, 0x73, 0x73, 0x75, 0x65, 0x72 + }; // "Issuer" + public static final byte[] TEST_SUBJECT_NAME = { + 0x53, 0x75, 0x62, 0x6A, 0x65, 0x63, 0x74 + }; // "Subject" + public static final byte[] KEY_USAGE_SIGN = {0x20}; // Key usage sign + + public static final short[] COSE_KEY_LABELS = { + KMCose.COSE_KEY_KEY_TYPE, + KMCose.COSE_KEY_KEY_ID, + KMCose.COSE_KEY_ALGORITHM, + KMCose.COSE_KEY_CURVE, + KMCose.COSE_KEY_PUBKEY_X, + KMCose.COSE_KEY_PUBKEY_Y, + KMCose.COSE_KEY_PRIV_KEY + }; + public static final short[] COSE_HEADER_LABELS = { + KMCose.COSE_LABEL_ALGORITHM, + KMCose.COSE_LABEL_KEYID, + KMCose.COSE_LABEL_IV, + KMCose.COSE_LABEL_COSE_KEY + }; + + /** + * Constructs the Cose MAC structure. + * + * @param protectedHeader Bstr pointer which holds the protected header. + * @param extAad Bstr pointer which holds the external Aad. + * @param payload Bstr pointer which holds the payload of the MAC structure. + * @return KMArray instance of MAC structure. + */ + public static short constructCoseMacStructure( + short protectedHeader, short extAad, short payload) { + // Create MAC Structure and compute HMAC as per https://tools.ietf.org/html/rfc8152#section-6.3 + // MAC_structure = [ + // context : "MAC" / "MAC0", + // protected : empty_or_serialized_map, + // external_aad : bstr, + // payload : bstr + // ] + short arrPtr = KMArray.instance(KMCose.COSE_MAC0_ENTRY_COUNT); + // 1 - Context + KMArray.cast(arrPtr) + .add( + (short) 0, + KMTextString.instance( + KMCose.MAC_CONTEXT, (short) 0, (short) KMCose.MAC_CONTEXT.length)); + // 2 - Protected headers. + KMArray.cast(arrPtr).add((short) 1, protectedHeader); + // 3 - external aad + KMArray.cast(arrPtr).add((short) 2, extAad); + // 4 - payload. + KMArray.cast(arrPtr).add((short) 3, payload); + return arrPtr; + } + + /** + * Constructs the COSE_MAC0 object. + * + * @param protectedHeader Bstr pointer which holds the protected header. + * @param unprotectedHeader Bstr pointer which holds the unprotected header. + * @param payload Bstr pointer which holds the payload of the MAC structure. + * @param tag Bstr pointer which holds the tag value. + * @return KMArray instance of COSE_MAC0 object. + */ + public static short constructCoseMac0( + short protectedHeader, short unprotectedHeader, short payload, short tag) { + // Construct Cose_MAC0 + // COSE_Mac0 = [ + // protectedHeader, + // unprotectedHeader, + // payload : bstr / nil, + // tag : bstr, + // ] + short arrPtr = KMArray.instance(KMCose.COSE_MAC0_ENTRY_COUNT); + // 1 - protected headers + KMArray.cast(arrPtr).add((short) 0, protectedHeader); + // 2 - unprotected headers + KMArray.cast(arrPtr).add((short) 1, unprotectedHeader); + // 2 - payload + KMArray.cast(arrPtr).add((short) 2, payload); + // 3 - tag + KMArray.cast(arrPtr).add((short) 3, tag); + // Do encode. + return arrPtr; + } + + /** + * Constructs the COSE_Signature structure. + * + * @param protectedHeader Bstr pointer which holds the protected header. + * @param extAad Bstr pointer which holds the aad. + * @param payload Bstr pointer which holds the payload. + * @return KMArray instance of COSE_Signature object. + */ + public static short constructCoseSignStructure( + short protectedHeader, short extAad, short payload) { + // Sig_structure = [ + // context : "Signature" / "Signature1" / "CounterSignature", + // body_protected : empty_or_serialized_map, + // ? sign_protected : empty_or_serialized_map, + // external_aad : bstr, + // payload : bstr + // ] + short arrPtr = KMArray.instance(KMCose.COSE_SIGN1_ENTRY_COUNT); + // 1 - Context + KMArray.cast(arrPtr) + .add( + (short) 0, + KMTextString.instance( + KMCose.SIGNATURE1_CONTEXT, (short) 0, (short) KMCose.SIGNATURE1_CONTEXT.length)); + // 2 - Protected headers. + KMArray.cast(arrPtr).add((short) 1, protectedHeader); + // 3 - external aad + KMArray.cast(arrPtr).add((short) 2, extAad); + // 4 - payload. + KMArray.cast(arrPtr).add((short) 3, payload); + return arrPtr; + } + + /** + * Constructs the COSE_Sign1 object. + * + * @param protectedHeader Bstr pointer which holds the protected header. + * @param unProtectedHeader Bstr pointer which holds the unprotected header. + * @param payload Bstr pointer which holds the payload. + * @param signature Bstr pointer which holds the signature. + * @return KMArray instance of COSE_Sign1 object. + */ + public static short constructCoseSign1( + short protectedHeader, short unProtectedHeader, short payload, short signature) { + // COSE_Sign = [ + // protectedHeader, + // unprotectedHeader, + // payload : bstr / nil, + // signatures : [+ COSE_Signature] + // ] + short arrPtr = KMArray.instance(KMCose.COSE_SIGN1_ENTRY_COUNT); + // 1 - protected headers + KMArray.cast(arrPtr).add((short) 0, protectedHeader); + // 2 - unprotected headers + KMArray.cast(arrPtr).add((short) 1, unProtectedHeader); + // 2 - payload + KMArray.cast(arrPtr).add((short) 2, payload); + // 3 - tag + KMArray.cast(arrPtr).add((short) 3, signature); + return arrPtr; + } + + /** + * Constructs array based on the tag values provided. + * + * @param tag array of tag values to be constructed. + * @param includeTestMode flag which indicates if TEST_COSE_KEY should be included or not. + * @return instance of KMArray. + */ + private static short handleCosePairTags( + short[] tag, short[] keyValues, short valueIndex, boolean includeTestMode) { + short index = 0; + // var is used to calculate the length of the array. + short var = 0; + short tagLen = (short) tag.length; + // var is used to calculate the length of the array. + while (index < tagLen) { + if (keyValues[index] != KMType.INVALID_VALUE) { + keyValues[(short) (index + valueIndex)] = + buildCosePairTag((byte) tag[index], keyValues[index]); + var++; + } + index++; + } + var += includeTestMode ? 1 : 0; + short arrPtr = KMArray.instance(var); + index = 0; + // var is used to index the array. + var = 0; + while (index < tagLen) { + if (keyValues[(short) (index + valueIndex)] != KMType.INVALID_VALUE) { + KMArray.cast(arrPtr).add(var++, keyValues[(short) (index + valueIndex)]); + } + index++; + } + return arrPtr; + } + + /** + * Constructs the COSE_sign1 payload for certificate. + * + * @param issuer instance of KMCosePairTextStringTag which contains issuer value. + * @param subject instance of KMCosePairTextStringTag which contains subject value. + * @param subPublicKey instance of KMCosePairByteBlobTag which contains encoded KMCoseKey. + * @param keyUsage instance of KMCosePairByteBlobTag which contains key usage value. + * @return instance of KMArray. + */ + public static short constructCoseCertPayload( + short issuer, short subject, short subPublicKey, short keyUsage) { + short certPayload = KMArray.instance((short) 4); + KMArray.cast(certPayload).add((short) 0, issuer); + KMArray.cast(certPayload).add((short) 1, subject); + KMArray.cast(certPayload).add((short) 2, subPublicKey); + KMArray.cast(certPayload).add((short) 3, keyUsage); + certPayload = KMCoseCertPayload.instance(certPayload); + KMCoseCertPayload.cast(certPayload).canonicalize(); + return certPayload; + } + + /** + * Construct headers structure. Headers can be part of COSE_Sign1, COSE_Encrypt, COSE_Mac0 and + * COSE_Key. + * + * @param alg instance of either KMNInteger or KMInteger, based on the sign of algorithm value. + * @param keyId instance of KMByteBlob which contains the key identifier. + * @param iv instance of KMByteblob which contains the iv buffer. + * @param ephemeralKey instance of KMCoseKey. + * @return instance of KMCoseHeaders. + */ + public static short constructHeaders( + short[] buff, short alg, short keyId, short iv, short ephemeralKey) { + buff[0] = alg; + buff[1] = keyId; + buff[2] = iv; + buff[3] = ephemeralKey; + for (short i = 4; i < 8; i++) { + buff[i] = KMType.INVALID_VALUE; + } + short ptr = handleCosePairTags(COSE_HEADER_LABELS, buff, (short) 4, false); + ptr = KMCoseHeaders.instance(ptr); + KMCoseHeaders.cast(ptr).canonicalize(); + return ptr; + } + + /** + * Construct Recipients structure for COSE_Encrypt message. + * + * @param protectedHeaders instance of KMByteBlob which contains encoded KMCoseHeaders. + * @param unprotectedHeaders instance of KMCoseHeaders. + * @param cipherText instance of KMSimple + * @return instance of KMArray. + */ + public static short constructRecipientsStructure( + short protectedHeaders, short unprotectedHeaders, short cipherText) { + // recipients : [+COSE_recipient] + // COSE_recipient = [ + // Headers, + // ciphertext : bstr / nil, + // ? recipients : [+COSE_recipient] + // ] + short arrPtr = KMArray.instance(COSE_ENCRYPT_RECIPIENT_ENTRY_COUNT); + // 1 - protected headers + KMArray.cast(arrPtr).add((short) 0, protectedHeaders); + // 2 - unprotected headers + KMArray.cast(arrPtr).add((short) 1, unprotectedHeaders); + // 2 - payload + KMArray.cast(arrPtr).add((short) 2, cipherText); + + short recipientsArrayPtr = KMArray.instance((short) 1); + KMArray.cast(recipientsArrayPtr).add((short) 0, arrPtr); + return recipientsArrayPtr; + } + + /** + * Construct Encrypt structure required for COSE_Encrypt message. + * + * @param protectedHeader instance of KMByteBlob which wraps KMCoseHeaders. + * @param aad instance of KMByteBlob. + * @return instance of KMArray. + */ + public static short constructCoseEncryptStructure(short protectedHeader, short aad) { + // Enc_structure = [ + // context : "Encrypt" / "Encrypt0" / "Enc_Recipient" / + // "Mac_Recipient" / "Rec_Recipient", + // protected : empty_or_serialized_map, + // external_aad : bstr + // ] + short arrPtr = KMArray.instance(COSE_ENCRYPT_STRUCTURE_ENTRY_COUNT); + // 1 - protected headers + KMArray.cast(arrPtr) + .add( + (short) 0, + KMTextString.instance( + KMCose.ENCRYPT_CONTEXT, (short) 0, (short) KMCose.ENCRYPT_CONTEXT.length)); + // 2 - unprotected headers + KMArray.cast(arrPtr).add((short) 1, protectedHeader); + // 2 - payload + KMArray.cast(arrPtr).add((short) 2, aad); + return arrPtr; + } + + /** + * Constructs COSE_Encrypt message. + * + * @param protectedHeader instance of KMByteBlob which wraps KMCoseHeaders. + * @param unProtectedHeader instance of KMCoseHeaders. + * @param cipherText instance of KMByteBlob containing the cipher text. + * @param recipients instance of KMArray containing the recipients instance + * @return instance of KMArray. + */ + public static short constructCoseEncrypt( + short protectedHeader, short unProtectedHeader, short cipherText, short recipients) { + // COSE_Encrypt = [ + // protectedHeader, + // unprotectedHeader, + // ciphertext : bstr / nil, + // recipients : [+COSE_recipient] + // ] + short arrPtr = KMArray.instance(KMCose.COSE_ENCRYPT_ENTRY_COUNT); + // 1 - protected headers + KMArray.cast(arrPtr).add((short) 0, protectedHeader); + // 2 - unprotected headers + KMArray.cast(arrPtr).add((short) 1, unProtectedHeader); + // 2 - payload + KMArray.cast(arrPtr).add((short) 2, cipherText); + // 3 - tag + KMArray.cast(arrPtr).add((short) 3, recipients); + return arrPtr; + } + + /** + * Constructs the instance of KMCosePair*Tag. + * + * @param key value of the key. + * @param valuePtr instance of one of KMType. + * @return instance of KMCosePair*Value object. + */ + public static short buildCosePairTag(byte key, short valuePtr) { + short type = KMType.getType(valuePtr); + short keyPtr; + if (key < 0) { + keyPtr = KMNInteger.uint_8(key); + } else { + keyPtr = KMInteger.uint_8(key); + } + switch (type) { + case KMType.INTEGER_TYPE: + return KMCosePairIntegerTag.instance(keyPtr, valuePtr); + case KMType.NEG_INTEGER_TYPE: + return KMCosePairNegIntegerTag.instance(keyPtr, valuePtr); + case KMType.BYTE_BLOB_TYPE: + return KMCosePairByteBlobTag.instance(keyPtr, valuePtr); + case KMType.TEXT_STRING_TYPE: + return KMCosePairTextStringTag.instance(keyPtr, valuePtr); + case KMType.COSE_KEY_TYPE: + return KMCosePairCoseKeyTag.instance(keyPtr, valuePtr); + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + return 0; + } + } + + /** + * Constructs a CoseKey with the provided input parameters. Note that construction of the key_ops + * label is not needed to be supported. In the KeyMint2.0 specifications: The CoseKey inside + * MacedPublicKeys and DiceCertChain does not have key_ops label. + * + * @param keyType Instance of the identification of the key type. + * @param keyId Instance of key identification value. + * @param keyAlg Instance of the algorithm that is used with this key. + * @param curve Instance of the EC curve that is used with this key. + * @param pubKey Buffer containing the public key. + * @param pubKeyOff Start offset of the buffer. + * @param pubKeyLen Length of the public key. + * @param privKeyPtr Instance of the private key. + * @param testMode Represents if key is used in test mode or production mode. + * @return Instance of the CoseKey structure. + */ + public static short constructCoseKey( + short[] buff, + short keyType, + short keyId, + short keyAlg, + short curve, + byte[] pubKey, + short pubKeyOff, + short pubKeyLen, + short privKeyPtr, + boolean testMode) { + if (pubKey[pubKeyOff] == 0x04) { // uncompressed format + pubKeyOff += 1; + pubKeyLen -= 1; + } + pubKeyLen = (short) (pubKeyLen / 2); + short xPtr = KMByteBlob.instance(pubKey, pubKeyOff, pubKeyLen); + short yPtr = KMByteBlob.instance(pubKey, (short) (pubKeyOff + pubKeyLen), pubKeyLen); + short coseKey = + constructCoseKey(buff, keyType, keyId, keyAlg, curve, xPtr, yPtr, privKeyPtr, testMode); + KMCoseKey.cast(coseKey).canonicalize(); + return coseKey; + } + + /** + * Constructs the cose key based on input parameters supplied. All the parameters must be + * instantiated from either KMInteger or KMNInteger or KMByteblob types. + * + * @param keyType instance of KMInteger/KMNInteger which holds valid COSE key types. + * @param keyId instance of KMByteBlob which holds key identifier value. + * @param keyAlg instance of KMInteger/KMNInteger which holds valid COSE key algorithm. + * @param curve instance of KMInteger/KMNInteger which holds valid COSE EC curve. + * @param pubX instance of KMByteBlob which holds EC public key's x value. + * @param pubY instance of KMByteBlob which holds EC public key's y value. + * @param priv instance of KMByteBlob which holds EC private value. + * @param includeTestKey flag which identifies whether to construct test key or production key. + * @return instance of the KMCoseKey object. + */ + public static short constructCoseKey( + short[] buff, + short keyType, + short keyId, + short keyAlg, + short curve, + short pubX, + short pubY, + short priv, + boolean includeTestKey) { + short valueIndex = 7; + buff[0] = keyType; + buff[1] = keyId; + buff[2] = keyAlg; + buff[3] = curve; + buff[4] = pubX; + buff[5] = pubY; + buff[6] = priv; + for (short i = valueIndex; i < 16; i++) { + buff[i] = KMType.INVALID_VALUE; + } + short arrPtr = handleCosePairTags(COSE_KEY_LABELS, buff, valueIndex, includeTestKey); + if (includeTestKey) { + short testKey = + KMCosePairSimpleValueTag.instance( + KMNInteger.uint_32(KMCose.COSE_TEST_KEY, (short) 0), + KMSimpleValue.instance(KMSimpleValue.NULL)); + KMArray.cast(arrPtr).add((short) (KMArray.cast(arrPtr).length() - 1), testKey); + } + arrPtr = KMCoseKey.instance(arrPtr); + KMCoseKey.cast(arrPtr).canonicalize(); + return arrPtr; + } + + /** + * Constructs key derivation context which is required to compute HKDF. + * + * @param publicKeyA public key buffer from the first party. + * @param publicKeyAOff start position of the public key buffer from first party. + * @param publicKeyALen length of the public key buffer from first party. + * @param publicKeyB public key buffer from the second party. + * @param publicKeyBOff start position of the public key buffer from second party. + * @param publicKeyBLen length of the public key buffer from second party. + * @param senderIsA true if caller is first party, false if caller is second party. + * @return instance of KMArray. + */ + public static short constructKdfContext( + byte[] publicKeyA, + short publicKeyAOff, + short publicKeyALen, + byte[] publicKeyB, + short publicKeyBOff, + short publicKeyBLen, + boolean senderIsA) { + short index = 0; + // Prepare sender info + short senderInfo = KMArray.instance((short) 3); + KMArray.cast(senderInfo) + .add(index++, KMByteBlob.instance(client, (short) 0, (short) client.length)); + KMArray.cast(senderInfo).add(index++, KMByteBlob.instance((short) 0)); + KMArray.cast(senderInfo) + .add( + index, + senderIsA + ? KMByteBlob.instance(publicKeyA, publicKeyAOff, publicKeyALen) + : KMByteBlob.instance(publicKeyB, publicKeyBOff, publicKeyBLen)); + + // Prepare recipient info + index = 0; + short recipientInfo = KMArray.instance((short) 3); + KMArray.cast(recipientInfo) + .add(index++, KMByteBlob.instance(server, (short) 0, (short) server.length)); + KMArray.cast(recipientInfo).add(index++, KMByteBlob.instance((short) 0)); + KMArray.cast(recipientInfo) + .add( + index, + senderIsA + ? KMByteBlob.instance(publicKeyB, publicKeyBOff, publicKeyBLen) + : KMByteBlob.instance(publicKeyA, publicKeyAOff, publicKeyALen)); + + // supply public info + index = 0; + short publicInfo = KMArray.instance((short) 2); + KMArray.cast(publicInfo).add(index++, KMInteger.uint_16(AES_GCM_KEY_SIZE_BITS)); + KMArray.cast(publicInfo).add(index, KMByteBlob.instance((short) 0)); + + // construct kdf context + index = 0; + short arrPtr = KMArray.instance((short) 4); + KMArray.cast(arrPtr).add(index++, KMInteger.uint_8(COSE_ALG_AES_GCM_256)); + KMArray.cast(arrPtr).add(index++, senderInfo); + KMArray.cast(arrPtr).add(index++, recipientInfo); + KMArray.cast(arrPtr).add(index, publicInfo); + + return arrPtr; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCoseCertPayload.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCoseCertPayload.java new file mode 100644 index 0000000..fff9cf8 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCoseCertPayload.java @@ -0,0 +1,136 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCoseCertPayload represents the COSE_Sign1 payload for each certificate in BCC. The supported + * key types are KMInteger, KMNInteger and the supported value types are KMByteBlob and + * KMTextString. It corresponds to a CBOR Map type. struct{byte TAG_TYPE; short length; short + * arrayPtr } where arrayPtr is a pointer to array with any KMCosePairTagType subtype instances. + */ +public class KMCoseCertPayload extends KMCoseMap { + + private static KMCoseCertPayload prototype; + + private KMCoseCertPayload() {} + + private static KMCoseCertPayload proto(short ptr) { + if (prototype == null) { + prototype = new KMCoseCertPayload(); + } + instanceTable[KM_COSE_CERT_PAYLOAD_OFFSET] = ptr; + return prototype; + } + + public static short exp() { + short arrPtr = KMArray.instance((short) 2); + KMArray arr = KMArray.cast(arrPtr); + arr.add((short) 0, KMCosePairTextStringTag.exp()); + arr.add((short) 1, KMCosePairByteBlobTag.exp()); + return KMCoseCertPayload.instance(arrPtr); + } + + public static short instance(short vals) { + short ptr = KMType.instance(COSE_CERT_PAYLOAD_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMCoseCertPayload cast(short ptr) { + if (heap[ptr] != COSE_CERT_PAYLOAD_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (heap[arrPtr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + @Override + public short getVals() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_CERT_PAYLOAD_OFFSET] + TLV_HEADER_SIZE)); + } + + @Override + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); + } + + @Override + public void canonicalize() { + KMCoseMap.canonicalize(getVals()); + } + + private short getValueType(short key, short significantKey) { + short arr = getVals(); + short length = length(); + short keyPtr; + short valPtr = 0; + short index = 0; + short tagType; + boolean found = false; + while (index < length) { + tagType = KMCosePairTagType.getTagValueType(KMArray.cast(arr).get(index)); + switch (tagType) { + case KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE: + keyPtr = KMCosePairByteBlobTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == KMCosePairTagType.getKeyValueShort(keyPtr) + && significantKey == KMCosePairTagType.getKeyValueSignificantShort(keyPtr)) { + valPtr = KMCosePairByteBlobTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + case KMType.COSE_PAIR_TEXT_STR_TAG_TYPE: + keyPtr = KMCosePairTextStringTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairTextStringTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + default: + break; + } + if (found) { + break; + } + index++; + } + return valPtr; + } + + public short getSubjectPublicKey() { + return getValueType( + Util.getShort(KMCose.SUBJECT_PUBLIC_KEY, (short) 2), // LSB + Util.getShort(KMCose.SUBJECT_PUBLIC_KEY, (short) 0) // MSB (Significant) + ); + } + + public short getSubject() { + return getValueType(KMCose.SUBJECT, KMType.INVALID_VALUE); + } + + public short getIssuer() { + return getValueType(KMCose.ISSUER, KMType.INVALID_VALUE); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCoseHeaders.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCoseHeaders.java new file mode 100644 index 0000000..0e722d2 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCoseHeaders.java @@ -0,0 +1,203 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCoseHeaders represents headers section from the Cose standard + * https://datatracker.ietf.org/doc/html/rfc8152#section-3. The supported key types are KMInteger, + * KMNInteger and the supported value types are KMInteger, KMNInteger, KMByteBlob, KMCoseKey. It + * corresponds to a CBOR Map type. struct{byte TAG_TYPE; short length; short arrayPtr } where + * arrayPtr is a pointer to array with any KMTag subtype instances. + */ +public class KMCoseHeaders extends KMCoseMap { + + private static KMCoseHeaders prototype; + + private KMCoseHeaders() {} + + private static KMCoseHeaders proto(short ptr) { + if (prototype == null) { + prototype = new KMCoseHeaders(); + } + instanceTable[KM_COSE_HEADERS_OFFSET] = ptr; + return prototype; + } + + public static short exp() { + short arrPtr = KMArray.instance((short) 4); + // CoseKey is internally an Array so evaluate it separately. + short coseKeyValueExp = KMCosePairCoseKeyTag.exp(); + KMArray arr = KMArray.cast(arrPtr); + arr.add((short) 0, KMCosePairIntegerTag.exp()); + arr.add((short) 1, KMCosePairNegIntegerTag.exp()); + arr.add((short) 2, KMCosePairByteBlobTag.exp()); + arr.add((short) 3, coseKeyValueExp); + return KMCoseHeaders.instance(arrPtr); + } + + public static short instance(short vals) { + short ptr = KMType.instance(COSE_HEADERS_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMCoseHeaders cast(short ptr) { + if (heap[ptr] != COSE_HEADERS_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (heap[arrPtr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + @Override + public short getVals() { + return Util.getShort(heap, (short) (instanceTable[KM_COSE_HEADERS_OFFSET] + TLV_HEADER_SIZE)); + } + + @Override + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); + } + + @Override + public void canonicalize() { + KMCoseMap.canonicalize(getVals()); + } + + private short getValueType(short key) { + short index = 0; + short len = length(); + short arr = getVals(); + short tagType; + short valPtr = 0; + short keyPtr; + boolean found = false; + while (index < len) { + tagType = KMCosePairTagType.getTagValueType(KMArray.cast(arr).get(index)); + switch (tagType) { + case KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE: + keyPtr = KMCosePairByteBlobTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairByteBlobTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + case KMType.COSE_PAIR_COSE_KEY_TAG_TYPE: + keyPtr = KMCosePairCoseKeyTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairCoseKeyTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + case KMType.COSE_PAIR_INT_TAG_TYPE: + keyPtr = KMCosePairIntegerTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairIntegerTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + case KMType.COSE_PAIR_NEG_INT_TAG_TYPE: + keyPtr = KMCosePairNegIntegerTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairNegIntegerTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + default: + break; + } + if (found) { + break; + } + index++; + } + return valPtr; + } + + public short getKeyIdentifier() { + return getValueType(KMCose.COSE_LABEL_KEYID); + } + + public short getCoseKey() { + return getValueType(KMCose.COSE_LABEL_COSE_KEY); + } + + public short getIV() { + return getValueType(KMCose.COSE_LABEL_IV); + } + + public short getAlgorithm() { + return getValueType(KMCose.COSE_LABEL_ALGORITHM); + } + + public boolean isDataValid(short[] buff, short alg, short keyIdPtr) { + short bufLen = 4; + buff[0] = KMCose.COSE_LABEL_ALGORITHM; + buff[1] = alg; + buff[2] = KMCose.COSE_LABEL_KEYID; + buff[3] = keyIdPtr; + boolean valid = false; + short value; + short ptr; + short tagIndex = 0; + while (tagIndex < bufLen) { + value = buff[(short) (tagIndex + 1)]; + if (value != KMType.INVALID_VALUE) { + valid = false; + ptr = getValueType(buff[tagIndex]); + switch (KMType.getType(ptr)) { + case KMType.BYTE_BLOB_TYPE: + if ((KMByteBlob.cast(value).length() == KMByteBlob.cast(ptr).length()) + && (0 + == Util.arrayCompare( + KMByteBlob.cast(value).getBuffer(), + KMByteBlob.cast(value).getStartOff(), + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()))) { + valid = true; + } + break; + case KMType.INTEGER_TYPE: + if (value == KMInteger.cast(ptr).getShort()) { + valid = true; + } + break; + case KMType.NEG_INTEGER_TYPE: + if ((byte) value == (byte) KMNInteger.cast(ptr).getShort()) { + valid = true; + } + break; + default: + break; + } + if (!valid) { + break; + } + } + tagIndex += 2; + } + return valid; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCoseKey.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCoseKey.java new file mode 100644 index 0000000..d3edc5f --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCoseKey.java @@ -0,0 +1,255 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCoseKey represents COSE_Key section from the Cose standard + * https://datatracker.ietf.org/doc/html/rfc8152#section-7 The supported key types are KMNInteger, + * KMInteger and the supported value types are KMInteger, KMNInteger, KMByteBlob, KMSimpleValue. It + * corresponds to a CBOR Map type. struct{byte TAG_TYPE; short length; short arrayPtr } where + * arrayPtr is a pointer to array with any KMTag subtype instances. Note that construction of the + * key_ops label is not needed to be supported. In the KeyMint2.0 specifications: The CoseKey inside + * MacedPublicKeys and DiceCertChain does not have key_ops label. + */ +public class KMCoseKey extends KMCoseMap { + + private static KMCoseKey prototype; + + private KMCoseKey() {} + + private static KMCoseKey proto(short ptr) { + if (prototype == null) { + prototype = new KMCoseKey(); + } + instanceTable[KM_COSE_KEY_OFFSET] = ptr; + return prototype; + } + + public static short exp() { + short arrPtr = KMArray.instance((short) 4); + KMArray arr = KMArray.cast(arrPtr); + arr.add((short) 0, KMCosePairIntegerTag.exp()); + arr.add((short) 1, KMCosePairNegIntegerTag.exp()); + arr.add((short) 2, KMCosePairByteBlobTag.exp()); + arr.add((short) 3, KMCosePairSimpleValueTag.exp()); + return KMCoseKey.instance(arrPtr); + } + + public static short instance(short vals) { + short ptr = KMType.instance(COSE_KEY_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMCoseKey cast(short ptr) { + if (heap[ptr] != COSE_KEY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (heap[arrPtr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + @Override + public short getVals() { + return Util.getShort(heap, (short) (instanceTable[KM_COSE_KEY_OFFSET] + TLV_HEADER_SIZE)); + } + + @Override + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); + } + + private short getValueType(short key, short significantKey) { + short arr = getVals(); + short length = length(); + short keyPtr; + short valPtr = 0; + short index = 0; + short tagType; + boolean found = false; + while (index < length) { + tagType = KMCosePairTagType.getTagValueType(KMArray.cast(arr).get(index)); + switch (tagType) { + case KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE: + keyPtr = KMCosePairByteBlobTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairByteBlobTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + case KMType.COSE_PAIR_INT_TAG_TYPE: + keyPtr = KMCosePairIntegerTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairIntegerTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + case KMType.COSE_PAIR_NEG_INT_TAG_TYPE: + keyPtr = KMCosePairNegIntegerTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairNegIntegerTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + case KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE: + keyPtr = KMCosePairSimpleValueTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == KMCosePairTagType.getKeyValueShort(keyPtr) + && significantKey == KMCosePairTagType.getKeyValueSignificantShort(keyPtr)) { + valPtr = KMCosePairSimpleValueTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + default: + break; + } + if (found) { + break; + } + index++; + } + return valPtr; + } + + public short getKeyIdentifier() { + return getValueType(KMCose.COSE_KEY_KEY_ID, KMType.INVALID_VALUE); + } + + public short getEcdsa256PublicKey(byte[] pubKey, short pubKeyOff) { + short baseOffset = pubKeyOff; + pubKey[pubKeyOff] = (byte) 0x04; // uncompressed. + pubKeyOff++; + short ptr = getValueType(KMCose.COSE_KEY_PUBKEY_X, KMType.INVALID_VALUE); + Util.arrayCopy( + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + pubKey, + pubKeyOff, + KMByteBlob.cast(ptr).length()); + pubKeyOff += KMByteBlob.cast(ptr).length(); + ptr = getValueType(KMCose.COSE_KEY_PUBKEY_Y, KMType.INVALID_VALUE); + Util.arrayCopy( + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + pubKey, + pubKeyOff, + KMByteBlob.cast(ptr).length()); + pubKeyOff += KMByteBlob.cast(ptr).length(); + return (short) (pubKeyOff - baseOffset); + } + + public short getPrivateKey(byte[] priv, short privOff) { + short ptr = getValueType(KMCose.COSE_KEY_PRIV_KEY, KMType.INVALID_VALUE); + Util.arrayCopy( + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + priv, + privOff, + KMByteBlob.cast(ptr).length()); + return KMByteBlob.cast(ptr).length(); + } + + public boolean isTestKey() { + short ptr = + getValueType( + Util.getShort(KMCose.COSE_TEST_KEY, (short) 2), // LSB + Util.getShort(KMCose.COSE_TEST_KEY, (short) 0) // MSB (Significant) + ); + boolean isTestKey = false; + if (ptr != 0) { + isTestKey = (KMSimpleValue.cast(ptr).getValue() == KMSimpleValue.NULL); + } + return isTestKey; + } + + /** + * Verifies the KMCoseKey values against the input values. Note that construction of the key_ops + * label is not needed to be supported. In the KeyMint2.0 specifications: The CoseKey inside + * MacedPublicKeys and DiceCertChain does not have key_ops label. + * + * @param keyType value of the key type + * @param keyIdPtr instance of KMByteBlob containing the key id. + * @param keyAlg value of the algorithm. + * @param keyOps value of the key operations. + * @param curve value of the curve. + * @return true if valid, otherwise false. + */ + public boolean isDataValid( + short[] buff, short keyType, short keyIdPtr, short keyAlg, short curve) { + short buffLen = 8; + buff[0] = KMCose.COSE_KEY_KEY_TYPE; + buff[1] = keyType; + buff[2] = KMCose.COSE_KEY_KEY_ID; + buff[3] = keyIdPtr; + buff[4] = KMCose.COSE_KEY_ALGORITHM; + buff[5] = keyAlg; + buff[6] = KMCose.COSE_KEY_CURVE; + buff[7] = curve; + boolean valid = false; + short ptr; + short tagIndex = 0; + short value; + while (tagIndex < buffLen) { + value = buff[(short) (tagIndex + 1)]; + if (value != KMType.INVALID_VALUE) { + valid = false; + ptr = getValueType(buff[tagIndex], KMType.INVALID_VALUE); + switch (KMType.getType(ptr)) { + case KMType.BYTE_BLOB_TYPE: + if ((KMByteBlob.cast(value).length() == KMByteBlob.cast(ptr).length()) + && (0 + == Util.arrayCompare( + KMByteBlob.cast(value).getBuffer(), + KMByteBlob.cast(value).getStartOff(), + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()))) { + valid = true; + } + break; + case KMType.INTEGER_TYPE: + if (value == KMInteger.cast(ptr).getShort()) { + valid = true; + } + break; + case KMType.NEG_INTEGER_TYPE: + if ((byte) value == (byte) KMNInteger.cast(ptr).getShort()) { + valid = true; + } + break; + } + if (!valid) { + break; + } + } + tagIndex += 2; + } + return valid; + } + + @Override + public void canonicalize() { + KMCoseMap.canonicalize(getVals()); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCoseMap.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCoseMap.java new file mode 100644 index 0000000..5d373a7 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCoseMap.java @@ -0,0 +1,171 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; + +/** + * This class represents either a Cose_key or Cose headers as defined in + * https://datatracker.ietf.org/doc/html/rfc8152 This is basically a map containing key value pairs. + * The label for the key can be (uint / int / tstr) and the value can be of any type. But this class + * is confined to support only key and value types which are required for remote key provisioning. + * So keys of type (int / uint) and values of type (int / uint / simple / bstr) only are supported. + * KMCoseHeaders and KMCoseKey implements this class. + */ +public abstract class KMCoseMap extends KMType { + + public static byte[] scratchpad; + + /** + * This function creates an instance of either KMCoseHeaders or KMCoseKey based on the type + * information provided. + * + * @param typePtr type information of the underlying KMType. + * @param arrPtr instance of KMArray. + * @return instance type of either KMCoseHeaders or KMCoseKey. + */ + public static short createInstanceFromType(short typePtr, short arrPtr) { + short mapType = KMType.getType(typePtr); + switch (mapType) { + case KMType.COSE_HEADERS_TYPE: + return KMCoseHeaders.instance(arrPtr); + case KMType.COSE_KEY_TYPE: + return KMCoseKey.instance(arrPtr); + case KMType.COSE_CERT_PAYLOAD_TYPE: + return KMCoseCertPayload.instance(arrPtr); + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + return 0; + } + } + + public static short getVals(short ptr) { + short mapType = KMType.getType(ptr); + switch (mapType) { + case KMType.COSE_HEADERS_TYPE: + return KMCoseHeaders.cast(ptr).getVals(); + case KMType.COSE_KEY_TYPE: + return KMCoseKey.cast(ptr).getVals(); + case KMType.COSE_CERT_PAYLOAD_TYPE: + return KMCoseCertPayload.cast(ptr).getVals(); + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + return 0; + } + } + + private static short getKey(short tagPtr) { + short tagType = KMCosePairTagType.getTagValueType(tagPtr); + switch (tagType) { + case KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE: + return KMCosePairByteBlobTag.cast(tagPtr).getKeyPtr(); + case KMType.COSE_PAIR_INT_TAG_TYPE: + return KMCosePairIntegerTag.cast(tagPtr).getKeyPtr(); + case KMType.COSE_PAIR_NEG_INT_TAG_TYPE: + return KMCosePairNegIntegerTag.cast(tagPtr).getKeyPtr(); + case KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE: + return KMCosePairSimpleValueTag.cast(tagPtr).getKeyPtr(); + case KMType.COSE_PAIR_COSE_KEY_TAG_TYPE: + return KMCosePairCoseKeyTag.cast(tagPtr).getKeyPtr(); + case KMType.COSE_PAIR_TEXT_STR_TAG_TYPE: + return KMCosePairTextStringTag.cast(tagPtr).getKeyPtr(); + default: + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return 0; + } + + private static void createScratchBuffer() { + if (scratchpad == null) { + scratchpad = JCSystem.makeTransientByteArray((short) 120, JCSystem.CLEAR_ON_RESET); + } + } + + protected static void canonicalize(short arr) { + canonicalize(arr, KMArray.cast(arr).length()); + } + + private static void swap(short ptr, short firstIndex, short secondIndex) { + if (KMType.getType(ptr) == KMType.ARRAY_TYPE) { + KMArray.cast(ptr).swap(firstIndex, secondIndex); + } else { + KMMap.cast(ptr).swap(firstIndex, secondIndex); + } + } + + private static boolean compareAndSwap(short ptr, short index) { + short firstKey; + short secondKey; + short firstKeyLen; + short secondKeyLen; + if (KMType.getType(ptr) == KMType.ARRAY_TYPE) { + firstKey = getKey(KMArray.cast(ptr).get(index)); + secondKey = getKey(KMArray.cast(ptr).get((short) (index + 1))); + } else { // Map + firstKey = KMMap.cast(ptr).getKey(index); + secondKey = KMMap.cast(ptr).getKey((short) (index + 1)); + } + firstKeyLen = + KMKeymasterApplet.encoder.encode( + firstKey, scratchpad, (short) 0, (short) scratchpad.length); + secondKeyLen = + KMKeymasterApplet.encoder.encode( + secondKey, scratchpad, firstKeyLen, (short) scratchpad.length); + if ((firstKeyLen > secondKeyLen) + || ((firstKeyLen == secondKeyLen) + && (0 + < Util.arrayCompare( + scratchpad, (short) 0, scratchpad, firstKeyLen, firstKeyLen)))) { + swap(ptr, index, (short) (index + 1)); + return true; + } + return false; + } + + /** + * Canonicalizes using bubble sort. + * + * @param ptr instance pointer of either array or map. + * @param length length of the array or map instance. + */ + public static void canonicalize(short ptr, short length) { + short index = 0; + short innerIndex = 0; + createScratchBuffer(); + boolean swapped; + while (index < length) { + swapped = false; + innerIndex = 0; + while (innerIndex < (short) (length - index - 1)) { + swapped |= compareAndSwap(ptr, innerIndex); + innerIndex++; + } + if (!swapped) { + break; + } + index++; + } + } + + public abstract short getVals(); + + public abstract short length(); + + public abstract void canonicalize(); +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairByteBlobTag.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairByteBlobTag.java new file mode 100644 index 0000000..04c3abe --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairByteBlobTag.java @@ -0,0 +1,137 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCosePairByteBlobTag represents a key-value type, where key can be KMInteger or KMNInteger and + * value is KMByteBlob type. struct{byte TAG_TYPE; short length; struct{short BYTE_BLOB_TYPE; short + * key; short value}}. + */ +public class KMCosePairByteBlobTag extends KMCosePairTagType { + + public static Object[] keys; + private static KMCosePairByteBlobTag prototype; + + private KMCosePairByteBlobTag() {} + + private static KMCosePairByteBlobTag proto(short ptr) { + if (prototype == null) { + prototype = new KMCosePairByteBlobTag(); + } + instanceTable[KM_COSE_KEY_BYTE_BLOB_VAL_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short ptr = instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), KMType.INVALID_VALUE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), KMByteBlob.exp()); + return ptr; + } + + public static short instance(short keyPtr, short valuePtr) { + if (!isKeyValueValid(keyPtr)) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (KMType.getType(valuePtr) != BYTE_BLOB_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short ptr = KMType.instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), keyPtr); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), valuePtr); + return ptr; + } + + public static KMCosePairByteBlobTag cast(short ptr) { + byte[] heap = repository.getHeap(); + if (heap[ptr] != COSE_PAIR_TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + // Validate the value pointer. + short valuePtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4)); + if (KMType.getType(valuePtr) != BYTE_BLOB_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + private static void createKeys() { + if (keys == null) { + keys = + new Object[] { + (Object) new byte[] {(byte) 0, (byte) 0, (byte) 0, KMCose.COSE_KEY_PUBKEY_X}, + (Object) new byte[] {(byte) 0, (byte) 0, (byte) 0, KMCose.COSE_KEY_PUBKEY_Y}, + (Object) new byte[] {(byte) 0, (byte) 0, (byte) 0, KMCose.COSE_KEY_PRIV_KEY}, + (Object) new byte[] {(byte) 0, (byte) 0, (byte) 0, KMCose.COSE_LABEL_IV}, + (Object) new byte[] {(byte) 0, (byte) 0, (byte) 0, KMCose.COSE_LABEL_KEYID}, + (Object) new byte[] {(byte) 0, (byte) 0, (byte) 0, KMCose.COSE_KEY_KEY_ID}, + (Object) KMCose.SUBJECT_PUBLIC_KEY, + (Object) KMCose.KEY_USAGE + }; + } + } + + public static boolean isKeyValueValid(short keyPtr) { + createKeys(); + short type = KMType.getType(keyPtr); + short offset = 0; + if (type == INTEGER_TYPE) { + offset = KMInteger.cast(keyPtr).getStartOff(); + } else if (type == NEG_INTEGER_TYPE) { + offset = KMNInteger.cast(keyPtr).getStartOff(); + } else { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short index = 0; + while (index < (short) keys.length) { + if (0 + == Util.arrayCompare( + (byte[]) keys[index], + (short) 0, + heap, + offset, + (short) ((byte[]) keys[index]).length)) { + return true; + } + index++; + } + return false; + } + + public short getValueType() { + return BYTE_BLOB_TYPE; + } + + @Override + public short getKeyPtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_BYTE_BLOB_VAL_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + @Override + public short getValuePtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_BYTE_BLOB_VAL_OFFSET] + TLV_HEADER_SIZE + 4)); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairCoseKeyTag.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairCoseKeyTag.java new file mode 100644 index 0000000..5290da2 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairCoseKeyTag.java @@ -0,0 +1,89 @@ +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCosePairCoseKeyTag represents a key-value type, where key can be KMInteger or KMNInteger and + * value is KMCoseKey type. struct{byte TAG_TYPE; short length; struct{short COSE_KEY_VALUE_TYPE; + * short key; short value}}. + */ +public class KMCosePairCoseKeyTag extends KMCosePairTagType { + + public static final byte[] keys = {KMCose.COSE_LABEL_COSE_KEY}; + private static KMCosePairCoseKeyTag prototype; + + private KMCosePairCoseKeyTag() {} + + private static KMCosePairCoseKeyTag proto(short ptr) { + if (prototype == null) { + prototype = new KMCosePairCoseKeyTag(); + } + instanceTable[KM_COSE_KEY_COSE_KEY_VAL_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short ptr = instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_COSE_KEY_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), KMType.INVALID_VALUE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), KMCoseKey.exp()); + return ptr; + } + + public static short instance(short keyPtr, short valuePtr) { + if (!isKeyValueValid(KMCosePairTagType.getKeyValueShort(keyPtr))) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (KMType.getType(valuePtr) != COSE_KEY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short ptr = KMType.instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_COSE_KEY_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), keyPtr); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), valuePtr); + return ptr; + } + + public static KMCosePairCoseKeyTag cast(short ptr) { + byte[] heap = repository.getHeap(); + if (heap[ptr] != COSE_PAIR_TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + // Validate the value pointer. + short valuePtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4)); + if (KMType.getType(valuePtr) != COSE_KEY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static boolean isKeyValueValid(short keyVal) { + short index = 0; + while (index < (short) keys.length) { + if ((byte) (keyVal & 0xFF) == keys[index]) { + return true; + } + index++; + } + return false; + } + + public short getValueType() { + return COSE_KEY_TYPE; + } + + @Override + public short getKeyPtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_COSE_KEY_VAL_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + @Override + public short getValuePtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_COSE_KEY_VAL_OFFSET] + TLV_HEADER_SIZE + 4)); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairIntegerTag.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairIntegerTag.java new file mode 100644 index 0000000..ea052a6 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairIntegerTag.java @@ -0,0 +1,92 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCosePairIntegerTag represents a key-value type, where key can be KMInteger or KMNInteger and + * value is KMInteger type. struct{byte TAG_TYPE; short length; struct{short INT_VALUE_TYPE; short + * key; short value}}. + */ +public class KMCosePairIntegerTag extends KMCosePairTagType { + + private static KMCosePairIntegerTag prototype; + + private KMCosePairIntegerTag() {} + + private static KMCosePairIntegerTag proto(short ptr) { + if (prototype == null) { + prototype = new KMCosePairIntegerTag(); + } + instanceTable[KM_COSE_KEY_INT_VAL_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short ptr = instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_INT_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), KMType.INVALID_VALUE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), KMInteger.exp()); + return ptr; + } + + public static short instance(short keyPtr, short valuePtr) { + short offset = KMCosePairTagType.getKeyStartOffset(keyPtr); + if (!KMCosePairTagType.isKeyPairValid( + heap, offset, KMCose.COSE_KEY_MAX_SIZE, KMInteger.cast(valuePtr).getShort())) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short ptr = KMType.instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_INT_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), keyPtr); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), valuePtr); + return ptr; + } + + public static KMCosePairIntegerTag cast(short ptr) { + byte[] heap = repository.getHeap(); + if (heap[ptr] != COSE_PAIR_TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + // Validate the value ptr. + short valuePtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4)); + if (INTEGER_TYPE != getType(valuePtr)) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public short getValueType() { + return INTEGER_TYPE; + } + + @Override + public short getKeyPtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_INT_VAL_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + @Override + public short getValuePtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_INT_VAL_OFFSET] + TLV_HEADER_SIZE + 4)); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairNegIntegerTag.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairNegIntegerTag.java new file mode 100644 index 0000000..7f01202 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairNegIntegerTag.java @@ -0,0 +1,92 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCosePairNegIntegerTag represents a key-value type, where key can be KMInteger or KMNInteger and + * value is KMNInteger type. struct{byte TAG_TYPE; short length; struct{short NINT_VALUE_TYPE; short + * key; short value}}. + */ +public class KMCosePairNegIntegerTag extends KMCosePairTagType { + + private static KMCosePairNegIntegerTag prototype; + + private KMCosePairNegIntegerTag() {} + + private static KMCosePairNegIntegerTag proto(short ptr) { + if (prototype == null) { + prototype = new KMCosePairNegIntegerTag(); + } + instanceTable[KM_COSE_KEY_NINT_VAL_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short ptr = instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_NEG_INT_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), KMType.INVALID_VALUE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), KMNInteger.exp()); + return ptr; + } + + public static KMCosePairNegIntegerTag cast(short ptr) { + byte[] heap = repository.getHeap(); + if (heap[ptr] != COSE_PAIR_TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + // Validate the value ptr. + short valuePtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4)); + if (NEG_INTEGER_TYPE != getType(valuePtr)) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static short instance(short keyPtr, short valuePtr) { + short offset = KMCosePairTagType.getKeyStartOffset(keyPtr); + if (!KMCosePairTagType.isKeyPairValid( + heap, offset, KMCose.COSE_KEY_MAX_SIZE, KMNInteger.cast(valuePtr).getShort())) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short ptr = KMType.instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_NEG_INT_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), keyPtr); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), valuePtr); + return ptr; + } + + public short getValueType() { + return NEG_INTEGER_TYPE; + } + + @Override + public short getKeyPtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_NINT_VAL_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + @Override + public short getValuePtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_NINT_VAL_OFFSET] + TLV_HEADER_SIZE + 4)); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairSimpleValueTag.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairSimpleValueTag.java new file mode 100644 index 0000000..a0d7da8 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairSimpleValueTag.java @@ -0,0 +1,76 @@ +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCosePairSimpleValueTag represents a key-value type, where key can be KMInteger or KMNInteger + * and value is KMSimpleValue type. struct{byte TAG_TYPE; short length; struct{short + * SIMPLE_VALUE_TYPE; short key; short value}}. + */ +public class KMCosePairSimpleValueTag extends KMCosePairTagType { + + private static KMCosePairSimpleValueTag prototype; + + private KMCosePairSimpleValueTag() {} + + private static KMCosePairSimpleValueTag proto(short ptr) { + if (prototype == null) { + prototype = new KMCosePairSimpleValueTag(); + } + instanceTable[KM_COSE_KEY_SIMPLE_VAL_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short ptr = instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), KMType.INVALID_VALUE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), KMSimpleValue.exp()); + return ptr; + } + + public static short instance(short keyPtr, short valuePtr) { + short offset = KMCosePairTagType.getKeyStartOffset(keyPtr); + if (!KMCosePairTagType.isKeyPairValid( + heap, offset, KMCose.COSE_KEY_MAX_SIZE, KMSimpleValue.cast(valuePtr).getValue())) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short ptr = KMType.instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), keyPtr); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), valuePtr); + return ptr; + } + + public static KMCosePairSimpleValueTag cast(short ptr) { + byte[] heap = repository.getHeap(); + if (heap[ptr] != COSE_PAIR_TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + // Validate the value pointer. + short valuePtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4)); + if (KMType.getType(valuePtr) != SIMPLE_VALUE_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public short getValueType() { + return SIMPLE_VALUE_TYPE; + } + + @Override + public short getKeyPtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_SIMPLE_VAL_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + @Override + public short getValuePtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_SIMPLE_VAL_OFFSET] + TLV_HEADER_SIZE + 4)); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairTagType.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairTagType.java new file mode 100644 index 0000000..baa0855 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairTagType.java @@ -0,0 +1,248 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * This class represents the a key-value types. This is basically a map containing key value pairs. + * The label for the key can be (uint / int / tstr) and the value can be of any type. But this class + * is confined to support only key and value types which are required for remote key provisioning. + * So keys of type (int / uint) and values of type (int / uint / simple / bstr) only are supported. + * The structure representing all the sub classes of KMCosePairTagType is as follows: + * KM_COSE_PAIR_TAG_TYPE(1byte), Length(2 bytes), COSE_PAIR_*_TAG_TYPE(2 bytes), Key(2 bytes), + * Value(2 bytes). Key can be either KMInteger or KMNInteger and Value can be either KMIntger or + * KMNinteger or KMSimpleValue or KMByteBlob or KMTextString or KMCoseKey. Each subclass of + * KMCosePairTagType is named after their corresponding value type of the Cose pair. + */ +public abstract class KMCosePairTagType extends KMType { + + /** + * Below table represents the allowed values for a key. The maximum length of the key can be 4 + * bytes so each key is represented as 4 bytes. The allowed values are placed next to their + * corresponding key. + */ + public static Object[] allowedKeyPairs; + + private static void createAllowedKeyPairs() { + if (allowedKeyPairs == null) { + allowedKeyPairs = + new Object[] { + // Key type + (Object) new byte[] {0, 0, 0, KMCose.COSE_KEY_KEY_TYPE}, + (Object) new byte[] {KMCose.COSE_KEY_TYPE_EC2, KMCose.COSE_KEY_TYPE_SYMMETRIC_KEY}, + // Key Algorithm + (Object) new byte[] {0, 0, 0, KMCose.COSE_KEY_ALGORITHM}, + (Object) + new byte[] { + KMCose.COSE_ALG_AES_GCM_256, + KMCose.COSE_ALG_HMAC_256, + KMCose.COSE_ALG_ECDH_ES_HKDF_256, + KMCose.COSE_ALG_ES256 + }, + // Key Curve + (Object) new byte[] {0, 0, 0, KMCose.COSE_KEY_CURVE}, + (Object) new byte[] {KMCose.COSE_ECCURVE_256}, + // Header Label Algorithm + (Object) new byte[] {0, 0, 0, KMCose.COSE_LABEL_ALGORITHM}, + (Object) + new byte[] { + KMCose.COSE_ALG_AES_GCM_256, + KMCose.COSE_ALG_HMAC_256, + KMCose.COSE_ALG_ES256, + KMCose.COSE_ALG_ECDH_ES_HKDF_256 + }, + // Test Key + KMCose.COSE_TEST_KEY, + (Object) new byte[] {KMSimpleValue.NULL}, + }; + } + } + + /** + * Validates the key and the values corresponding to key. + * + * @param key Buffer containing the key. + * @param keyOff Offset in the buffer from where key starts. + * @param keyLen Length of the key buffer. + * @param value Value corresponding to the key. + * @return true if key pair is valid, otherwise false. + */ + public static boolean isKeyPairValid(byte[] key, short keyOff, short keyLen, short value) { + short index = 0; + short valueIdx; + byte[] values; + boolean valid = false; + createAllowedKeyPairs(); + while (index < allowedKeyPairs.length) { + valueIdx = 0; + if (isEqual( + (byte[]) allowedKeyPairs[index], + (short) 0, + (short) ((byte[]) allowedKeyPairs[index]).length, + key, + keyOff, + keyLen)) { + values = (byte[]) allowedKeyPairs[(short) (index + 1)]; + while (valueIdx < values.length) { + if (values[valueIdx] == (byte) value) { + valid = true; + break; + } + valueIdx++; + } + if (valid) { + break; + } + } + index += (short) 2; + } + return valid; + } + + /** + * Compares two key buffers. + * + * @param key1 First buffer containing the key. + * @param offset1 Offset of the first buffer. + * @param length1 Length of the first buffer. + * @param key2 Second buffer containing the key. + * @param offset2 Offset of the second buffer. + * @param length2 Length of the second buffer. + * @return true if both keys are equal, otherwise false. + */ + private static boolean isEqual( + byte[] key1, short offset1, short length1, byte[] key2, short offset2, short length2) { + if (length1 != length2) { + return false; + } + return (0 == KMInteger.unsignedByteArrayCompare(key1, offset1, key2, offset2, length1)); + } + + /** + * Returns the short value of the key. + * + * @param keyPtr Pointer to either KMInteger or KMNInteger + * @return value of the key as short. + */ + public static short getKeyValueShort(short keyPtr) { + short type = KMType.getType(keyPtr); + short value = 0; + if (type == INTEGER_TYPE) { + value = KMInteger.cast(keyPtr).getShort(); + } else if (type == NEG_INTEGER_TYPE) { + value = KMNInteger.cast(keyPtr).getShort(); + } else { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return value; + } + + /** + * Returns the significant short value of the key. + * + * @param keyPtr Pointer to either KMInteger or KMNInteger + * @return value of the key as short. + */ + public static short getKeyValueSignificantShort(short keyPtr) { + short type = KMType.getType(keyPtr); + short value = 0; + if (type == INTEGER_TYPE) { + value = KMInteger.cast(keyPtr).getSignificantShort(); + } else if (type == NEG_INTEGER_TYPE) { + value = KMNInteger.cast(keyPtr).getSignificantShort(); + } else { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return value; + } + + public static void getKeyValue(short keyPtr, byte[] dest, short offset, short len) { + short type = KMType.getType(keyPtr); + if (type == INTEGER_TYPE) { + KMInteger.cast(keyPtr).getValue(dest, offset, len); + } else if (type == NEG_INTEGER_TYPE) { + KMNInteger.cast(keyPtr).getValue(dest, offset, len); + } else { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + } + + /** + * Returns the key offset from the key pointer. + * + * @param keyPtr Pointer to either KMInteger or KMNInteger + * @return offset from where the key starts. + */ + public static short getKeyStartOffset(short keyPtr) { + short type = KMType.getType(keyPtr); + short offset = 0; + if (type == INTEGER_TYPE) { + offset = KMInteger.cast(keyPtr).getStartOff(); + } else if (type == NEG_INTEGER_TYPE) { + offset = KMNInteger.cast(keyPtr).getStartOff(); + } else { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return offset; + } + + /** + * Returns the key length. + * + * @param keyPtr pointer to either KMInteger/KMInteger. + * @return length of the key. + */ + public static short getKeyLength(short keyPtr) { + short type = KMType.getType(keyPtr); + short len = 0; + if (type == INTEGER_TYPE) { + len = KMInteger.cast(keyPtr).length(); + } else if (type == NEG_INTEGER_TYPE) { + len = KMNInteger.cast(keyPtr).length(); + } else { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return len; + } + + /** + * This function returns one of COSE_KEY_TAG_*_VALUE_TYPE tag information. + * + * @param ptr Pointer to one of the KMCoseKey*Value class. + * @return Tag value type. + */ + public static short getTagValueType(short ptr) { + return Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + } + + /** + * This function returns the key pointer. + * + * @return key pointer. + */ + public abstract short getKeyPtr(); + + /** + * This function returns the value pointer. + * + * @return value pointer. + */ + public abstract short getValuePtr(); +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairTextStringTag.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairTextStringTag.java new file mode 100644 index 0000000..99506b6 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMCosePairTextStringTag.java @@ -0,0 +1,91 @@ +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCosePairTextStringTag represents a key-value type, where key can be KMInteger or KMNInteger and + * value is KMTextString type. struct{byte TAG_TYPE; short length; struct{short TXT_STR_VALUE_TYPE; + * short key; short value}}. + */ +public class KMCosePairTextStringTag extends KMCosePairTagType { + + public static final byte[] keys = { + KMCose.ISSUER, KMCose.SUBJECT, + }; + private static KMCosePairTextStringTag prototype; + + private KMCosePairTextStringTag() {} + + private static KMCosePairTextStringTag proto(short ptr) { + if (prototype == null) { + prototype = new KMCosePairTextStringTag(); + } + instanceTable[KM_COSE_KEY_TXT_STR_VAL_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short ptr = instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_TEXT_STR_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), KMType.INVALID_VALUE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), KMTextString.exp()); + return ptr; + } + + public static short instance(short keyPtr, short valuePtr) { + if (!isKeyValueValid(KMCosePairTagType.getKeyValueShort(keyPtr))) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (KMType.getType(valuePtr) != TEXT_STRING_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short ptr = KMType.instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_TEXT_STR_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), keyPtr); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), valuePtr); + return ptr; + } + + public static KMCosePairTextStringTag cast(short ptr) { + byte[] heap = repository.getHeap(); + if (heap[ptr] != COSE_PAIR_TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + // Validate the value pointer. + short valuePtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4)); + if (KMType.getType(valuePtr) != TEXT_STRING_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static boolean isKeyValueValid(short keyVal) { + short index = 0; + while (index < (short) keys.length) { + if ((byte) (keyVal & 0xFF) == keys[index]) { + return true; + } + index++; + } + return false; + } + + public short getValueType() { + return TEXT_STRING_TYPE; + } + + @Override + public short getKeyPtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_TXT_STR_VAL_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + @Override + public short getValuePtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_TXT_STR_VAL_OFFSET] + TLV_HEADER_SIZE + 4)); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMDecoder.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMDecoder.java new file mode 100644 index 0000000..dd05b13 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMDecoder.java @@ -0,0 +1,781 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import com.android.javacard.seprovider.KMException; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; + +/** + * This class decodes the CBOR format data into a KMType structure. It interprets the input CBOR + * format using the input expression provided. Validation of KeyMint tags and tag types happens in + * the process of decoding, while constructing the subtype of a KMType structure. + */ +public class KMDecoder { + + // major types + private static final short UINT_TYPE = 0x00; + private static final short NEG_INT_TYPE = 0x20; + private static final short BYTES_TYPE = 0x40; + private static final short TSTR_TYPE = 0x60; + private static final short ARRAY_TYPE = 0x80; + private static final short MAP_TYPE = 0xA0; + private static final short SIMPLE_VALUE_TYPE = 0xE0; + private static final short SEMANTIC_TAG_TYPE = 0xC0; + + // masks + private static final short ADDITIONAL_MASK = 0x1F; + private static final short MAJOR_TYPE_MASK = 0xE0; + + // value length + private static final short UINT8_LENGTH = 0x18; + private static final short UINT16_LENGTH = 0x19; + private static final short UINT32_LENGTH = 0x1A; + private static final short UINT64_LENGTH = 0x1B; + + private static final byte SCRATCH_BUF_SIZE = 6; + private static final byte START_OFFSET = 0; + private static final byte LEN_OFFSET = 2; + private static final byte TAG_KEY_OFFSET = 4; + private Object[] bufferRef; + private short[] scratchBuf; + + public KMDecoder() { + bufferRef = JCSystem.makeTransientObjectArray((short) 1, JCSystem.CLEAR_ON_RESET); + scratchBuf = JCSystem.makeTransientShortArray(SCRATCH_BUF_SIZE, JCSystem.CLEAR_ON_RESET); + bufferRef[0] = null; + scratchBuf[START_OFFSET] = (short) 0; + scratchBuf[LEN_OFFSET] = (short) 0; + scratchBuf[TAG_KEY_OFFSET] = (short) 0; + } + + public short decode(short expression, byte[] buffer, short startOff, short length) { + bufferRef[0] = buffer; + scratchBuf[START_OFFSET] = startOff; + scratchBuf[LEN_OFFSET] = (short) (startOff + length); + return decode(expression); + } + + public short decodeArray(short exp, byte[] buffer, short startOff, short length) { + bufferRef[0] = buffer; + scratchBuf[START_OFFSET] = startOff; + scratchBuf[LEN_OFFSET] = (short) (startOff + length); + short payloadLength = readMajorTypeWithPayloadLength(ARRAY_TYPE); + short expLength = KMArray.cast(exp).length(); + if (payloadLength > expLength) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + short index = 0; + short obj; + short type; + short arrPtr = KMArray.instance(payloadLength); + while (index < payloadLength) { + type = KMArray.cast(exp).get(index); + obj = decode(type); + KMArray.cast(arrPtr).add(index, obj); + index++; + } + return arrPtr; + } + + private short decode(short exp) { + byte type = KMType.getType(exp); + switch (type) { + case KMType.BYTE_BLOB_TYPE: + return decodeByteBlob(exp); + case KMType.TEXT_STRING_TYPE: + return decodeTstr(exp); + case KMType.INTEGER_TYPE: + return decodeInteger(exp); + case KMType.SIMPLE_VALUE_TYPE: + return decodeSimpleValue(exp); + case KMType.SEMANTIC_TAG_TYPE: + return decodeSemanticTagValue(exp); + case KMType.NEG_INTEGER_TYPE: + return decodeNegInteger(exp); + case KMType.ARRAY_TYPE: + return decodeArray(exp); + case KMType.MAP_TYPE: + return decodeMap(exp); + case KMType.ENUM_TYPE: + return decodeEnum(exp); + case KMType.KEY_PARAM_TYPE: + return decodeKeyParam(exp); + case KMType.KEY_CHAR_TYPE: + return decodeKeyChar(exp); + case KMType.VERIFICATION_TOKEN_TYPE: + return decodeVerificationToken(exp); + case KMType.HMAC_SHARING_PARAM_TYPE: + return decodeHmacSharingParam(exp); + case KMType.HW_AUTH_TOKEN_TYPE: + return decodeHwAuthToken(exp); + case KMType.COSE_KEY_TYPE: + case KMType.COSE_HEADERS_TYPE: + case KMType.COSE_CERT_PAYLOAD_TYPE: + return decodeCoseMap(exp); + case KMType.COSE_PAIR_TAG_TYPE: + short tagValueType = KMCosePairTagType.getTagValueType(exp); + return decodeCosePairTag(tagValueType, exp); + case KMType.TAG_TYPE: + short tagType = KMTag.getTagType(exp); + return decodeTag(tagType, exp); + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + return 0; + } + } + + private short decodeTag(short tagType, short exp) { + switch (tagType) { + case KMType.BIGNUM_TAG: + return decodeBignumTag(exp); + case KMType.BYTES_TAG: + return decodeBytesTag(exp); + case KMType.BOOL_TAG: + return decodeBoolTag(exp); + case KMType.UINT_TAG: + case KMType.ULONG_TAG: + case KMType.DATE_TAG: + return decodeIntegerTag(exp); + case KMType.ULONG_ARRAY_TAG: + case KMType.UINT_ARRAY_TAG: + return decodeIntegerArrayTag(exp); + case KMType.ENUM_TAG: + return decodeEnumTag(exp); + case KMType.ENUM_ARRAY_TAG: + return decodeEnumArrayTag(exp); + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + return 0; + } + } + + private short decodeVerificationToken(short exp) { + short vals = decode(KMVerificationToken.cast(exp).getVals()); + return KMVerificationToken.instance(vals); + } + + private short decodeHwAuthToken(short exp) { + short vals = decode(KMHardwareAuthToken.cast(exp).getVals()); + return KMHardwareAuthToken.instance(vals); + } + + private short decodeHmacSharingParam(short exp) { + short vals = decode(KMHmacSharingParameters.cast(exp).getVals()); + return KMHmacSharingParameters.instance(vals); + } + + private short decodeKeyChar(short exp) { + short vals = decode(KMKeyCharacteristics.cast(exp).getVals()); + return KMKeyCharacteristics.instance(vals); + } + + private short decodeCosePairKey(short exp) { + byte[] buffer = (byte[]) bufferRef[0]; + short startOff = scratchBuf[START_OFFSET]; + short keyPtr = (short) 0; + // Cose Key should be always either UINT or Negative int + if ((buffer[startOff] & MAJOR_TYPE_MASK) == UINT_TYPE) { + keyPtr = decodeInteger(exp); + } else if ((buffer[startOff] & MAJOR_TYPE_MASK) == NEG_INT_TYPE) { + keyPtr = decodeNegInteger(exp); + } else { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + return keyPtr; + } + + private short decodeCosePairSimpleValueTag(short exp) { + short keyPtr = decodeCosePairKey((KMCosePairSimpleValueTag.cast(exp).getKeyPtr())); + short valuePtr = decode(KMCosePairSimpleValueTag.cast(exp).getValuePtr()); + return KMCosePairSimpleValueTag.instance(keyPtr, valuePtr); + } + + private short decodeCosePairIntegerValueTag(short exp) { + short keyPtr = decodeCosePairKey((KMCosePairIntegerTag.cast(exp).getKeyPtr())); + short valuePtr = decode(KMCosePairIntegerTag.cast(exp).getValuePtr()); + return KMCosePairIntegerTag.instance(keyPtr, valuePtr); + } + + private short decodeCosePairNegIntegerTag(short exp) { + short keyPtr = decodeCosePairKey((KMCosePairNegIntegerTag.cast(exp).getKeyPtr())); + short valuePtr = decode(KMCosePairNegIntegerTag.cast(exp).getValuePtr()); + return KMCosePairNegIntegerTag.instance(keyPtr, valuePtr); + } + + private short decodeCosePairTxtStringTag(short exp) { + short keyPtr = decodeCosePairKey((KMCosePairTextStringTag.cast(exp).getKeyPtr())); + short valuePtr = decode(KMCosePairTextStringTag.cast(exp).getValuePtr()); + return KMCosePairTextStringTag.instance(keyPtr, valuePtr); + } + + private short decodeCosePairCoseKeyTag(short exp) { + short keyPtr = decodeCosePairKey((KMCosePairCoseKeyTag.cast(exp).getKeyPtr())); + short valuePtr = decode(KMCosePairCoseKeyTag.cast(exp).getValuePtr()); + return KMCosePairCoseKeyTag.instance(keyPtr, valuePtr); + } + + private short decodeCosePairByteBlobTag(short exp) { + short keyPtr = decodeCosePairKey((KMCosePairByteBlobTag.cast(exp).getKeyPtr())); + short valuePtr = decode(KMCosePairByteBlobTag.cast(exp).getValuePtr()); + return KMCosePairByteBlobTag.instance(keyPtr, valuePtr); + } + + private short peekCosePairTagType() { + byte[] buffer = (byte[]) bufferRef[0]; + short startOff = scratchBuf[START_OFFSET]; + // This decoder is confined to support only key and value types which are required for remote + // key provisioning. So keys of type (int / uint) and values of type (int / uint / simple / bstr + // / tstr / Cosekey) only are supported. + if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE + && (buffer[startOff] & MAJOR_TYPE_MASK) != NEG_INT_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + + short additionalMask = (short) (buffer[startOff] & ADDITIONAL_MASK); + short increment = 0; + if (additionalMask < UINT8_LENGTH) { + increment++; + } else if (additionalMask == UINT8_LENGTH) { + increment += 2; + } else if (additionalMask == UINT16_LENGTH) { + increment += 3; + } else if (additionalMask == UINT32_LENGTH) { + increment += 5; + } else { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short majorType = (short) (buffer[(short) (startOff + increment)] & MAJOR_TYPE_MASK); + short tagValueType = 0; + if (majorType == BYTES_TYPE) { + tagValueType = KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE; + } else if (majorType == UINT_TYPE) { + tagValueType = KMType.COSE_PAIR_INT_TAG_TYPE; + } else if (majorType == NEG_INT_TYPE) { + tagValueType = KMType.COSE_PAIR_NEG_INT_TAG_TYPE; + } else if (majorType == MAP_TYPE) { + tagValueType = KMType.COSE_PAIR_COSE_KEY_TAG_TYPE; + } else if (majorType == SIMPLE_VALUE_TYPE) { + tagValueType = KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE; + } else if (majorType == TSTR_TYPE) { + tagValueType = KMType.COSE_PAIR_TEXT_STR_TAG_TYPE; + } else { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + return tagValueType; + } + + private short decodeCosePairTag(short tagValueType, short exp) { + switch (tagValueType) { + case KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE: + return decodeCosePairByteBlobTag(exp); + case KMType.COSE_PAIR_NEG_INT_TAG_TYPE: + return decodeCosePairNegIntegerTag(exp); + case KMType.COSE_PAIR_INT_TAG_TYPE: + return decodeCosePairIntegerValueTag(exp); + case KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE: + return decodeCosePairSimpleValueTag(exp); + case KMType.COSE_PAIR_COSE_KEY_TAG_TYPE: + return decodeCosePairCoseKeyTag(exp); + case KMType.COSE_PAIR_TEXT_STR_TAG_TYPE: + return decodeCosePairTxtStringTag(exp); + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + return 0; + } + } + + private short decodeCoseMap(short exp) { + short payloadLength = readMajorTypeWithPayloadLength(MAP_TYPE); + // get allowed key pairs + short allowedKeyPairs = KMCoseMap.getVals(exp); + short vals = KMArray.instance(payloadLength); + short length = KMArray.cast(allowedKeyPairs).length(); + short index = 0; + boolean tagFound; + short tagInd; + short cosePairTagType; + short tagClass; + short allowedType; + short obj; + + // For each tag in payload ... + while (index < payloadLength) { + tagFound = false; + tagInd = 0; + cosePairTagType = peekCosePairTagType(); + // Check against the allowed tags ... + while (tagInd < length) { + tagClass = KMArray.cast(allowedKeyPairs).get(tagInd); + allowedType = KMCosePairTagType.getTagValueType(tagClass); + if (allowedType == cosePairTagType) { + obj = decode(tagClass); + KMArray.cast(vals).add(index, obj); + tagFound = true; + break; + } + tagInd++; + } + if (!tagFound) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } else { + index++; + } + } + return KMCoseMap.createInstanceFromType(exp, vals); + } + + private short decodeKeyParam(short exp) { + short payloadLength = readMajorTypeWithPayloadLength(MAP_TYPE); + // allowed tags + short allowedTags = KMKeyParameters.cast(exp).getVals(); + short tagRule = KMArray.cast(allowedTags).get((short) 0); + boolean ignoreInvalidTags = KMEnum.cast(tagRule).getVal() == KMType.IGNORE_INVALID_TAGS; + short vals = KMArray.instance(payloadLength); + short length = KMArray.cast(allowedTags).length(); + short index = 0; + boolean tagFound; + short tagInd; + short tagType; + short tagClass; + short allowedType; + short obj; + short arrPos = 0; + // For each tag in payload ... + while (index < payloadLength) { + tagFound = false; + tagInd = 1; + tagType = peekTagType(); + // Check against the allowed tags ... + while (tagInd < length) { + tagClass = KMArray.cast(allowedTags).get(tagInd); + allowedType = KMTag.getTagType(tagClass); + // If it is part of allowed tags ... + if (tagType == allowedType) { + // then decodeByteBlob and add that to the array. + try { + tagFound = true; + obj = decode(tagClass); + KMArray.cast(vals).add(arrPos++, obj); + break; + } catch (KMException e) { + if (KMException.reason() == KMError.INVALID_TAG) { + if (!ignoreInvalidTags) { + KMException.throwIt(KMError.INVALID_TAG); + } + } else { + KMException.throwIt(KMException.reason()); + } + break; + } + } + tagInd++; + } + if (!tagFound) { + KMException.throwIt(KMError.INVALID_TAG); + } else { + index++; + } + } + KMArray.cast(vals).setLength(arrPos); + return KMKeyParameters.instance(vals); + } + + private short decodeEnumArrayTag(short exp) { + readTagKey(KMEnumArrayTag.cast(exp).getTagType()); + return KMEnumArrayTag.instance( + scratchBuf[TAG_KEY_OFFSET], decode(KMEnumArrayTag.cast(exp).getValues())); + } + + private short decodeIntegerArrayTag(short exp) { + readTagKey(KMIntegerArrayTag.cast(exp).getTagType()); + // the values are array of integers. + return KMIntegerArrayTag.instance( + KMIntegerArrayTag.cast(exp).getTagType(), + scratchBuf[TAG_KEY_OFFSET], + decode(KMIntegerArrayTag.cast(exp).getValues())); + } + + private short decodeIntegerTag(short exp) { + readTagKey(KMIntegerTag.cast(exp).getTagType()); + // the value is an integer + return KMIntegerTag.instance( + KMIntegerTag.cast(exp).getTagType(), + scratchBuf[TAG_KEY_OFFSET], + decode(KMIntegerTag.cast(exp).getValue())); + } + + private short decodeBytesTag(short exp) { + readTagKey(KMByteTag.cast(exp).getTagType()); + // The value must be byte blob + return KMByteTag.instance(scratchBuf[TAG_KEY_OFFSET], decode(KMByteTag.cast(exp).getValue())); + } + + private short decodeBignumTag(short exp) { + readTagKey(KMBignumTag.cast(exp).getTagType()); + // The value must be byte blob + return KMBignumTag.instance( + scratchBuf[TAG_KEY_OFFSET], decode(KMBignumTag.cast(exp).getValue())); + } + + private short decodeMap(short exp) { + short payloadLength = readMajorTypeWithPayloadLength(MAP_TYPE); + short mapPtr = KMMap.instance(payloadLength); + short index = 0; + short type; + short keyobj; + short valueobj; + while (index < payloadLength) { + type = KMMap.cast(exp).getKey(index); + keyobj = decode(type); + type = KMMap.cast(exp).getKeyValue(index); + valueobj = decode(type); + KMMap.cast(mapPtr).add(index, keyobj, valueobj); + index++; + } + return mapPtr; + } + + private short decodeArray(short exp) { + short payloadLength = readMajorTypeWithPayloadLength(ARRAY_TYPE); + short arrPtr = KMArray.instance(payloadLength); + short index = 0; + short type; + short obj; + // check whether array contains one type of objects or multiple types + if (KMArray.cast(exp).containedType() + == KMType.INVALID_VALUE) { // multiple types specified by expression. + if (KMArray.cast(exp).length() != KMArray.ANY_ARRAY_LENGTH) { + if (KMArray.cast(exp).length() != payloadLength) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + } + while (index < payloadLength) { + type = KMArray.cast(exp).get(index); + obj = decode(type); + KMArray.cast(arrPtr).add(index, obj); + index++; + } + } else { // Array is a Vector containing objects of one type + type = KMArray.cast(exp).containedType(); + while (index < payloadLength) { + obj = decode(type); + KMArray.cast(arrPtr).add(index, obj); + index++; + } + } + return arrPtr; + } + + private short decodeEnumTag(short exp) { + readTagKey(KMEnumTag.cast(exp).getTagType()); + byte[] buffer = (byte[]) bufferRef[0]; + short startOff = scratchBuf[START_OFFSET]; + // Enum Tag value will always be integer with max 1 byte length. + if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short len = (short) (buffer[startOff] & ADDITIONAL_MASK); + byte enumVal = 0; + if (len > UINT8_LENGTH) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + if (len < UINT8_LENGTH) { + enumVal = (byte) (len & ADDITIONAL_MASK); + incrementStartOff((short) 1); + } else if (len == UINT8_LENGTH) { + incrementStartOff((short) 1); + // startOff is incremented so update the startOff + // with latest value before using it. + startOff = scratchBuf[START_OFFSET]; + enumVal = buffer[startOff]; + incrementStartOff((short) 1); + } + return KMEnumTag.instance(scratchBuf[TAG_KEY_OFFSET], enumVal); + } + + private short decodeBoolTag(short exp) { + readTagKey(KMBoolTag.cast(exp).getTagType()); + byte[] buffer = (byte[]) bufferRef[0]; + short startOff = scratchBuf[START_OFFSET]; + // BOOL Tag is a leaf node and it must always have tiny encoded uint value = 1. + if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if ((byte) (buffer[startOff] & ADDITIONAL_MASK) != 0x01) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + incrementStartOff((short) 1); + return KMBoolTag.instance(scratchBuf[TAG_KEY_OFFSET]); + } + + private short decodeEnum(short exp) { + byte[] buffer = (byte[]) bufferRef[0]; + short startOff = scratchBuf[START_OFFSET]; + // Enum value will always be integer with max 1 byte length. + if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short len = (short) (buffer[startOff] & ADDITIONAL_MASK); + byte enumVal; + if (len > UINT8_LENGTH) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + if (len < UINT8_LENGTH) { + enumVal = (byte) (len & ADDITIONAL_MASK); + incrementStartOff((short) 1); + } else { + incrementStartOff((short) 1); + // startOff is incremented so update the startOff + // with latest value before using it. + startOff = scratchBuf[START_OFFSET]; + enumVal = buffer[startOff]; + incrementStartOff((short) 1); + } + return KMEnum.instance(KMEnum.cast(exp).getEnumType(), enumVal); + } + + private short decodeSimpleValue(short exp) { + short startOff = scratchBuf[START_OFFSET]; + byte[] buffer = (byte[]) bufferRef[0]; + if ((buffer[startOff] & MAJOR_TYPE_MASK) != SIMPLE_VALUE_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + byte addInfo = (byte) (buffer[startOff] & ADDITIONAL_MASK); + incrementStartOff((short) 1); + return KMSimpleValue.instance(addInfo); + } + + private short decodeSemanticTagValue(short exp) { + // Decode tag. + short tag = readMajorTypeWithInteger(exp, SEMANTIC_TAG_TYPE, UINT32_LENGTH); + // Decode value pointer. + short valuePtr = decode(KMSemanticTag.cast(exp).getValuePtr()); + return KMSemanticTag.instance(tag, valuePtr); + } + + private short readMajorTypeWithInteger(short exp, short majorType, short maxLimit) { + short inst; + short startOff = scratchBuf[START_OFFSET]; + byte[] buffer = (byte[]) bufferRef[0]; + if ((buffer[startOff] & MAJOR_TYPE_MASK) != majorType) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short len = (short) (buffer[startOff] & ADDITIONAL_MASK); + if (len > maxLimit) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + incrementStartOff((short) 1); + // startOff is incremented so update the startOff + // with latest value before using it. + startOff = scratchBuf[START_OFFSET]; + if (len < UINT8_LENGTH) { + inst = KMInteger.uint_8((byte) (len & ADDITIONAL_MASK)); + } else if (len == UINT8_LENGTH) { + inst = KMInteger.instance(buffer, startOff, (short) 1); + incrementStartOff((short) 1); + } else if (len == UINT16_LENGTH) { + inst = KMInteger.instance(buffer, startOff, (short) 2); + incrementStartOff((short) 2); + } else if (len == UINT32_LENGTH) { + inst = KMInteger.instance(buffer, startOff, (short) 4); + incrementStartOff((short) 4); + } else { + inst = KMInteger.instance(buffer, startOff, (short) 8); + incrementStartOff((short) 8); + } + return inst; + } + + private short decodeInteger(short exp) { + return readMajorTypeWithInteger(exp, UINT_TYPE, UINT64_LENGTH); + } + + private short decodeNegIntegerValue(byte addInfo, byte[] buf, short startOffset) { + short inst; + short len = 0; + short scratchpad; + if (addInfo < UINT8_LENGTH) { + addInfo = (byte) (-1 - addInfo); + inst = KMNInteger.uint_8(addInfo); + } else { + switch (addInfo) { + case UINT8_LENGTH: + len = 1; + break; + case UINT16_LENGTH: + len = 2; + break; + case UINT32_LENGTH: + len = 4; + break; + case UINT64_LENGTH: + len = 8; + break; + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + // Do (-1 - N), as per cbor negative integer decoding rule. + // N is the integer value. + scratchpad = KMByteBlob.instance((short) (len * 3)); + byte[] input = KMByteBlob.cast(scratchpad).getBuffer(); + short offset = KMByteBlob.cast(scratchpad).getStartOff(); + Util.arrayFillNonAtomic(input, offset, len, (byte) -1); + Util.arrayCopyNonAtomic(buf, startOffset, input, (short) (offset + len), len); + KMUtils.subtract( + input, offset, (short) (offset + len), (short) (offset + 2 * len), (byte) len); + inst = KMNInteger.instance(input, (short) (offset + 2 * len), len); + incrementStartOff(len); + } + return inst; + } + + private short decodeNegInteger(short exp) { + short startOff = scratchBuf[START_OFFSET]; + byte[] buffer = (byte[]) bufferRef[0]; + if ((buffer[startOff] & MAJOR_TYPE_MASK) != NEG_INT_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short len = (short) (buffer[startOff] & ADDITIONAL_MASK); + if (len > UINT64_LENGTH) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + incrementStartOff((short) 1); + // startOff is incremented so update the startOff + // with latest value before using it. + startOff = scratchBuf[START_OFFSET]; + return decodeNegIntegerValue((byte) len, buffer, startOff); + } + + private short decodeTstr(short exp) { + short payloadLength = readMajorTypeWithPayloadLength(TSTR_TYPE); + short inst = + KMTextString.instance((byte[]) bufferRef[0], scratchBuf[START_OFFSET], payloadLength); + incrementStartOff(payloadLength); + return inst; + } + + private short decodeByteBlob(short exp) { + short payloadLength = readMajorTypeWithPayloadLength(BYTES_TYPE); + short inst = + KMByteBlob.instance((byte[]) bufferRef[0], scratchBuf[START_OFFSET], payloadLength); + incrementStartOff(payloadLength); + return inst; + } + + private short peekTagType() { + byte[] buffer = (byte[]) bufferRef[0]; + short startOff = scratchBuf[START_OFFSET]; + if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + + if ((short) (buffer[startOff] & ADDITIONAL_MASK) != UINT32_LENGTH) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + return (short) + ((Util.makeShort(buffer[(short) (startOff + 1)], buffer[(short) (startOff + 2)])) + & KMType.TAG_TYPE_MASK); + } + + private void readTagKey(short expectedTagType) { + byte[] buffer = (byte[]) bufferRef[0]; + short startOff = scratchBuf[START_OFFSET]; + if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if ((byte) (buffer[startOff] & ADDITIONAL_MASK) != UINT32_LENGTH) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + incrementStartOff((short) 1); + short tagType = readShort(); + scratchBuf[TAG_KEY_OFFSET] = readShort(); + if (tagType != expectedTagType) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + } + + // payload length cannot be more then 16 bits. + private short readMajorTypeWithPayloadLength(short majorType) { + short payloadLength; + byte val = readByte(); + if ((short) (val & MAJOR_TYPE_MASK) != majorType) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short lenType = (short) (val & ADDITIONAL_MASK); + if (lenType > UINT16_LENGTH) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + if (lenType < UINT8_LENGTH) { + payloadLength = lenType; + } else if (lenType == UINT8_LENGTH) { + payloadLength = (short) (readByte() & 0xFF); + } else { + payloadLength = readShort(); + } + return payloadLength; + } + + private short readShort() { + byte[] buffer = (byte[]) bufferRef[0]; + short startOff = scratchBuf[START_OFFSET]; + short val = Util.makeShort(buffer[startOff], buffer[(short) (startOff + 1)]); + incrementStartOff((short) 2); + return val; + } + + private byte readByte() { + short startOff = scratchBuf[START_OFFSET]; + byte val = ((byte[]) bufferRef[0])[startOff]; + incrementStartOff((short) 1); + return val; + } + + private void incrementStartOff(short inc) { + scratchBuf[START_OFFSET] += inc; + if (scratchBuf[START_OFFSET] > scratchBuf[LEN_OFFSET]) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + } + + public short readKeyblobVersion(byte[] buf, short bufOffset, short bufLen) { + bufferRef[0] = buf; + scratchBuf[START_OFFSET] = bufOffset; + scratchBuf[LEN_OFFSET] = (short) (bufOffset + bufLen); + short arrayLen = readMajorTypeWithPayloadLength(ARRAY_TYPE); + if (arrayLen == 0) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short version = KMType.INVALID_VALUE; + try { + version = decodeInteger(KMInteger.exp()); + } catch (Exception e) { + // Fail to decode Integer. It can happen if it is an old KeyBlob. + } + return version; + } + + public short readCertificateChainHeaderLen(byte[] buf, short bufOffset, short bufLen) { + bufferRef[0] = buf; + scratchBuf[START_OFFSET] = bufOffset; + scratchBuf[LEN_OFFSET] = (short) (bufOffset + bufLen); + readMajorTypeWithPayloadLength(BYTES_TYPE); + return (short) (scratchBuf[START_OFFSET] - bufOffset); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMEncoder.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMEncoder.java new file mode 100644 index 0000000..65394bd --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMEncoder.java @@ -0,0 +1,777 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import com.android.javacard.seprovider.KMException; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; + +/** + * This class encodes KMType structures to a cbor format data recursively. Encoded bytes are written + * on the buffer provided by the caller. An exception will be thrown if the encoded data length is + * greater than the buffer length provided. + */ +public class KMEncoder { + + // major types + private static final byte UINT_TYPE = 0x00; + private static final byte NEG_INT_TYPE = 0x20; + private static final byte BYTES_TYPE = 0x40; + private static final byte TSTR_TYPE = 0x60; + private static final byte ARRAY_TYPE = (byte) 0x80; + private static final byte MAP_TYPE = (byte) 0xA0; + private static final byte SIMPLE_VALUE_TYPE = (byte) 0xE0; + private static final byte SEMANTIC_TAG_TYPE = (byte) 0xC0; + + // masks + private static final byte ADDITIONAL_MASK = 0x1F; + + // value length + private static final byte UINT8_LENGTH = (byte) 0x18; + private static final byte UINT16_LENGTH = (byte) 0x19; + private static final byte UINT32_LENGTH = (byte) 0x1A; + private static final byte UINT64_LENGTH = (byte) 0x1B; + private static final short TINY_PAYLOAD = 0x17; + private static final short SHORT_PAYLOAD = 0x100; + private static final byte STACK_SIZE = 50; + private static final byte SCRATCH_BUF_SIZE = 6; + private static final byte START_OFFSET = 0; + private static final byte LEN_OFFSET = 2; + private static final byte STACK_PTR_OFFSET = 4; + + private Object[] bufferRef; + private short[] scratchBuf; + private short[] stack; + + public KMEncoder() { + bufferRef = JCSystem.makeTransientObjectArray((short) 1, JCSystem.CLEAR_ON_RESET); + scratchBuf = JCSystem.makeTransientShortArray(SCRATCH_BUF_SIZE, JCSystem.CLEAR_ON_RESET); + stack = JCSystem.makeTransientShortArray(STACK_SIZE, JCSystem.CLEAR_ON_RESET); + bufferRef[0] = null; + scratchBuf[START_OFFSET] = (short) 0; + scratchBuf[LEN_OFFSET] = (short) 0; + scratchBuf[STACK_PTR_OFFSET] = (short) 0; + } + + private void push(short objPtr) { + stack[scratchBuf[STACK_PTR_OFFSET]] = objPtr; + scratchBuf[STACK_PTR_OFFSET]++; + } + + private short pop() { + scratchBuf[STACK_PTR_OFFSET]--; + return stack[scratchBuf[STACK_PTR_OFFSET]]; + } + + private void encode(short obj) { + push(obj); + } + + /** + * This functions encodes the given object into the provider buffer space in cbor format. + * + * @param object Object to be encoded into cbor data. + * @param buffer Output where cbor data is copied. + * @param startOff is the start offset of the buffer. + * @param bufLen length of the buffer + * @param encoderOutLimitLen excepted encoded output length. + * @return length of the encoded buffer. + */ + public short encode( + short object, byte[] buffer, short startOff, short bufLen, short encoderOutLimitLen) { + scratchBuf[STACK_PTR_OFFSET] = 0; + bufferRef[0] = buffer; + scratchBuf[START_OFFSET] = startOff; + if ((short) (startOff + encoderOutLimitLen) > bufLen) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + scratchBuf[LEN_OFFSET] = (short) (startOff + encoderOutLimitLen); + push(object); + encode(); + return (short) (scratchBuf[START_OFFSET] - startOff); + } + + public short encode(short object, byte[] buffer, short startOff, short bufLen) { + return encode(object, buffer, startOff, bufLen, (short) (bufLen - startOff)); + } + + // array{KMError.OK,Array{KMByteBlobs}} + public short encodeCert(byte[] certBuffer, short bufferStart, short certStart, short certLength) { + if (bufferStart > certStart) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + bufferRef[0] = certBuffer; + scratchBuf[START_OFFSET] = certStart; + scratchBuf[LEN_OFFSET] = (short) (certStart + 1); + // Byte Header + cert length + scratchBuf[START_OFFSET] -= getEncodedBytesLength(certLength); + // Array header - 1 elements i.e. 1 byte + scratchBuf[START_OFFSET]--; + if (scratchBuf[START_OFFSET] < bufferStart) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + bufferStart = scratchBuf[START_OFFSET]; + writeMajorTypeWithLength(ARRAY_TYPE, (short) 1); // Array of 1 elements + writeMajorTypeWithLength(BYTES_TYPE, certLength); // Cert Byte Blob of length + return bufferStart; + } + + private void encode() { + while (scratchBuf[STACK_PTR_OFFSET] > 0) { + short exp = pop(); + byte type = KMType.getType(exp); + switch (type) { + case KMType.BYTE_BLOB_TYPE: + encodeByteBlob(exp); + break; + case KMType.TEXT_STRING_TYPE: + encodeTextString(exp); + break; + case KMType.INTEGER_TYPE: + encodeUnsignedInteger(exp); + break; + case KMType.SIMPLE_VALUE_TYPE: + encodeSimpleValue(exp); + break; + case KMType.NEG_INTEGER_TYPE: + encodeNegInteger(exp); + break; + case KMType.ARRAY_TYPE: + encodeArray(exp); + break; + case KMType.MAP_TYPE: + encodeMap(exp); + break; + case KMType.ENUM_TYPE: + encodeEnum(exp); + break; + case KMType.KEY_PARAM_TYPE: + encodeKeyParam(exp); + break; + case KMType.SEMANTIC_TAG_TYPE: + encodeSemanticTag(exp); + break; + case KMType.COSE_KEY_TYPE: + case KMType.COSE_HEADERS_TYPE: + case KMType.COSE_CERT_PAYLOAD_TYPE: + encodeCoseMap(exp); + break; + case KMType.KEY_CHAR_TYPE: + encodeKeyChar(exp); + break; + case KMType.VERIFICATION_TOKEN_TYPE: + encodeVeriToken(exp); + break; + case KMType.HMAC_SHARING_PARAM_TYPE: + encodeHmacSharingParam(exp); + break; + case KMType.HW_AUTH_TOKEN_TYPE: + encodeHwAuthToken(exp); + break; + case KMType.TAG_TYPE: + short tagType = KMTag.getTagType(exp); + encodeTag(tagType, exp); + break; + case KMType.COSE_PAIR_TAG_TYPE: + short cosePairTagType = KMCosePairTagType.getTagValueType(exp); + encodeCosePairTag(cosePairTagType, exp); + break; + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + } + } + + private void encodeCosePairIntegerTag(short exp) { + KMCosePairIntegerTag cosePairIntTag = KMCosePairIntegerTag.cast(exp); + // push key and value ptr in stack to get encoded. + encode(cosePairIntTag.getValuePtr()); + encode(cosePairIntTag.getKeyPtr()); + } + + private void encodeCosePairByteBlobTag(short exp) { + KMCosePairByteBlobTag cosePairByteBlobTag = KMCosePairByteBlobTag.cast(exp); + // push key and value ptr in stack to get encoded. + encode(cosePairByteBlobTag.getValuePtr()); + encode(cosePairByteBlobTag.getKeyPtr()); + } + + private void encodeCosePairCoseKeyTag(short exp) { + KMCosePairCoseKeyTag cosePairCoseKeyTag = KMCosePairCoseKeyTag.cast(exp); + // push key and value ptr in stack to get encoded. + encode(cosePairCoseKeyTag.getValuePtr()); + encode(cosePairCoseKeyTag.getKeyPtr()); + } + + private void encodeCosePairTextStringTag(short exp) { + KMCosePairTextStringTag cosePairTextStringTag = KMCosePairTextStringTag.cast(exp); + // push key and value ptr in stack to get encoded. + encode(cosePairTextStringTag.getValuePtr()); + encode(cosePairTextStringTag.getKeyPtr()); + } + + private void encodeCosePairSimpleValueTag(short exp) { + KMCosePairSimpleValueTag cosePairSimpleValueTag = KMCosePairSimpleValueTag.cast(exp); + // push key and value ptr in stack to get encoded. + encode(cosePairSimpleValueTag.getValuePtr()); + encode(cosePairSimpleValueTag.getKeyPtr()); + } + + private void encodeCosePairNegIntegerTag(short exp) { + KMCosePairNegIntegerTag cosePairNegIntegerTag = KMCosePairNegIntegerTag.cast(exp); + // push key and value ptr in stack to get encoded. + encode(cosePairNegIntegerTag.getValuePtr()); + encode(cosePairNegIntegerTag.getKeyPtr()); + } + + private void encodeCosePairTag(short tagType, short exp) { + switch (tagType) { + case KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE: + encodeCosePairByteBlobTag(exp); + return; + case KMType.COSE_PAIR_INT_TAG_TYPE: + encodeCosePairIntegerTag(exp); + return; + case KMType.COSE_PAIR_NEG_INT_TAG_TYPE: + encodeCosePairNegIntegerTag(exp); + return; + case KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE: + encodeCosePairSimpleValueTag(exp); + return; + case KMType.COSE_PAIR_TEXT_STR_TAG_TYPE: + encodeCosePairTextStringTag(exp); + return; + case KMType.COSE_PAIR_COSE_KEY_TAG_TYPE: + encodeCosePairCoseKeyTag(exp); + return; + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + } + + private void encodeTag(short tagType, short exp) { + switch (tagType) { + case KMType.BIGNUM_TAG: + encodeBignumTag(exp); + return; + case KMType.BYTES_TAG: + encodeBytesTag(exp); + return; + case KMType.BOOL_TAG: + encodeBoolTag(exp); + return; + case KMType.UINT_TAG: + case KMType.ULONG_TAG: + case KMType.DATE_TAG: + encodeIntegerTag(exp); + return; + case KMType.ULONG_ARRAY_TAG: + case KMType.UINT_ARRAY_TAG: + encodeIntegerArrayTag(exp); + return; + case KMType.ENUM_TAG: + encodeEnumTag(exp); + return; + case KMType.ENUM_ARRAY_TAG: + encodeEnumArrayTag(exp); + return; + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + } + + private void encodeCoseMap(short obj) { + encodeAsMap(KMCoseMap.getVals(obj)); + } + + private void encodeKeyParam(short obj) { + encodeAsMap(KMKeyParameters.cast(obj).getVals()); + } + + private void encodeKeyChar(short obj) { + encode(KMKeyCharacteristics.cast(obj).getVals()); + } + + private void encodeVeriToken(short obj) { + encode(KMVerificationToken.cast(obj).getVals()); + } + + private void encodeHwAuthToken(short obj) { + encode(KMHardwareAuthToken.cast(obj).getVals()); + } + + private void encodeHmacSharingParam(short obj) { + encode(KMHmacSharingParameters.cast(obj).getVals()); + } + + private void encodeArray(short obj) { + writeMajorTypeWithLength(ARRAY_TYPE, KMArray.cast(obj).length()); + short len = KMArray.cast(obj).length(); + short index = (short) (len - 1); + short subObj; + while (index >= 0) { + subObj = KMArray.cast(obj).get(index); + if (subObj != KMType.INVALID_VALUE) { + encode(subObj); + } + index--; + } + } + + public void encodeArrayOnlyLength(short arrLength, byte[] buffer, short offset, short length) { + bufferRef[0] = buffer; + scratchBuf[START_OFFSET] = offset; + scratchBuf[LEN_OFFSET] = (short) (offset + length + 1); + writeMajorTypeWithLength(ARRAY_TYPE, length); + } + + private void encodeMap(short obj) { + writeMajorTypeWithLength(MAP_TYPE, KMMap.cast(obj).length()); + short len = KMMap.cast(obj).length(); + short index = (short) (len - 1); + while (index >= 0) { + encode(KMMap.cast(obj).getKeyValue(index)); + encode(KMMap.cast(obj).getKey(index)); + index--; + } + } + + private void encodeAsMap(short obj) { + writeMajorTypeWithLength(MAP_TYPE, KMArray.cast(obj).length()); + short len = KMArray.cast(obj).length(); + short index = (short) (len - 1); + short inst; + while (index >= 0) { + inst = KMArray.cast(obj).get(index); + encode(inst); + index--; + } + } + + private void encodeIntegerArrayTag(short obj) { + writeTag(KMIntegerArrayTag.cast(obj).getTagType(), KMIntegerArrayTag.cast(obj).getKey()); + encode(KMIntegerArrayTag.cast(obj).getValues()); + } + + private void encodeEnumArrayTag(short obj) { + writeTag(KMEnumArrayTag.cast(obj).getTagType(), KMEnumArrayTag.cast(obj).getKey()); + encode(KMEnumArrayTag.cast(obj).getValues()); + } + + private void encodeIntegerTag(short obj) { + writeTag(KMIntegerTag.cast(obj).getTagType(), KMIntegerTag.cast(obj).getKey()); + encode(KMIntegerTag.cast(obj).getValue()); + } + + private void encodeBignumTag(short obj) { + writeTag(KMBignumTag.getTagType(obj), KMBignumTag.getKey(obj)); + encode(KMBignumTag.cast(obj).getValue()); + } + + private void encodeBytesTag(short obj) { + writeTag(KMByteTag.cast(obj).getTagType(), KMByteTag.cast(obj).getKey()); + encode(KMByteTag.cast(obj).getValue()); + } + + private void encodeBoolTag(short obj) { + writeTag(KMBoolTag.cast(obj).getTagType(), KMBoolTag.cast(obj).getKey()); + writeByteValue(KMBoolTag.cast(obj).getVal()); + } + + private void encodeEnumTag(short obj) { + writeTag(KMEnumTag.cast(obj).getTagType(), KMEnumTag.cast(obj).getKey()); + writeByteValue(KMEnumTag.cast(obj).getValue()); + } + + private void encodeEnum(short obj) { + writeByteValue(KMEnum.cast(obj).getVal()); + } + + private void encodeInteger(byte[] val, short len, short startOff, short majorType) { + // find out the most significant byte + short msbIndex = findMsb(val, startOff, len); + // find the difference between most significant byte and len + short diff = (short) (len - msbIndex); + if (diff == 0) { + writeByte((byte) (majorType | 0)); + } else if ((diff == 1) + && (val[(short) (startOff + msbIndex)] < UINT8_LENGTH) + && (val[(short) (startOff + msbIndex)] >= 0)) { + writeByte((byte) (majorType | val[(short) (startOff + msbIndex)])); + } else if (diff == 1) { + writeByte((byte) (majorType | UINT8_LENGTH)); + writeByte(val[(short) (startOff + msbIndex)]); + } else if (diff == 2) { + writeByte((byte) (majorType | UINT16_LENGTH)); + writeBytes(val, (short) (startOff + msbIndex), (short) 2); + } else if (diff <= 4) { + writeByte((byte) (majorType | UINT32_LENGTH)); + writeBytes(val, (short) (startOff + len - 4), (short) 4); + } else { + writeByte((byte) (majorType | UINT64_LENGTH)); + writeBytes(val, startOff, (short) 8); + } + } + + // find out the most significant byte + public short findMsb(byte[] buf, short offset, short len) { + byte index = 0; + // find out the most significant byte + while (index < len) { + if (buf[(short) (offset + index)] > 0) { + break; + } else if (buf[(short) (offset + index)] < 0) { + break; + } + index++; // index will be equal to len if value is 0. + } + return index; + } + + public void computeOnesCompliment(short msbIndex, byte[] buf, short offset, short len) { + // find the difference between most significant byte and len + short diff = (short) (len - msbIndex); + short correctedOffset = offset; + short correctedLen = len; + // The offset and length of the buffer for Short and Byte types should be + // corrected before computing the 1s compliment. The reason for doing this + // is to avoid computation of 1s compliment on the MSB bytes. + if (diff == 0) { + // Fail + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } else if (diff == 1) { + correctedOffset = (short) (offset + 3); + correctedLen = 1; + } else if (diff == 2) { + correctedOffset = (short) (offset + 2); + correctedLen = 2; + } + // For int and long values the len and offset values are always proper. + // int - 4 bytes + // long - 8 bytes. + KMUtils.computeOnesCompliment(buf, correctedOffset, correctedLen); + } + + // Encoding rule for negative Integers is taken from + // https://datatracker.ietf.org/doc/html/rfc7049#section-2.1, Major type 1. + public short handleNegIntegerEncodingRule(byte[] buf, short offset, short len) { + short msbIndex = findMsb(buf, offset, len); + // Do -1-N, where N is the negative integer + // The value of -1-N is equal to the 1s compliment of N. + computeOnesCompliment(msbIndex, buf, offset, len); + return msbIndex; + } + + // Note: This function modifies the buffer's actual value. So after encoding, restore the original + // value by calling removeNegIntegerEncodingRule(). + public short applyNegIntegerEncodingRule(byte[] buf, short offset, short len) { + return handleNegIntegerEncodingRule(buf, offset, len); + } + + public void removeNegIntegerEncodingRule( + byte[] buf, short offset, short len, short origMsbIndex) { + // Do -1-N, where N is the negative integer + // The value of -1-N is equal to the 1s compliment of N. + computeOnesCompliment(origMsbIndex, buf, offset, len); + } + + private void encodeNegInteger(short obj) { + byte[] val = KMNInteger.cast(obj).getBuffer(); + short len = KMNInteger.cast(obj).length(); + short startOff = KMNInteger.cast(obj).getStartOff(); + short msbIndex = applyNegIntegerEncodingRule(val, startOff, len); + encodeInteger(val, len, startOff, NEG_INT_TYPE); + removeNegIntegerEncodingRule(val, startOff, len, msbIndex); + } + + private void encodeSemanticTag(short obj) { + short tag = KMSemanticTag.cast(obj).getKeyPtr(); + encode(KMSemanticTag.cast(obj).getValuePtr()); + encodeInteger( + KMInteger.cast(tag).getBuffer(), + KMInteger.cast(tag).length(), + KMInteger.cast(tag).getStartOff(), + SEMANTIC_TAG_TYPE); + } + + private void encodeUnsignedInteger(short obj) { + byte[] val = KMInteger.cast(obj).getBuffer(); + short len = KMInteger.cast(obj).length(); + short startOff = KMInteger.cast(obj).getStartOff(); + encodeInteger(val, len, startOff, UINT_TYPE); + } + + private void encodeSimpleValue(short obj) { + byte value = KMSimpleValue.cast(obj).getValue(); + writeByte((byte) (SIMPLE_VALUE_TYPE | value)); + } + + private void encodeTextString(short obj) { + writeMajorTypeWithLength(TSTR_TYPE, KMTextString.cast(obj).length()); + writeBytes( + KMTextString.cast(obj).getBuffer(), + KMTextString.cast(obj).getStartOff(), + KMTextString.cast(obj).length()); + } + + public short encodeByteBlobHeader(short bufLen, byte[] buffer, short startOff, short length) { + bufferRef[0] = buffer; + scratchBuf[START_OFFSET] = startOff; + scratchBuf[LEN_OFFSET] = (short) (startOff + length + 1); + writeMajorTypeWithLength(BYTES_TYPE, bufLen); + return (short) (scratchBuf[START_OFFSET] - startOff); + } + + private void encodeByteBlob(short obj) { + writeMajorTypeWithLength(BYTES_TYPE, KMByteBlob.cast(obj).length()); + writeBytes( + KMByteBlob.cast(obj).getBuffer(), + KMByteBlob.cast(obj).getStartOff(), + KMByteBlob.cast(obj).length()); + } + + public short getEncodedLength(short ptr) { + short len = 0; + short type = KMType.getType(ptr); + switch (type) { + case KMType.BYTE_BLOB_TYPE: + len += getEncodedByteBlobLength(ptr); + break; + case KMType.TEXT_STRING_TYPE: + len += getEncodedTextStringLength(ptr); + break; + case KMType.INTEGER_TYPE: + len += getEncodedIntegerLength(ptr); + break; + case KMType.NEG_INTEGER_TYPE: + len += getEncodedNegIntegerLength(ptr); + break; + case KMType.ARRAY_TYPE: + len += getEncodedArrayLen(ptr); + break; + case KMType.MAP_TYPE: + len += getEncodedMapLen(ptr); + break; + case KMType.COSE_PAIR_TAG_TYPE: + short cosePairTagType = KMCosePairTagType.getTagValueType(ptr); + len += getEncodedCosePairTagLen(cosePairTagType, ptr); + break; + case KMType.COSE_KEY_TYPE: + case KMType.COSE_HEADERS_TYPE: + case KMType.COSE_CERT_PAYLOAD_TYPE: + len += getEncodedArrayLen(KMCoseMap.getVals(ptr)); + break; + default: + KMException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return len; + } + + private short getEncodedCosePairTagLen(short tagType, short exp) { + short length = 0; + switch (tagType) { + case KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE: + KMCosePairByteBlobTag cosePairByteBlobTag = KMCosePairByteBlobTag.cast(exp); + length = getEncodedLength(cosePairByteBlobTag.getKeyPtr()); + length += getEncodedLength(cosePairByteBlobTag.getValuePtr()); + break; + case KMType.COSE_PAIR_INT_TAG_TYPE: + KMCosePairIntegerTag cosePairIntTag = KMCosePairIntegerTag.cast(exp); + length = getEncodedLength(cosePairIntTag.getValuePtr()); + length += getEncodedLength(cosePairIntTag.getKeyPtr()); + break; + case KMType.COSE_PAIR_NEG_INT_TAG_TYPE: + KMCosePairNegIntegerTag cosePairNegIntegerTag = KMCosePairNegIntegerTag.cast(exp); + length = getEncodedLength(cosePairNegIntegerTag.getValuePtr()); + length += getEncodedLength(cosePairNegIntegerTag.getKeyPtr()); + break; + case KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE: + KMCosePairSimpleValueTag cosePairSimpleValueTag = KMCosePairSimpleValueTag.cast(exp); + length = getEncodedLength(cosePairSimpleValueTag.getValuePtr()); + length += getEncodedLength(cosePairSimpleValueTag.getKeyPtr()); + break; + case KMType.COSE_PAIR_TEXT_STR_TAG_TYPE: + KMCosePairTextStringTag cosePairTextStringTag = KMCosePairTextStringTag.cast(exp); + length = getEncodedLength(cosePairTextStringTag.getValuePtr()); + length += getEncodedLength(cosePairTextStringTag.getKeyPtr()); + break; + case KMType.COSE_PAIR_COSE_KEY_TAG_TYPE: + KMCosePairCoseKeyTag cosePairCoseKeyTag = KMCosePairCoseKeyTag.cast(exp); + length = getEncodedLength(cosePairCoseKeyTag.getValuePtr()); + length += getEncodedLength(cosePairCoseKeyTag.getKeyPtr()); + break; + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + return length; + } + + private short getEncodedMapLen(short obj) { + short mapLen = KMMap.cast(obj).length(); + short len = getEncodedBytesLength(mapLen); + short index = 0; + while (index < mapLen) { + len += getEncodedLength(KMMap.cast(obj).getKey(index)); + len += getEncodedLength(KMMap.cast(obj).getKeyValue(index)); + index++; + } + return len; + } + + private short getEncodedArrayLen(short obj) { + short arrLen = KMArray.cast(obj).length(); + short len = getEncodedBytesLength(arrLen); + short index = 0; + short subObj; + while (index < arrLen) { + subObj = KMArray.cast(obj).get(index); + if (subObj != KMType.INVALID_VALUE) { + len += getEncodedLength(subObj); + } + index++; + } + return len; + } + + public short getEncodedBytesLength(short len) { + short ret = 0; + if (len < KMEncoder.UINT8_LENGTH && len >= 0) { + ret = 1; + } else if (len >= KMEncoder.UINT8_LENGTH && len <= (short) 0x00FF) { + ret = 2; + } else if (len > (short) 0x00FF && len <= (short) 0x7FFF) { + ret = 3; + } else { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + return ret; + } + + private short getEncodedByteBlobLength(short obj) { + short len = KMByteBlob.cast(obj).length(); + len += getEncodedBytesLength(len); + return len; + } + + private short getEncodedTextStringLength(short obj) { + short len = KMTextString.cast(obj).length(); + len += getEncodedBytesLength(len); + return len; + } + + private short getEncodedNegIntegerLength(short obj) { + byte[] buf = KMNInteger.cast(obj).getBuffer(); + short len = KMNInteger.cast(obj).length(); + short offset = KMNInteger.cast(obj).getStartOff(); + short msbIndex = applyNegIntegerEncodingRule(buf, offset, len); + short ret = getEncodedIntegerLength(buf, offset, len); + removeNegIntegerEncodingRule(buf, offset, len, msbIndex); + return ret; + } + + private short getEncodedIntegerLength(byte[] val, short startOff, short len) { + short msbIndex = findMsb(val, startOff, len); + // find the difference between most significant byte and len + short diff = (short) (len - msbIndex); + switch (diff) { + case 0: + case 1: // Byte + if ((val[(short) (startOff + msbIndex)] < KMEncoder.UINT8_LENGTH) + && (val[(short) (startOff + msbIndex)] >= 0)) { + return (short) 1; + } else { + return (short) 2; + } + case 2: // Short + return (short) 3; + case 3: + case 4: // UInt32 + return (short) 5; + case 5: + case 6: + case 7: + case 8: // UInt64 + return (short) 9; + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + return 0; + } + + private short getEncodedIntegerLength(short obj) { + byte[] val = KMInteger.cast(obj).getBuffer(); + short len = KMInteger.cast(obj).length(); + short startOff = KMInteger.cast(obj).getStartOff(); + return getEncodedIntegerLength(val, startOff, len); + } + + private void writeByteValue(byte val) { + if ((val < UINT8_LENGTH) && (val >= 0)) { + writeByte((byte) (UINT_TYPE | val)); + } else { + writeByte((byte) (UINT_TYPE | UINT8_LENGTH)); + writeByte(val); + } + } + + private void writeTag(short tagType, short tagKey) { + writeByte((byte) (UINT_TYPE | UINT32_LENGTH)); + writeShort(tagType); + writeShort(tagKey); + } + + private void writeMajorTypeWithLength(byte majorType, short len) { + if (len <= TINY_PAYLOAD) { + writeByte((byte) (majorType | (byte) (len & ADDITIONAL_MASK))); + } else if (len < SHORT_PAYLOAD) { + writeByte((byte) (majorType | UINT8_LENGTH)); + writeByte((byte) (len & 0xFF)); + } else { + writeByte((byte) (majorType | UINT16_LENGTH)); + writeShort(len); + } + } + + private void writeBytes(byte[] buf, short start, short len) { + byte[] buffer = (byte[]) bufferRef[0]; + Util.arrayCopyNonAtomic(buf, start, buffer, scratchBuf[START_OFFSET], len); + incrementStartOff(len); + } + + private void writeShort(short val) { + byte[] buffer = (byte[]) bufferRef[0]; + buffer[scratchBuf[START_OFFSET]] = (byte) ((val >> 8) & 0xFF); + incrementStartOff((short) 1); + buffer[scratchBuf[START_OFFSET]] = (byte) ((val & 0xFF)); + incrementStartOff((short) 1); + } + + private void writeByte(byte val) { + byte[] buffer = (byte[]) bufferRef[0]; + buffer[scratchBuf[START_OFFSET]] = val; + incrementStartOff((short) 1); + } + + private void incrementStartOff(short inc) { + scratchBuf[START_OFFSET] += inc; + if (scratchBuf[START_OFFSET] >= scratchBuf[LEN_OFFSET]) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMEnum.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMEnum.java new file mode 100644 index 0000000..44bf477 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMEnum.java @@ -0,0 +1,166 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMEnum represents an enumeration specified in android keymaster hal specifications. It + * corresponds to uint CBOR type and it is a byte value. struct{byte ENUM_TYPE; short length; + * struct{short enumType; byte val}} + */ +public class KMEnum extends KMType { + + private static KMEnum prototype; + + // The allowed enum types. + private static short[] types = { + HARDWARE_TYPE, + KEY_FORMAT, + KEY_DERIVATION_FUNCTION, + VERIFIED_BOOT_STATE, + DEVICE_LOCKED, + USER_AUTH_TYPE, + PURPOSE, + ECCURVE, + RULE + }; + + private static Object[] enums = null; + + private KMEnum() {} + + private static KMEnum proto(short ptr) { + if (prototype == null) { + prototype = new KMEnum(); + } + KMType.instanceTable[KM_ENUM_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + return KMType.exp(ENUM_TYPE); + } + + public static KMEnum cast(short ptr) { + if (heap[ptr] != ENUM_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + 1)) == INVALID_VALUE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static short instance(short enumType) { + if (!validateEnum(enumType, NO_VALUE)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = KMType.instance(ENUM_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), enumType); + return ptr; + } + + public static short instance(short enumType, byte val) { + if (!validateEnum(enumType, val)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = KMType.instance(ENUM_TYPE, (short) 3); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), enumType); + heap[(short) (ptr + TLV_HEADER_SIZE + 2)] = val; + return ptr; + } + + private static void create() { + // The allowed enum values to corresponding enum types in the types array. + if (enums == null) { + enums = + new Object[] { + new byte[] {SOFTWARE, TRUSTED_ENVIRONMENT, STRONGBOX}, + new byte[] {X509, PKCS8, RAW}, + new byte[] { + DERIVATION_NONE, + RFC5869_SHA256, + ISO18033_2_KDF1_SHA1, + ISO18033_2_KDF1_SHA256, + ISO18033_2_KDF2_SHA1, + ISO18033_2_KDF2_SHA256 + }, + new byte[] {SELF_SIGNED_BOOT, VERIFIED_BOOT, UNVERIFIED_BOOT, FAILED_BOOT}, + new byte[] {DEVICE_LOCKED_TRUE, DEVICE_LOCKED_FALSE}, + new byte[] {USER_AUTH_NONE, PASSWORD, FINGERPRINT, BOTH}, + new byte[] {ENCRYPT, DECRYPT, SIGN, VERIFY, WRAP_KEY, ATTEST_KEY, AGREE_KEY}, + new byte[] {P_224, P_256, P_384, P_521}, + new byte[] {IGNORE_INVALID_TAGS, FAIL_ON_INVALID_TAGS} + }; + } + } + + // isValidTag enumeration keys and values. + private static boolean validateEnum(short key, byte value) { + create(); + byte[] vals; + short enumInd; + // check if key exists + short index = (short) types.length; + while (--index >= 0) { + if (types[index] == key) { + // check if value given + if (value != NO_VALUE) { + // check if the value exist + vals = (byte[]) enums[index]; + enumInd = (short) vals.length; + while (--enumInd >= 0) { + if (vals[enumInd] == value) { + // return true if value exist + return true; + } + } + // return false if value does not exist + return false; + } + // return true if key exist and value not given + return true; + } + } + // return false if key does not exist + return false; + } + + public short length() { + return Util.getShort(heap, (short) (KMType.instanceTable[KM_ENUM_OFFSET] + 1)); + } + + public byte getVal() { + return heap[(short) (KMType.instanceTable[KM_ENUM_OFFSET] + TLV_HEADER_SIZE + 2)]; + } + + public void setVal(byte val) { + heap[(short) (KMType.instanceTable[KM_ENUM_OFFSET] + TLV_HEADER_SIZE + 2)] = val; + } + + public short getEnumType() { + return Util.getShort(heap, (short) (KMType.instanceTable[KM_ENUM_OFFSET] + TLV_HEADER_SIZE)); + } + + public void setEnumType(short type) { + Util.setShort(heap, (short) (KMType.instanceTable[KM_ENUM_OFFSET] + TLV_HEADER_SIZE), type); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMEnumArrayTag.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMEnumArrayTag.java new file mode 100644 index 0000000..ea73c40 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMEnumArrayTag.java @@ -0,0 +1,305 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMEnumArrayTag represents ENUM_REP tag type. It has following structure, struct{byte TAG_TYPE; + * short length; struct{short ENUM_ARRAY_TAG; short tagKey; sequence of byte values}} + */ +public class KMEnumArrayTag extends KMTag { + + private static KMEnumArrayTag prototype; + + // The allowed tag keys of enum array type. + private static short[] tags = {PURPOSE, BLOCK_MODE, DIGEST, PADDING, RSA_OAEP_MGF_DIGEST}; + + // Tag Values. + private static Object[] enums = null; + + private KMEnumArrayTag() {} + + private static KMEnumArrayTag proto(short ptr) { + if (prototype == null) { + prototype = new KMEnumArrayTag(); + } + KMType.instanceTable[KM_ENUM_ARRAY_TAG_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short blobPtr = KMByteBlob.exp(); + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), ENUM_ARRAY_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), INVALID_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), blobPtr); + return ptr; + } + + public static short instance(short key) { + byte[] vals = getAllowedEnumValues(key); + if (vals == null) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short blobPtr = KMByteBlob.exp(); + return instance(key, blobPtr); + } + + public static short instance(short key, short byteBlob) { + byte[] allowedVals = getAllowedEnumValues(key); + if (allowedVals == null) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + KMByteBlob blob = KMByteBlob.cast(byteBlob); + short byteIndex = 0; + short enumIndex; + boolean validValue; + while (byteIndex < blob.length()) { + enumIndex = 0; + validValue = false; + while (enumIndex < allowedVals.length) { + if (blob.get(byteIndex) == allowedVals[enumIndex]) { + validValue = true; + break; + } + enumIndex++; + } + if (!validValue) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + byteIndex++; + } + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), ENUM_ARRAY_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), key); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), byteBlob); + return ptr; + } + + public static KMEnumArrayTag cast(short ptr) { + if (heap[ptr] != TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)) != ENUM_ARRAY_TAG) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static void create() { + if (enums == null) { + // allowed tag values. + enums = + new Object[] { + new byte[] {ENCRYPT, DECRYPT, SIGN, VERIFY, WRAP_KEY, ATTEST_KEY, AGREE_KEY}, + new byte[] {ECB, CBC, CTR, GCM}, + new byte[] {DIGEST_NONE, MD5, SHA1, SHA2_224, SHA2_256, SHA2_384, SHA2_512}, + new byte[] { + PADDING_NONE, RSA_OAEP, RSA_PSS, RSA_PKCS1_1_5_ENCRYPT, RSA_PKCS1_1_5_SIGN, PKCS7 + }, + new byte[] {DIGEST_NONE, MD5, SHA1, SHA2_224, SHA2_256, SHA2_384, SHA2_512}, + }; + } + } + + private static byte[] getAllowedEnumValues(short key) { + create(); + short index = (short) tags.length; + while (--index >= 0) { + if (tags[index] == key) { + return (byte[]) enums[index]; + } + } + return null; + } + + public static short getValues(short tagId, short params, byte[] buf, short start) { + short tag = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, tagId, params); + if (tag == KMType.INVALID_VALUE) { + return KMType.INVALID_VALUE; + } + tag = KMEnumArrayTag.cast(tag).getValues(); + return KMByteBlob.cast(tag).getValues(buf, start); + } + + public static boolean contains(short tagId, short tagValue, short params) { + short tag = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, tagId, params); + if (tag != KMType.INVALID_VALUE) { + short index = 0; + while (index < KMEnumArrayTag.cast(tag).length()) { + if (tagValue == KMEnumArrayTag.cast(tag).get(index)) { + return true; + } + index++; + } + } + return false; + } + + public static short length(short tagId, short params) { + short tag = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, tagId, params); + if (tag != KMType.INVALID_VALUE) { + return KMEnumArrayTag.cast(tag).length(); + } + return KMType.INVALID_VALUE; + } + + public short getKey() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_ENUM_ARRAY_TAG_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short getTagType() { + return KMType.ENUM_ARRAY_TAG; + } + + public short getValues() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_ENUM_ARRAY_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + } + + public short length() { + short blobPtr = + Util.getShort( + heap, (short) (KMType.instanceTable[KM_ENUM_ARRAY_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + return KMByteBlob.cast(blobPtr).length(); + } + + public short get(short index) { + return KMByteBlob.cast(getValues()).get(index); + } + + public boolean contains(short tagValue) { + short index = 0; + while (index < length()) { + if (get(index) == (byte) tagValue) { + return true; + } + index++; + } + return false; + } + + public boolean isValidDigests(byte alg) { + short index = 0; + short digest; + while (index < length()) { + digest = get(index); + switch (alg) { + case KMType.EC: + case KMType.RSA: + if (digest != KMType.DIGEST_NONE && digest != KMType.SHA2_256 && digest != KMType.SHA1) { + return false; + } + break; + case KMType.HMAC: + if (digest != KMType.SHA2_256) { + return false; + } + break; + case KMType.AES: + case KMType.DES: + if (digest != KMType.DIGEST_NONE) { + return false; + } + break; + default: + return false; + } + index++; + } + return true; + } + + public boolean isValidPaddingModes(byte alg) { + short index = 0; + short padding; + while (index < length()) { + padding = get(index); + switch (alg) { + case KMType.RSA: + if (padding != KMType.RSA_OAEP + && padding != KMType.PADDING_NONE + && padding != KMType.RSA_PKCS1_1_5_SIGN + && padding != KMType.RSA_PKCS1_1_5_ENCRYPT + && padding != KMType.RSA_PSS) { + return false; + } + break; + case KMType.AES: + case KMType.DES: + if (padding != KMType.PKCS7 && padding != KMType.PADDING_NONE) { + return false; + } + break; + case KMType.EC: + case KMType.HMAC: + if (padding != PADDING_NONE) { + return false; + } + break; + default: + return false; + } + index++; + } + return true; + } + + public boolean isValidPurpose(byte alg) { + short index = 0; + short purpose; + while (index < length()) { + purpose = get(index); + switch (purpose) { + case KMType.DECRYPT: + case KMType.ENCRYPT: + if (alg != KMType.RSA && alg != KMType.AES && alg != KMType.DES) { + return false; + } + break; + case KMType.SIGN: + case KMType.VERIFY: + if (alg != KMType.HMAC && alg != KMType.RSA && alg != KMType.EC) { + return false; + } + break; + case KMType.WRAP_KEY: + if (alg != KMType.RSA) { + return false; + } + break; + default: + return false; + } + index++; + } + return true; + } + + public boolean isValidBlockMode(byte alg) { + if (alg == KMType.AES || alg == KMType.DES) { + return true; + } else { + return false; + } + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMEnumTag.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMEnumTag.java new file mode 100644 index 0000000..a7bcbe6 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMEnumTag.java @@ -0,0 +1,152 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMEnumTag represents ENUM Tag type specified in android keymaster hal specifications. struct{byte + * TAG_TYPE; short length; struct{short ENUM_TAG; short tagKey; byte value}} + */ +public class KMEnumTag extends KMTag { + + private static KMEnumTag prototype; + + // The allowed tag keys of type enum tag. + private static short[] tags = { + ALGORITHM, ECCURVE, BLOB_USAGE_REQ, USER_AUTH_TYPE, ORIGIN, HARDWARE_TYPE + }; + + private static Object[] enums = null; + + private KMEnumTag() {} + + private static KMEnumTag proto(short ptr) { + if (prototype == null) { + prototype = new KMEnumTag(); + } + KMType.instanceTable[KM_ENUM_TAG_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short ptr = instance(TAG_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), ENUM_TAG); + return ptr; + } + + public static short instance(short key) { + if (!validateEnum(key, NO_VALUE)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = KMType.instance(TAG_TYPE, (short) 4); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), ENUM_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), key); + return ptr; + } + + public static short instance(short key, byte val) { + if (!validateEnum(key, val)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(TAG_TYPE, (short) 5); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), ENUM_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), key); + heap[(short) (ptr + TLV_HEADER_SIZE + 4)] = val; + return ptr; + } + + public static KMEnumTag cast(short ptr) { + if (heap[ptr] != TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)) != ENUM_TAG) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static void create() { + if (enums == null) { + // enum tag values. + enums = + new Object[] { + new byte[] {RSA, DES, EC, AES, HMAC}, + new byte[] {P_224, P_256, P_384, P_521, CURVE_25519}, + new byte[] {STANDALONE, REQUIRES_FILE_SYSTEM}, + new byte[] {USER_AUTH_NONE, PASSWORD, FINGERPRINT, BOTH, ANY}, + new byte[] {GENERATED, DERIVED, IMPORTED, UNKNOWN, SECURELY_IMPORTED}, + new byte[] {SOFTWARE, TRUSTED_ENVIRONMENT, STRONGBOX} + }; + } + } + + // isValidTag enumeration keys and values. + private static boolean validateEnum(short key, byte value) { + create(); + byte[] vals; + short enumInd; + // check if key exists + short index = (short) tags.length; + while (--index >= 0) { + if (tags[index] == key) { + // check if value given + if (value != NO_VALUE) { + // check if the value exist + vals = (byte[]) enums[index]; + enumInd = (short) vals.length; + while (--enumInd >= 0) { + if (vals[enumInd] == value) { + // return true if value exist + return true; + } + } + // return false if value does not exist + return false; + } + // return true if key exist and value not given + return true; + } + } + // return false if key does not exist + return false; + } + + public static short getValue(short tagKey, short keyParameters) { + short tagPtr = KMKeyParameters.findTag(KMType.ENUM_TAG, tagKey, keyParameters); + if (tagPtr != KMType.INVALID_VALUE) { + return heap[(short) (tagPtr + TLV_HEADER_SIZE + 4)]; + } + return KMType.INVALID_VALUE; + } + + public short getKey() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_ENUM_TAG_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short getTagType() { + return KMType.ENUM_TAG; + } + + public byte getValue() { + return heap[(short) (KMType.instanceTable[KM_ENUM_TAG_OFFSET] + TLV_HEADER_SIZE + 4)]; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMError.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMError.java new file mode 100644 index 0000000..bbae870 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMError.java @@ -0,0 +1,134 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.keymaster; + +/** + * KMError includes all the error codes from android keymaster hal specifications. The values are + * positive unlike negative values in keymaster hal. + */ +public class KMError { + + public static final short OK = 0; + public static final short UNSUPPORTED_PURPOSE = 2; + public static final short INCOMPATIBLE_PURPOSE = 3; + public static final short UNSUPPORTED_ALGORITHM = 4; + public static final short INCOMPATIBLE_ALGORITHM = 5; + public static final short UNSUPPORTED_KEY_SIZE = 6; + public static final short UNSUPPORTED_BLOCK_MODE = 7; + public static final short INCOMPATIBLE_BLOCK_MODE = 8; + public static final short UNSUPPORTED_MAC_LENGTH = 9; + public static final short UNSUPPORTED_PADDING_MODE = 10; + public static final short INCOMPATIBLE_PADDING_MODE = 11; + public static final short UNSUPPORTED_DIGEST = 12; + public static final short INCOMPATIBLE_DIGEST = 13; + + public static final short UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM = 19; + + /** For PKCS8 & PKCS12 */ + public static final short INVALID_INPUT_LENGTH = 21; + + public static final short KEY_USER_NOT_AUTHENTICATED = 26; + public static final short INVALID_OPERATION_HANDLE = 28; + public static final short INSUFFICIENT_BUFFER_SPACE = 29; + public static final short VERIFICATION_FAILED = 30; + public static final short TOO_MANY_OPERATIONS = 31; + public static final short INVALID_KEY_BLOB = 33; + + public static final short INVALID_ARGUMENT = 38; + public static final short UNSUPPORTED_TAG = 39; + public static final short INVALID_TAG = 40; + public static final short IMPORT_PARAMETER_MISMATCH = 44; + public static final short OPERATION_CANCELLED = 46; + + public static final short MISSING_NONCE = 51; + public static final short INVALID_NONCE = 52; + public static final short MISSING_MAC_LENGTH = 53; + public static final short CALLER_NONCE_PROHIBITED = 55; + public static final short KEY_MAX_OPS_EXCEEDED = 56; + public static final short INVALID_MAC_LENGTH = 57; + public static final short MISSING_MIN_MAC_LENGTH = 58; + public static final short UNSUPPORTED_MIN_MAC_LENGTH = 59; + public static final short UNSUPPORTED_EC_CURVE = 61; + public static final short KEY_REQUIRES_UPGRADE = 62; + + public static final short ATTESTATION_CHALLENGE_MISSING = 63; + public static final short ATTESTATION_APPLICATION_ID_MISSING = 65; + public static final short CANNOT_ATTEST_IDS = 66; + public static final short ROLLBACK_RESISTANCE_UNAVAILABLE = 67; + + public static final short NO_USER_CONFIRMATION = 71; + public static final short DEVICE_LOCKED = 72; + public static final short EARLY_BOOT_ENDED = 73; + public static final short ATTESTATION_KEYS_NOT_PROVISIONED = 74; + public static final short INCOMPATIBLE_MGF_DIGEST = 78; + public static final short UNSUPPORTED_MGF_DIGEST = 79; + public static final short MISSING_NOT_BEFORE = 80; + public static final short MISSING_NOT_AFTER = 81; + public static final short MISSING_ISSUER_SUBJECT_NAME = 82; + public static final short INVALID_ISSUER_SUBJECT_NAME = 83; + + public static final short UNIMPLEMENTED = 100; + public static final short UNKNOWN_ERROR = 1000; + + // Extended errors + public static final short SW_CONDITIONS_NOT_SATISFIED = 10001; + public static final short UNSUPPORTED_CLA = 10002; + public static final short INVALID_P1P2 = 10003; + public static final short UNSUPPORTED_INSTRUCTION = 10004; + public static final short CMD_NOT_ALLOWED = 10005; + public static final short SW_WRONG_LENGTH = 10006; + public static final short INVALID_DATA = 10007; + + // Crypto errors + public static final short CRYPTO_ILLEGAL_USE = 10008; + public static final short CRYPTO_ILLEGAL_VALUE = 10009; + public static final short CRYPTO_INVALID_INIT = 10010; + public static final short CRYPTO_NO_SUCH_ALGORITHM = 10011; + public static final short CRYPTO_UNINITIALIZED_KEY = 10012; + // Generic Unknown error. + public static final short GENERIC_UNKNOWN_ERROR = 10013; + + // Remote key provisioning error codes. + public static final short STATUS_FAILED = 32000; + public static final short STATUS_INVALID_MAC = 32001; + public static final short STATUS_PRODUCTION_KEY_IN_TEST_REQUEST = 32002; + public static final short STATUS_TEST_KEY_IN_PRODUCTION_REQUEST = 32003; + public static final short STATUS_INVALID_EEK = 32004; + public static final short INVALID_STATE = 32005; + + public static short translate(short err) { + switch (err) { + case SW_CONDITIONS_NOT_SATISFIED: + case UNSUPPORTED_CLA: + case INVALID_P1P2: + case INVALID_DATA: + case CRYPTO_ILLEGAL_USE: + case CRYPTO_ILLEGAL_VALUE: + case CRYPTO_INVALID_INIT: + case CRYPTO_UNINITIALIZED_KEY: + case GENERIC_UNKNOWN_ERROR: + case CMD_NOT_ALLOWED: + case UNKNOWN_ERROR: + return UNKNOWN_ERROR; + case CRYPTO_NO_SUCH_ALGORITHM: + return UNSUPPORTED_ALGORITHM; + case UNSUPPORTED_INSTRUCTION: + case SW_WRONG_LENGTH: + return UNIMPLEMENTED; + } + return err; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMHardwareAuthToken.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMHardwareAuthToken.java new file mode 100644 index 0000000..e6b1d37 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMHardwareAuthToken.java @@ -0,0 +1,171 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMHardwareAuthToken represents HardwareAuthToken structure from android keymaster hal + * specifications. It corresponds to CBOR array type. struct{byte HW_AUTH_TOKEN_TYPE; short + * length=2; short arrayPtr} where arrayPtr is a pointer to ordered array with following elements: + * {KMInteger Challenge; KMInteger UserId; KMInteger AuthenticatorId; UserAuthType + * HwAuthenticatorId; KMInteger TimeStamp; KMByteBlob Mac} + */ +public class KMHardwareAuthToken extends KMType { + + public static final byte CHALLENGE = 0x00; + public static final byte USER_ID = 0x01; + public static final byte AUTHENTICATOR_ID = 0x02; + public static final byte HW_AUTHENTICATOR_TYPE = 0x03; + public static final byte TIMESTAMP = 0x04; + public static final byte MAC = 0x05; + + private static KMHardwareAuthToken prototype; + + private KMHardwareAuthToken() {} + + public static short exp() { + short arrPtr = KMArray.instance((short) 6); + KMArray arr = KMArray.cast(arrPtr); + arr.add(CHALLENGE, KMInteger.exp()); + arr.add(USER_ID, KMInteger.exp()); + arr.add(AUTHENTICATOR_ID, KMInteger.exp()); + arr.add(HW_AUTHENTICATOR_TYPE, KMEnum.instance(KMType.USER_AUTH_TYPE)); + arr.add(TIMESTAMP, KMInteger.exp()); + arr.add(MAC, KMByteBlob.exp()); + return instance(arrPtr); + } + + private static KMHardwareAuthToken proto(short ptr) { + if (prototype == null) { + prototype = new KMHardwareAuthToken(); + } + KMType.instanceTable[KM_HARDWARE_AUTH_TOKEN_OFFSET] = ptr; + return prototype; + } + + public static short instance() { + short arrPtr = KMArray.instance((short) 6); + KMArray arr = KMArray.cast(arrPtr); + arr.add(CHALLENGE, KMInteger.uint_16((short) 0)); + arr.add(USER_ID, KMInteger.uint_16((short) 0)); + arr.add(AUTHENTICATOR_ID, KMInteger.uint_16((short) 0)); + arr.add(HW_AUTHENTICATOR_TYPE, KMEnum.instance(KMType.USER_AUTH_TYPE, KMType.USER_AUTH_NONE)); + arr.add(TIMESTAMP, KMInteger.uint_16((short) 0)); + arr.add(MAC, KMByteBlob.instance((short) 0)); + return instance(arrPtr); + } + + public static short instance(short vals) { + KMArray arr = KMArray.cast(vals); + if (arr.length() != 6) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + short ptr = KMType.instance(HW_AUTH_TOKEN_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMHardwareAuthToken cast(short ptr) { + if (heap[ptr] != HW_AUTH_TOKEN_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (heap[arrPtr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public short getVals() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_HARDWARE_AUTH_TOKEN_OFFSET] + TLV_HEADER_SIZE)); + } + + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); + } + + public short getChallenge() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(CHALLENGE); + } + + public void setChallenge(short vals) { + KMInteger.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(CHALLENGE, vals); + } + + public short getUserId() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(USER_ID); + } + + public void setUserId(short vals) { + KMInteger.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(USER_ID, vals); + } + + public short getAuthenticatorId() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(AUTHENTICATOR_ID); + } + + public void setAuthenticatorId(short vals) { + KMInteger.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(AUTHENTICATOR_ID, vals); + } + + public short getHwAuthenticatorType() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(HW_AUTHENTICATOR_TYPE); + } + + public void setHwAuthenticatorType(short vals) { + KMEnum.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(HW_AUTHENTICATOR_TYPE, vals); + } + + public short getTimestamp() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(TIMESTAMP); + } + + public void setTimestamp(short vals) { + KMInteger.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(TIMESTAMP, vals); + } + + public short getMac() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(MAC); + } + + public void setMac(short vals) { + KMByteBlob.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(MAC, vals); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMHmacSharingParameters.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMHmacSharingParameters.java new file mode 100644 index 0000000..8642803 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMHmacSharingParameters.java @@ -0,0 +1,110 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMHmacSharingParameters represents HmacSharingParameters structure from android keymaster hal + * specifications. It corresponds to CBOR array type. struct{byte HMAC_SHARING_PARAM_TYPE; short + * length=2; short arrayPtr} where arrayPtr is a pointer to ordered array with following elements: + * {KMByteBlob Seed; KMByteBlob Nonce} + */ +public class KMHmacSharingParameters extends KMType { + + public static final byte SEED = 0x00; + public static final byte NONCE = 0x01; + + private static KMHmacSharingParameters prototype; + + private KMHmacSharingParameters() {} + + public static short exp() { + short arrPtr = KMArray.instance((short) 2); + KMArray arr = KMArray.cast(arrPtr); + arr.add(SEED, KMByteBlob.exp()); + arr.add(NONCE, KMByteBlob.exp()); + return instance(arrPtr); + } + + private static KMHmacSharingParameters proto(short ptr) { + if (prototype == null) { + prototype = new KMHmacSharingParameters(); + } + KMType.instanceTable[KM_HMAC_SHARING_PARAMETERS_OFFSET] = ptr; + return prototype; + } + + public static short instance() { + short arrPtr = KMArray.instance((short) 2); + return instance(arrPtr); + } + + public static short instance(short vals) { + short ptr = KMType.instance(HMAC_SHARING_PARAM_TYPE, (short) 2); + if (KMArray.cast(vals).length() != 2) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMHmacSharingParameters cast(short ptr) { + if (heap[ptr] != HMAC_SHARING_PARAM_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (heap[arrPtr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public short getVals() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_HMAC_SHARING_PARAMETERS_OFFSET] + TLV_HEADER_SIZE)); + } + + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); + } + + public short getNonce() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(NONCE); + } + + public void setNonce(short vals) { + KMByteBlob.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(NONCE, vals); + } + + public short getSeed() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(SEED); + } + + public void setSeed(short vals) { + KMByteBlob.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(SEED, vals); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMInteger.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMInteger.java new file mode 100644 index 0000000..b09de0f --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMInteger.java @@ -0,0 +1,215 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import com.android.javacard.seprovider.KMException; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * Represents 8 bit, 16 bit, 32 bit and 64 bit unsigned integer. It corresponds to CBOR uint type. + * struct{byte INTEGER_TYPE; short length; 4 or 8 bytes of value} + */ +public class KMInteger extends KMType { + + public static final byte UINT_32 = 4; + public static final byte UINT_64 = 8; + private static KMInteger prototype; + + protected KMInteger() {} + + private static KMInteger proto(short ptr) { + if (prototype == null) { + prototype = new KMInteger(); + } + KMType.instanceTable[KM_INTEGER_OFFSET] = ptr; + return prototype; + } + + // | TYPE(1) | LEN(2) | DATA(4 / 8) | + public static short exp() { + return KMType.exp(INTEGER_TYPE); + } + + // return an empty integer instance + public static short instance(short length) { + if ((length <= 0) || (length > 8)) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + if (length > 4) { + length = UINT_64; + } else { + length = UINT_32; + } + return KMType.instance(INTEGER_TYPE, length); + } + + public static short instance(byte[] num, short srcOff, short length) { + if (length > 8) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + if (length == 1) { + return uint_8(num[srcOff]); + } else if (length == 2) { + return uint_16(Util.getShort(num, srcOff)); + } else if (length == 4) { + return uint_32(num, srcOff); + } else { + return uint_64(num, srcOff); + } + } + + public static KMInteger cast(short ptr) { + byte[] heap = repository.getHeap(); + if (heap[ptr] != INTEGER_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + 1)) == INVALID_VALUE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + // create integer and copy byte value + public static short uint_8(byte num) { + short ptr = instance(UINT_32); + heap[(short) (ptr + TLV_HEADER_SIZE + 3)] = num; + return ptr; + } + + // create integer and copy short value + public static short uint_16(short num) { + short ptr = instance(UINT_32); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), num); + return ptr; + } + + // create integer and copy integer value + public static short uint_32(byte[] num, short offset) { + short ptr = instance(UINT_32); + Util.arrayCopy(num, offset, heap, (short) (ptr + TLV_HEADER_SIZE), UINT_32); + return ptr; + } + + // create integer and copy integer value + public static short uint_64(byte[] num, short offset) { + short ptr = instance(UINT_64); + Util.arrayCopy(num, offset, heap, (short) (ptr + TLV_HEADER_SIZE), UINT_64); + return ptr; + } + + public static short compare(short num1, short num2) { + short num1Buf = repository.alloc((short) 8); + short num2Buf = repository.alloc((short) 8); + Util.arrayFillNonAtomic(repository.getHeap(), num1Buf, (short) 8, (byte) 0); + Util.arrayFillNonAtomic(repository.getHeap(), num2Buf, (short) 8, (byte) 0); + short len = KMInteger.cast(num1).length(); + KMInteger.cast(num1).getValue(repository.getHeap(), (short) (num1Buf + (short) (8 - len)), len); + len = KMInteger.cast(num2).length(); + KMInteger.cast(num2).getValue(repository.getHeap(), (short) (num2Buf + (short) (8 - len)), len); + return KMInteger.unsignedByteArrayCompare( + repository.getHeap(), num1Buf, repository.getHeap(), num2Buf, (short) 8); + } + + public static byte unsignedByteArrayCompare( + byte[] a1, short offset1, byte[] a2, short offset2, short length) { + byte count = (byte) 0; + short val1 = (short) 0; + short val2 = (short) 0; + + for (; count < length; count++) { + val1 = (short) (a1[(short) (count + offset1)] & 0x00FF); + val2 = (short) (a2[(short) (count + offset2)] & 0x00FF); + + if (val1 < val2) { + return -1; + } + if (val1 > val2) { + return 1; + } + } + return 0; + } + + // Get the length of the integer + public short length() { + return Util.getShort(heap, (short) (getBaseOffset() + 1)); + } + + // Get the buffer pointer in which blob is contained. + public byte[] getBuffer() { + return heap; + } + + // Get the start of value + public short getStartOff() { + return (short) (getBaseOffset() + TLV_HEADER_SIZE); + } + + public void getValue(byte[] dest, short destOff, short length) { + if (length < length()) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + if (length > length()) { + length = length(); + destOff += length; + } + Util.arrayCopyNonAtomic(heap, getStartOff(), dest, destOff, length); + } + + public void setValue(byte[] src, short srcOff) { + Util.arrayCopyNonAtomic(src, srcOff, heap, getStartOff(), length()); + } + + public short value(byte[] dest, short destOff) { + Util.arrayCopyNonAtomic(heap, getStartOff(), dest, destOff, length()); + return length(); + } + + public short toLittleEndian(byte[] dest, short destOff) { + short index = (short) (length() - 1); + while (index >= 0) { + dest[destOff++] = heap[(short) (instanceTable[KM_INTEGER_OFFSET] + TLV_HEADER_SIZE + index)]; + index--; + } + return length(); + } + + public short getShort() { + return Util.getShort(heap, (short) (getStartOff() + 2)); + } + + public short getSignificantShort() { + return Util.getShort(heap, getStartOff()); + } + + public byte getByte() { + return heap[(short) (getStartOff() + 3)]; + } + + public boolean isZero() { + if (getShort() == 0 && getSignificantShort() == 0) { + return true; + } + return false; + } + + protected short getBaseOffset() { + return instanceTable[KM_INTEGER_OFFSET]; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMIntegerArrayTag.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMIntegerArrayTag.java new file mode 100644 index 0000000..bf45981 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMIntegerArrayTag.java @@ -0,0 +1,163 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMIntegerArrayTag represents UINT_REP and ULONG_REP tags specified in keymaster hal specs. + * struct{byte TAG_TYPE; short length; struct{short UINT_TAG/ULONG_TAG; short tagKey; short arrPtr}, + * where arrPtr is the pointer to KMArray of KMInteger instances. + */ +public class KMIntegerArrayTag extends KMTag { + + private static final short[] tags = {USER_SECURE_ID}; + private static KMIntegerArrayTag prototype; + + private KMIntegerArrayTag() {} + + private static KMIntegerArrayTag proto(short ptr) { + if (prototype == null) { + prototype = new KMIntegerArrayTag(); + } + KMType.instanceTable[KM_INTEGER_ARRAY_TAG_OFFSET] = ptr; + return prototype; + } + + public static short exp(short tagType) { + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short arrPtr = KMArray.exp(KMInteger.exp()); + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), tagType); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), INVALID_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), arrPtr); + return ptr; + } + + public static short instance(short tagType, short key) { + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (!validateKey(key)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short arrPtr = KMArray.exp(); + return instance(tagType, key, arrPtr); + } + + public static short instance(short tagType, short key, short arrObj) { + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (!validateKey(key)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (heap[arrObj] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), tagType); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), key); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), arrObj); + return ptr; + } + + public static KMIntegerArrayTag cast(short ptr) { + if (heap[ptr] != TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short tagType = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + private static boolean validateKey(short key) { + short index = (short) tags.length; + while (--index >= 0) { + if (tags[index] == key) { + return true; + } + } + return false; + } + + private static boolean validateTagType(short tagType) { + return (tagType == ULONG_ARRAY_TAG) || (tagType == UINT_ARRAY_TAG); + } + + public static boolean contains(short tagId, short tagValue, short params) { + short tag = KMKeyParameters.findTag(KMType.UINT_ARRAY_TAG, tagId, params); + if (tag != KMType.INVALID_VALUE) { + short index = 0; + tag = KMIntegerArrayTag.cast(tag).getValues(); + while (index < KMArray.cast(tag).length()) { + if (KMInteger.compare(tagValue, KMArray.cast(tag).get(index)) == 0) { + return true; + } + index++; + } + } + return false; + } + + public short getTagType() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_INTEGER_ARRAY_TAG_OFFSET] + TLV_HEADER_SIZE)); + } + + public short getKey() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_INTEGER_ARRAY_TAG_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short getValues() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_INTEGER_ARRAY_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + } + + public short length() { + short ptr = getValues(); + return KMArray.cast(ptr).length(); + } + + public void add(short index, short val) { + KMArray arr = KMArray.cast(getValues()); + arr.add(index, val); + } + + public short get(short index) { + KMArray arr = KMArray.cast(getValues()); + return arr.get(index); + } + + public boolean contains(short tagValue) { + short index = 0; + while (index < length()) { + if (KMInteger.compare(tagValue, get(index)) == 0) { + return true; + } + index++; + } + return false; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMIntegerTag.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMIntegerTag.java new file mode 100644 index 0000000..d4c4458 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMIntegerTag.java @@ -0,0 +1,218 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMIntegerTag represents UINT, ULONG and DATE tags specified in keymaster hal specs. struct{byte + * TAG_TYPE; short length; struct{short UINT_TAG/ULONG_TAG/DATE_TAG; short tagKey; 4 or 8 byte + * value}} + */ +public class KMIntegerTag extends KMTag { + + // Allowed tag keys. + private static final short[] tags = { + // UINT + KEYSIZE, + MIN_MAC_LENGTH, + MIN_SEC_BETWEEN_OPS, + MAX_USES_PER_BOOT, + USERID, + AUTH_TIMEOUT, + OS_VERSION, + OS_PATCH_LEVEL, + VENDOR_PATCH_LEVEL, + BOOT_PATCH_LEVEL, + MAC_LENGTH, + // ULONG + RSA_PUBLIC_EXPONENT, + // DATE + ACTIVE_DATETIME, + ORIGINATION_EXPIRE_DATETIME, + USAGE_EXPIRE_DATETIME, + CREATION_DATETIME, + CERTIFICATE_NOT_BEFORE, + CERTIFICATE_NOT_AFTER, + USAGE_COUNT_LIMIT, + // custom tag + AUTH_TIMEOUT_MILLIS, + }; + private static KMIntegerTag prototype; + + private KMIntegerTag() {} + + private static KMIntegerTag proto(short ptr) { + if (prototype == null) { + prototype = new KMIntegerTag(); + } + KMType.instanceTable[KM_INTEGER_TAG_OFFSET] = ptr; + return prototype; + } + + public static short exp(short tagType) { + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short intPtr = KMInteger.exp(); + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), tagType); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), INVALID_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), intPtr); + return ptr; + } + + public static short instance(short tagType, short key) { + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (!validateKey(key)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short intPtr = KMInteger.exp(); + return instance(tagType, key, intPtr); + } + + public static short instance(short tagType, short key, short intObj) { + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (!validateKey(key)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (heap[intObj] != INTEGER_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), tagType); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), key); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), intObj); + return ptr; + } + + public static KMIntegerTag cast(short ptr) { + if (heap[ptr] != TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short tagType = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + private static boolean validateKey(short key) { + short index = (short) tags.length; + while (--index >= 0) { + if (tags[index] == key) { + return true; + } + } + return false; + } + + private static boolean validateTagType(short tagType) { + return (tagType == DATE_TAG) || (tagType == UINT_TAG) || (tagType == ULONG_TAG); + } + + public static short getShortValue(short tagType, short tagKey, short keyParameters) { + short ptr; + if (tagType == UINT_TAG) { + ptr = KMKeyParameters.findTag(KMType.UINT_TAG, tagKey, keyParameters); + if (ptr != KMType.INVALID_VALUE) { + ptr = KMIntegerTag.cast(ptr).getValue(); + if (KMInteger.cast(ptr).getSignificantShort() == 0) { + return KMInteger.cast(ptr).getShort(); + } + } + } + return KMType.INVALID_VALUE; + } + + public static short getValue( + byte[] buf, short offset, short tagType, short tagKey, short keyParameters) { + short ptr; + if ((tagType == UINT_TAG) || (tagType == ULONG_TAG) || (tagType == DATE_TAG)) { + ptr = KMKeyParameters.findTag(tagType, tagKey, keyParameters); + if (ptr != KMType.INVALID_VALUE) { + ptr = KMIntegerTag.cast(ptr).getValue(); + return KMInteger.cast(ptr).value(buf, offset); + } + } + return KMType.INVALID_VALUE; + } + + public short getTagType() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_INTEGER_TAG_OFFSET] + TLV_HEADER_SIZE)); + } + + public short getKey() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_INTEGER_TAG_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short getValue() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_INTEGER_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + } + + public short length() { + KMInteger obj = KMInteger.cast(getValue()); + return obj.length(); + } + + public boolean isValidKeySize(byte alg) { + short val = KMIntegerTag.cast(KMType.instanceTable[KM_INTEGER_TAG_OFFSET]).getValue(); + if (KMInteger.cast(val).getSignificantShort() != 0) { + return false; + } + val = KMInteger.cast(val).getShort(); + switch (alg) { + case KMType.RSA: + if (val == 2048) { + return true; + } + break; + case KMType.AES: + if (val == 128 || val == 256) { + return true; + } + break; + case KMType.DES: + if (val == 168) { + return true; + } + break; + case KMType.EC: + if (val == 256) { + return true; + } + break; + case KMType.HMAC: + if (val % 8 == 0 && val >= 64 && val <= 512) { + return true; + } + break; + default: + break; + } + return false; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeyCharacteristics.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeyCharacteristics.java new file mode 100644 index 0000000..37b8b7f --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeyCharacteristics.java @@ -0,0 +1,124 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMKeyCharacteristics represents KeyCharacteristics structure from android keymaster hal + * specifications. It corresponds to CBOR array type. struct{byte KEY_CHAR_TYPE; short length=3; + * short arrayPtr} where arrayPtr is a pointer to ordered array with 1 or 3 following elements: + * {KMKeyParameters sb; KMKeyParameters tee; KMKeyParameters keystore} + */ +public class KMKeyCharacteristics extends KMType { + + public static final byte STRONGBOX_ENFORCED = 0x00; + public static final byte TEE_ENFORCED = 0x01; + public static final byte KEYSTORE_ENFORCED = 0x02; + private static KMKeyCharacteristics prototype; + + private KMKeyCharacteristics() {} + + public static short exp() { + short keyParamExp = KMKeyParameters.exp(); + short arrPtr = KMArray.instance((short) 3); + + KMArray arr = KMArray.cast(arrPtr); + arr.add(STRONGBOX_ENFORCED, keyParamExp); + arr.add(TEE_ENFORCED, keyParamExp); + arr.add(KEYSTORE_ENFORCED, keyParamExp); + return instance(arrPtr); + } + + private static KMKeyCharacteristics proto(short ptr) { + if (prototype == null) { + prototype = new KMKeyCharacteristics(); + } + KMType.instanceTable[KM_KEY_CHARACTERISTICS_OFFSET] = ptr; + return prototype; + } + + public static short instance() { + short arrPtr = KMArray.instance((short) 3); + return instance(arrPtr); + } + + public static short instance(short vals) { + short ptr = KMType.instance(KEY_CHAR_TYPE, (short) 3); + if (KMArray.cast(vals).length() != 3) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMKeyCharacteristics cast(short ptr) { + if (heap[ptr] != KEY_CHAR_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (heap[arrPtr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public short getVals() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_KEY_CHARACTERISTICS_OFFSET] + TLV_HEADER_SIZE)); + } + + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); + } + + public short getKeystoreEnforced() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(KEYSTORE_ENFORCED); + } + + public void setKeystoreEnforced(short ptr) { + KMKeyParameters.cast(ptr); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(KEYSTORE_ENFORCED, ptr); + } + + public short getTeeEnforced() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(TEE_ENFORCED); + } + + public void setTeeEnforced(short ptr) { + KMKeyParameters.cast(ptr); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(TEE_ENFORCED, ptr); + } + + public short getStrongboxEnforced() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(STRONGBOX_ENFORCED); + } + + public void setStrongboxEnforced(short ptr) { + KMKeyParameters.cast(ptr); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(STRONGBOX_ENFORCED, ptr); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeyParameters.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeyParameters.java new file mode 100644 index 0000000..54ab6ee --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeyParameters.java @@ -0,0 +1,472 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import com.android.javacard.seprovider.KMException; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMKeyParameters represents KeyParameters structure from android keymaster hal specifications. It + * corresponds to CBOR map type. struct{byte KEY_PARAM_TYPE; short length=2; short arrayPtr} where + * arrayPtr is a pointer to array with any KMTag subtype instances. + */ +public class KMKeyParameters extends KMType { + + private static final short[] customTags = { + KMType.ULONG_TAG, KMType.AUTH_TIMEOUT_MILLIS, + }; + private static final short[] tagArr = { + // Unsupported tags. + KMType.BOOL_TAG, KMType.TRUSTED_USER_PRESENCE_REQUIRED, + KMType.UINT_TAG, KMType.MIN_SEC_BETWEEN_OPS + }; + private static final short[] hwEnforcedTagArr = { + // HW Enforced + KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, + KMType.ENUM_TAG, KMType.ALGORITHM, + KMType.UINT_TAG, KMType.KEYSIZE, + KMType.ULONG_TAG, KMType.RSA_PUBLIC_EXPONENT, + KMType.ENUM_TAG, KMType.BLOB_USAGE_REQ, + KMType.ENUM_ARRAY_TAG, KMType.DIGEST, + KMType.ENUM_ARRAY_TAG, KMType.PADDING, + KMType.ENUM_ARRAY_TAG, KMType.BLOCK_MODE, + KMType.ENUM_ARRAY_TAG, KMType.RSA_OAEP_MGF_DIGEST, + KMType.BOOL_TAG, KMType.NO_AUTH_REQUIRED, + KMType.BOOL_TAG, KMType.CALLER_NONCE, + KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, + KMType.ENUM_TAG, KMType.ECCURVE, + KMType.BOOL_TAG, KMType.INCLUDE_UNIQUE_ID, + KMType.BOOL_TAG, KMType.ROLLBACK_RESISTANCE, + KMType.BOOL_TAG, KMType.EARLY_BOOT_ONLY, + KMType.BOOL_TAG, KMType.BOOTLOADER_ONLY, + KMType.UINT_TAG, KMType.MAX_USES_PER_BOOT, + }; + private static final short[] swEnforcedTagsArr = { + KMType.DATE_TAG, KMType.ACTIVE_DATETIME, + KMType.DATE_TAG, KMType.ORIGINATION_EXPIRE_DATETIME, + KMType.DATE_TAG, KMType.USAGE_EXPIRE_DATETIME, + KMType.UINT_TAG, KMType.USERID, + KMType.DATE_TAG, KMType.CREATION_DATETIME, + KMType.UINT_TAG, KMType.USAGE_COUNT_LIMIT, + KMType.BOOL_TAG, KMType.ALLOW_WHILE_ON_BODY, + KMType.UINT_TAG, KMType.MAX_BOOT_LEVEL, + }; + private static final short[] teeEnforcedTagsArr = { + KMType.ULONG_ARRAY_TAG, KMType.USER_SECURE_ID, + KMType.UINT_TAG, KMType.AUTH_TIMEOUT, + KMType.ENUM_TAG, KMType.USER_AUTH_TYPE, + KMType.BOOL_TAG, KMType.UNLOCKED_DEVICE_REQUIRED, + KMType.BOOL_TAG, KMType.TRUSTED_CONFIRMATION_REQUIRED, + }; + private static final short[] invalidTagsArr = { + KMType.BYTES_TAG, KMType.NONCE, + KMType.BYTES_TAG, KMType.ASSOCIATED_DATA, + KMType.BYTES_TAG, KMType.UNIQUE_ID, + KMType.UINT_TAG, KMType.MAC_LENGTH, + }; + private static KMKeyParameters prototype; + + private KMKeyParameters() {} + + private static KMKeyParameters proto(short ptr) { + if (prototype == null) { + prototype = new KMKeyParameters(); + } + KMType.instanceTable[KM_KEY_PARAMETERS_OFFSET] = ptr; + return prototype; + } + + public static short exp() { + short arrPtr = KMArray.instance((short) 11); + KMArray arr = KMArray.cast(arrPtr); + arr.add((short) 0, KMEnum.instance(KMType.RULE, KMType.FAIL_ON_INVALID_TAGS)); + arr.add((short) 1, KMIntegerTag.exp(UINT_TAG)); + arr.add((short) 2, KMIntegerArrayTag.exp(UINT_ARRAY_TAG)); + arr.add((short) 3, KMIntegerTag.exp(ULONG_TAG)); + arr.add((short) 4, KMIntegerTag.exp(DATE_TAG)); + arr.add((short) 5, KMIntegerArrayTag.exp(ULONG_ARRAY_TAG)); + arr.add((short) 6, KMEnumTag.exp()); + arr.add((short) 7, KMEnumArrayTag.exp()); + arr.add((short) 8, KMByteTag.exp()); + arr.add((short) 9, KMBoolTag.exp()); + arr.add((short) 10, KMBignumTag.exp()); + return instance(arrPtr); + } + + public static short expAny() { + short arrPtr = KMArray.instance((short) 11); + KMArray arr = KMArray.cast(arrPtr); + arr.add((short) 0, KMEnum.instance(KMType.RULE, KMType.IGNORE_INVALID_TAGS)); + arr.add((short) 1, KMIntegerTag.exp(UINT_TAG)); + arr.add((short) 2, KMIntegerArrayTag.exp(UINT_ARRAY_TAG)); + arr.add((short) 3, KMIntegerTag.exp(ULONG_TAG)); + arr.add((short) 4, KMIntegerTag.exp(DATE_TAG)); + arr.add((short) 5, KMIntegerArrayTag.exp(ULONG_ARRAY_TAG)); + arr.add((short) 6, KMEnumTag.exp()); + arr.add((short) 7, KMEnumArrayTag.exp()); + arr.add((short) 8, KMByteTag.exp()); + arr.add((short) 9, KMBoolTag.exp()); + arr.add((short) 10, KMBignumTag.exp()); + return instance(arrPtr); + } + + public static short instance(short vals) { + short ptr = KMType.instance(KEY_PARAM_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMKeyParameters cast(short ptr) { + if (heap[ptr] != KEY_PARAM_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (heap[arrPtr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static short findTag(short tagType, short tagKey, short keyParam) { + KMKeyParameters instParam = KMKeyParameters.cast(keyParam); + return instParam.findTag(tagType, tagKey); + } + + public static boolean hasUnsupportedTags(short keyParamsPtr) { + byte index = 0; + short tagInd; + short tagPtr; + short tagKey; + short tagType; + short arrPtr = KMKeyParameters.cast(keyParamsPtr).getVals(); + short len = KMArray.cast(arrPtr).length(); + while (index < len) { + tagInd = 0; + tagPtr = KMArray.cast(arrPtr).get(index); + tagKey = KMTag.getKey(tagPtr); + tagType = KMTag.getTagType(tagPtr); + while (tagInd < (short) tagArr.length) { + if ((tagArr[tagInd] == tagType) && (tagArr[(short) (tagInd + 1)] == tagKey)) { + return true; + } + tagInd += 2; + } + index++; + } + return false; + } + + // KDF, ECIES_SINGLE_HASH_MODE missing from types.hal + public static short makeSbEnforced( + short keyParamsPtr, + byte origin, + short osVersionObjPtr, + short osPatchObjPtr, + short vendorPatchObjPtr, + short bootPatchObjPtr, + byte[] scratchPad) { + byte index = 0; + short tagInd; + short arrInd = 0; + short tagPtr; + short tagKey; + short tagType; + short arrPtr = KMKeyParameters.cast(keyParamsPtr).getVals(); + short len = KMArray.cast(arrPtr).length(); + while (index < len) { + tagInd = 0; + tagPtr = KMArray.cast(arrPtr).get(index); + tagKey = KMTag.getKey(tagPtr); + tagType = KMTag.getTagType(tagPtr); + if (!isValidTag(tagType, tagKey)) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + while (tagInd < (short) hwEnforcedTagArr.length) { + if ((hwEnforcedTagArr[tagInd] == tagType) + && (hwEnforcedTagArr[(short) (tagInd + 1)] == tagKey)) { + Util.setShort(scratchPad, arrInd, tagPtr); + arrInd += 2; + break; + } + tagInd += 2; + } + index++; + } + short originTag = KMEnumTag.instance(KMType.ORIGIN, origin); + Util.setShort(scratchPad, arrInd, originTag); + arrInd += 2; + short osVersionTag = KMIntegerTag.instance(KMType.UINT_TAG, KMType.OS_VERSION, osVersionObjPtr); + Util.setShort(scratchPad, arrInd, osVersionTag); + arrInd += 2; + short osPatchTag = KMIntegerTag.instance(KMType.UINT_TAG, KMType.OS_PATCH_LEVEL, osPatchObjPtr); + Util.setShort(scratchPad, arrInd, osPatchTag); + arrInd += 2; + short vendorPatchTag = + KMIntegerTag.instance(KMType.UINT_TAG, KMType.VENDOR_PATCH_LEVEL, vendorPatchObjPtr); + Util.setShort(scratchPad, arrInd, vendorPatchTag); + arrInd += 2; + short bootPatchTag = + KMIntegerTag.instance(KMType.UINT_TAG, KMType.BOOT_PATCH_LEVEL, bootPatchObjPtr); + Util.setShort(scratchPad, arrInd, bootPatchTag); + arrInd += 2; + return createKeyParameters(scratchPad, (short) (arrInd / 2)); + } + + public static short makeHwEnforced(short sb, short tee) { + short len = KMKeyParameters.cast(sb).length(); + len += KMKeyParameters.cast(tee).length(); + short hwEnf = KMArray.instance(len); + sb = KMKeyParameters.cast(sb).getVals(); + tee = KMKeyParameters.cast(tee).getVals(); + len = KMArray.cast(sb).length(); + short src = 0; + short dest = 0; + short val = 0; + while (src < len) { + val = KMArray.cast(sb).get(src); + KMArray.cast(hwEnf).add(dest, val); + src++; + dest++; + } + src = 0; + len = KMArray.cast(tee).length(); + while (src < len) { + val = KMArray.cast(tee).get(src); + KMArray.cast(hwEnf).add(dest, val); + src++; + dest++; + } + return KMKeyParameters.instance(hwEnf); + } + + // ALL_USERS, EXPORTABLE missing from types.hal + public static short makeKeystoreEnforced(short keyParamsPtr, byte[] scratchPad) { + byte index = 0; + short tagInd; + short arrInd = 0; + short tagPtr; + short tagKey; + short tagType; + short arrPtr = KMKeyParameters.cast(keyParamsPtr).getVals(); + short len = KMArray.cast(arrPtr).length(); + while (index < len) { + tagInd = 0; + tagPtr = KMArray.cast(arrPtr).get(index); + tagKey = KMTag.getKey(tagPtr); + tagType = KMTag.getTagType(tagPtr); + if (!isValidTag(tagType, tagKey)) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + while (tagInd < (short) swEnforcedTagsArr.length) { + if ((swEnforcedTagsArr[tagInd] == tagType) + && (swEnforcedTagsArr[(short) (tagInd + 1)] == tagKey)) { + Util.setShort(scratchPad, arrInd, tagPtr); + arrInd += 2; + break; + } + tagInd += 2; + } + index++; + } + return createKeyParameters(scratchPad, (short) (arrInd / 2)); + } + + public static short makeTeeEnforced(short keyParamsPtr, byte[] scratchPad) { + byte index = 0; + short tagInd; + short arrInd = 0; + short tagPtr; + short tagKey; + short tagType; + short arrPtr = KMKeyParameters.cast(keyParamsPtr).getVals(); + short len = KMArray.cast(arrPtr).length(); + while (index < len) { + tagInd = 0; + tagPtr = KMArray.cast(arrPtr).get(index); + tagKey = KMTag.getKey(tagPtr); + tagType = KMTag.getTagType(tagPtr); + if (!isValidTag(tagType, tagKey)) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + while (tagInd < (short) teeEnforcedTagsArr.length) { + if ((teeEnforcedTagsArr[tagInd] == tagType) + && (teeEnforcedTagsArr[(short) (tagInd + 1)] == tagKey)) { + Util.setShort(scratchPad, arrInd, tagPtr); + arrInd += 2; + break; + } + tagInd += 2; + } + index++; + } + return createKeyParameters(scratchPad, (short) (arrInd / 2)); + } + + public static short makeHidden(short keyParamsPtr, short rootOfTrustBlob, byte[] scratchPad) { + short appId = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_ID, keyParamsPtr); + if (appId != KMTag.INVALID_VALUE) { + appId = KMByteTag.cast(appId).getValue(); + if (KMByteBlob.cast(appId).length() == 0) { + appId = KMTag.INVALID_VALUE; + } + } + short appData = + KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_DATA, keyParamsPtr); + if (appData != KMTag.INVALID_VALUE) { + appData = KMByteTag.cast(appData).getValue(); + if (KMByteBlob.cast(appData).length() == 0) { + appData = KMTag.INVALID_VALUE; + } + } + return makeHidden(appId, appData, rootOfTrustBlob, scratchPad); + } + + public static short makeHidden( + short appIdBlob, short appDataBlob, short rootOfTrustBlob, byte[] scratchPad) { + // Order in which the hidden array is created should not change. + short index = 0; + KMByteBlob.cast(rootOfTrustBlob); + Util.setShort(scratchPad, index, rootOfTrustBlob); + index += 2; + if (appIdBlob != KMTag.INVALID_VALUE) { + KMByteBlob.cast(appIdBlob); + Util.setShort(scratchPad, index, appIdBlob); + index += 2; + } + if (appDataBlob != KMTag.INVALID_VALUE) { + KMByteBlob.cast(appDataBlob); + Util.setShort(scratchPad, index, appDataBlob); + index += 2; + } + return createKeyParameters(scratchPad, (short) (index / 2)); + } + + public static boolean isValidTag(short tagType, short tagKey) { + short index = 0; + if (tagKey == KMType.INVALID_TAG) { + return false; + } + while (index < invalidTagsArr.length) { + if ((tagType == invalidTagsArr[index]) && (tagKey == invalidTagsArr[(short) (index + 1)])) { + return false; + } + index += 2; + } + return true; + } + + public static short createKeyParameters(byte[] ptrArr, short len) { + short arrPtr = KMArray.instance(len); + short index = 0; + short ptr = 0; + while (index < len) { + KMArray.cast(arrPtr).add(index, Util.getShort(ptrArr, ptr)); + index++; + ptr += 2; + } + return KMKeyParameters.instance(arrPtr); + } + + public static short makeCustomTags(short keyParams, byte[] scratchPad) { + short index = 0; + short tagPtr; + short offset = 0; + short len = (short) customTags.length; + short tagType; + while (index < len) { + tagType = customTags[(short) (index + 1)]; + switch (tagType) { + case KMType.AUTH_TIMEOUT_MILLIS: + short authTimeOutTag = + KMKeyParameters.cast(keyParams).findTag(KMType.UINT_TAG, KMType.AUTH_TIMEOUT); + if (authTimeOutTag != KMType.INVALID_VALUE) { + tagPtr = createAuthTimeOutMillisTag(authTimeOutTag, scratchPad, offset); + Util.setShort(scratchPad, offset, tagPtr); + offset += 2; + } + break; + default: + break; + } + index += 2; + } + return createKeyParameters(scratchPad, (short) (offset / 2)); + } + + public static short createAuthTimeOutMillisTag( + short authTimeOutTag, byte[] scratchPad, short offset) { + short authTime = KMIntegerTag.cast(authTimeOutTag).getValue(); + Util.arrayFillNonAtomic(scratchPad, offset, (short) 40, (byte) 0); + Util.arrayCopyNonAtomic( + KMInteger.cast(authTime).getBuffer(), + KMInteger.cast(authTime).getStartOff(), + scratchPad, + (short) (offset + 8 - KMInteger.cast(authTime).length()), + KMInteger.cast(authTime).length()); + KMUtils.convertToMilliseconds(scratchPad, offset, (short) (offset + 8), (short) (offset + 16)); + return KMIntegerTag.instance( + KMType.ULONG_TAG, + KMType.AUTH_TIMEOUT_MILLIS, + KMInteger.uint_64(scratchPad, (short) (offset + 8))); + } + + public short getVals() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_KEY_PARAMETERS_OFFSET] + TLV_HEADER_SIZE)); + } + + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); + } + + public short findTag(short tagType, short tagKey) { + KMArray vals = KMArray.cast(getVals()); + short index = 0; + short length = vals.length(); + short key; + short type; + short ret = KMType.INVALID_VALUE; + short obj; + while (index < length) { + obj = vals.get(index); + key = KMTag.getKey(obj); + type = KMTag.getTagType(obj); + if ((tagKey == key) && (tagType == type)) { + ret = obj; + break; + } + index++; + } + return ret; + } + + public void deleteCustomTags() { + short arrPtr = getVals(); + short index = (short) (customTags.length - 1); + short obj; + while (index >= 0) { + obj = findTag(customTags[(short) (index - 1)], customTags[index]); + if (obj != KMType.INVALID_VALUE) { + KMArray.cast(arrPtr).deleteLastEntry(); + } + index -= 2; + } + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java new file mode 100644 index 0000000..02401e7 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java @@ -0,0 +1,5146 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import com.android.javacard.seprovider.KMAttestationCert; +import com.android.javacard.seprovider.KMDataStoreConstants; +import com.android.javacard.seprovider.KMException; +import com.android.javacard.seprovider.KMKey; +import com.android.javacard.seprovider.KMOperation; +import com.android.javacard.seprovider.KMSEProvider; +import javacard.framework.APDU; +import javacard.framework.Applet; +import javacard.framework.AppletEvent; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; +import javacard.security.CryptoException; +import javacardx.apdu.ExtendedLength; + +/** + * KMKeymasterApplet implements the javacard applet. It creates an instance of the KMRepository and + * other install time objects. It also implements the keymaster state machine and handles javacard + * applet life cycle events. + */ +public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLength { + + // Constants. + // Represents RSA_PUBLIC_EXPONENT value 65537. + public static final byte[] F4 = {0x01, 0x00, 0x01}; + // Block size of AES algorithm. + public static final byte AES_BLOCK_SIZE = 16; + // Block size of DES algorithm. + public static final byte DES_BLOCK_SIZE = 8; + // The Key size in bits for the master key. + public static final short MASTER_KEY_SIZE = 128; + // The Key size of the transport key used in importWrappedKey. + public static final byte WRAPPING_KEY_SIZE = 32; + // The maximum allowed simultaneous operations. + public static final byte MAX_OPERATIONS_COUNT = 4; + // The size of the verified boot key in ROT. + public static final byte VERIFIED_BOOT_KEY_SIZE = 32; + // The size of the verified boot hash in ROT. + public static final byte VERIFIED_BOOT_HASH_SIZE = 32; + // The security level of TEE. + public static final byte TRUSTED_ENVIRONMENT = 1; + // "Keymaster HMAC Verification" - used for HMAC key verification. + public static final byte[] sharingCheck = { + 0x4B, 0x65, 0x79, 0x6D, 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x48, 0x4D, 0x41, 0x43, 0x20, 0x56, + 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E + }; + // The ckdfLabel "KeymasterSharedMac" in hex. + public static final byte[] ckdfLabel = { + 0x4B, 0x65, 0x79, 0x6D, 0x61, 0x73, 0x74, 0x65, 0x72, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4D, + 0x61, 0x63 + }; + // The "Auth Verification" string in hex. + public static final byte[] authVerification = { + 0x41, 0x75, 0x74, 0x68, 0x20, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, + 0x6E + }; + // The "confirmation token" string in hex. + public static final byte[] confirmationToken = { + 0x63, 0x6F, 0x6E, 0x66, 0x69, 0x72, 0x6D, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x74, 0x6F, 0x6B, + 0x65, 0x6E + }; + // The maximum buffer size for the encoded COSE structures. + public static final short MAX_COSE_BUF_SIZE = (short) 1024; + // Maximum allowed buffer size for to encode the key parameters + // which is used while creating mac for key parameters. + public static final short MAX_KEY_PARAMS_BUF_SIZE = (short) 3072; // 3K + // Temporary variables array size to store intermediary results. + public static final byte TMP_VARIABLE_ARRAY_SIZE = 5; + // Data Dictionary items + // Maximum Dictionary size. + public static final byte DATA_ARRAY_SIZE = 39; + // Below are the offsets of the data dictionary items. + public static final byte KEY_PARAMETERS = 0; + public static final byte KEY_CHARACTERISTICS = 1; + public static final byte HIDDEN_PARAMETERS = 2; + public static final byte HW_PARAMETERS = 3; + public static final byte SW_PARAMETERS = 4; + public static final byte AUTH_DATA = 5; + public static final byte AUTH_TAG = 6; + public static final byte NONCE = 7; + public static final byte KEY_BLOB = 8; + public static final byte AUTH_DATA_LENGTH = 9; + public static final byte SECRET = 10; + public static final byte ROT = 11; + public static final byte DERIVED_KEY = 12; + public static final byte RSA_PUB_EXPONENT = 13; + public static final byte APP_ID = 14; + public static final byte APP_DATA = 15; + public static final byte PUB_KEY = 16; + public static final byte IMPORTED_KEY_BLOB = 17; + public static final byte ORIGIN = 18; + public static final byte NOT_USED = 19; + public static final byte MASKING_KEY = 20; + public static final byte HMAC_SHARING_PARAMS = 21; + public static final byte OP_HANDLE = 22; + public static final byte IV = 23; + public static final byte INPUT_DATA = 24; + public static final byte OUTPUT_DATA = 25; + public static final byte HW_TOKEN = 26; + public static final byte VERIFICATION_TOKEN = 27; + public static final byte SIGNATURE = 28; + public static final byte ATTEST_KEY_BLOB = 29; + public static final byte ATTEST_KEY_PARAMS = 30; + public static final byte ATTEST_KEY_ISSUER = 31; + public static final byte CERTIFICATE = 32; + public static final byte PLAIN_SECRET = 33; + public static final byte TEE_PARAMETERS = 34; + public static final byte SB_PARAMETERS = 35; + public static final byte CONFIRMATION_TOKEN = 36; + public static final byte KEY_BLOB_VERSION_DATA_OFFSET = 37; + public static final byte CUSTOM_TAGS = 38; + // Below are the Keyblob offsets. + public static final byte KEY_BLOB_VERSION_OFFSET = 0; + public static final byte KEY_BLOB_SECRET = 1; + public static final byte KEY_BLOB_NONCE = 2; + public static final byte KEY_BLOB_AUTH_TAG = 3; + public static final byte KEY_BLOB_PARAMS = 4; + public static final byte KEY_BLOB_CUSTOM_TAGS = 5; + public static final byte KEY_BLOB_PUB_KEY = 6; + // AES GCM Auth tag length to be used while encrypting or decrypting the KeyBlob. + public static final byte AES_GCM_AUTH_TAG_LENGTH = 16; + // AES GCM nonce length to be used while encrypting or decrypting the KeyBlob. + public static final byte AES_GCM_NONCE_LENGTH = 12; + // KEYBLOB_CURRENT_VERSION goes into KeyBlob and will affect all + // the KeyBlobs if it is changed. please increment this + // version number whenever you change anything related to + // KeyBlob (structure, encryption algorithm etc). + public static final short KEYBLOB_CURRENT_VERSION = 3; + // KeyBlob Verion 1 constant. + public static final short KEYBLOB_VERSION_1 = 1; + // Array sizes of KeyBlob under different versions. + // The array size of a Symmetric key's KeyBlob for Version2 and Version3 + public static final byte SYM_KEY_BLOB_SIZE_V2_V3 = 6; + // The array size of a Asymmetric key's KeyBlob for Version2 and Version3 + public static final byte ASYM_KEY_BLOB_SIZE_V2_V3 = 7; + // The array size of a Symmetric key's KeyBlob for Version1 + public static final byte SYM_KEY_BLOB_SIZE_V1 = 5; + // The array size of a Asymmetric key's KeyBlob for Version1 + public static final byte ASYM_KEY_BLOB_SIZE_V1 = 6; + // The array size of a Symmetric key's KeyBlob for Version0 + public static final byte SYM_KEY_BLOB_SIZE_V0 = 4; + // The array size of a Asymmetric key's KeyBlob for Version0 + public static final byte ASYM_KEY_BLOB_SIZE_V0 = 5; + // Key type constants + // Represents the type of the Symmetric key. + public static final byte SYM_KEY_TYPE = 0; + // Represents the type of the Asymmetric key. + public static final byte ASYM_KEY_TYPE = 1; + // SHA-256 Digest length in bits + public static final short SHA256_DIGEST_LEN_BITS = 256; + // Minimum HMAC length in bits + public static final short MIN_HMAC_LENGTH_BITS = 64; + // Below are the constants for provision reporting status + public static final short NOT_PROVISIONED = 0x0000; + public static final short PROVISION_STATUS_ATTESTATION_KEY = 0x0001; + public static final short PROVISION_STATUS_ATTESTATION_CERT_CHAIN = 0x0002; + public static final short PROVISION_STATUS_ATTESTATION_CERT_PARAMS = 0x0004; + public static final short PROVISION_STATUS_ATTEST_IDS = 0x0008; + public static final short PROVISION_STATUS_PRESHARED_SECRET = 0x0010; + public static final short PROVISION_STATUS_PROVISIONING_LOCKED = 0x0020; + public static final short PROVISION_STATUS_DEVICE_UNIQUE_KEYPAIR = 0x0040; + public static final short PROVISION_STATUS_ADDITIONAL_CERT_CHAIN = 0x0080; + public static final short PROVISION_STATUS_SE_LOCKED = 0x0100; + public static final short PROVISION_STATUS_OEM_PUBLIC_KEY = 0x0200; + public static final short PROVISION_STATUS_SECURE_BOOT_MODE = 0x0400; + // This is the P1P2 constant of the APDU command header. + protected static final short KM_HAL_VERSION = (short) 0x5000; + // OEM lock / unlock verification constants. + // This is the verification label to authenticate the OEM to lock the provisioning for the + // OEM provision commands. + protected static final byte[] OEM_LOCK_PROVISION_VERIFICATION_LABEL = { // "OEM Provisioning Lock" + 0x4f, 0x45, 0x4d, 0x20, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x69, 0x6e, 0x67, + 0x20, 0x4c, 0x6f, 0x63, 0x6b + }; + // This is the verification label to authenticate the OEM to unlock the provisioning for the + // OEM provision commands. + protected static final byte[] OEM_UNLOCK_PROVISION_VERIFICATION_LABEL = { // "Enable RMA" + 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x52, 0x4d, 0x41 + }; + // The maximum size of the seed allowed for the RNG entropy + protected static final short MAX_SEED_SIZE = 2048; + // The maximum size of the certificate returned by the generate key command. + protected static final short MAX_CERT_SIZE = 3000; + // The maximum size of the encoded key characteristics in CBOR. + protected static final short MAX_KEY_CHARS_SIZE = 512; + // The maximum size of the serialized KeyBlob. + protected static final short MAX_KEYBLOB_SIZE = 1024; + // The maximum size of the Auth data which is used while encrypting/decrypting the KeyBlob. + private static final short MAX_AUTH_DATA_SIZE = (short) 512; + // The minimum bits in length for AES-GCM tag. + private static final short MIN_GCM_TAG_LENGTH_BITS = (short) 96; + // The maximum bits in length for AES-GCM tag. + private static final short MAX_GCM_TAG_LENGTH_BITS = (short) 128; + // Subject is a fixed field with only CN= Android Keystore Key - same for all the keys + private static final byte[] defaultSubject = { + 0x30, 0x1F, 0x31, 0x1D, 0x30, 0x1B, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x14, 0x41, 0x6e, 0x64, + 0x72, 0x6f, 0x69, 0x64, 0x20, 0x4B, 0x65, 0x79, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x20, 0x4B, 0x65, + 0x79 + }; + // Constant for Dec 31, 9999 in milliseconds in hex. + private static final byte[] dec319999Ms = { + (byte) 0, (byte) 0, (byte) 0xE6, (byte) 0x77, (byte) 0xD2, (byte) 0x1F, (byte) 0xD8, (byte) 0x18 + }; + // Dec 31, 9999 represented in Generalized time format YYYYMMDDhhmmssZ. + // "99991231235959Z" in hex. Refer RFC 5280 section 4.1.2.5.2 + private static final byte[] dec319999 = { + 0x39, 0x39, 0x39, 0x39, 0x31, 0x32, 0x33, 0x31, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, + }; + // Jan 01, 1970 represented in UTC time format YYMMDDhhmmssZ. + // "700101000000Z" in hex. Refer RFC 5280 section 4.1.2.5.1 + private static final byte[] jan01970 = { + 0x37, 0x30, 0x30, 0x31, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, + }; + // The KeyMint name "JavacardKeymintDevice" returned from getHwInfo. + private static final byte[] JavacardKeymintDevice = { + 0x4a, 0x61, 0x76, 0x61, 0x63, 0x61, 0x72, 0x64, 0x4b, 0x65, 0x79, 0x6d, 0x69, 0x6e, 0x74, 0x44, + 0x65, 0x76, 0x69, 0x63, 0x65, + }; + // The KeyMint author name "Google" returned from getHwInfo. + public static final byte[] Google = {0x47, 0x6F, 0x6F, 0x67, 0x6C, 0x65}; + // Attestation ID tags to be included in attestation record. + private static final short[] attTags = { + KMType.ATTESTATION_ID_BRAND, + KMType.ATTESTATION_ID_DEVICE, + KMType.ATTESTATION_ID_IMEI, + KMType.ATTESTATION_ID_MANUFACTURER, + KMType.ATTESTATION_ID_MEID, + KMType.ATTESTATION_ID_MODEL, + KMType.ATTESTATION_ID_PRODUCT, + KMType.ATTESTATION_ID_SERIAL + }; + // Below are the constants of instructions in APDU command header. + // Top 32 commands are reserved for provisioning. + private static final byte KEYMINT_CMD_APDU_START = 0x20; + // RKP + public static final byte INS_GET_RKP_HARDWARE_INFO = KEYMINT_CMD_APDU_START + 27; // 0x3B + public static final byte INS_GENERATE_RKP_KEY_CMD = KEYMINT_CMD_APDU_START + 28; // 0x3C + public static final byte INS_BEGIN_SEND_DATA_CMD = KEYMINT_CMD_APDU_START + 29; // 0x3D + public static final byte INS_UPDATE_KEY_CMD = KEYMINT_CMD_APDU_START + 30; // 0x3E + // Constant + public static final byte INS_UPDATE_EEK_CHAIN_CMD = KEYMINT_CMD_APDU_START + 31; // 0x3F + public static final byte INS_UPDATE_CHALLENGE_CMD = KEYMINT_CMD_APDU_START + 32; // 0x40 + public static final byte INS_FINISH_SEND_DATA_CMD = KEYMINT_CMD_APDU_START + 33; // 0x41 + public static final byte INS_GET_RESPONSE_CMD = KEYMINT_CMD_APDU_START + 34; // 0x42 + private static final byte INS_GENERATE_KEY_CMD = KEYMINT_CMD_APDU_START + 1; // 0x21 + private static final byte INS_IMPORT_KEY_CMD = KEYMINT_CMD_APDU_START + 2; // 0x22 + private static final byte INS_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 3; // 0x23 + private static final byte INS_EXPORT_KEY_CMD = KEYMINT_CMD_APDU_START + 4; // 0x24 + private static final byte INS_ATTEST_KEY_CMD = KEYMINT_CMD_APDU_START + 5; // 0x25 + private static final byte INS_UPGRADE_KEY_CMD = KEYMINT_CMD_APDU_START + 6; // 0x26 + private static final byte INS_DELETE_KEY_CMD = KEYMINT_CMD_APDU_START + 7; // 0x27 + private static final byte INS_DELETE_ALL_KEYS_CMD = KEYMINT_CMD_APDU_START + 8; // 0x28 + private static final byte INS_ADD_RNG_ENTROPY_CMD = KEYMINT_CMD_APDU_START + 9; // 0x29 + private static final byte INS_COMPUTE_SHARED_HMAC_CMD = KEYMINT_CMD_APDU_START + 10; // 0x2A + private static final byte INS_DESTROY_ATT_IDS_CMD = KEYMINT_CMD_APDU_START + 11; // 0x2B + private static final byte INS_VERIFY_AUTHORIZATION_CMD = KEYMINT_CMD_APDU_START + 12; // 0x2C + private static final byte INS_GET_HMAC_SHARING_PARAM_CMD = KEYMINT_CMD_APDU_START + 13; // 0x2D + private static final byte INS_GET_KEY_CHARACTERISTICS_CMD = KEYMINT_CMD_APDU_START + 14; // 0x2E + private static final byte INS_GET_HW_INFO_CMD = KEYMINT_CMD_APDU_START + 15; // 0x2F + private static final byte INS_BEGIN_OPERATION_CMD = KEYMINT_CMD_APDU_START + 16; // 0x30 + private static final byte INS_UPDATE_OPERATION_CMD = KEYMINT_CMD_APDU_START + 17; // 0x31 + private static final byte INS_FINISH_OPERATION_CMD = KEYMINT_CMD_APDU_START + 18; // 0x32 + private static final byte INS_ABORT_OPERATION_CMD = KEYMINT_CMD_APDU_START + 19; // 0x33 + private static final byte INS_DEVICE_LOCKED_CMD = KEYMINT_CMD_APDU_START + 20; // 0x34 + private static final byte INS_EARLY_BOOT_ENDED_CMD = KEYMINT_CMD_APDU_START + 21; // 0x35 + private static final byte INS_GET_CERT_CHAIN_CMD = KEYMINT_CMD_APDU_START + 22; // 0x36 + private static final byte INS_UPDATE_AAD_OPERATION_CMD = KEYMINT_CMD_APDU_START + 23; // 0x37 + private static final byte INS_BEGIN_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 24; // 0x38 + private static final byte INS_FINISH_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 25; // 0x39 + private static final byte INS_INIT_STRONGBOX_CMD = KEYMINT_CMD_APDU_START + 26; // 0x3A + // The instructions from 0x43 to 0x4C will be reserved for KeyMint 1.0 for any future use. + // KeyMint 2.0 Instructions + private static final byte INS_GET_ROT_CHALLENGE_CMD = KEYMINT_CMD_APDU_START + 45; // 0x4D + private static final byte INS_GET_ROT_DATA_CMD = KEYMINT_CMD_APDU_START + 46; // 0x4E + private static final byte INS_SEND_ROT_DATA_CMD = KEYMINT_CMD_APDU_START + 47; // 0x4F + private static final byte KEYMINT_CMD_APDU_END = KEYMINT_CMD_APDU_START + 48; // 0x50 + private static final byte INS_END_KM_CMD = 0x7F; + // Instruction values from 0xCD to 0xFF are completely reserved for Vendors to use and + // will never be used by the base line code in future. + private static final byte INS_KM_VENDOR_START_CMD = (byte) 0xCD; + private static final byte INS_KM_VENDOR_END_CMD = (byte) 0xFF; + // Index in apduFlagsStatus[] to check if instruction command is case 4 type in the Apdu + protected static final byte APDU_CASE4_COMMAND_STATUS_INDEX = 0; + // Index in apduFlagsStatus[] to check if Apdu setIncomingAndReceive function is called + protected static final byte APDU_INCOMING_AND_RECEIVE_STATUS_INDEX = 1; + // The maximum buffer size of combined seed and nonce. + private static final byte HMAC_SHARED_PARAM_MAX_SIZE = 64; + // Instance of RemotelyProvisionedComponentDevice, used to redirect the rkp commands. + protected static KMRemotelyProvisionedComponentDevice rkp; + // Instance of Cbor encoder. + protected static KMEncoder encoder; + // Instance of Cbor decoder. + protected static KMDecoder decoder; + // Instance of KMRepository class for memory management. + protected static KMRepository repository; + // Instance of KMSEProvider for doing crypto operations. + protected static KMSEProvider seProvider; + // Holds the instance of KMOperationStates. A maximum of 4 instances of KMOperatioState is + // allowed. + protected static KMOperationState[] opTable; + // Instance of KMKeymintDataStore which helps to store and retrieve the data. + protected static KMKeymintDataStore kmDataStore; + + // Short array used to store the temporary results. + protected static short[] tmpVariables; + // Short array used to hold the dictionary items. + protected static short[] data; + // Buffer to store the transportKey which is used in the import wrapped key. Import wrapped + // key is divided into two stages 1. BEGIN_IMPORT_WRAPPED_KEY 2. FINISH_IMPORT_WRAPPED_KEY. + // The transportKey is retrieved and stored in this buffer at stage 1) and is later used in + // stage 2). + protected static byte[] wrappingKey; + // Transient byte array used to store the flags if APDU command type is of case 4 and if + // APDU setIncomingAndReceive() function is called or not. + protected static byte[] apduStatusFlags; + + /** Registers this applet. */ + protected KMKeymasterApplet(KMSEProvider seImpl) { + seProvider = seImpl; + boolean isUpgrading = seProvider.isUpgrading(); + repository = new KMRepository(isUpgrading); + encoder = new KMEncoder(); + decoder = new KMDecoder(); + kmDataStore = new KMKeymintDataStore(seProvider, repository); + data = JCSystem.makeTransientShortArray(DATA_ARRAY_SIZE, JCSystem.CLEAR_ON_DESELECT); + tmpVariables = + JCSystem.makeTransientShortArray(TMP_VARIABLE_ARRAY_SIZE, JCSystem.CLEAR_ON_DESELECT); + wrappingKey = + JCSystem.makeTransientByteArray((short) (WRAPPING_KEY_SIZE + 1), JCSystem.CLEAR_ON_RESET); + resetWrappingKey(); + apduStatusFlags = JCSystem.makeTransientByteArray((short) 2, JCSystem.CLEAR_ON_RESET); + opTable = new KMOperationState[MAX_OPERATIONS_COUNT]; + short index = 0; + while (index < MAX_OPERATIONS_COUNT) { + opTable[index] = new KMOperationState(); + index++; + } + KMType.initialize(); + if (!isUpgrading) { + kmDataStore.createMasterKey(MASTER_KEY_SIZE); + } + // initialize default values + initHmacNonceAndSeed(); + rkp = + new KMRemotelyProvisionedComponentDevice( + encoder, decoder, repository, seProvider, kmDataStore); + } + + /** Sends a response, may be extended response, as requested by the command. */ + public static void sendOutgoing(APDU apdu, short resp) { + // TODO handle the extended buffer stuff. We can reuse this. + short bufferStartOffset = repository.allocAvailableMemory(); + byte[] buffer = repository.getHeap(); + // TODO we can change the following to incremental send. + short bufferLength = + encoder.encode(resp, buffer, bufferStartOffset, repository.getHeapReclaimIndex()); + if (((short) (bufferLength + bufferStartOffset)) > ((short) repository.getHeap().length)) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + + /* In T=0 protocol, On a case 4 command, setIncomingAndReceive() must + * be invoked prior to calling setOutgoing(). Otherwise, erroneous + * behavior may result + * */ + if (apduStatusFlags[APDU_CASE4_COMMAND_STATUS_INDEX] == 1 + && apduStatusFlags[APDU_INCOMING_AND_RECEIVE_STATUS_INDEX] == 0 + && APDU.getProtocol() == APDU.PROTOCOL_T0) { + apdu.setIncomingAndReceive(); + } + // Send data + apdu.setOutgoing(); + apdu.setOutgoingLength(bufferLength); + apdu.sendBytesLong(buffer, bufferStartOffset, bufferLength); + } + + /** Receives data, which can be extended data, as requested by the command instance. */ + public static short receiveIncoming(APDU apdu, short reqExp) { + byte[] srcBuffer = apdu.getBuffer(); + short recvLen = apdu.setIncomingAndReceive(); + short srcOffset = apdu.getOffsetCdata(); + apduStatusFlags[APDU_INCOMING_AND_RECEIVE_STATUS_INDEX] = 1; + // TODO add logic to handle the extended length buffer. In this case the memory can be reused + // from extended buffer. + short bufferLength = apdu.getIncomingLength(); + short bufferStartOffset = repository.allocReclaimableMemory(bufferLength); + short index = bufferStartOffset; + byte[] buffer = repository.getHeap(); + while (recvLen > 0 && ((short) (index - bufferStartOffset) < bufferLength)) { + Util.arrayCopyNonAtomic(srcBuffer, srcOffset, buffer, index, recvLen); + index += recvLen; + recvLen = apdu.receiveBytes(srcOffset); + } + short req = decoder.decode(reqExp, buffer, bufferStartOffset, bufferLength); + repository.reclaimMemory(bufferLength); + return req; + } + + private static short createKeyBlobInstance(byte keyType) { + short arrayLen = 0; + switch (keyType) { + case ASYM_KEY_TYPE: + arrayLen = ASYM_KEY_BLOB_SIZE_V2_V3; + break; + case SYM_KEY_TYPE: + arrayLen = SYM_KEY_BLOB_SIZE_V2_V3; + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + return KMArray.instance(arrayLen); + } + + private static void addTags(short params, boolean hwEnforced, KMAttestationCert cert) { + short index = 0; + short arr = KMKeyParameters.cast(params).getVals(); + short len = KMArray.cast(arr).length(); + short tag; + while (index < len) { + tag = KMArray.cast(arr).get(index); + cert.extensionTag(tag, hwEnforced); + index++; + } + } + + private static void setUniqueId(KMAttestationCert cert, short attAppId, byte[] scratchPad) { + if (!KMTag.isPresent(data[KEY_PARAMETERS], KMType.BOOL_TAG, KMType.INCLUDE_UNIQUE_ID)) { + return; + } + // temporal count T + short time = + KMKeyParameters.findTag(KMType.DATE_TAG, KMType.CREATION_DATETIME, data[KEY_PARAMETERS]); + if (time == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + time = KMIntegerTag.cast(time).getValue(); + + // Reset After Rotation R - it will be part of HW Enforced key + // characteristics + byte resetAfterRotation = 0; + if (KMTag.isPresent(data[KEY_PARAMETERS], KMType.BOOL_TAG, KMType.RESET_SINCE_ID_ROTATION)) { + resetAfterRotation = 0x01; + } + + cert.makeUniqueId( + scratchPad, + (short) 0, + KMInteger.cast(time).getBuffer(), + KMInteger.cast(time).getStartOff(), + KMInteger.cast(time).length(), + KMByteBlob.cast(attAppId).getBuffer(), + KMByteBlob.cast(attAppId).getStartOff(), + KMByteBlob.cast(attAppId).length(), + resetAfterRotation, + kmDataStore.getMasterKey()); + } + + private static void validateRSAKey(byte[] scratchPad) { + // Read key size + if (!KMTag.isValidKeySize(data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + if (!KMTag.isValidPublicExponent(data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + } + + // Generate key handlers + private static void generateRSAKey(byte[] scratchPad) { + // Validate RSA Key + validateRSAKey(scratchPad); + // Now generate 2048 bit RSA keypair for the given exponent + short[] lengths = tmpVariables; + data[PUB_KEY] = KMByteBlob.instance((short) 256); + data[SECRET] = KMByteBlob.instance((short) 256); + seProvider.createAsymmetricKey( + KMType.RSA, + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + KMByteBlob.cast(data[PUB_KEY]).getBuffer(), + KMByteBlob.cast(data[PUB_KEY]).getStartOff(), + KMByteBlob.cast(data[PUB_KEY]).length(), + lengths); + + data[KEY_BLOB] = createKeyBlobInstance(ASYM_KEY_TYPE); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PUB_KEY, data[PUB_KEY]); + } + + private static void validateAESKey() { + // Read key size + if (!KMTag.isValidKeySize(data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + // Read Block mode - array of byte values + if (KMTag.isPresent(data[KEY_PARAMETERS], KMType.ENUM_ARRAY_TAG, KMType.BLOCK_MODE)) { + short blockModes = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.BLOCK_MODE, data[KEY_PARAMETERS]); + // If it is a GCM mode + if (KMEnumArrayTag.cast(blockModes).contains(KMType.GCM)) { + // Min mac length must be present + KMTag.assertPresence( + data[KEY_PARAMETERS], + KMType.UINT_TAG, + KMType.MIN_MAC_LENGTH, + KMError.MISSING_MIN_MAC_LENGTH); + short macLength = + KMKeyParameters.findTag(KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, data[KEY_PARAMETERS]); + macLength = KMIntegerTag.cast(macLength).getValue(); + // Validate the MIN_MAC_LENGTH for AES - should be multiple of 8, less then 128 bits + // and greater the 96 bits + if (KMInteger.cast(macLength).getSignificantShort() != 0 + || KMInteger.cast(macLength).getShort() > 128 + || KMInteger.cast(macLength).getShort() < 96 + || (KMInteger.cast(macLength).getShort() % 8) != 0) { + KMException.throwIt(KMError.UNSUPPORTED_MIN_MAC_LENGTH); + } + } + } + } + + private static void generateAESKey(byte[] scratchPad) { + validateAESKey(); + short keysize = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + short len = seProvider.createSymmetricKey(KMType.AES, keysize, scratchPad, (short) 0); + data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, len); + data[KEY_BLOB] = createKeyBlobInstance(SYM_KEY_TYPE); + } + + private static void validateECKeys() { + // Read key size + short ecCurve = KMEnumTag.getValue(KMType.ECCURVE, data[KEY_PARAMETERS]); + /* In KeyMint 2.0, If EC_CURVE not provided, generateKey + * must return ErrorCode::UNSUPPORTED_KEY_SIZE or ErrorCode::UNSUPPORTED_EC_CURVE. + */ + if (ecCurve != KMType.P_256) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + short ecKeySize = KMEnumTag.getValue(KMType.KEYSIZE, data[KEY_PARAMETERS]); + if ((ecKeySize != KMType.INVALID_VALUE) && !KMTag.isValidKeySize(data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + } + + private static void generateECKeys(byte[] scratchPad) { + validateECKeys(); + short[] lengths = tmpVariables; + seProvider.createAsymmetricKey( + KMType.EC, + scratchPad, + (short) 0, + (short) 128, + scratchPad, + (short) 128, + (short) 128, + lengths); + data[PUB_KEY] = KMByteBlob.instance(scratchPad, (short) 128, lengths[1]); + data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, lengths[0]); + data[KEY_BLOB] = createKeyBlobInstance(ASYM_KEY_TYPE); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PUB_KEY, data[PUB_KEY]); + } + + private static void validateTDESKey() { + if (!KMTag.isValidKeySize(data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + // Read Minimum Mac length - it must not be present + KMTag.assertAbsence( + data[KEY_PARAMETERS], KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, KMError.INVALID_TAG); + } + + private static void generateTDESKey(byte[] scratchPad) { + validateTDESKey(); + short len = seProvider.createSymmetricKey(KMType.DES, (short) 168, scratchPad, (short) 0); + data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, len); + data[KEY_BLOB] = createKeyBlobInstance(SYM_KEY_TYPE); + } + + private static void validateHmacKey() { + // If params does not contain any digest throw unsupported digest error. + KMTag.assertPresence( + data[KEY_PARAMETERS], KMType.ENUM_ARRAY_TAG, KMType.DIGEST, KMError.UNSUPPORTED_DIGEST); + + // check whether digest sizes are greater then or equal to min mac length. + // Only SHA256 digest must be supported. + if (!KMEnumArrayTag.contains(KMType.DIGEST, KMType.SHA2_256, data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.UNSUPPORTED_DIGEST); + } + // Read Minimum Mac length + KMTag.assertPresence( + data[KEY_PARAMETERS], + KMType.UINT_TAG, + KMType.MIN_MAC_LENGTH, + KMError.MISSING_MIN_MAC_LENGTH); + short minMacLength = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, data[KEY_PARAMETERS]); + + if (((short) (minMacLength % 8) != 0) + || minMacLength < MIN_HMAC_LENGTH_BITS + || minMacLength > SHA256_DIGEST_LEN_BITS) { + KMException.throwIt(KMError.UNSUPPORTED_MIN_MAC_LENGTH); + } + // Read Keysize + if (!KMTag.isValidKeySize(data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + } + + private static void generateHmacKey(byte[] scratchPad) { + validateHmacKey(); + short keysize = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + // generate HMAC Key + short len = seProvider.createSymmetricKey(KMType.HMAC, keysize, scratchPad, (short) 0); + data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, len); + data[KEY_BLOB] = createKeyBlobInstance(SYM_KEY_TYPE); + } + + // This function is only called from processUpgradeKey command. + // 1. Update the latest values of OSVersion, OSPatch, VendorPatch and BootPatch in the + // KeyBlob's KeyCharacteristics. + // 2. Re-create KeyBlob's KeyCharacteristics from HW_PARAMS to make sure we don't miss + // anything which happens in these functions makeSbEnforced and makeTeeEnforced in + // the future. Like validations. + // 3. No need to create Keystore Enforced list here as it is not required to be included in + // the KeyBlob's KeyCharacteristics. + // 4. No need to create KeyCharacteristics as upgradeKey does not require to return any + // KeyCharacteristics back. + private static void upgradeKeyBlobKeyCharacteristics(short hwParams, byte[] scratchPad) { + short osVersion = kmDataStore.getOsVersion(); + short osPatch = kmDataStore.getOsPatch(); + short vendorPatch = kmDataStore.getVendorPatchLevel(); + short bootPatch = kmDataStore.getBootPatchLevel(); + data[SB_PARAMETERS] = + KMKeyParameters.makeSbEnforced( + hwParams, (byte) data[ORIGIN], osVersion, osPatch, vendorPatch, bootPatch, scratchPad); + data[TEE_PARAMETERS] = KMKeyParameters.makeTeeEnforced(hwParams, scratchPad); + data[HW_PARAMETERS] = KMKeyParameters.makeHwEnforced(data[SB_PARAMETERS], data[TEE_PARAMETERS]); + } + + private static void makeKeyCharacteristics(byte[] scratchPad) { + short osVersion = kmDataStore.getOsVersion(); + short osPatch = kmDataStore.getOsPatch(); + short vendorPatch = kmDataStore.getVendorPatchLevel(); + short bootPatch = kmDataStore.getBootPatchLevel(); + data[SB_PARAMETERS] = + KMKeyParameters.makeSbEnforced( + data[KEY_PARAMETERS], + (byte) data[ORIGIN], + osVersion, + osPatch, + vendorPatch, + bootPatch, + scratchPad); + data[TEE_PARAMETERS] = KMKeyParameters.makeTeeEnforced(data[KEY_PARAMETERS], scratchPad); + data[SW_PARAMETERS] = KMKeyParameters.makeKeystoreEnforced(data[KEY_PARAMETERS], scratchPad); + data[HW_PARAMETERS] = KMKeyParameters.makeHwEnforced(data[SB_PARAMETERS], data[TEE_PARAMETERS]); + data[KEY_CHARACTERISTICS] = KMKeyCharacteristics.instance(); + KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).setStrongboxEnforced(data[SB_PARAMETERS]); + KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).setKeystoreEnforced(data[SW_PARAMETERS]); + KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).setTeeEnforced(data[TEE_PARAMETERS]); + } + + private static void createEncryptedKeyBlob(byte[] scratchPad) { + // make root of trust blob + data[ROT] = readROT(scratchPad, KEYBLOB_CURRENT_VERSION); + if (data[ROT] == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + // make hidden key params list + data[HIDDEN_PARAMETERS] = + KMKeyParameters.makeHidden(data[KEY_PARAMETERS], data[ROT], scratchPad); + data[KEY_BLOB_VERSION_DATA_OFFSET] = KMInteger.uint_16(KEYBLOB_CURRENT_VERSION); + // create custom tags + data[CUSTOM_TAGS] = KMKeyParameters.makeCustomTags(data[HW_PARAMETERS], scratchPad); + // encrypt the secret and cryptographically attach that to authorization data + encryptSecret(scratchPad); + // create key blob array + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_SECRET, data[SECRET]); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_AUTH_TAG, data[AUTH_TAG]); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_NONCE, data[NONCE]); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_VERSION_OFFSET, data[KEY_BLOB_VERSION_DATA_OFFSET]); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_CUSTOM_TAGS, data[CUSTOM_TAGS]); + + short tempChar = KMKeyCharacteristics.instance(); + short emptyParam = KMArray.instance((short) 0); + emptyParam = KMKeyParameters.instance(emptyParam); + KMKeyCharacteristics.cast(tempChar).setStrongboxEnforced(data[SB_PARAMETERS]); + KMKeyCharacteristics.cast(tempChar).setKeystoreEnforced(emptyParam); + KMKeyCharacteristics.cast(tempChar).setTeeEnforced(data[TEE_PARAMETERS]); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PARAMS, tempChar); + } + + // Read RoT + public static short readROT(byte[] scratchPad, short version) { + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + short len = kmDataStore.getBootKey(scratchPad, (short) 0); + // As per IKeyMintDevice.aidl specification The root of trust + // consists of verifyBootKey, boot state and device locked. + if (version <= KEYBLOB_VERSION_1) { + // To parse old keyblobs verified boot hash is included in + // the root of trust. + len += kmDataStore.getVerifiedBootHash(scratchPad, (short) len); + } + short bootState = kmDataStore.getBootState(); + len = Util.setShort(scratchPad, len, bootState); + if (kmDataStore.isDeviceBootLocked()) { + scratchPad[len] = (byte) 1; + } else { + scratchPad[len] = (byte) 0; + } + len++; + return KMByteBlob.instance(scratchPad, (short) 0, len); + } + + private static void encryptSecret(byte[] scratchPad) { + // make nonce + data[NONCE] = KMByteBlob.instance(AES_GCM_NONCE_LENGTH); + data[AUTH_TAG] = KMByteBlob.instance(AES_GCM_AUTH_TAG_LENGTH); + seProvider.newRandomNumber( + KMByteBlob.cast(data[NONCE]).getBuffer(), + KMByteBlob.cast(data[NONCE]).getStartOff(), + KMByteBlob.cast(data[NONCE]).length()); + // derive master key - stored in derivedKey + short len = deriveKey(scratchPad); + len = + seProvider.aesGCMEncrypt( + KMByteBlob.cast(data[DERIVED_KEY]).getBuffer(), + KMByteBlob.cast(data[DERIVED_KEY]).getStartOff(), + KMByteBlob.cast(data[DERIVED_KEY]).length(), + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + scratchPad, + (short) 0, + KMByteBlob.cast(data[NONCE]).getBuffer(), + KMByteBlob.cast(data[NONCE]).getStartOff(), + KMByteBlob.cast(data[NONCE]).length(), + null, + (short) 0, + (short) 0, + KMByteBlob.cast(data[AUTH_TAG]).getBuffer(), + KMByteBlob.cast(data[AUTH_TAG]).getStartOff(), + KMByteBlob.cast(data[AUTH_TAG]).length()); + + if (len > 0 && len != KMByteBlob.cast(data[SECRET]).length()) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, len); + } + + private static byte getKeyType(short hardwareParams) { + short alg = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, hardwareParams); + if (KMEnumTag.cast(alg).getValue() == KMType.RSA + || KMEnumTag.cast(alg).getValue() == KMType.EC) { + return ASYM_KEY_TYPE; + } + return SYM_KEY_TYPE; + } + + private static void makeAuthData(short version, byte[] scratchPad) { + // For KeyBlob V2: Auth Data includes HW_PARAMETERS, HIDDEN_PARAMETERS, CUSTOM_TAGS, VERSION and + // PUB_KEY. + // For KeyBlob V1: Auth Data includes HW_PARAMETERS, HIDDEN_PARAMETERS, VERSION and PUB_KEY. + // For KeyBlob V0: Auth Data includes HW_PARAMETERS, HIDDEN_PARAMETERS and PUB_KEY. + // VERSION is included only for KeyBlobs having version >= 1. + // PUB_KEY is included for only ASYMMETRIC KeyBlobs. + short index = 0; + short numParams = 0; + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 10, (byte) 0); + byte keyType = getKeyType(data[HW_PARAMETERS]); + // Copy the relevant parameters in the scratchPad in the order + // 1. HW_PARAMETERS + // 2. HIDDEN_PARAMETERS + // 3. VERSION ( Only Version >= 1) + // 4. PUB_KEY ( Only for Asymmetric Keys) + switch (version) { + case (short) 0: + numParams = 2; + Util.setShort(scratchPad, (short) 0, KMKeyParameters.cast(data[HW_PARAMETERS]).getVals()); + Util.setShort( + scratchPad, (short) 2, KMKeyParameters.cast(data[HIDDEN_PARAMETERS]).getVals()); + // For Asymmetric Keys include the PUB_KEY. + if (keyType == ASYM_KEY_TYPE) { + numParams = 3; + Util.setShort(scratchPad, (short) 4, data[PUB_KEY]); + } + break; + case (short) 1: + numParams = 3; + Util.setShort(scratchPad, (short) 0, KMKeyParameters.cast(data[HW_PARAMETERS]).getVals()); + Util.setShort( + scratchPad, (short) 2, KMKeyParameters.cast(data[HIDDEN_PARAMETERS]).getVals()); + Util.setShort(scratchPad, (short) 4, data[KEY_BLOB_VERSION_DATA_OFFSET]); + // For Asymmetric Keys include the PUB_KEY. + if (keyType == ASYM_KEY_TYPE) { + numParams = 4; + Util.setShort(scratchPad, (short) 6, data[PUB_KEY]); + } + break; + case (short) 2: + numParams = 4; + Util.setShort(scratchPad, (short) 0, KMKeyParameters.cast(data[HW_PARAMETERS]).getVals()); + Util.setShort( + scratchPad, (short) 2, KMKeyParameters.cast(data[HIDDEN_PARAMETERS]).getVals()); + Util.setShort(scratchPad, (short) 4, KMKeyParameters.cast(data[CUSTOM_TAGS]).getVals()); + Util.setShort(scratchPad, (short) 6, data[KEY_BLOB_VERSION_DATA_OFFSET]); + // For Asymmetric Keys include the PUB_KEY. + if (keyType == ASYM_KEY_TYPE) { + numParams = 5; + Util.setShort(scratchPad, (short) 8, data[PUB_KEY]); + } + break; + default: + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + short prevReclaimIndex = repository.getHeapReclaimIndex(); + short authIndex = repository.allocReclaimableMemory(MAX_AUTH_DATA_SIZE); + index = 0; + short len = 0; + Util.arrayFillNonAtomic(repository.getHeap(), authIndex, MAX_AUTH_DATA_SIZE, (byte) 0); + while (index < numParams) { + short tag = Util.getShort(scratchPad, (short) (index * 2)); + len = encoder.encode(tag, repository.getHeap(), (short) (authIndex + 32), prevReclaimIndex); + Util.arrayCopyNonAtomic( + repository.getHeap(), + authIndex, + repository.getHeap(), + (short) (authIndex + len + 32), + (short) 32); + len = + seProvider.messageDigest256( + repository.getHeap(), + (short) (authIndex + 32), + (short) (len + 32), + repository.getHeap(), + authIndex); + if (len != 32) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + index++; + } + short authDataIndex = repository.alloc(len); + Util.arrayCopyNonAtomic( + repository.getHeap(), authIndex, repository.getHeap(), authDataIndex, len); + repository.reclaimMemory(MAX_AUTH_DATA_SIZE); + data[AUTH_DATA] = authDataIndex; + data[AUTH_DATA_LENGTH] = len; + } + + private static short deriveKeyForOldKeyBlobs(byte[] scratchPad) { + // KeyDerivation: + // 1. Do HMAC Sign, Auth data. + // 2. HMAC Sign generates an output of 32 bytes length. + // Consume only first 16 bytes as derived key. + // Hmac sign. + short len = + seProvider.hmacKDF( + kmDataStore.getMasterKey(), + repository.getHeap(), + data[AUTH_DATA], + data[AUTH_DATA_LENGTH], + scratchPad, + (short) 0); + if (len < 16) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + len = 16; + data[DERIVED_KEY] = KMByteBlob.instance(scratchPad, (short) 0, len); + return len; + } + + private static short deriveKey(byte[] scratchPad) { + // For KeyBlob V3: Auth Data includes HW_PARAMETERS, HIDDEN_PARAMETERS, CUSTOM_TAGS, VERSION and + // PUB_KEY. + short index = 0; + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 10, (byte) 0); + byte keyType = getKeyType(data[HW_PARAMETERS]); + // Copy the relevant parameters in the scratchPad in the order + // 1. HW_PARAMETERS + // 2. HIDDEN_PARAMETERS + // 3. CUSTOM_TAGS + // 3. VERSION ( Only Version >= 1) + // 4. PUB_KEY ( Only for Asymmetric Keys) + short numParams = 4; + Util.setShort(scratchPad, (short) 0, KMKeyParameters.cast(data[HW_PARAMETERS]).getVals()); + Util.setShort(scratchPad, (short) 2, KMKeyParameters.cast(data[HIDDEN_PARAMETERS]).getVals()); + Util.setShort(scratchPad, (short) 4, KMKeyParameters.cast(data[CUSTOM_TAGS]).getVals()); + Util.setShort(scratchPad, (short) 6, data[KEY_BLOB_VERSION_DATA_OFFSET]); + // For Asymmetric Keys include the PUB_KEY. + if (keyType == ASYM_KEY_TYPE) { + numParams = 5; + Util.setShort(scratchPad, (short) 8, data[PUB_KEY]); + } + short prevReclaimIndex = repository.getHeapReclaimIndex(); + short authIndex = repository.allocReclaimableMemory(MAX_AUTH_DATA_SIZE); + Util.arrayFillNonAtomic(repository.getHeap(), authIndex, MAX_AUTH_DATA_SIZE, (byte) 0); + short len = 0; + KMOperation operation = null; + try { + operation = + seProvider.initSymmetricOperation( + KMType.SIGN, + KMType.HMAC, + KMType.SHA2_256, + KMType.PADDING_NONE, + (byte) KMType.INVALID_VALUE, + (Object) kmDataStore.getMasterKey(), + KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY, + (byte[]) null, + (short) 0, + (short) 0, + (short) 0, + false); + + byte arrayHeader = (byte) 0x80; + arrayHeader |= (byte) numParams; + ((byte[]) repository.getHeap())[authIndex] = arrayHeader; + operation.update(repository.getHeap(), authIndex, (short) 1); + + while (index < numParams) { + short tag = Util.getShort(scratchPad, (short) (index * 2)); + len = encoder.encode(tag, repository.getHeap(), (short) authIndex, prevReclaimIndex); + operation.update(repository.getHeap(), authIndex, len); + index++; + } + repository.reclaimMemory(MAX_AUTH_DATA_SIZE); + // KeyDerivation: + // 1. Do HMAC Sign, Auth data. + // 2. HMAC Sign generates an output of 32 bytes length. + // Consume only first 16 bytes as derived key. + // Hmac sign. + len = operation.sign(scratchPad, (short) 0, (short) 0, scratchPad, (short) 0); + } finally { + if (operation != null) { + operation.abort(); + } + } + if (len < 16) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + len = 16; + data[DERIVED_KEY] = KMByteBlob.instance(scratchPad, (short) 0, len); + return len; + } + + public static void sendResponse(APDU apdu, short err) { + short resp = KMArray.instance((short) 1); + err = KMError.translate(err); + short error = KMInteger.uint_16(err); + KMArray.cast(resp).add((short) 0, error); + sendOutgoing(apdu, resp); + } + + public static void generateRkpKey(byte[] scratchPad, short keyParams) { + data[KEY_PARAMETERS] = keyParams; + generateECKeys(scratchPad); + // create key blob + data[ORIGIN] = KMType.GENERATED; + makeKeyCharacteristics(scratchPad); + createEncryptedKeyBlob(scratchPad); + short prevReclaimIndex = repository.getHeapReclaimIndex(); + short offset = repository.allocReclaimableMemory(MAX_KEYBLOB_SIZE); + data[KEY_BLOB] = + encoder.encode( + data[KEY_BLOB], repository.getHeap(), offset, prevReclaimIndex, MAX_KEYBLOB_SIZE); + data[KEY_BLOB] = KMByteBlob.instance(repository.getHeap(), offset, data[KEY_BLOB]); + repository.reclaimMemory(MAX_KEYBLOB_SIZE); + } + + public static short getPubKey() { + return data[PUB_KEY]; + } + + public static short getPivateKey() { + return data[KEY_BLOB]; + } + + /** + * Encodes the object to the provided apdu buffer. + * + * @param object Object to be encoded. + * @param apduBuf Buffer on which the encoded data is copied. + * @param apduOff Start offset of the buffer. + * @param maxLen Max value of the expected out length. + * @return length of the encoded buffer. + */ + public static short encodeToApduBuffer( + short object, byte[] apduBuf, short apduOff, short maxLen) { + short prevReclaimIndex = repository.getHeapReclaimIndex(); + short offset = repository.allocReclaimableMemory(maxLen); + short len = encoder.encode(object, repository.getHeap(), offset, prevReclaimIndex, maxLen); + Util.arrayCopyNonAtomic(repository.getHeap(), offset, apduBuf, apduOff, len); + // release memory + repository.reclaimMemory(maxLen); + return len; + } + + public static short validateCertChain( + boolean validateEekRoot, + byte expCertAlg, + byte expLeafCertAlg, + short certChainArr, + byte[] scratchPad, + Object[] authorizedEekRoots) { + short len = KMArray.cast(certChainArr).length(); + short coseHeadersExp = KMCoseHeaders.exp(); + // prepare exp for coseky + short coseKeyExp = KMCoseKey.exp(); + short ptr1; + short ptr2; + short signStructure; + short encodedLen; + short prevCoseKey = 0; + short keySize; + short alg = expCertAlg; + short index; + for (index = 0; index < len; index++) { + ptr1 = KMArray.cast(certChainArr).get(index); + + // validate protected Headers + ptr2 = KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_PROTECTED_PARAMS_OFFSET); + ptr2 = + decoder.decode( + coseHeadersExp, + KMByteBlob.cast(ptr2).getBuffer(), + KMByteBlob.cast(ptr2).getStartOff(), + KMByteBlob.cast(ptr2).length()); + if (!KMCoseHeaders.cast(ptr2).isDataValid(rkp.rkpTmpVariables, alg, KMType.INVALID_VALUE)) { + KMException.throwIt(KMError.STATUS_FAILED); + } + + // parse and get the public key from payload. + ptr2 = KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_PAYLOAD_OFFSET); + ptr2 = + decoder.decode( + coseKeyExp, + KMByteBlob.cast(ptr2).getBuffer(), + KMByteBlob.cast(ptr2).getStartOff(), + KMByteBlob.cast(ptr2).length()); + if ((index == (short) (len - 1)) && len > 1) { + alg = expLeafCertAlg; + } + if (!KMCoseKey.cast(ptr2) + .isDataValid( + rkp.rkpTmpVariables, + KMCose.COSE_KEY_TYPE_EC2, + KMType.INVALID_VALUE, + alg, + KMCose.COSE_ECCURVE_256)) { + KMException.throwIt(KMError.STATUS_FAILED); + } + if (prevCoseKey == 0) { + prevCoseKey = ptr2; + } + // Get the public key. + keySize = KMCoseKey.cast(prevCoseKey).getEcdsa256PublicKey(scratchPad, (short) 0); + if (keySize != 65) { + KMException.throwIt(KMError.STATUS_FAILED); + } + if (validateEekRoot && (index == 0)) { + boolean found = false; + // In prod mode the first pubkey should match a well-known Google public key. + for (short i = 0; i < (short) authorizedEekRoots.length; i++) { + if (0 + == Util.arrayCompare( + scratchPad, + (short) 0, + (byte[]) authorizedEekRoots[i], + (short) 0, + (short) ((byte[]) authorizedEekRoots[i]).length)) { + found = true; + break; + } + } + if (!found) { + KMException.throwIt(KMError.STATUS_FAILED); + } + } + // Validate signature. + signStructure = + KMCose.constructCoseSignStructure( + KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_PROTECTED_PARAMS_OFFSET), + KMByteBlob.instance((short) 0), + KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_PAYLOAD_OFFSET)); + encodedLen = + KMKeymasterApplet.encodeToApduBuffer( + signStructure, scratchPad, keySize, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + + short signatureLen = + rkp.encodeES256CoseSignSignature( + KMByteBlob.cast(KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_SIGNATURE_OFFSET)) + .getBuffer(), + KMByteBlob.cast(KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_SIGNATURE_OFFSET)) + .getStartOff(), + KMByteBlob.length(KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_SIGNATURE_OFFSET)), + scratchPad, + (short) (keySize + encodedLen)); + + if (!seProvider.ecVerify256( + scratchPad, + (short) 0, + keySize, + scratchPad, + keySize, + encodedLen, + scratchPad, + (short) (keySize + encodedLen), + signatureLen)) { + KMException.throwIt(KMError.STATUS_FAILED); + } + prevCoseKey = ptr2; + } + return prevCoseKey; + } + + public static short generateBcc(boolean testMode, byte[] scratchPad) { + if (!testMode && kmDataStore.isProvisionLocked()) { + KMException.throwIt(KMError.STATUS_FAILED); + } + KMKey deviceUniqueKey = kmDataStore.getRkpDeviceUniqueKeyPair(testMode); + short temp = deviceUniqueKey.getPublicKey(scratchPad, (short) 0); + short coseKey = + KMCose.constructCoseKey( + rkp.rkpTmpVariables, + KMInteger.uint_8(KMCose.COSE_KEY_TYPE_EC2), + KMType.INVALID_VALUE, + KMNInteger.uint_8(KMCose.COSE_ALG_ES256), + KMInteger.uint_8(KMCose.COSE_ECCURVE_256), + scratchPad, + (short) 0, + temp, + KMType.INVALID_VALUE, + false); + temp = + KMKeymasterApplet.encodeToApduBuffer( + coseKey, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + // Construct payload. + short payload = + KMCose.constructCoseCertPayload( + KMCosePairTextStringTag.instance( + KMInteger.uint_8(KMCose.ISSUER), + KMTextString.instance( + KMCose.TEST_ISSUER_NAME, (short) 0, (short) KMCose.TEST_ISSUER_NAME.length)), + KMCosePairTextStringTag.instance( + KMInteger.uint_8(KMCose.SUBJECT), + KMTextString.instance( + KMCose.TEST_SUBJECT_NAME, (short) 0, (short) KMCose.TEST_SUBJECT_NAME.length)), + KMCosePairByteBlobTag.instance( + KMNInteger.uint_32(KMCose.SUBJECT_PUBLIC_KEY, (short) 0), + KMByteBlob.instance(scratchPad, (short) 0, temp)), + KMCosePairByteBlobTag.instance( + KMNInteger.uint_32(KMCose.KEY_USAGE, (short) 0), + KMByteBlob.instance( + KMCose.KEY_USAGE_SIGN, (short) 0, (short) KMCose.KEY_USAGE_SIGN.length))); + // temp temporarily holds the length of encoded cert payload. + temp = + KMKeymasterApplet.encodeToApduBuffer( + payload, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + payload = KMByteBlob.instance(scratchPad, (short) 0, temp); + + // protected header + short protectedHeader = + KMCose.constructHeaders( + rkp.rkpTmpVariables, + KMNInteger.uint_8(KMCose.COSE_ALG_ES256), + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE); + // temp temporarily holds the length of encoded headers. + temp = + KMKeymasterApplet.encodeToApduBuffer( + protectedHeader, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + protectedHeader = KMByteBlob.instance(scratchPad, (short) 0, temp); + + // unprotected headers. + short arr = KMArray.instance((short) 0); + short unprotectedHeader = KMCoseHeaders.instance(arr); + + // construct cose sign structure. + short coseSignStructure = + KMCose.constructCoseSignStructure(protectedHeader, KMByteBlob.instance((short) 0), payload); + // temp temporarily holds the length of encoded sign structure. + // Encode cose Sign_Structure. + temp = + KMKeymasterApplet.encodeToApduBuffer( + coseSignStructure, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + // do sign + short len = + seProvider.signWithDeviceUniqueKey( + deviceUniqueKey, scratchPad, (short) 0, temp, scratchPad, temp); + len = + KMAsn1Parser.instance() + .decodeEcdsa256Signature(KMByteBlob.instance(scratchPad, temp, len), scratchPad, temp); + coseSignStructure = KMByteBlob.instance(scratchPad, temp, len); + + // construct cose_sign1 + short coseSign1 = + KMCose.constructCoseSign1(protectedHeader, unprotectedHeader, payload, coseSignStructure); + + // [Cose_Key, Cose_Sign1] + short bcc = KMArray.instance((short) 2); + KMArray.cast(bcc).add((short) 0, coseKey); + KMArray.cast(bcc).add((short) 1, coseSign1); + return bcc; + } + + protected void initHmacNonceAndSeed() { + short nonce = repository.alloc((short) 32); + seProvider.newRandomNumber( + repository.getHeap(), nonce, KMKeymintDataStore.HMAC_SEED_NONCE_SIZE); + kmDataStore.initHmacNonce(repository.getHeap(), nonce, KMKeymintDataStore.HMAC_SEED_NONCE_SIZE); + } + + private void releaseAllOperations() { + short index = 0; + while (index < MAX_OPERATIONS_COUNT) { + opTable[index].reset(); + index++; + } + } + + private KMOperationState reserveOperation(short algorithm, short opHandle) { + short index = 0; + while (index < MAX_OPERATIONS_COUNT) { + if (opTable[index].getAlgorithm() == KMType.INVALID_VALUE) { + opTable[index].reset(); + opTable[index].setAlgorithm(algorithm); + opTable[index].setHandle( + KMInteger.cast(opHandle).getBuffer(), + KMInteger.cast(opHandle).getStartOff(), + KMInteger.cast(opHandle).length()); + return opTable[index]; + } + index++; + } + return null; + } + + private KMOperationState findOperation(short handle) { + return findOperation( + KMInteger.cast(handle).getBuffer(), + KMInteger.cast(handle).getStartOff(), + KMInteger.cast(handle).length()); + } + + private KMOperationState findOperation(byte[] opHandle, short start, short len) { + short index = 0; + while (index < MAX_OPERATIONS_COUNT) { + if (opTable[index].compare(opHandle, start, len) == 0) { + if (opTable[index].getAlgorithm() != KMType.INVALID_VALUE) { + return opTable[index]; + } + } + index++; + } + return null; + } + + private void releaseOperation(KMOperationState op) { + op.reset(); + } + + /** + * Selects this applet. + * + * @return Returns true if the keymaster is in correct state + */ + @Override + public boolean select() { + repository.onSelect(); + return true; + } + + /** De-selects this applet. */ + @Override + public void deselect() { + repository.onDeselect(); + } + + /** Uninstalls the applet after cleaning the repository. */ + @Override + public void uninstall() { + repository.onUninstall(); + } + + protected short mapISOErrorToKMError(short reason) { + switch (reason) { + case ISO7816.SW_CLA_NOT_SUPPORTED: + return KMError.UNSUPPORTED_CLA; + case ISO7816.SW_CONDITIONS_NOT_SATISFIED: + return KMError.SW_CONDITIONS_NOT_SATISFIED; + case ISO7816.SW_COMMAND_NOT_ALLOWED: + return KMError.CMD_NOT_ALLOWED; + case ISO7816.SW_DATA_INVALID: + return KMError.INVALID_DATA; + case ISO7816.SW_INCORRECT_P1P2: + return KMError.INVALID_P1P2; + case ISO7816.SW_INS_NOT_SUPPORTED: + return KMError.UNSUPPORTED_INSTRUCTION; + case ISO7816.SW_WRONG_LENGTH: + return KMError.SW_WRONG_LENGTH; + case ISO7816.SW_UNKNOWN: + default: + return KMError.UNKNOWN_ERROR; + } + } + + protected short mapCryptoErrorToKMError(short reason) { + switch (reason) { + case CryptoException.ILLEGAL_USE: + return KMError.CRYPTO_ILLEGAL_USE; + case CryptoException.ILLEGAL_VALUE: + return KMError.CRYPTO_ILLEGAL_VALUE; + case CryptoException.INVALID_INIT: + return KMError.CRYPTO_INVALID_INIT; + case CryptoException.NO_SUCH_ALGORITHM: + return KMError.CRYPTO_NO_SUCH_ALGORITHM; + case CryptoException.UNINITIALIZED_KEY: + return KMError.CRYPTO_UNINITIALIZED_KEY; + default: + return KMError.UNKNOWN_ERROR; + } + } + + public void updateApduStatusFlags(short apduIns) { + switch (apduIns) { + case INS_EXPORT_KEY_CMD: + case INS_DELETE_ALL_KEYS_CMD: + case INS_DESTROY_ATT_IDS_CMD: + case INS_VERIFY_AUTHORIZATION_CMD: + case INS_GET_HMAC_SHARING_PARAM_CMD: + case INS_GET_HW_INFO_CMD: + case INS_EARLY_BOOT_ENDED_CMD: + case INS_GET_ROT_CHALLENGE_CMD: + case INS_GET_ROT_DATA_CMD: + case INS_GET_RKP_HARDWARE_INFO: + case INS_FINISH_SEND_DATA_CMD: + case INS_GET_RESPONSE_CMD: + apduStatusFlags[APDU_CASE4_COMMAND_STATUS_INDEX] = 0; + break; + default: + // By default the instruction is set to case 4 command instruction. + break; + } + } + + /** + * Processes an incoming APDU and handles it using command objects. + * + * @param apdu the incoming APDU + */ + @Override + public void process(APDU apdu) { + try { + resetTransientBuffers(); + repository.onProcess(); + // If this is select applet apdu which is selecting this applet then return + if (apdu.isISOInterindustryCLA()) { + if (selectingApplet()) { + return; + } + } + byte[] apduBuffer = apdu.getBuffer(); + byte apduIns = apduBuffer[ISO7816.OFFSET_INS]; + if (!isKeyMintReady(apduIns)) { + ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED); + } + switch (apduIns) { + case INS_INIT_STRONGBOX_CMD: + processInitStrongBoxCmd(apdu); + sendResponse(apdu, KMError.OK); + return; + case INS_GENERATE_KEY_CMD: + processGenerateKey(apdu); + break; + case INS_IMPORT_KEY_CMD: + processImportKeyCmd(apdu); + break; + case INS_BEGIN_IMPORT_WRAPPED_KEY_CMD: + processBeginImportWrappedKeyCmd(apdu); + break; + case INS_FINISH_IMPORT_WRAPPED_KEY_CMD: + processFinishImportWrappedKeyCmd(apdu); + break; + case INS_EXPORT_KEY_CMD: + processExportKeyCmd(apdu); + break; + case INS_UPGRADE_KEY_CMD: + processUpgradeKeyCmd(apdu); + break; + case INS_DELETE_KEY_CMD: + processDeleteKeyCmd(apdu); + break; + case INS_DELETE_ALL_KEYS_CMD: + processDeleteAllKeysCmd(apdu); + break; + case INS_ADD_RNG_ENTROPY_CMD: + processAddRngEntropyCmd(apdu); + break; + case INS_COMPUTE_SHARED_HMAC_CMD: + processComputeSharedHmacCmd(apdu); + break; + case INS_DESTROY_ATT_IDS_CMD: + processDestroyAttIdsCmd(apdu); + break; + case INS_VERIFY_AUTHORIZATION_CMD: + processVerifyAuthorizationCmd(apdu); + break; + case INS_GET_HMAC_SHARING_PARAM_CMD: + processGetHmacSharingParamCmd(apdu); + break; + case INS_GET_KEY_CHARACTERISTICS_CMD: + processGetKeyCharacteristicsCmd(apdu); + break; + case INS_GET_HW_INFO_CMD: + processGetHwInfoCmd(apdu); + break; + case INS_BEGIN_OPERATION_CMD: + processBeginOperationCmd(apdu); + break; + case INS_UPDATE_OPERATION_CMD: + processUpdateOperationCmd(apdu); + break; + case INS_FINISH_OPERATION_CMD: + processFinishOperationCmd(apdu); + break; + case INS_ABORT_OPERATION_CMD: + processAbortOperationCmd(apdu); + break; + case INS_DEVICE_LOCKED_CMD: + processDeviceLockedCmd(apdu); + break; + case INS_EARLY_BOOT_ENDED_CMD: + processEarlyBootEndedCmd(apdu); + break; + case INS_UPDATE_AAD_OPERATION_CMD: + processUpdateAadOperationCmd(apdu); + break; + case INS_GENERATE_RKP_KEY_CMD: + case INS_BEGIN_SEND_DATA_CMD: + case INS_UPDATE_CHALLENGE_CMD: + case INS_UPDATE_EEK_CHAIN_CMD: + case INS_UPDATE_KEY_CMD: + case INS_FINISH_SEND_DATA_CMD: + case INS_GET_RESPONSE_CMD: + case INS_GET_RKP_HARDWARE_INFO: + rkp.process(apduIns, apdu); + break; + // KeyMint 2.0 + case INS_GET_ROT_CHALLENGE_CMD: + processGetRootOfTrustChallenge(apdu); + break; + case INS_GET_ROT_DATA_CMD: + sendResponse(apdu, KMError.UNIMPLEMENTED); + break; + case INS_SEND_ROT_DATA_CMD: + processSendRootOfTrust(apdu); + break; + default: + ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); + } + } catch (KMException exception) { + freeOperations(); + resetWrappingKey(); + sendResponse(apdu, KMException.reason()); + } catch (ISOException exp) { + freeOperations(); + resetWrappingKey(); + sendResponse(apdu, mapISOErrorToKMError(exp.getReason())); + } catch (CryptoException e) { + freeOperations(); + resetWrappingKey(); + sendResponse(apdu, mapCryptoErrorToKMError(e.getReason())); + } catch (Exception e) { + freeOperations(); + resetWrappingKey(); + sendResponse(apdu, KMError.GENERIC_UNKNOWN_ERROR); + } finally { + repository.clean(); + } + } + + private void processGetRootOfTrustChallenge(APDU apdu) { + byte[] scratchpad = apdu.getBuffer(); + // Generate 16-byte random challenge nonce, used to prove freshness when exchanging root of + // trust data. + seProvider.newRandomNumber(scratchpad, (short) 0, (short) 16); + kmDataStore.setChallenge(scratchpad, (short) 0, (short) 16); + short challenge = KMByteBlob.instance(scratchpad, (short) 0, (short) 16); + short arr = KMArray.instance((short) 2); + KMArray.cast(arr).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(arr).add((short) 1, challenge); + sendOutgoing(apdu, arr); + } + + private short sendRootOfTrustCmd(APDU apdu) { + short arrInst = KMArray.instance((short) 4); + short headers = KMCoseHeaders.exp(); + KMArray.cast(arrInst).add((short) 0, KMByteBlob.exp()); + KMArray.cast(arrInst).add((short) 1, headers); + KMArray.cast(arrInst).add((short) 2, KMByteBlob.exp()); + KMArray.cast(arrInst).add((short) 3, KMByteBlob.exp()); + short semanticTag = KMSemanticTag.exp(arrInst); + short arr = KMArray.exp(semanticTag); + return receiveIncoming(apdu, arr); + } + + private void processSendRootOfTrust(APDU apdu) { + byte[] scratchPad = apdu.getBuffer(); + short cmd = KMType.INVALID_VALUE; + // As per VTS if the input data is empty or not well-formed + // CoseMac return VERIFICATION_FAILED error. + try { + cmd = sendRootOfTrustCmd(apdu); + } catch (Exception e) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + + short semanticTag = KMArray.cast(cmd).get((short) 0); + short coseMacPtr = KMSemanticTag.cast(semanticTag).getValuePtr(); + // Exp for KMCoseHeaders + short coseHeadersExp = KMCoseHeaders.exp(); + // validate protected Headers + short ptr = KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_PROTECTED_PARAMS_OFFSET); + ptr = + decoder.decode( + coseHeadersExp, + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()); + + if (!KMCoseHeaders.cast(ptr) + .isDataValid(tmpVariables, KMCose.COSE_ALG_HMAC_256, KMType.INVALID_VALUE)) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + + // Validate the Mac + short len = kmDataStore.getChallenge(scratchPad, (short) 0); + short extAad = KMByteBlob.instance(scratchPad, (short) 0, len); + // Compute CoseMac Structure and compare the macs. + short rotPayload = KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_PAYLOAD_OFFSET); + short macStructure = + KMCose.constructCoseMacStructure( + KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_PROTECTED_PARAMS_OFFSET), + extAad, + rotPayload); + short encodedLen = + KMKeymasterApplet.encodeToApduBuffer( + macStructure, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + + if (!seProvider.hmacVerify( + kmDataStore.getComputedHmacKey(), + scratchPad, + (short) 0, + encodedLen, + KMByteBlob.cast(KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_TAG_OFFSET)).getBuffer(), + KMByteBlob.cast(KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_TAG_OFFSET)).getStartOff(), + KMByteBlob.cast(KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_TAG_OFFSET)).length())) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + // Store the data only once after reboot. + // Allow set boot params only when the host device reboots and the applet is in + // active state. If host does not support boot signal event, then allow this + // instruction any time. + kmDataStore.getDeviceBootStatus(scratchPad, (short) 0); + if (((scratchPad[0] & KMKeymintDataStore.SET_BOOT_PARAMS_SUCCESS) == 0)) { + // store the data. + storeRootOfTrust(rotPayload, scratchPad); + kmDataStore.setDeviceBootStatus(KMKeymintDataStore.SET_BOOT_PARAMS_SUCCESS); + } + // Invalidate the challenge + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 16, (byte) 0); + kmDataStore.setChallenge(scratchPad, (short) 0, (short) 16); + sendResponse(apdu, KMError.OK); + } + + private void storeRootOfTrust(short rotPayload, byte[] scratchPad) { + short byteBlobExp = KMByteBlob.exp(); + short intExp = KMInteger.exp(); + short boolExp = KMSimpleValue.exp(); + short arr = KMArray.instance((short) 5); + KMArray.cast(arr).add((short) 0, byteBlobExp); // Verfied boot key. + KMArray.cast(arr).add((short) 1, boolExp); // deviceLocked. + KMArray.cast(arr).add((short) 2, intExp); // Verified Boot State. + KMArray.cast(arr).add((short) 3, byteBlobExp); // Verfied boot hash. + KMArray.cast(arr).add((short) 4, intExp); // Boot patch level + short semanticExp = KMSemanticTag.exp(arr); + + short semanticPtr = + decoder.decode( + semanticExp, + KMByteBlob.cast(rotPayload).getBuffer(), + KMByteBlob.cast(rotPayload).getStartOff(), + KMByteBlob.cast(rotPayload).length()); + short rotArr = KMSemanticTag.cast(semanticPtr).getValuePtr(); + // Store verified boot key + short ptr = KMArray.cast(rotArr).get((short) 0); + kmDataStore.setBootKey( + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()); + // Store Boot device locked. + ptr = KMArray.cast(rotArr).get((short) 1); + kmDataStore.setDeviceLocked( + (KMSimpleValue.cast(ptr).getValue() == KMSimpleValue.TRUE) ? true : false); + // Store verified boot state + ptr = KMArray.cast(rotArr).get((short) 2); + kmDataStore.setBootState(KMInteger.cast(ptr).getShort()); + // Store Verified boot hash + ptr = KMArray.cast(rotArr).get((short) 3); + kmDataStore.setVerifiedBootHash( + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()); + // Store boot patch level + ptr = KMArray.cast(rotArr).get((short) 4); + kmDataStore.setBootPatchLevel( + KMInteger.cast(ptr).getBuffer(), + KMInteger.cast(ptr).getStartOff(), + KMInteger.cast(ptr).length()); + } + + // After every device boot, the Keymaster becomes ready to execute all the commands only after + // 1. boot parameters are set, + // 2. system properties are set and + // 3. computed the shared secret successfully. + private boolean isKeyMintReady(byte apduIns) { + if (kmDataStore.isDeviceReady()) { + return true; + } + // Below commands are allowed even if the Keymaster is not ready. + switch (apduIns) { + case INS_GET_HW_INFO_CMD: + case INS_GET_RKP_HARDWARE_INFO: + case INS_ADD_RNG_ENTROPY_CMD: + case INS_GET_HMAC_SHARING_PARAM_CMD: + case INS_COMPUTE_SHARED_HMAC_CMD: + case INS_EARLY_BOOT_ENDED_CMD: + case INS_INIT_STRONGBOX_CMD: + case INS_GET_ROT_CHALLENGE_CMD: + case INS_SEND_ROT_DATA_CMD: + return true; + default: + break; + } + return false; + } + + private void generateUniqueOperationHandle(byte[] buf, short offset, short len) { + do { + seProvider.newRandomNumber(buf, offset, len); + } while (null != findOperation(buf, offset, len)); + } + + private void freeOperations() { + if (data[OP_HANDLE] != KMType.INVALID_VALUE) { + KMOperationState op = findOperation(data[OP_HANDLE]); + if (op != null) { + releaseOperation(op); + } + } + } + + private void processEarlyBootEndedCmd(APDU apdu) { + kmDataStore.setEarlyBootEndedStatus(true); + sendResponse(apdu, KMError.OK); + } + + private short deviceLockedCmd(APDU apdu) { + short cmd = KMArray.instance((short) 2); + short ptr = KMVerificationToken.exp(); + // passwordOnly + KMArray.cast(cmd).add((short) 0, KMInteger.exp()); + // verification token + KMArray.cast(cmd).add((short) 1, ptr); + return receiveIncoming(apdu, cmd); + } + + private void processDeviceLockedCmd(APDU apdu) { + short cmd = deviceLockedCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + short passwordOnly = KMArray.cast(cmd).get((short) 0); + short verToken = KMArray.cast(cmd).get((short) 1); + passwordOnly = KMInteger.cast(passwordOnly).getByte(); + validateVerificationToken(verToken, scratchPad); + short verTime = KMVerificationToken.cast(verToken).getTimestamp(); + short lastDeviceLockedTime; + try { + lastDeviceLockedTime = kmDataStore.getDeviceTimeStamp(); + } catch (KMException e) { + lastDeviceLockedTime = KMInteger.uint_8((byte) 0); + } + if (KMInteger.compare(verTime, lastDeviceLockedTime) > 0) { + Util.arrayFillNonAtomic(scratchPad, (short) 0, KMInteger.UINT_64, (byte) 0); + KMInteger.cast(verTime).getValue(scratchPad, (short) 0, KMInteger.UINT_64); + kmDataStore.setDeviceLock(true); + kmDataStore.setDeviceLockPasswordOnly(passwordOnly == 0x01); + kmDataStore.setDeviceLockTimestamp(scratchPad, (short) 0, KMInteger.UINT_64); + } + sendResponse(apdu, KMError.OK); + } + + private void resetWrappingKey() { + if (!isValidWrappingKey()) { + return; + } + Util.arrayFillNonAtomic(wrappingKey, (short) 1, WRAPPING_KEY_SIZE, (byte) 0); + wrappingKey[0] = -1; + } + + private boolean isValidWrappingKey() { + return wrappingKey[0] != -1; + } + + private short getWrappingKey() { + return KMByteBlob.instance(wrappingKey, (short) 1, WRAPPING_KEY_SIZE); + } + + private void setWrappingKey(short key) { + if (KMByteBlob.cast(key).length() != WRAPPING_KEY_SIZE) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + wrappingKey[0] = 0; + Util.arrayCopyNonAtomic( + KMByteBlob.cast(key).getBuffer(), + KMByteBlob.cast(key).getStartOff(), + wrappingKey, + (short) 1, + WRAPPING_KEY_SIZE); + } + + protected void resetTransientBuffers() { + short index = 0; + while (index < data.length) { + data[index] = KMType.INVALID_VALUE; + index++; + } + index = 0; + while (index < tmpVariables.length) { + tmpVariables[index] = KMType.INVALID_VALUE; + index++; + } + } + + public void sendOutgoing( + APDU apdu, KMAttestationCert cert, short certStart, short keyblob, short keyChars) { + // This is the special case where the output is encoded manually without using + // the encoder algorithm. Encoder creates a duplicate copy for each KMType Object. + // The output of the generateKey, importKey and importWrappedKey commands are huge so + // by manually encoding we can avoid duplicate copies. + // The output data is directly written to the end of heap in the below order + // output = [ + // errorCode : uint // ErrorCode + // keyBlob : bstr // KeyBlob. + // keyChars + // certifcate + // ] + // certificate = [ + // x509_cert : bstr // X509 certificate + // ] + // keyChars = { // Map + // } + byte[] buffer = repository.getHeap(); + + if (cert == null) { + // This happens for Symmetric keys. + short bufferStart = repository.allocReclaimableMemory((short) 1); + buffer[bufferStart] = (byte) 0x80; // Array of 0 length. + } else { + // Encode the certificate into cbor data at the end of the heap + // certData = [ + // x509_cert : bstr // X509 certificate + // ] + short bufferStart = + encoder.encodeCert( + repository.getHeap(), certStart, cert.getCertStart(), cert.getCertLength()); + // reclaim the unused memory in the certificate. + repository.reclaimMemory((short) (bufferStart - certStart)); + } + + // Encode KeyCharacteristics at the end of heap just before data[CERTIFICATE] + encodeKeyCharacteristics(keyChars); + // and encode it to the end of the buffer before KEY_CHARACTERISTICS + encodeKeyBlob(keyblob); + // Write Array header and ErrorCode before data[KEY_BLOB] + short bufferStartOffset = repository.allocReclaimableMemory((short) 2); + Util.setShort(buffer, bufferStartOffset, (short) 0x8400); + + short bufferLength = (short) (KMRepository.HEAP_SIZE - bufferStartOffset); + /* In T=0 protocol, On a case 4 command, setIncomingAndReceive() must + * be invoked prior to calling setOutgoing(). Otherwise, erroneous + * behavior may result + * */ + if (apduStatusFlags[APDU_CASE4_COMMAND_STATUS_INDEX] == 1 + && apduStatusFlags[APDU_INCOMING_AND_RECEIVE_STATUS_INDEX] == 0 + && APDU.getProtocol() == APDU.PROTOCOL_T0) { + apdu.setIncomingAndReceive(); + } + // Send data + apdu.setOutgoing(); + apdu.setOutgoingLength(bufferLength); + apdu.sendBytesLong(buffer, bufferStartOffset, bufferLength); + } + + private void processGetHwInfoCmd(APDU apdu) { + // No arguments expected + final byte version = 2; + // Make the response + short respPtr = KMArray.instance((short) 6); + KMArray resp = KMArray.cast(respPtr); + resp.add((short) 0, KMInteger.uint_16(KMError.OK)); + resp.add((short) 1, KMInteger.uint_8(version)); + resp.add((short) 2, KMEnum.instance(KMType.HARDWARE_TYPE, KMType.STRONGBOX)); + resp.add( + (short) 3, + KMByteBlob.instance( + JavacardKeymintDevice, (short) 0, (short) JavacardKeymintDevice.length)); + resp.add((short) 4, KMByteBlob.instance(Google, (short) 0, (short) Google.length)); + resp.add((short) 5, KMInteger.uint_8((byte) 1)); + // send buffer to host + sendOutgoing(apdu, respPtr); + } + + private short addRngEntropyCmd(APDU apdu) { + short cmd = KMArray.instance((short) 1); + // Rng entropy + KMArray.cast(cmd).add((short) 0, KMByteBlob.exp()); + return receiveIncoming(apdu, cmd); + } + + private void processAddRngEntropyCmd(APDU apdu) { + // Receive the incoming request fully from the host. + short cmd = addRngEntropyCmd(apdu); + // Process + KMByteBlob blob = KMByteBlob.cast(KMArray.cast(cmd).get((short) 0)); + // Maximum 2KiB of seed is allowed. + if (blob.length() > MAX_SEED_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + seProvider.addRngEntropy(blob.getBuffer(), blob.getStartOff(), blob.length()); + sendResponse(apdu, KMError.OK); + } + + private short getKeyCharacteristicsCmd(APDU apdu) { + short cmd = KMArray.instance((short) 3); + KMArray.cast(cmd).add((short) 0, KMByteBlob.exp()); + KMArray.cast(cmd).add((short) 1, KMByteBlob.exp()); + KMArray.cast(cmd).add((short) 2, KMByteBlob.exp()); + return receiveIncoming(apdu, cmd); + } + + private void processGetKeyCharacteristicsCmd(APDU apdu) { + // Receive the incoming request fully from the host. + short cmd = getKeyCharacteristicsCmd(apdu); + // Re-purpose the apdu buffer as scratch pad. + byte[] scratchPad = apdu.getBuffer(); + data[KEY_BLOB] = KMArray.cast(cmd).get((short) 0); + data[APP_ID] = KMArray.cast(cmd).get((short) 1); + data[APP_DATA] = KMArray.cast(cmd).get((short) 2); + if (KMByteBlob.cast(data[APP_ID]).length() > KMByteTag.MAX_APP_ID_APP_DATA_SIZE + || KMByteBlob.cast(data[APP_DATA]).length() > KMByteTag.MAX_APP_ID_APP_DATA_SIZE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (!KMByteBlob.cast(data[APP_ID]).isValid()) { + data[APP_ID] = KMType.INVALID_VALUE; + } + if (!KMByteBlob.cast(data[APP_DATA]).isValid()) { + data[APP_DATA] = KMType.INVALID_VALUE; + } + // Check if key requires upgrade. The KeyBlob is parsed inside isKeyUpgradeRequired + // function itself. + if (isKeyUpgradeRequired(data[KEY_BLOB], data[APP_ID], data[APP_DATA], scratchPad)) { + KMException.throwIt(KMError.KEY_REQUIRES_UPGRADE); + } + // make response. + short resp = KMArray.instance((short) 2); + KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(resp).add((short) 1, data[KEY_CHARACTERISTICS]); + sendOutgoing(apdu, resp); + } + + private void processGetHmacSharingParamCmd(APDU apdu) { + // No Arguments + // Create HMAC Sharing Parameters + short params = KMHmacSharingParameters.instance(); + short nonce = kmDataStore.getHmacNonce(); + short seed = KMByteBlob.instance((short) 0); + KMHmacSharingParameters.cast(params).setNonce(nonce); + KMHmacSharingParameters.cast(params).setSeed(seed); + // prepare the response + short resp = KMArray.instance((short) 2); + KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(resp).add((short) 1, params); + sendOutgoing(apdu, resp); + } + + private void processDeleteAllKeysCmd(APDU apdu) { + // No arguments + // This function is triggered when a factory reset event occurs. + // Regenerate the master key to render all keys unusable. + kmDataStore.regenerateMasterKey(); + // Send ok + sendResponse(apdu, KMError.OK); + } + + private short createKeyBlobExp(short version) { + short keyBlob = KMType.INVALID_VALUE; + short byteBlobExp = KMByteBlob.exp(); + short keyChar = KMKeyCharacteristics.exp(); + short keyParam = KMKeyParameters.exp(); + switch (version) { + case (short) 0: + // Old KeyBlob has a maximum of 5 elements. + keyBlob = KMArray.instance(ASYM_KEY_BLOB_SIZE_V0); + KMArray.cast(keyBlob).add((short) 0, byteBlobExp); // Secret + KMArray.cast(keyBlob).add((short) 1, byteBlobExp); // Nonce + KMArray.cast(keyBlob).add((short) 2, byteBlobExp); // AuthTag + KMArray.cast(keyBlob).add((short) 3, keyChar); // KeyChars + KMArray.cast(keyBlob).add((short) 4, byteBlobExp); // PubKey + break; + case (short) 1: + keyBlob = KMArray.instance(ASYM_KEY_BLOB_SIZE_V1); + KMArray.cast(keyBlob).add((short) 0, KMInteger.exp()); // Version + KMArray.cast(keyBlob).add((short) 1, byteBlobExp); // Secret + KMArray.cast(keyBlob).add((short) 2, byteBlobExp); // Nonce + KMArray.cast(keyBlob).add((short) 3, byteBlobExp); // AuthTag + KMArray.cast(keyBlob).add((short) 4, keyChar); // KeyChars + KMArray.cast(keyBlob).add((short) 5, byteBlobExp); // PubKey + break; + case (short) 2: + case (short) 3: + keyBlob = KMArray.instance(ASYM_KEY_BLOB_SIZE_V2_V3); + KMArray.cast(keyBlob).add(KMKeymasterApplet.KEY_BLOB_VERSION_OFFSET, KMInteger.exp()); + KMArray.cast(keyBlob).add(KMKeymasterApplet.KEY_BLOB_SECRET, byteBlobExp); + KMArray.cast(keyBlob).add(KMKeymasterApplet.KEY_BLOB_AUTH_TAG, byteBlobExp); + KMArray.cast(keyBlob).add(KMKeymasterApplet.KEY_BLOB_NONCE, byteBlobExp); + KMArray.cast(keyBlob).add(KMKeymasterApplet.KEY_BLOB_PARAMS, keyChar); + KMArray.cast(keyBlob).add(KMKeymasterApplet.KEY_BLOB_CUSTOM_TAGS, keyParam); + KMArray.cast(keyBlob).add(KMKeymasterApplet.KEY_BLOB_PUB_KEY, byteBlobExp); + break; + default: + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + return keyBlob; + } + + private void processDeleteKeyCmd(APDU apdu) { + // Send ok + sendResponse(apdu, KMError.OK); + } + + private short computeSharedHmacCmd(APDU apdu) { + short params = KMHmacSharingParameters.exp(); + short paramsVec = KMArray.exp(params); + short cmd = KMArray.instance((short) 1); + KMArray.cast(cmd).add((short) 0, paramsVec); + return receiveIncoming(apdu, cmd); + } + + private void processComputeSharedHmacCmd(APDU apdu) { + // Receive the incoming request fully from the host into buffer. + short cmd = computeSharedHmacCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + data[HMAC_SHARING_PARAMS] = KMArray.cast(cmd).get((short) 0); + // Concatenate HMAC Params + // tmpVariables[0] + short paramsLen = KMArray.cast(data[HMAC_SHARING_PARAMS]).length(); // total number of params + // tmpVariables[1] + short concateBuffer = repository.alloc((short) (paramsLen * HMAC_SHARED_PARAM_MAX_SIZE)); + // tmpVariables[2] + short paramIndex = 0; // index for params + // tmpVariables[3] + short bufferIndex = 0; // index for concatenation buffer + // To check if nonce created by Strongbox is found. This value becomes 1 if both + // seed and nonce created here are found in hmac sharing parameters received. + // tmpVariables[7] = 0; + short found = 0; + // tmpVariables[9] + short nonce = kmDataStore.getHmacNonce(); + + while (paramIndex < paramsLen) { + // read HmacSharingParam + // tmpVariables[4] + short param = KMArray.cast(data[HMAC_SHARING_PARAMS]).get(paramIndex); + // get seed - 32 bytes max + // tmpVariables[5] + short seed = KMHmacSharingParameters.cast(param).getSeed(); + // tmpVariables[6] + short seedLength = KMByteBlob.cast(seed).length(); + // if seed is present + if (seedLength != 0) { + // then copy that to concatenation buffer + Util.arrayCopyNonAtomic( + KMByteBlob.cast(seed).getBuffer(), + KMByteBlob.cast(seed).getStartOff(), + repository.getHeap(), + (short) (concateBuffer + bufferIndex), // concat index + seedLength); + bufferIndex += seedLength; // increment the concat index + } else if (found == 0) { + found = 1; // Applet does not have any seed. Potentially + } + // if nonce is present get nonce - 32 bytes + // tmpVariables[5] + short paramNonce = KMHmacSharingParameters.cast(param).getNonce(); + short nonceLen = KMByteBlob.cast(paramNonce).length(); + // if nonce is less then 32 - it is an error + if (nonceLen < 32) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // copy nonce to concatenation buffer + Util.arrayCopyNonAtomic( + KMByteBlob.cast(paramNonce).getBuffer(), + KMByteBlob.cast(paramNonce).getStartOff(), + repository.getHeap(), + (short) (concateBuffer + bufferIndex), // index + nonceLen); + + // Check if the nonce generated here is present in the hmacSharingParameters array. + // Otherwise throw INVALID_ARGUMENT error. + if (found == 1) { + if (0 + == Util.arrayCompare( + repository.getHeap(), + (short) (concateBuffer + bufferIndex), + KMByteBlob.cast(nonce).getBuffer(), + KMByteBlob.cast(nonce).getStartOff(), + nonceLen)) { + found = 2; // hmac nonce for this keymaster found. + } else { + found = 0; + } + } + bufferIndex += nonceLen; // increment by nonce length + paramIndex++; // go to next hmac param in the vector + } + if (found != 2) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // generate the key and store it in scratch pad - 32 bytes + // tmpVariables[6] + short keyLen = + seProvider.cmacKDF( + kmDataStore.getPresharedKey(), + ckdfLabel, + (short) 0, + (short) ckdfLabel.length, + repository.getHeap(), + concateBuffer, + bufferIndex, + scratchPad, + (short) 0); + + // persist the computed hmac key. + kmDataStore.createComputedHmacKey(scratchPad, (short) 0, keyLen); + // Generate sharingKey verification signature and store that in scratch pad. + // tmpVariables[5] + short signLen = + seProvider.hmacSign( + scratchPad, + (short) 0, + keyLen, + sharingCheck, + (short) 0, + (short) sharingCheck.length, + scratchPad, + keyLen); + kmDataStore.setDeviceBootStatus(KMKeymintDataStore.NEGOTIATED_SHARED_SECRET_SUCCESS); + // verification signature blob - 32 bytes + // tmpVariables[1] + short signature = KMByteBlob.instance(scratchPad, keyLen, signLen); + // prepare the response + short resp = KMArray.instance((short) 2); + KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(resp).add((short) 1, signature); + sendOutgoing(apdu, resp); + } + + private short upgradeKeyCmd(APDU apdu) { + short cmd = KMArray.instance((short) 2); + short keyParams = KMKeyParameters.exp(); + KMArray.cast(cmd).add((short) 0, KMByteBlob.exp()); // Key Blob + KMArray.cast(cmd).add((short) 1, keyParams); // Key Params + return receiveIncoming(apdu, cmd); + } + + private boolean isKeyUpgradeRequired( + short keyBlob, short appId, short appData, byte[] scratchPad) { + // Check if the KeyBlob is compatible. If there is any change in the KeyBlob, the version + // Parameter in the KeyBlob should be updated to the next version. + short version = readKeyBlobVersion(keyBlob); + parseEncryptedKeyBlob(keyBlob, appId, appData, scratchPad, version); + if (version < KEYBLOB_CURRENT_VERSION) { + return true; + } + short bootPatchLevel = kmDataStore.getBootPatchLevel(); + // Fill the key-value properties in the scratchpad + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 16, (byte) 0); + Util.setShort(scratchPad, (short) 0, KMType.OS_VERSION); + Util.setShort(scratchPad, (short) 2, kmDataStore.getOsVersion()); + Util.setShort(scratchPad, (short) 4, KMType.OS_PATCH_LEVEL); + Util.setShort(scratchPad, (short) 6, kmDataStore.getOsPatch()); + Util.setShort(scratchPad, (short) 8, KMType.VENDOR_PATCH_LEVEL); + Util.setShort(scratchPad, (short) 10, kmDataStore.getVendorPatchLevel()); + Util.setShort(scratchPad, (short) 12, KMType.BOOT_PATCH_LEVEL); + Util.setShort(scratchPad, (short) 14, bootPatchLevel); + short index = 0; + short tag; + short systemParam; + boolean isKeyUpgradeRequired = false; + while (index < 16) { + tag = Util.getShort(scratchPad, index); + systemParam = Util.getShort(scratchPad, (short) (index + 2)); + // validate the tag and check if key needs upgrade. + short tagValue = KMKeyParameters.findTag(KMType.UINT_TAG, tag, data[HW_PARAMETERS]); + tagValue = KMIntegerTag.cast(tagValue).getValue(); + short zero = KMInteger.uint_8((byte) 0); + if (tagValue != KMType.INVALID_VALUE) { + // OS version in key characteristics must be less the OS version stored in Javacard or the + // stored version must be zero. Then only upgrade is allowed else it is invalid argument. + if ((tag == KMType.OS_VERSION + && KMInteger.compare(tagValue, systemParam) == 1 + && KMInteger.compare(systemParam, zero) == 0)) { + // Key needs upgrade. + isKeyUpgradeRequired = true; + } else if ((KMInteger.compare(tagValue, systemParam) == -1)) { + // Each os version or patch level associated with the key must be less than it's + // corresponding value stored in Javacard, then only upgrade is allowed otherwise it + // is invalid argument. + isKeyUpgradeRequired = true; + } else if (KMInteger.compare(tagValue, systemParam) == 1) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + } else { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + index += 4; + } + return isKeyUpgradeRequired; + } + + private void processUpgradeKeyCmd(APDU apdu) { + // Receive the incoming request fully from the host into buffer. + short cmd = upgradeKeyCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + + short keyBlob = KMArray.cast(cmd).get((short) 0); + data[KEY_PARAMETERS] = KMArray.cast(cmd).get((short) 1); + short appId = getApplicationId(data[KEY_PARAMETERS]); + short appData = getApplicationData(data[KEY_PARAMETERS]); + + data[KEY_BLOB] = KMType.INVALID_VALUE; + // Check if the KeyBlob requires upgrade. The KeyBlob is parsed inside isKeyUpgradeRequired + // function itself, but if there is a difference in the KeyBlob version isKeyUpgradeRequired() + // does not parse the KeyBlob. + boolean isKeyUpgradeRequired = isKeyUpgradeRequired(keyBlob, appId, appData, scratchPad); + if (isKeyUpgradeRequired) { + // copy origin + data[ORIGIN] = KMEnumTag.getValue(KMType.ORIGIN, data[HW_PARAMETERS]); + byte keyType = getKeyType(data[HW_PARAMETERS]); + switch (keyType) { + case ASYM_KEY_TYPE: + data[KEY_BLOB] = KMArray.instance(ASYM_KEY_BLOB_SIZE_V2_V3); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PUB_KEY, data[PUB_KEY]); + break; + case SYM_KEY_TYPE: + data[KEY_BLOB] = KMArray.instance(SYM_KEY_BLOB_SIZE_V2_V3); + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + // Update the system properties to the latest values and also re-create the KeyBlob's + // KeyCharacteristics to make sure all the values are up-to-date with the latest applet + // changes. + upgradeKeyBlobKeyCharacteristics(data[HW_PARAMETERS], scratchPad); + // create new key blob with current os version etc. + createEncryptedKeyBlob(scratchPad); + short prevReclaimIndex = repository.getHeapReclaimIndex(); + short offset = repository.allocReclaimableMemory(MAX_KEYBLOB_SIZE); + data[KEY_BLOB] = + encoder.encode( + data[KEY_BLOB], repository.getHeap(), offset, prevReclaimIndex, MAX_KEYBLOB_SIZE); + data[KEY_BLOB] = KMByteBlob.instance(repository.getHeap(), offset, data[KEY_BLOB]); + repository.reclaimMemory(MAX_KEYBLOB_SIZE); + } else { + data[KEY_BLOB] = KMByteBlob.instance((short) 0); + } + // prepare the response + short resp = KMArray.instance((short) 2); + KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(resp).add((short) 1, data[KEY_BLOB]); + sendOutgoing(apdu, resp); + } + + private void processExportKeyCmd(APDU apdu) { + sendResponse(apdu, KMError.UNIMPLEMENTED); + } + + private void processWrappingKeyBlob(short keyBlob, short wrapParams, byte[] scratchPad) { + // Read App Id and App Data if any from un wrapping key params + data[APP_ID] = getApplicationId(wrapParams); + data[APP_DATA] = getApplicationData(wrapParams); + data[KEY_PARAMETERS] = wrapParams; + data[KEY_BLOB] = keyBlob; + // Check if key requires upgrade. The KeyBlob is parsed inside isKeyUpgradeRequired + // function itself. + if (isKeyUpgradeRequired(data[KEY_BLOB], data[APP_ID], data[APP_DATA], scratchPad)) { + KMException.throwIt(KMError.KEY_REQUIRES_UPGRADE); + } + validateWrappingKeyBlob(); + } + + private void validateWrappingKeyBlob() { + // check whether the wrapping key is RSA with purpose KEY_WRAP, padding RSA_OAEP and Digest + // SHA2_256. + KMTag.assertPresence( + data[SB_PARAMETERS], + KMType.ENUM_TAG, + KMType.ALGORITHM, + KMError.UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM); + if (KMEnumTag.getValue(KMType.ALGORITHM, data[HW_PARAMETERS]) != KMType.RSA) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM); + } + if (!KMEnumArrayTag.contains(KMType.DIGEST, KMType.SHA2_256, data[HW_PARAMETERS])) { + KMException.throwIt(KMError.INCOMPATIBLE_DIGEST); + } + if (!KMEnumArrayTag.contains(KMType.PADDING, KMType.RSA_OAEP, data[HW_PARAMETERS])) { + KMException.throwIt(KMError.INCOMPATIBLE_PADDING_MODE); + } + if (!KMEnumArrayTag.contains(KMType.PURPOSE, KMType.WRAP_KEY, data[HW_PARAMETERS])) { + KMException.throwIt((KMError.INCOMPATIBLE_PURPOSE)); + } + + // Check that the digest and padding mode specified in unwrapping parameters are SHA2_256 + // and RSA_OAEP respectively. + if (!KMEnumArrayTag.contains(KMType.DIGEST, KMType.SHA2_256, data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.INCOMPATIBLE_DIGEST); + } + if (!KMEnumArrayTag.contains(KMType.PADDING, KMType.RSA_OAEP, data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.INCOMPATIBLE_PADDING_MODE); + } + } + + private short decryptTransportKey( + short privExp, short modulus, short transportKey, byte[] scratchPad) { + short length = + seProvider.rsaDecipherOAEP256( + KMByteBlob.cast(privExp).getBuffer(), + KMByteBlob.cast(privExp).getStartOff(), + KMByteBlob.cast(privExp).length(), + KMByteBlob.cast(modulus).getBuffer(), + KMByteBlob.cast(modulus).getStartOff(), + KMByteBlob.cast(modulus).length(), + KMByteBlob.cast(transportKey).getBuffer(), + KMByteBlob.cast(transportKey).getStartOff(), + KMByteBlob.cast(transportKey).length(), + scratchPad, + (short) 0); + return KMByteBlob.instance(scratchPad, (short) 0, length); + } + + private void unmask(short data, short maskingKey) { + short dataLength = KMByteBlob.cast(data).length(); + short maskLength = KMByteBlob.cast(maskingKey).length(); + // Length of masking key and transport key must be same. + if (maskLength != dataLength) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + short index = 0; // index + // Xor every byte of masking and key and store the result in data[SECRET] + while (index < maskLength) { + short var1 = (short) (((short) KMByteBlob.cast(maskingKey).get(index)) & 0x00FF); + short var2 = (short) (((short) KMByteBlob.cast(data).get(index)) & 0x00FF); + KMByteBlob.cast(data).add(index, (byte) (var1 ^ var2)); + index++; + } + } + + private short beginImportWrappedKeyCmd(APDU apdu) { + short cmd = KMArray.instance((short) 4); + short params = KMKeyParameters.expAny(); + KMArray.cast(cmd).add((short) 0, KMByteBlob.exp()); // Encrypted Transport Key + KMArray.cast(cmd).add((short) 1, KMByteBlob.exp()); // Wrapping Key KeyBlob + KMArray.cast(cmd).add((short) 2, KMByteBlob.exp()); // Masking Key + params = KMKeyParameters.exp(); + KMArray.cast(cmd).add((short) 3, params); // Wrapping key blob Params + return receiveIncoming(apdu, cmd); + } + + private void processBeginImportWrappedKeyCmd(APDU apdu) { + // Receive the incoming request fully from the host into buffer. + short cmd = beginImportWrappedKeyCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + // Step -1 parse the wrapping key blob + // read wrapping key blob + short keyBlob = KMArray.cast(cmd).get((short) 1); + // read un wrapping key params + short wrappingKeyParameters = KMArray.cast(cmd).get((short) 3); + processWrappingKeyBlob(keyBlob, wrappingKeyParameters, scratchPad); + // Step 2 - decrypt the encrypted transport key - 32 bytes AES-GCM key + short transportKey = + decryptTransportKey( + data[SECRET], data[PUB_KEY], KMArray.cast(cmd).get((short) 0), scratchPad); + // Step 3 - XOR the decrypted AES-GCM key with with masking key + unmask(transportKey, KMArray.cast(cmd).get((short) 2)); + if (isValidWrappingKey()) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + setWrappingKey(transportKey); + sendResponse(apdu, KMError.OK); + } + + private short aesGCMEncrypt( + short aesSecret, short input, short nonce, short authData, short authTag, byte[] scratchPad) { + Util.arrayFillNonAtomic(scratchPad, (short) 0, KMByteBlob.cast(input).length(), (byte) 0); + short len = + seProvider.aesGCMEncrypt( + KMByteBlob.cast(aesSecret).getBuffer(), + KMByteBlob.cast(aesSecret).getStartOff(), + KMByteBlob.cast(aesSecret).length(), + KMByteBlob.cast(input).getBuffer(), + KMByteBlob.cast(input).getStartOff(), + KMByteBlob.cast(input).length(), + scratchPad, + (short) 0, + KMByteBlob.cast(nonce).getBuffer(), + KMByteBlob.cast(nonce).getStartOff(), + KMByteBlob.cast(nonce).length(), + KMByteBlob.cast(authData).getBuffer(), + KMByteBlob.cast(authData).getStartOff(), + KMByteBlob.cast(authData).length(), + KMByteBlob.cast(authTag).getBuffer(), + KMByteBlob.cast(authTag).getStartOff(), + KMByteBlob.cast(authTag).length()); + return KMByteBlob.instance(scratchPad, (short) 0, len); + } + + private short aesGCMDecrypt( + short aesSecret, short input, short nonce, short authData, short authTag, byte[] scratchPad) { + Util.arrayFillNonAtomic(scratchPad, (short) 0, KMByteBlob.cast(input).length(), (byte) 0); + if (!seProvider.aesGCMDecrypt( + KMByteBlob.cast(aesSecret).getBuffer(), + KMByteBlob.cast(aesSecret).getStartOff(), + KMByteBlob.cast(aesSecret).length(), + KMByteBlob.cast(input).getBuffer(), + KMByteBlob.cast(input).getStartOff(), + KMByteBlob.cast(input).length(), + scratchPad, + (short) 0, + KMByteBlob.cast(nonce).getBuffer(), + KMByteBlob.cast(nonce).getStartOff(), + KMByteBlob.cast(nonce).length(), + KMByteBlob.cast(authData).getBuffer(), + KMByteBlob.cast(authData).getStartOff(), + KMByteBlob.cast(authData).length(), + KMByteBlob.cast(authTag).getBuffer(), + KMByteBlob.cast(authTag).getStartOff(), + KMByteBlob.cast(authTag).length())) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + return KMByteBlob.instance(scratchPad, (short) 0, KMByteBlob.cast(input).length()); + } + + private short finishImportWrappedKeyCmd(APDU apdu) { + short cmd = KMArray.instance((short) 8); + short params = KMKeyParameters.expAny(); + KMArray.cast(cmd).add((short) 0, params); // Key Params of wrapped key + KMArray.cast(cmd).add((short) 1, KMEnum.instance(KMType.KEY_FORMAT)); // Key Format + KMArray.cast(cmd).add((short) 2, KMByteBlob.exp()); // Wrapped Import Key Blob + KMArray.cast(cmd).add((short) 3, KMByteBlob.exp()); // Auth Tag + KMArray.cast(cmd).add((short) 4, KMByteBlob.exp()); // IV - Nonce + KMArray.cast(cmd).add((short) 5, KMByteBlob.exp()); // Wrapped Key ASSOCIATED AUTH DATA + KMArray.cast(cmd).add((short) 6, KMInteger.exp()); // Password Sid + KMArray.cast(cmd).add((short) 7, KMInteger.exp()); // Biometric Sid + return receiveIncoming(apdu, cmd); + } + + // TODO remove cmd later on + private void processFinishImportWrappedKeyCmd(APDU apdu) { + short cmd = finishImportWrappedKeyCmd(apdu); + short keyParameters = KMArray.cast(cmd).get((short) 0); + short keyFmt = KMArray.cast(cmd).get((short) 1); + keyFmt = KMEnum.cast(keyFmt).getVal(); + validateImportKey(keyParameters, keyFmt); + byte[] scratchPad = apdu.getBuffer(); + // Step 4 - AES-GCM decrypt the wrapped key + data[INPUT_DATA] = KMArray.cast(cmd).get((short) 2); + data[AUTH_TAG] = KMArray.cast(cmd).get((short) 3); + data[NONCE] = KMArray.cast(cmd).get((short) 4); + data[AUTH_DATA] = KMArray.cast(cmd).get((short) 5); + + if (!isValidWrappingKey()) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + data[IMPORTED_KEY_BLOB] = + aesGCMDecrypt( + getWrappingKey(), + data[INPUT_DATA], + data[NONCE], + data[AUTH_DATA], + data[AUTH_TAG], + scratchPad); + resetWrappingKey(); + // Step 5 - Import decrypted key + data[ORIGIN] = KMType.SECURELY_IMPORTED; + data[KEY_PARAMETERS] = keyParameters; + // create key blob array + importKey(apdu, keyFmt, scratchPad); + } + + private KMAttestationCert makeCommonCert(byte[] scratchPad) { + short alg = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, data[KEY_PARAMETERS]); + boolean rsaCert = KMEnumTag.cast(alg).getValue() == KMType.RSA; + KMAttestationCert cert = KMAttestationCertImpl.instance(rsaCert, seProvider); + + short subject = + KMKeyParameters.findTag( + KMType.BYTES_TAG, KMType.CERTIFICATE_SUBJECT_NAME, data[KEY_PARAMETERS]); + + // If no subject name is specified then use the default subject name. + if (subject == KMType.INVALID_VALUE || KMByteTag.cast(subject).length() == 0) { + subject = KMByteBlob.instance(defaultSubject, (short) 0, (short) defaultSubject.length); + } else { + subject = KMByteTag.cast(subject).getValue(); + } + cert.subjectName(subject); + // Validity period must be specified + short notBefore = + KMKeyParameters.findTag( + KMType.DATE_TAG, KMType.CERTIFICATE_NOT_BEFORE, data[KEY_PARAMETERS]); + if (notBefore == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.MISSING_NOT_BEFORE); + } + notBefore = KMIntegerTag.cast(notBefore).getValue(); + short notAfter = + KMKeyParameters.findTag( + KMType.DATE_TAG, KMType.CERTIFICATE_NOT_AFTER, data[KEY_PARAMETERS]); + if (notAfter == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.MISSING_NOT_AFTER); + } + notAfter = KMIntegerTag.cast(notAfter).getValue(); + // VTS sends notBefore == Epoch. + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 8, (byte) 0); + short epoch = KMInteger.instance(scratchPad, (short) 0, (short) 8); + short end = KMInteger.instance(dec319999Ms, (short) 0, (short) dec319999Ms.length); + if (KMInteger.compare(notBefore, epoch) == 0) { + cert.notBefore( + KMByteBlob.instance(jan01970, (short) 0, (short) jan01970.length), true, scratchPad); + } else { + cert.notBefore(notBefore, false, scratchPad); + } + // VTS sends notAfter == Dec 31st 9999 + if (KMInteger.compare(notAfter, end) == 0) { + cert.notAfter( + KMByteBlob.instance(dec319999, (short) 0, (short) dec319999.length), true, scratchPad); + } else { + cert.notAfter(notAfter, false, scratchPad); + } + // Serial number + short serialNum = + KMKeyParameters.findTag( + KMType.BIGNUM_TAG, KMType.CERTIFICATE_SERIAL_NUM, data[KEY_PARAMETERS]); + if (serialNum != KMType.INVALID_VALUE) { + serialNum = KMBignumTag.cast(serialNum).getValue(); + } else { + serialNum = KMByteBlob.instance((short) 1); + KMByteBlob.cast(serialNum).add((short) 0, (byte) 1); + } + cert.serialNumber(serialNum); + return cert; + } + + private KMAttestationCert makeAttestationCert( + short attKeyBlob, short attKeyParam, short attChallenge, short issuer, byte[] scratchPad) { + KMAttestationCert cert = makeCommonCert(scratchPad); + + // Read App Id and App Data. + short appId = getApplicationId(attKeyParam); + short appData = getApplicationData(attKeyParam); + // Take backup of the required global variables KEY_BLOB, PUB_KEY, SECRET, KEY_CHAR + // and HW_PARAMS before they get overridden by isKeyUpgradeRequired() function. + short origBlob = data[KEY_BLOB]; + short pubKey = data[PUB_KEY]; + short privKey = data[SECRET]; + short hwParams = data[HW_PARAMETERS]; + short keyChars = data[KEY_CHARACTERISTICS]; + short customTags = data[CUSTOM_TAGS]; + // Check if key requires upgrade for attestKeyBlob. The KeyBlob is parsed inside + // isKeyUpgradeRequired function itself. + if (isKeyUpgradeRequired(attKeyBlob, appId, appData, scratchPad)) { + KMException.throwIt(KMError.KEY_REQUIRES_UPGRADE); + } + // Get the private key of the attest key. + short attestationKeySecret = KMArray.cast(data[KEY_BLOB]).get(KEY_BLOB_SECRET); + // Get the KeyCharacteristics and SB param of the attest key + short attestKeyCharacteristics = KMArray.cast(data[KEY_BLOB]).get(KEY_BLOB_PARAMS); + short attestKeySbParams = + KMKeyCharacteristics.cast(attestKeyCharacteristics).getStrongboxEnforced(); + // If the attest key's purpose is not "attest key" then error. + short attKeyPurpose = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, attestKeySbParams); + if (!KMEnumArrayTag.cast(attKeyPurpose).contains(KMType.ATTEST_KEY)) { + KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); + } + KMAsn1Parser asn1Decoder = KMAsn1Parser.instance(); + try { + asn1Decoder.validateDerSubject(issuer); + } catch (KMException e) { + KMException.throwIt(KMError.INVALID_ISSUER_SUBJECT_NAME); + } + if (KMByteBlob.cast(issuer).length() > KMConfigurations.MAX_SUBJECT_DER_LEN) { + KMException.throwIt(KMError.INVALID_ISSUER_SUBJECT_NAME); + } + // If issuer is not present then it is an error + if (KMByteBlob.cast(issuer).length() <= 0) { + KMException.throwIt(KMError.MISSING_ISSUER_SUBJECT_NAME); + } + short alg = KMEnumTag.getValue(KMType.ALGORITHM, attestKeySbParams); + if (alg == KMType.RSA) { + short attestationKeyPublic = KMArray.cast(data[KEY_BLOB]).get(KEY_BLOB_PUB_KEY); + cert.rsaAttestKey(attestationKeySecret, attestationKeyPublic, KMType.ATTESTATION_CERT); + } else if (alg == KMType.EC) { + cert.ecAttestKey(attestationKeySecret, KMType.ATTESTATION_CERT); + } else { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + cert.attestationChallenge(attChallenge); + cert.issuer(issuer); + + // Restore back the global variables. + data[PUB_KEY] = pubKey; + data[SECRET] = privKey; + data[KEY_BLOB] = origBlob; + data[HW_PARAMETERS] = hwParams; + data[KEY_CHARACTERISTICS] = keyChars; + data[CUSTOM_TAGS] = customTags; + data[SW_PARAMETERS] = + KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getKeystoreEnforced(); + data[TEE_PARAMETERS] = KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getTeeEnforced(); + data[SB_PARAMETERS] = + KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getStrongboxEnforced(); + cert.publicKey(data[PUB_KEY]); + + // Save attestation application id - must be present. + short attAppId = + KMKeyParameters.findTag( + KMType.BYTES_TAG, KMType.ATTESTATION_APPLICATION_ID, data[KEY_PARAMETERS]); + if (attAppId == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.ATTESTATION_APPLICATION_ID_MISSING); + } + cert.extensionTag(attAppId, false); + // unique id byte blob - uses application id and temporal month count of + // creation time. + attAppId = KMByteTag.cast(attAppId).getValue(); + setUniqueId(cert, attAppId, scratchPad); + // Add Attestation Ids if present + addAttestationIds(cert, scratchPad); + + // Add Tags + addTags(data[HW_PARAMETERS], true, cert); + addTags(data[SW_PARAMETERS], false, cert); + // Add Device Boot locked status + cert.deviceLocked(kmDataStore.isDeviceBootLocked()); + // VB data + cert.verifiedBootHash(getVerifiedBootHash(scratchPad)); + cert.verifiedBootKey(getBootKey(scratchPad)); + cert.verifiedBootState((byte) kmDataStore.getBootState()); + return cert; + } + + private KMAttestationCert makeSelfSignedCert( + short attPrivKey, short attPubKey, short mode, byte[] scratchPad) { + KMAttestationCert cert = makeCommonCert(scratchPad); + short alg = KMEnumTag.getValue(KMType.ALGORITHM, data[KEY_PARAMETERS]); + short subject = + KMKeyParameters.findTag( + KMType.BYTES_TAG, KMType.CERTIFICATE_SUBJECT_NAME, data[KEY_PARAMETERS]); + // If no subject name is specified then use the default subject name. + if (subject == KMType.INVALID_VALUE || KMByteTag.cast(subject).length() == 0) { + subject = KMByteBlob.instance(defaultSubject, (short) 0, (short) defaultSubject.length); + } else { + subject = KMByteTag.cast(subject).getValue(); + } + + if (alg == KMType.RSA) { + cert.rsaAttestKey(attPrivKey, attPubKey, (byte) mode); + } else { + cert.ecAttestKey(attPrivKey, (byte) mode); + } + cert.issuer(subject); + cert.subjectName(subject); + cert.publicKey(attPubKey); + return cert; + } + + protected short getBootKey(byte[] scratchPad) { + Util.arrayFillNonAtomic(scratchPad, (short) 0, VERIFIED_BOOT_KEY_SIZE, (byte) 0); + short len = kmDataStore.getBootKey(scratchPad, (short) 0); + if (len != VERIFIED_BOOT_KEY_SIZE) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + return KMByteBlob.instance(scratchPad, (short) 0, VERIFIED_BOOT_KEY_SIZE); + } + + protected short getVerifiedBootHash(byte[] scratchPad) { + Util.arrayFillNonAtomic(scratchPad, (short) 0, VERIFIED_BOOT_HASH_SIZE, (byte) 0); + short len = kmDataStore.getVerifiedBootHash(scratchPad, (short) 0); + if (len != VERIFIED_BOOT_HASH_SIZE) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + return KMByteBlob.instance(scratchPad, (short) 0, VERIFIED_BOOT_HASH_SIZE); + } + + // -------------------------------- + // Only add the Attestation ids which are requested in the attestation parameters. + // If the requested attestation ids are not provisioned or deleted then + // throw CANNOT_ATTEST_IDS error. If there is mismatch in the attestation + // id values of both the requested parameters and the provisioned parameters + // then throw INVALID_TAG error. + private void addAttestationIds(KMAttestationCert cert, byte[] scratchPad) { + byte index = 0; + short attIdTag; + short attIdTagValue; + short storedAttIdLen; + while (index < (short) attTags.length) { + attIdTag = KMKeyParameters.findTag(KMType.BYTES_TAG, attTags[index], data[KEY_PARAMETERS]); + if (attIdTag != KMType.INVALID_VALUE) { + attIdTagValue = KMByteTag.cast(attIdTag).getValue(); + storedAttIdLen = kmDataStore.getAttestationId(attTags[index], scratchPad, (short) 0); + // Return CANNOT_ATTEST_IDS if Attestation IDs are not provisioned or + // Attestation IDs are deleted. + if (storedAttIdLen == 0) { + KMException.throwIt(KMError.CANNOT_ATTEST_IDS); + } + // Return INVALID_TAG if Attestation IDs does not match. + if ((storedAttIdLen != KMByteBlob.cast(attIdTagValue).length()) + || (0 + != Util.arrayCompare( + scratchPad, + (short) 0, + KMByteBlob.cast(attIdTagValue).getBuffer(), + KMByteBlob.cast(attIdTagValue).getStartOff(), + storedAttIdLen))) { + KMException.throwIt(KMError.CANNOT_ATTEST_IDS); + } + short blob = KMByteBlob.instance(scratchPad, (short) 0, storedAttIdLen); + cert.extensionTag(KMByteTag.instance(attTags[index], blob), true); + } + index++; + } + } + + private void processDestroyAttIdsCmd(APDU apdu) { + kmDataStore.deleteAttestationIds(); + sendResponse(apdu, KMError.OK); + } + + private void processVerifyAuthorizationCmd(APDU apdu) { + sendResponse(apdu, KMError.UNIMPLEMENTED); + } + + private short abortOperationCmd(APDU apdu) { + short cmd = KMArray.instance((short) 1); + KMArray.cast(cmd).add((short) 0, KMInteger.exp()); + return receiveIncoming(apdu, cmd); + } + + private void processAbortOperationCmd(APDU apdu) { + short cmd = abortOperationCmd(apdu); + data[OP_HANDLE] = KMArray.cast(cmd).get((short) 0); + KMOperationState op = findOperation(data[OP_HANDLE]); + if (op == null) { + sendResponse(apdu, KMError.INVALID_OPERATION_HANDLE); + } else { + releaseOperation(op); + sendResponse(apdu, KMError.OK); + } + } + + private short finishOperationCmd(APDU apdu) { + short cmd = KMArray.instance((short) 6); + KMArray.cast(cmd).add((short) 0, KMInteger.exp()); // op handle + KMArray.cast(cmd).add((short) 1, KMByteBlob.exp()); // input data + KMArray.cast(cmd).add((short) 2, KMByteBlob.exp()); // signature + short authToken = KMHardwareAuthToken.exp(); + KMArray.cast(cmd).add((short) 3, authToken); // auth token + short verToken = KMVerificationToken.exp(); + KMArray.cast(cmd).add((short) 4, verToken); // time stamp token + KMArray.cast(cmd).add((short) 5, KMByteBlob.exp()); // confirmation token + return receiveIncoming(apdu, cmd); + } + + private void processFinishOperationCmd(APDU apdu) { + short cmd = finishOperationCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + data[OP_HANDLE] = KMArray.cast(cmd).get((short) 0); + data[INPUT_DATA] = KMArray.cast(cmd).get((short) 1); + data[SIGNATURE] = KMArray.cast(cmd).get((short) 2); + data[HW_TOKEN] = KMArray.cast(cmd).get((short) 3); + data[VERIFICATION_TOKEN] = KMArray.cast(cmd).get((short) 4); + data[CONFIRMATION_TOKEN] = KMArray.cast(cmd).get((short) 5); + // Check Operation Handle + KMOperationState op = findOperation(data[OP_HANDLE]); + if (op == null) { + KMException.throwIt(KMError.INVALID_OPERATION_HANDLE); + } + // Authorize the finish operation + authorizeUpdateFinishOperation(op, scratchPad); + switch (op.getPurpose()) { + case KMType.SIGN: + finishTrustedConfirmationOperation(op); + case KMType.VERIFY: + finishSigningVerifyingOperation(op, scratchPad); + break; + case KMType.ENCRYPT: + finishEncryptOperation(op, scratchPad); + break; + case KMType.DECRYPT: + finishDecryptOperation(op, scratchPad); + break; + case KMType.AGREE_KEY: + finishKeyAgreementOperation(op, scratchPad); + break; + } + if (data[OUTPUT_DATA] == KMType.INVALID_VALUE) { + data[OUTPUT_DATA] = KMByteBlob.instance((short) 0); + } + // Remove the operation handle + releaseOperation(op); + + // make response + short resp = KMArray.instance((short) 2); + KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(resp).add((short) 1, data[OUTPUT_DATA]); + sendOutgoing(apdu, resp); + } + + private void finishEncryptOperation(KMOperationState op, byte[] scratchPad) { + if (op.getAlgorithm() != KMType.AES && op.getAlgorithm() != KMType.DES) { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + finishAesDesOperation(op); + } + + private void finishDecryptOperation(KMOperationState op, byte[] scratchPad) { + short len = KMByteBlob.cast(data[INPUT_DATA]).length(); + switch (op.getAlgorithm()) { + case KMType.RSA: + // Fill the scratch pad with zero + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + if (op.getPadding() == KMType.PADDING_NONE && len != 256) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + len = + op.getOperation() + .finish( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + len, + scratchPad, + (short) 0); + + data[OUTPUT_DATA] = KMByteBlob.instance(scratchPad, (short) 0, len); + break; + case KMType.AES: + case KMType.DES: + finishAesDesOperation(op); + break; + } + } + + private void finishAesDesOperation(KMOperationState op) { + short len = KMByteBlob.cast(data[INPUT_DATA]).length(); + short blockSize = AES_BLOCK_SIZE; + if (op.getAlgorithm() == KMType.DES) { + blockSize = DES_BLOCK_SIZE; + } + + if (op.getPurpose() == KMType.DECRYPT + && len > 0 + && (op.getBlockMode() == KMType.ECB || op.getBlockMode() == KMType.CBC) + && ((short) (len % blockSize) != 0)) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + + if (op.getBlockMode() == KMType.GCM) { + if (op.getPurpose() == KMType.DECRYPT && (len < (short) (op.getMacLength() / 8))) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + if (op.isAesGcmUpdateAllowed()) { + op.setAesGcmUpdateComplete(); + } + // Get the output size + len = op.getOperation().getAESGCMOutputSize(len, (short) (op.getMacLength() / 8)); + } + // If padding i.e. pkcs7 then add padding to right + // Output data can at most one block size more the input data in case of pkcs7 encryption + // In case of gcm we will allocate extra memory of the size equal to blocksize. + data[OUTPUT_DATA] = KMByteBlob.instance((short) (len + 2 * blockSize)); + try { + len = + op.getOperation() + .finish( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length(), + KMByteBlob.cast(data[OUTPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[OUTPUT_DATA]).getStartOff()); + } catch (CryptoException e) { + if (e.getReason() == CryptoException.ILLEGAL_USE) { + // As per VTS, zero length input on AES/DES with PADDING_NONE Should return a zero length + // output. But JavaCard fails with CryptoException.ILLEGAL_USE if no input data is + // provided via update() method. So ignore this exception in case if all below conditions + // are satisfied and simply return empty output. + // 1. padding mode is PADDING_NONE. + // 2. No input message is processed in update(). + // 3. Zero length input data is passed in finish operation. + if ((op.getPadding() == KMType.PADDING_NONE) + && !op.isInputMsgProcessed() + && (KMByteBlob.cast(data[INPUT_DATA]).length() == 0)) { + len = 0; + } else { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + } + } + KMByteBlob.cast(data[OUTPUT_DATA]).setLength(len); + } + + private void finishKeyAgreementOperation(KMOperationState op, byte[] scratchPad) { + try { + KMAsn1Parser pkcs8 = KMAsn1Parser.instance(); + short blob = pkcs8.decodeEcSubjectPublicKeyInfo(data[INPUT_DATA]); + short len = + op.getOperation() + .finish( + KMByteBlob.cast(blob).getBuffer(), + KMByteBlob.cast(blob).getStartOff(), + KMByteBlob.cast(blob).length(), + scratchPad, + (short) 0); + data[OUTPUT_DATA] = KMByteBlob.instance((short) 32); + Util.arrayCopyNonAtomic( + scratchPad, + (short) 0, + KMByteBlob.cast(data[OUTPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[OUTPUT_DATA]).getStartOff(), + len); + } catch (CryptoException e) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + } + + private void finishSigningVerifyingOperation(KMOperationState op, byte[] scratchPad) { + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + switch (op.getAlgorithm()) { + case KMType.RSA: + // If there is no padding we can treat signing as a RSA decryption operation. + try { + if (op.getPurpose() == KMType.SIGN) { + // len of signature will be 256 bytes - but it can be less then 256 bytes + short len = + op.getOperation() + .sign( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length(), + scratchPad, + (short) 0); + // Maximum output size of signature is 256 bytes. - the signature will always be + // positive + data[OUTPUT_DATA] = KMByteBlob.instance((short) 256); + Util.arrayCopyNonAtomic( + scratchPad, + (short) 0, + KMByteBlob.cast(data[OUTPUT_DATA]).getBuffer(), + (short) (KMByteBlob.cast(data[OUTPUT_DATA]).getStartOff() + 256 - len), + len); + } else { + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + } catch (CryptoException e) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + break; + case KMType.EC: + short len = KMByteBlob.cast(data[INPUT_DATA]).length(); + // If DIGEST NONE then truncate the input data to 32 bytes. + if (op.getDigest() == KMType.DIGEST_NONE && len > 32) { + len = 32; + } + if (op.getPurpose() == KMType.SIGN) { + // len of signature will be 512 bits i.e. 64 bytes + len = + op.getOperation() + .sign( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + len, + scratchPad, + (short) 0); + data[OUTPUT_DATA] = KMByteBlob.instance(scratchPad, (short) 0, len); + } else { + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + break; + case KMType.HMAC: + // As per Keymaster HAL documentation, the length of the Hmac output can + // be decided by using TAG_MAC_LENGTH in Keyparameters. But there is no + // such provision to control the length of the Hmac output using JavaCard + // crypto APIs and the current implementation always returns 32 bytes + // length of Hmac output. So to provide support to TAG_MAC_LENGTH + // feature, we truncate the output signature to TAG_MAC_LENGTH and return + // the truncated signature back to the caller. At the time of verfication + // we again compute the signature of the plain text input, truncate it to + // TAG_MAC_LENGTH and compare it with the input signature for + // verification. + op.getOperation() + .sign( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length(), + scratchPad, + (short) 0); + if (op.getPurpose() == KMType.SIGN) { + // Copy only signature of mac length size. + data[OUTPUT_DATA] = + KMByteBlob.instance(scratchPad, (short) 0, (short) (op.getMacLength() / 8)); + } else if (op.getPurpose() == KMType.VERIFY) { + if ((KMByteBlob.cast(data[SIGNATURE]).length() < (MIN_HMAC_LENGTH_BITS / 8)) + || KMByteBlob.cast(data[SIGNATURE]).length() > (SHA256_DIGEST_LEN_BITS / 8)) { + KMException.throwIt(KMError.UNSUPPORTED_MAC_LENGTH); + } + if ((KMByteBlob.cast(data[SIGNATURE]).length() < (short) (op.getMinMacLength() / 8))) { + KMException.throwIt(KMError.INVALID_MAC_LENGTH); + } + + if (0 + != Util.arrayCompare( + scratchPad, + (short) 0, + KMByteBlob.cast(data[SIGNATURE]).getBuffer(), + KMByteBlob.cast(data[SIGNATURE]).getStartOff(), + KMByteBlob.cast(data[SIGNATURE]).length())) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + data[OUTPUT_DATA] = KMByteBlob.instance((short) 0); + } + break; + default: // This is should never happen + KMException.throwIt(KMError.OPERATION_CANCELLED); + break; + } + } + + private void authorizeUpdateFinishOperation(KMOperationState op, byte[] scratchPad) { + // If one time user Authentication is required + if (op.isSecureUserIdReqd() && !op.isAuthTimeoutValidated()) { + // Validate Verification Token. + validateVerificationToken(data[VERIFICATION_TOKEN], scratchPad); + // validate operation handle. + short ptr = KMVerificationToken.cast(data[VERIFICATION_TOKEN]).getChallenge(); + if (KMInteger.compare(ptr, op.getHandle()) != 0) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + tmpVariables[0] = op.getAuthTime(); + tmpVariables[2] = KMVerificationToken.cast(data[VERIFICATION_TOKEN]).getTimestamp(); + if (tmpVariables[2] == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + if (KMInteger.compare(tmpVariables[0], tmpVariables[2]) < 0) { + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); + } + op.setAuthTimeoutValidated(true); + } else if (op.isAuthPerOperationReqd()) { // If Auth per operation is required + if (!validateHwToken(data[HW_TOKEN], scratchPad)) { + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); + } + tmpVariables[0] = KMHardwareAuthToken.cast(data[HW_TOKEN]).getChallenge(); + if (KMInteger.compare(data[OP_HANDLE], tmpVariables[0]) != 0) { + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); + } + if (!authTokenMatches(op.getUserSecureId(), op.getAuthType(), scratchPad)) { + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); + } + } + } + + private void authorizeKeyUsageForCount(byte[] scratchPad) { + short scratchPadOff = 0; + Util.arrayFillNonAtomic(scratchPad, scratchPadOff, (short) 12, (byte) 0); + + short usageLimitBufLen = + KMIntegerTag.getValue( + scratchPad, + scratchPadOff, + KMType.UINT_TAG, + KMType.MAX_USES_PER_BOOT, + data[HW_PARAMETERS]); + + if (usageLimitBufLen == KMType.INVALID_VALUE) { + return; + } + + if (usageLimitBufLen > 4) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + + if (kmDataStore.isAuthTagPersisted(data[AUTH_TAG])) { + // Get current counter, update and increment it. + short len = + kmDataStore.getRateLimitedKeyCount( + data[AUTH_TAG], scratchPad, (short) (scratchPadOff + 4)); + if (len != 4) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + if (0 + >= KMInteger.unsignedByteArrayCompare( + scratchPad, scratchPadOff, scratchPad, (short) (scratchPadOff + 4), (short) 4)) { + KMException.throwIt(KMError.KEY_MAX_OPS_EXCEEDED); + } + // Increment the counter. + Util.arrayFillNonAtomic(scratchPad, scratchPadOff, len, (byte) 0); + Util.setShort(scratchPad, (short) (scratchPadOff + 2), (short) 1); + KMUtils.add( + scratchPad, + scratchPadOff, + (short) (scratchPadOff + len), + (short) (scratchPadOff + len * 2)); + + kmDataStore.setRateLimitedKeyCount( + data[AUTH_TAG], scratchPad, (short) (scratchPadOff + len * 2), len); + } else { + // Persist auth tag. + if (!kmDataStore.persistAuthTag(data[AUTH_TAG])) { + KMException.throwIt(KMError.TOO_MANY_OPERATIONS); + } + } + } + + private void authorizeDeviceUnlock(byte[] scratchPad) { + // If device is locked and key characteristics requires unlocked device then check whether + // HW auth token has correct timestamp. + short ptr = + KMKeyParameters.findTag( + KMType.BOOL_TAG, KMType.UNLOCKED_DEVICE_REQUIRED, data[HW_PARAMETERS]); + + if (ptr != KMType.INVALID_VALUE && kmDataStore.getDeviceLock()) { + if (data[HW_TOKEN] == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.DEVICE_LOCKED); + } + ptr = KMHardwareAuthToken.cast(data[HW_TOKEN]).getTimestamp(); + // Check if the current auth time stamp is greater than device locked time stamp + short ts = kmDataStore.getDeviceTimeStamp(); + if (KMInteger.compare(ptr, ts) <= 0) { + KMException.throwIt(KMError.DEVICE_LOCKED); + } + // Now check if the device unlock requires password only authentication and whether + // auth token is generated through password authentication or not. + if (kmDataStore.getDeviceLockPasswordOnly()) { + ptr = KMHardwareAuthToken.cast(data[HW_TOKEN]).getHwAuthenticatorType(); + ptr = KMEnum.cast(ptr).getVal(); + if (((byte) ptr & KMType.PASSWORD) == 0) { + KMException.throwIt(KMError.DEVICE_LOCKED); + } + } + // Unlock the device + // repository.deviceLockedFlag = false; + kmDataStore.setDeviceLock(false); + kmDataStore.clearDeviceLockTimeStamp(); + } + } + + private boolean verifyVerificationTokenMacInBigEndian(short verToken, byte[] scratchPad) { + // concatenation length will be 37 + length of verified parameters list - which + // is typically empty + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + // Add "Auth Verification" - 17 bytes. + Util.arrayCopyNonAtomic( + authVerification, (short) 0, scratchPad, (short) 0, (short) authVerification.length); + short len = (short) authVerification.length; + // concatenate challenge - 8 bytes + short ptr = KMVerificationToken.cast(verToken).getChallenge(); + KMInteger.cast(ptr) + .value( + scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length()))); + len += KMInteger.UINT_64; + // concatenate timestamp -8 bytes + ptr = KMVerificationToken.cast(verToken).getTimestamp(); + KMInteger.cast(ptr) + .value( + scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length()))); + len += KMInteger.UINT_64; + // concatenate security level - 4 bytes + scratchPad[(short) (len + 3)] = TRUSTED_ENVIRONMENT; + len += KMInteger.UINT_32; + // hmac the data + ptr = KMVerificationToken.cast(verToken).getMac(); + + return seProvider.hmacVerify( + kmDataStore.getComputedHmacKey(), + scratchPad, + (short) 0, + len, + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()); + } + + private void validateVerificationToken(short verToken, byte[] scratchPad) { + short ptr = KMVerificationToken.cast(verToken).getMac(); + // If mac length is zero then token is empty. + if (KMByteBlob.cast(ptr).length() == 0) { + KMException.throwIt(KMError.INVALID_MAC_LENGTH); + } + if (!verifyVerificationTokenMacInBigEndian(verToken, scratchPad)) { + // Throw Exception if none of the combination works. + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + } + + private short updateOperationCmd(APDU apdu) { + short cmd = KMArray.instance((short) 4); + // Arguments + KMArray.cast(cmd).add((short) 0, KMInteger.exp()); + KMArray.cast(cmd).add((short) 1, KMByteBlob.exp()); + short authToken = KMHardwareAuthToken.exp(); + KMArray.cast(cmd).add((short) 2, authToken); + short verToken = KMVerificationToken.exp(); + KMArray.cast(cmd).add((short) 3, verToken); + return receiveIncoming(apdu, cmd); + } + + private void processUpdateOperationCmd(APDU apdu) { + short cmd = updateOperationCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + data[OP_HANDLE] = KMArray.cast(cmd).get((short) 0); + data[INPUT_DATA] = KMArray.cast(cmd).get((short) 1); + data[HW_TOKEN] = KMArray.cast(cmd).get((short) 2); + data[VERIFICATION_TOKEN] = KMArray.cast(cmd).get((short) 3); + + // Input data must be present even if it is zero length. + if (data[INPUT_DATA] == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + + // Check Operation Handle and get op state + // Check Operation Handle + KMOperationState op = findOperation(data[OP_HANDLE]); + if (op == null) { + KMException.throwIt(KMError.INVALID_OPERATION_HANDLE); + } + // authorize the update operation + authorizeUpdateFinishOperation(op, scratchPad); + + if (op.getPurpose() == KMType.SIGN || op.getPurpose() == KMType.VERIFY) { + // update the data. + op.getOperation() + .update( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length()); + // update trusted confirmation operation + updateTrustedConfirmationOperation(op); + + data[OUTPUT_DATA] = KMType.INVALID_VALUE; + } else if (op.getPurpose() == KMType.ENCRYPT || op.getPurpose() == KMType.DECRYPT) { + // Update for encrypt/decrypt using RSA will not be supported because to do this op state + // will have to buffer the data - so reject the update if it is rsa algorithm. + if (op.getAlgorithm() == KMType.RSA) { + KMException.throwIt(KMError.OPERATION_CANCELLED); + } + short len = KMByteBlob.cast(data[INPUT_DATA]).length(); + short blockSize = DES_BLOCK_SIZE; + if (op.getAlgorithm() == KMType.AES) { + blockSize = AES_BLOCK_SIZE; + if (op.getBlockMode() == KMType.GCM) { + // if input data present + if (len > 0) { + // no more future updateAAD allowed if input data present. + if (op.isAesGcmUpdateAllowed()) { + op.setAesGcmUpdateComplete(); + } + } + } + } + // Allocate output buffer as input data is already block aligned + data[OUTPUT_DATA] = KMByteBlob.instance((short) (len + 2 * blockSize)); + // Otherwise just update the data. + // HAL consumes all the input and maintains a buffered data inside it. So the + // applet sends the inputConsumed length as same as the input length. + try { + len = + op.getOperation() + .update( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length(), + KMByteBlob.cast(data[OUTPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[OUTPUT_DATA]).getStartOff()); + } catch (CryptoException e) { + KMException.throwIt(KMError.INVALID_TAG); + } + if (KMByteBlob.cast(data[INPUT_DATA]).length() > 0) { + // This flag is used to denote that an input data of length > 0 is received and processed + // successfully in update command. This flag is later used in the finish operation + // to handle a particular use case, where a zero length input data on AES/DES algorithm + // with PADDING_NONE should return a zero length output with OK response. + op.setProcessedInputMsg(true); + } + // Adjust the Output data if it is not equal to input data. + // This happens in case of JCardSim provider. + KMByteBlob.cast(data[OUTPUT_DATA]).setLength(len); + } + + if (data[OUTPUT_DATA] == KMType.INVALID_VALUE) { + data[OUTPUT_DATA] = KMByteBlob.instance((short) 0); + } + // Persist if there are any updates. + // op.persist(); + // make response + short resp = KMArray.instance((short) 2); + KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(resp).add((short) 1, data[OUTPUT_DATA]); + sendOutgoing(apdu, resp); + } + + private short updateAadOperationCmd(APDU apdu) { + short cmd = KMArray.instance((short) 4); + KMArray.cast(cmd).add((short) 0, KMInteger.exp()); + KMArray.cast(cmd).add((short) 1, KMByteBlob.exp()); + short authToken = KMHardwareAuthToken.exp(); + KMArray.cast(cmd).add((short) 2, authToken); + short verToken = KMVerificationToken.exp(); + KMArray.cast(cmd).add((short) 3, verToken); + return receiveIncoming(apdu, cmd); + } + + private void processUpdateAadOperationCmd(APDU apdu) { + short cmd = updateAadOperationCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + data[OP_HANDLE] = KMArray.cast(cmd).get((short) 0); + data[INPUT_DATA] = KMArray.cast(cmd).get((short) 1); + data[HW_TOKEN] = KMArray.cast(cmd).get((short) 2); + data[VERIFICATION_TOKEN] = KMArray.cast(cmd).get((short) 3); + + // Input data must be present even if it is zero length. + if (data[INPUT_DATA] == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // Check Operation Handle and get op state + // Check Operation Handle + KMOperationState op = findOperation(data[OP_HANDLE]); + if (op == null) { + KMException.throwIt(KMError.INVALID_OPERATION_HANDLE); + } + if (op.getAlgorithm() != KMType.AES) { + KMException.throwIt(KMError.INCOMPATIBLE_ALGORITHM); + } + if (op.getBlockMode() != KMType.GCM) { + KMException.throwIt(KMError.INCOMPATIBLE_BLOCK_MODE); + } + if (!op.isAesGcmUpdateAllowed()) { + KMException.throwIt(KMError.INVALID_TAG); + } + if (op.getPurpose() != KMType.ENCRYPT && op.getPurpose() != KMType.DECRYPT) { + KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); + } + // authorize the update operation + authorizeUpdateFinishOperation(op, scratchPad); + try { + op.getOperation() + .updateAAD( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length()); + } catch (CryptoException exp) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + // make response + short resp = KMArray.instance((short) 1); + KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); + sendOutgoing(apdu, resp); + } + + private short beginOperationCmd(APDU apdu) { + short cmd = KMArray.instance((short) 4); + // Arguments + short params = KMKeyParameters.expAny(); + KMArray.cast(cmd).add((short) 0, KMEnum.instance(KMType.PURPOSE)); + KMArray.cast(cmd).add((short) 1, KMByteBlob.exp()); + KMArray.cast(cmd).add((short) 2, params); + short authToken = KMHardwareAuthToken.exp(); + KMArray.cast(cmd).add((short) 3, authToken); + return receiveIncoming(apdu, cmd); + } + + private void processBeginOperationCmd(APDU apdu) { + // Receive the incoming request fully from the host into buffer. + short cmd = beginOperationCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + short purpose = KMArray.cast(cmd).get((short) 0); + data[KEY_BLOB] = KMArray.cast(cmd).get((short) 1); + data[KEY_PARAMETERS] = KMArray.cast(cmd).get((short) 2); + data[HW_TOKEN] = KMArray.cast(cmd).get((short) 3); + purpose = KMEnum.cast(purpose).getVal(); + // Check for app id and app data. + data[APP_ID] = getApplicationId(data[KEY_PARAMETERS]); + data[APP_DATA] = getApplicationData(data[KEY_PARAMETERS]); + // Check if key requires upgrade. The KeyBlob is parsed inside isKeyUpgradeRequired + // function itself. + if (isKeyUpgradeRequired(data[KEY_BLOB], data[APP_ID], data[APP_DATA], scratchPad)) { + KMException.throwIt(KMError.KEY_REQUIRES_UPGRADE); + } + KMTag.assertPresence( + data[SB_PARAMETERS], KMType.ENUM_TAG, KMType.ALGORITHM, KMError.UNSUPPORTED_ALGORITHM); + short algorithm = KMEnumTag.getValue(KMType.ALGORITHM, data[SB_PARAMETERS]); + // If Blob usage tag is present in key characteristics then it should be standalone. + if (KMTag.isPresent(data[SB_PARAMETERS], KMType.ENUM_TAG, KMType.BLOB_USAGE_REQ)) { + if (KMEnumTag.getValue(KMType.BLOB_USAGE_REQ, data[SB_PARAMETERS]) != KMType.STANDALONE) { + KMException.throwIt(KMError.UNSUPPORTED_TAG); + } + } + + // Generate a random number for operation handle + short buf = KMByteBlob.instance(KMOperationState.OPERATION_HANDLE_SIZE); + generateUniqueOperationHandle( + KMByteBlob.cast(buf).getBuffer(), + KMByteBlob.cast(buf).getStartOff(), + KMByteBlob.cast(buf).length()); + /* opHandle is a KMInteger and is encoded as KMInteger when it is returned back. */ + short opHandle = + KMInteger.instance( + KMByteBlob.cast(buf).getBuffer(), + KMByteBlob.cast(buf).getStartOff(), + KMByteBlob.cast(buf).length()); + KMOperationState op = reserveOperation(algorithm, opHandle); + if (op == null) { + KMException.throwIt(KMError.TOO_MANY_OPERATIONS); + } + data[OP_HANDLE] = op.getHandle(); + op.setPurpose((byte) purpose); + op.setKeySize(KMByteBlob.cast(data[SECRET]).length()); + authorizeAndBeginOperation(op, scratchPad); + switch (op.getPurpose()) { + case KMType.SIGN: + beginTrustedConfirmationOperation(op); + case KMType.VERIFY: + beginSignVerifyOperation(op); + break; + case KMType.ENCRYPT: + case KMType.DECRYPT: + beginCipherOperation(op); + break; + case KMType.AGREE_KEY: + beginKeyAgreementOperation(op); + break; + default: + KMException.throwIt(KMError.UNIMPLEMENTED); + break; + } + short iv = KMType.INVALID_VALUE; + // If the data[IV] is required to be returned. + // As per VTS, for the decryption operation don't send the iv back. + if (data[IV] != KMType.INVALID_VALUE + && op.getPurpose() != KMType.DECRYPT + && op.getBlockMode() != KMType.ECB) { + iv = KMArray.instance((short) 1); + if (op.getAlgorithm() == KMType.DES && op.getBlockMode() == KMType.CBC) { + // For AES/DES we are generate an random iv of length 16 bytes. + // While sending the iv back for DES/CBC mode of opeation only send + // 8 bytes back. + short ivBlob = KMByteBlob.instance((short) 8); + Util.arrayCopy( + KMByteBlob.cast(data[IV]).getBuffer(), + KMByteBlob.cast(data[IV]).getStartOff(), + KMByteBlob.cast(ivBlob).getBuffer(), + KMByteBlob.cast(ivBlob).getStartOff(), + (short) 8); + data[IV] = ivBlob; + } + KMArray.cast(iv).add((short) 0, KMByteTag.instance(KMType.NONCE, data[IV])); + } else { + iv = KMArray.instance((short) 0); + } + short macLen = 0; + if (op.getMacLength() != KMType.INVALID_VALUE) { + macLen = (short) (op.getMacLength() / 8); + } + short params = KMKeyParameters.instance(iv); + short resp = KMArray.instance((short) 5); + KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(resp).add((short) 1, params); + KMArray.cast(resp).add((short) 2, data[OP_HANDLE]); + KMArray.cast(resp).add((short) 3, KMInteger.uint_8(op.getBufferingMode())); + KMArray.cast(resp).add((short) 4, KMInteger.uint_16(macLen)); + sendOutgoing(apdu, resp); + } + + private void authorizeAlgorithm(KMOperationState op) { + short alg = KMEnumTag.getValue(KMType.ALGORITHM, data[HW_PARAMETERS]); + if (alg == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + op.setAlgorithm((byte) alg); + } + + private void authorizePurpose(KMOperationState op) { + switch (op.getAlgorithm()) { + case KMType.AES: + case KMType.DES: + if (op.getPurpose() == KMType.SIGN + || op.getPurpose() == KMType.VERIFY + || op.getPurpose() == KMType.AGREE_KEY) { + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + break; + case KMType.EC: + if (op.getPurpose() == KMType.ENCRYPT || op.getPurpose() == KMType.DECRYPT) { + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + break; + case KMType.HMAC: + if (op.getPurpose() == KMType.ENCRYPT + || op.getPurpose() == KMType.DECRYPT + || op.getPurpose() == KMType.AGREE_KEY) { + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + break; + case KMType.RSA: + if (op.getPurpose() == KMType.AGREE_KEY) { + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + break; + default: + break; + } + if (!KMEnumArrayTag.contains(KMType.PURPOSE, op.getPurpose(), data[HW_PARAMETERS])) { + KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); + } + } + + private void authorizeDigest(KMOperationState op) { + short digests = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.DIGEST, data[HW_PARAMETERS]); + op.setDigest(KMType.DIGEST_NONE); + short param = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.DIGEST, data[KEY_PARAMETERS]); + if (param != KMType.INVALID_VALUE) { + if (KMEnumArrayTag.cast(param).length() != 1) { + KMException.throwIt(KMError.UNSUPPORTED_DIGEST); + } + param = KMEnumArrayTag.cast(param).get((short) 0); + if (!KMEnumArrayTag.cast(digests).contains(param)) { + KMException.throwIt(KMError.INCOMPATIBLE_DIGEST); + } + op.setDigest((byte) param); + } else if (KMEnumArrayTag.contains( + KMType.PADDING, KMType.RSA_PKCS1_1_5_SIGN, data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.UNSUPPORTED_DIGEST); + } + short paramPadding = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PADDING, data[KEY_PARAMETERS]); + if (paramPadding != KMType.INVALID_VALUE) { + if (KMEnumArrayTag.cast(paramPadding).length() != 1) { + // TODO vts fails because it expects UNSUPPORTED_PADDING_MODE + KMException.throwIt(KMError.UNSUPPORTED_PADDING_MODE); + } + paramPadding = KMEnumArrayTag.cast(paramPadding).get((short) 0); + } + switch (op.getAlgorithm()) { + case KMType.RSA: + if ((paramPadding == KMType.RSA_OAEP || paramPadding == KMType.RSA_PSS) + && param == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.UNSUPPORTED_DIGEST); + } + break; + case KMType.EC: + case KMType.HMAC: + if ((param == KMType.INVALID_VALUE && op.getPurpose() != KMType.AGREE_KEY) + || !isDigestSupported(op.getAlgorithm(), op.getDigest())) { + KMException.throwIt(KMError.UNSUPPORTED_DIGEST); + } + break; + default: + break; + } + } + + private void authorizePadding(KMOperationState op) { + short paddings = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PADDING, data[HW_PARAMETERS]); + op.setPadding(KMType.PADDING_NONE); + short param = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PADDING, data[KEY_PARAMETERS]); + if (param != KMType.INVALID_VALUE) { + if (KMEnumArrayTag.cast(param).length() != 1) { + KMException.throwIt(KMError.UNSUPPORTED_PADDING_MODE); + } + param = KMEnumArrayTag.cast(param).get((short) 0); + if (!KMEnumArrayTag.cast(paddings).contains(param)) { + KMException.throwIt(KMError.INCOMPATIBLE_PADDING_MODE); + } + } + switch (op.getAlgorithm()) { + case KMType.RSA: + if (param == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.UNSUPPORTED_PADDING_MODE); + } + if ((op.getPurpose() == KMType.SIGN || op.getPurpose() == KMType.VERIFY) + && param != KMType.PADDING_NONE + && param != KMType.RSA_PSS + && param != KMType.RSA_PKCS1_1_5_SIGN) { + KMException.throwIt(KMError.UNSUPPORTED_PADDING_MODE); + } + if ((op.getPurpose() == KMType.ENCRYPT || op.getPurpose() == KMType.DECRYPT) + && param != KMType.PADDING_NONE + && param != KMType.RSA_OAEP + && param != KMType.RSA_PKCS1_1_5_ENCRYPT) { + KMException.throwIt(KMError.UNSUPPORTED_PADDING_MODE); + } + + if (param == KMType.PADDING_NONE && op.getDigest() != KMType.DIGEST_NONE) { + KMException.throwIt(KMError.INCOMPATIBLE_DIGEST); + } + if ((param == KMType.RSA_OAEP || param == KMType.RSA_PSS) + && op.getDigest() == KMType.DIGEST_NONE) { + KMException.throwIt(KMError.INCOMPATIBLE_DIGEST); + } + if (op.getPurpose() == KMType.SIGN + || op.getPurpose() == KMType.VERIFY + || param == KMType.RSA_OAEP) { + // Digest is mandatory in these cases. + if (!isDigestSupported(op.getAlgorithm(), op.getDigest())) { + KMException.throwIt(KMError.UNSUPPORTED_DIGEST); + } + } + if (param == KMType.RSA_OAEP) { + short mgfDigest = + KMKeyParameters.findTag( + KMType.ENUM_ARRAY_TAG, KMType.RSA_OAEP_MGF_DIGEST, data[KEY_PARAMETERS]); + if (mgfDigest != KMType.INVALID_VALUE) { + if (KMEnumArrayTag.cast(mgfDigest).length() != 1) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + mgfDigest = KMEnumArrayTag.cast(mgfDigest).get((short) 0); + if (mgfDigest == KMType.DIGEST_NONE) { + KMException.throwIt(KMError.UNSUPPORTED_MGF_DIGEST); + } + + } else { + mgfDigest = KMType.SHA1; + } + short mgfDigestHwParams = + KMKeyParameters.findTag( + KMType.ENUM_ARRAY_TAG, KMType.RSA_OAEP_MGF_DIGEST, data[HW_PARAMETERS]); + if ((mgfDigestHwParams != KMType.INVALID_VALUE) + && (!KMEnumArrayTag.cast(mgfDigestHwParams).contains(mgfDigest))) { + KMException.throwIt(KMError.INCOMPATIBLE_MGF_DIGEST); + } + if (mgfDigest != KMType.SHA1 && mgfDigest != KMType.SHA2_256) { + KMException.throwIt(KMError.UNSUPPORTED_MGF_DIGEST); + } + op.setMgfDigest((byte) mgfDigest); + } + op.setPadding((byte) param); + break; + case KMType.DES: + case KMType.AES: + if (param == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.UNSUPPORTED_PADDING_MODE); + } + op.setPadding((byte) param); + break; + default: + break; + } + } + + private void authorizeBlockModeAndMacLength(KMOperationState op) { + short param = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.BLOCK_MODE, data[KEY_PARAMETERS]); + if (param != KMType.INVALID_VALUE) { + if (KMEnumArrayTag.cast(param).length() != 1) { + KMException.throwIt(KMError.UNSUPPORTED_BLOCK_MODE); + } + param = KMEnumArrayTag.cast(param).get((short) 0); + } + if (KMType.AES == op.getAlgorithm() || KMType.DES == op.getAlgorithm()) { + if (!KMEnumArrayTag.contains(KMType.BLOCK_MODE, param, data[HW_PARAMETERS])) { + KMException.throwIt(KMError.INCOMPATIBLE_BLOCK_MODE); + } + } + short macLen = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.MAC_LENGTH, data[KEY_PARAMETERS]); + switch (op.getAlgorithm()) { + case KMType.AES: + // Validate the block mode. + switch (param) { + case KMType.ECB: + case KMType.CBC: + case KMType.CTR: + case KMType.GCM: + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_BLOCK_MODE); + } + if (param == KMType.GCM) { + if (op.getPadding() != KMType.PADDING_NONE || op.getPadding() == KMType.PKCS7) { + KMException.throwIt(KMError.INCOMPATIBLE_PADDING_MODE); + } + if (macLen == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.MISSING_MAC_LENGTH); + } + short minMacLen = + KMIntegerTag.getShortValue( + KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, data[HW_PARAMETERS]); + if (minMacLen == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + if (macLen % 8 != 0 + || macLen > MAX_GCM_TAG_LENGTH_BITS + || macLen < MIN_GCM_TAG_LENGTH_BITS) { + KMException.throwIt(KMError.UNSUPPORTED_MAC_LENGTH); + } + if (macLen < minMacLen) { + KMException.throwIt(KMError.INVALID_MAC_LENGTH); + } + op.setMacLength(macLen); + } + if (param == KMType.CTR) { + if (op.getPadding() != KMType.PADDING_NONE || op.getPadding() == KMType.PKCS7) { + KMException.throwIt(KMError.INCOMPATIBLE_PADDING_MODE); + } + } + break; + case KMType.DES: + // Validate the block mode. + switch (param) { + case KMType.ECB: + case KMType.CBC: + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_BLOCK_MODE); + } + if (param == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + break; + case KMType.HMAC: + short minMacLen = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, data[HW_PARAMETERS]); + if (minMacLen == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + op.setMinMacLength(minMacLen); + if (macLen == KMType.INVALID_VALUE) { + if (op.getPurpose() == KMType.SIGN) { + KMException.throwIt(KMError.MISSING_MAC_LENGTH); + } + } else { + // MAC length may not be specified for verify. + if (op.getPurpose() == KMType.VERIFY) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + if (macLen % 8 != 0 || macLen > SHA256_DIGEST_LEN_BITS || macLen < MIN_HMAC_LENGTH_BITS) { + KMException.throwIt(KMError.UNSUPPORTED_MAC_LENGTH); + } + if (macLen < minMacLen) { + KMException.throwIt(KMError.INVALID_MAC_LENGTH); + } + op.setMacLength(macLen); + } + break; + default: + break; + } + op.setBlockMode((byte) param); + } + + private void authorizeAndBeginOperation(KMOperationState op, byte[] scratchPad) { + authorizePurpose(op); + authorizeDigest(op); + authorizePadding(op); + authorizeBlockModeAndMacLength(op); + if (!validateHwToken(data[HW_TOKEN], scratchPad)) { + data[HW_TOKEN] = KMType.INVALID_VALUE; + } + authorizeUserSecureIdAuthTimeout(op, scratchPad); + authorizeDeviceUnlock(scratchPad); + authorizeKeyUsageForCount(scratchPad); + + KMTag.assertAbsence( + data[HW_PARAMETERS], KMType.BOOL_TAG, KMType.BOOTLOADER_ONLY, KMError.INVALID_KEY_BLOB); + + // Validate early boot + // VTS expects error code EARLY_BOOT_ONLY during begin operation if early boot ended tag is + // present + if (kmDataStore.getEarlyBootEndedStatus()) { + KMTag.assertAbsence( + data[HW_PARAMETERS], KMType.BOOL_TAG, KMType.EARLY_BOOT_ONLY, KMError.EARLY_BOOT_ENDED); + } + + // Authorize Caller Nonce - if caller nonce absent in key char and nonce present in + // key params then fail if it is not a Decrypt operation + data[IV] = KMType.INVALID_VALUE; + + if (!KMTag.isPresent(data[HW_PARAMETERS], KMType.BOOL_TAG, KMType.CALLER_NONCE) + && KMTag.isPresent(data[KEY_PARAMETERS], KMType.BYTES_TAG, KMType.NONCE) + && op.getPurpose() != KMType.DECRYPT) { + KMException.throwIt(KMError.CALLER_NONCE_PROHIBITED); + } + + short nonce = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.NONCE, data[KEY_PARAMETERS]); + // If Nonce is present then check whether the size of nonce is correct. + if (nonce != KMType.INVALID_VALUE) { + data[IV] = KMByteTag.cast(nonce).getValue(); + // For CBC mode - iv must be 8 bytes + if (op.getBlockMode() == KMType.CBC + && op.getAlgorithm() == KMType.DES + && KMByteBlob.cast(data[IV]).length() != 8) { + KMException.throwIt(KMError.INVALID_NONCE); + } + + // For GCM mode - IV must be 12 bytes + if (KMByteBlob.cast(data[IV]).length() != 12 && op.getBlockMode() == KMType.GCM) { + KMException.throwIt(KMError.INVALID_NONCE); + } + + // For AES CBC and CTR modes IV must be 16 bytes + if ((op.getBlockMode() == KMType.CBC || op.getBlockMode() == KMType.CTR) + && op.getAlgorithm() == KMType.AES + && KMByteBlob.cast(data[IV]).length() != 16) { + KMException.throwIt(KMError.INVALID_NONCE); + } + } else if (op.getAlgorithm() == KMType.AES || op.getAlgorithm() == KMType.DES) { + + // For symmetric decryption iv is required + if (op.getPurpose() == KMType.DECRYPT + && (op.getBlockMode() == KMType.CBC + || op.getBlockMode() == KMType.GCM + || op.getBlockMode() == KMType.CTR)) { + KMException.throwIt(KMError.MISSING_NONCE); + } else if (op.getBlockMode() == KMType.ECB) { + // For ECB we create zero length nonce + data[IV] = KMByteBlob.instance((short) 0); + } else if (op.getPurpose() == KMType.ENCRYPT) { + + // For encrypt mode if nonce is absent then create random nonce of correct length + byte ivLen = 16; + if (op.getBlockMode() == KMType.GCM) { + ivLen = 12; + } else if (op.getAlgorithm() == KMType.DES) { + ivLen = 8; + } + data[IV] = KMByteBlob.instance(ivLen); + seProvider.newRandomNumber( + KMByteBlob.cast(data[IV]).getBuffer(), + KMByteBlob.cast(data[IV]).getStartOff(), + KMByteBlob.cast(data[IV]).length()); + } + } + } + + private void beginKeyAgreementOperation(KMOperationState op) { + if (op.getAlgorithm() != KMType.EC) { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + + op.setOperation( + seProvider.initAsymmetricOperation( + (byte) op.getPurpose(), + (byte) op.getAlgorithm(), + (byte) op.getPadding(), + (byte) op.getDigest(), + KMType.DIGEST_NONE, /* No MGF1 Digest */ + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + null, + (short) 0, + (short) 0)); + } + + private void beginCipherOperation(KMOperationState op) { + switch (op.getAlgorithm()) { + case KMType.RSA: + try { + if (op.getPurpose() == KMType.DECRYPT) { + op.setOperation( + seProvider.initAsymmetricOperation( + (byte) op.getPurpose(), + (byte) op.getAlgorithm(), + (byte) op.getPadding(), + (byte) op.getDigest(), + (byte) op.getMgfDigest(), + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + KMByteBlob.cast(data[PUB_KEY]).getBuffer(), + KMByteBlob.cast(data[PUB_KEY]).getStartOff(), + KMByteBlob.cast(data[PUB_KEY]).length())); + } else { + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + } catch (CryptoException exp) { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + break; + case KMType.AES: + case KMType.DES: + if (op.getBlockMode() == KMType.GCM) { + op.setAesGcmUpdateStart(); + } + try { + op.setOperation( + seProvider.initSymmetricOperation( + (byte) op.getPurpose(), + (byte) op.getAlgorithm(), + (byte) op.getDigest(), + (byte) op.getPadding(), + (byte) op.getBlockMode(), + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + KMByteBlob.cast(data[IV]).getBuffer(), + KMByteBlob.cast(data[IV]).getStartOff(), + KMByteBlob.cast(data[IV]).length(), + op.getMacLength())); + } catch (CryptoException exception) { + if (exception.getReason() == CryptoException.ILLEGAL_VALUE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } else if (exception.getReason() == CryptoException.NO_SUCH_ALGORITHM) { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + } + } + } + + private void beginTrustedConfirmationOperation(KMOperationState op) { + // Check for trusted confirmation - if required then set the signer in op state. + if (KMKeyParameters.findTag( + KMType.BOOL_TAG, KMType.TRUSTED_CONFIRMATION_REQUIRED, data[HW_PARAMETERS]) + != KMType.INVALID_VALUE) { + + op.setTrustedConfirmationSigner( + seProvider.initTrustedConfirmationSymmetricOperation(kmDataStore.getComputedHmacKey())); + + op.getTrustedConfirmationSigner() + .update(confirmationToken, (short) 0, (short) confirmationToken.length); + } + } + + private void beginSignVerifyOperation(KMOperationState op) { + switch (op.getAlgorithm()) { + case KMType.RSA: + try { + if (op.getPurpose() == KMType.SIGN) { + op.setOperation( + seProvider.initAsymmetricOperation( + (byte) op.getPurpose(), + (byte) op.getAlgorithm(), + (byte) op.getPadding(), + (byte) op.getDigest(), + KMType.DIGEST_NONE, /* No MGF Digest */ + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + KMByteBlob.cast(data[PUB_KEY]).getBuffer(), + KMByteBlob.cast(data[PUB_KEY]).getStartOff(), + KMByteBlob.cast(data[PUB_KEY]).length())); + } else { + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + } catch (CryptoException exp) { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + break; + case KMType.EC: + try { + if (op.getPurpose() == KMType.SIGN) { + op.setOperation( + seProvider.initAsymmetricOperation( + (byte) op.getPurpose(), + (byte) op.getAlgorithm(), + (byte) op.getPadding(), + (byte) op.getDigest(), + KMType.DIGEST_NONE, /* No MGF Digest */ + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + null, + (short) 0, + (short) 0)); + } else { + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + } catch (CryptoException exp) { + // Javacard does not support NO digest based signing. + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + break; + case KMType.HMAC: + // As per Keymaster HAL documentation, the length of the Hmac output can + // be decided by using TAG_MAC_LENGTH in Keyparameters. But there is no + // such provision to control the length of the Hmac output using JavaCard + // crypto APIs and the current implementation always returns 32 bytes + // length of Hmac output. So to provide support to TAG_MAC_LENGTH + // feature, we truncate the output signature to TAG_MAC_LENGTH and return + // the truncated signature back to the caller. At the time of verfication + // we again compute the signature of the plain text input, truncate it to + // TAG_MAC_LENGTH and compare it with the input signature for + // verification. So this is the reason we are using KMType.SIGN directly + // instead of using op.getPurpose(). + try { + op.setOperation( + seProvider.initSymmetricOperation( + (byte) KMType.SIGN, + (byte) op.getAlgorithm(), + (byte) op.getDigest(), + (byte) op.getPadding(), + (byte) op.getBlockMode(), + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + null, + (short) 0, + (short) 0, + (short) 0)); + } catch (CryptoException exp) { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + break; + } + } + + private boolean isHwAuthTokenContainsMatchingSecureId(short hwAuthToken, short secureUserIdsObj) { + short secureUserId = KMHardwareAuthToken.cast(hwAuthToken).getUserId(); + if (!KMInteger.cast(secureUserId).isZero()) { + if (KMIntegerArrayTag.cast(secureUserIdsObj).contains(secureUserId)) { + return true; + } + } + + short authenticatorId = KMHardwareAuthToken.cast(hwAuthToken).getAuthenticatorId(); + if (!KMInteger.cast(authenticatorId).isZero()) { + if (KMIntegerArrayTag.cast(secureUserIdsObj).contains(authenticatorId)) { + return true; + } + } + return false; + } + + private boolean authTokenMatches(short userSecureIdsPtr, short authType, byte[] scratchPad) { + if (data[HW_TOKEN] == KMType.INVALID_VALUE) { + return false; + } + if (!isHwAuthTokenContainsMatchingSecureId(data[HW_TOKEN], userSecureIdsPtr)) { + return false; + } + // check auth type + tmpVariables[2] = KMHardwareAuthToken.cast(data[HW_TOKEN]).getHwAuthenticatorType(); + tmpVariables[2] = KMEnum.cast(tmpVariables[2]).getVal(); + if (((byte) tmpVariables[2] & (byte) authType) == 0) { + return false; + } + return true; + } + + private void authorizeUserSecureIdAuthTimeout(KMOperationState op, byte[] scratchPad) { + short authTime; + short authType; + // Authorize User Secure Id and Auth timeout + short userSecureIdPtr = + KMKeyParameters.findTag(KMType.ULONG_ARRAY_TAG, KMType.USER_SECURE_ID, data[HW_PARAMETERS]); + if (userSecureIdPtr != KMType.INVALID_VALUE) { + // Authentication required. + if (KMType.INVALID_VALUE + != KMKeyParameters.findTag( + KMType.BOOL_TAG, KMType.NO_AUTH_REQUIRED, data[HW_PARAMETERS])) { + // Key has both USER_SECURE_ID and NO_AUTH_REQUIRED + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + // authenticator type must be provided. + if (KMType.INVALID_VALUE + == (authType = KMEnumTag.getValue(KMType.USER_AUTH_TYPE, data[HW_PARAMETERS]))) { + // Authentication required, but no auth type found. + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); + } + + short authTimeoutTagPtr = + KMKeyParameters.findTag(KMType.UINT_TAG, KMType.AUTH_TIMEOUT, data[HW_PARAMETERS]); + if (authTimeoutTagPtr != KMType.INVALID_VALUE) { + // authenticate user + if (!authTokenMatches(userSecureIdPtr, authType, scratchPad)) { + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); + } + + authTimeoutTagPtr = + KMKeyParameters.findTag( + KMType.ULONG_TAG, KMType.AUTH_TIMEOUT_MILLIS, data[CUSTOM_TAGS]); + if (authTimeoutTagPtr == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + authTime = KMIntegerTag.cast(authTimeoutTagPtr).getValue(); + // set the one time auth + op.setOneTimeAuthReqd(true); + // set the authentication time stamp in operation state + authTime = + addIntegers( + authTime, KMHardwareAuthToken.cast(data[HW_TOKEN]).getTimestamp(), scratchPad); + op.setAuthTime( + KMInteger.cast(authTime).getBuffer(), KMInteger.cast(authTime).getStartOff()); + // auth time validation will happen in update or finish + op.setAuthTimeoutValidated(false); + } else { + // auth per operation required + // store user secure id and authType in OperationState. + op.setUserSecureId(userSecureIdPtr); + op.setAuthType((byte) authType); + // set flags + op.setOneTimeAuthReqd(false); + op.setAuthPerOperationReqd(true); + } + } + } + + private boolean verifyHwTokenMacInBigEndian(short hwToken, byte[] scratchPad) { + // The challenge, userId and authenticatorId, authenticatorType and timestamp + // are in network order (big-endian). + short len = 0; + // add 0 + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + len = 1; + // concatenate challenge - 8 bytes + short ptr = KMHardwareAuthToken.cast(hwToken).getChallenge(); + KMInteger.cast(ptr) + .value( + scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length()))); + len += KMInteger.UINT_64; + // concatenate user id - 8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getUserId(); + KMInteger.cast(ptr) + .value( + scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length()))); + len += KMInteger.UINT_64; + // concatenate authenticator id - 8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getAuthenticatorId(); + KMInteger.cast(ptr) + .value( + scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length()))); + len += KMInteger.UINT_64; + // concatenate authenticator type - 4 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getHwAuthenticatorType(); + scratchPad[(short) (len + 3)] = KMEnum.cast(ptr).getVal(); + len += KMInteger.UINT_32; + // concatenate timestamp -8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getTimestamp(); + KMInteger.cast(ptr) + .value( + scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length()))); + len += KMInteger.UINT_64; + + ptr = KMHardwareAuthToken.cast(hwToken).getMac(); + + return seProvider.hmacVerify( + kmDataStore.getComputedHmacKey(), + scratchPad, + (short) 0, + len, + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()); + } + + private boolean verifyHwTokenMacInLittleEndian(short hwToken, byte[] scratchPad) { + // The challenge, userId and authenticatorId values are in little endian order, + // but authenticatorType and timestamp are in network order (big-endian). + short len = 0; + // add 0 + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + len = 1; + // concatenate challenge - 8 bytes + short ptr = KMHardwareAuthToken.cast(hwToken).getChallenge(); + KMInteger.cast(ptr).toLittleEndian(scratchPad, len); + len += KMInteger.UINT_64; + // concatenate user id - 8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getUserId(); + KMInteger.cast(ptr).toLittleEndian(scratchPad, len); + len += KMInteger.UINT_64; + // concatenate authenticator id - 8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getAuthenticatorId(); + KMInteger.cast(ptr).toLittleEndian(scratchPad, len); + len += KMInteger.UINT_64; + // concatenate authenticator type - 4 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getHwAuthenticatorType(); + scratchPad[(short) (len + 3)] = KMEnum.cast(ptr).getVal(); + len += KMInteger.UINT_32; + // concatenate timestamp - 8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getTimestamp(); + KMInteger.cast(ptr) + .value(scratchPad, (short) (len + (short) (8 - KMInteger.cast(ptr).length()))); + len += KMInteger.UINT_64; + + ptr = KMHardwareAuthToken.cast(hwToken).getMac(); + + return seProvider.hmacVerify( + kmDataStore.getComputedHmacKey(), + scratchPad, + (short) 0, + len, + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()); + } + + private boolean validateHwToken(short hwToken, byte[] scratchPad) { + // CBOR Encoding is always big endian + short ptr = KMHardwareAuthToken.cast(hwToken).getMac(); + // If mac length is zero then token is empty. + if (KMByteBlob.cast(ptr).length() == 0) { + return false; + } + if (KMConfigurations.TEE_MACHINE_TYPE == KMConfigurations.LITTLE_ENDIAN) { + return verifyHwTokenMacInLittleEndian(hwToken, scratchPad); + } else { + return verifyHwTokenMacInBigEndian(hwToken, scratchPad); + } + } + + private short importKeyCmd(APDU apdu) { + short cmd = KMArray.instance((short) 6); + // Arguments + short params = KMKeyParameters.expAny(); + KMArray.cast(cmd).add((short) 0, params); + KMArray.cast(cmd).add((short) 1, KMEnum.instance(KMType.KEY_FORMAT)); + KMArray.cast(cmd).add((short) 2, KMByteBlob.exp()); + KMArray.cast(cmd).add((short) 3, KMByteBlob.exp()); // attest key + KMArray.cast(cmd).add((short) 4, params); // attest key params + KMArray.cast(cmd).add((short) 5, KMByteBlob.exp()); // issuer + return receiveIncoming(apdu, cmd); + } + + private void processImportKeyCmd(APDU apdu) { + // Receive the incoming request fully from the host into buffer. + short cmd = importKeyCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + data[KEY_PARAMETERS] = KMArray.cast(cmd).get((short) 0); + short keyFmt = KMArray.cast(cmd).get((short) 1); + data[IMPORTED_KEY_BLOB] = KMArray.cast(cmd).get((short) 2); + data[ATTEST_KEY_BLOB] = KMArray.cast(cmd).get((short) 3); + data[ATTEST_KEY_PARAMS] = KMArray.cast(cmd).get((short) 4); + data[ATTEST_KEY_ISSUER] = KMArray.cast(cmd).get((short) 5); + keyFmt = KMEnum.cast(keyFmt).getVal(); + + data[CERTIFICATE] = KMArray.instance((short) 0); // by default the cert is empty. + data[ORIGIN] = KMType.IMPORTED; + importKey(apdu, keyFmt, scratchPad); + } + + private void validateImportKey(short params, short keyFmt) { + short attKeyPurpose = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, params); + // ATTEST_KEY cannot be combined with any other purpose. + if (attKeyPurpose != KMType.INVALID_VALUE + && KMEnumArrayTag.cast(attKeyPurpose).contains(KMType.ATTEST_KEY) + && KMEnumArrayTag.cast(attKeyPurpose).length() > 1) { + KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); + } + // Rollback protection not supported + KMTag.assertAbsence( + params, + KMType.BOOL_TAG, + KMType.ROLLBACK_RESISTANCE, + KMError.ROLLBACK_RESISTANCE_UNAVAILABLE); + // As per specification, Early boot keys may not be imported at all, if Tag::EARLY_BOOT_ONLY is + // provided to IKeyMintDevice::importKey + KMTag.assertAbsence(params, KMType.BOOL_TAG, KMType.EARLY_BOOT_ONLY, KMError.EARLY_BOOT_ENDED); + // Check if the tags are supported. + if (KMKeyParameters.hasUnsupportedTags(params)) { + KMException.throwIt(KMError.UNSUPPORTED_TAG); + } + // Algorithm must be present + KMTag.assertPresence(params, KMType.ENUM_TAG, KMType.ALGORITHM, KMError.INVALID_ARGUMENT); + short alg = KMEnumTag.getValue(KMType.ALGORITHM, params); + // key format must be raw if aes, des or hmac and pkcs8 for rsa and ec. + if ((alg == KMType.AES || alg == KMType.DES || alg == KMType.HMAC) && keyFmt != KMType.RAW) { + KMException.throwIt(KMError.UNIMPLEMENTED); + } + if ((alg == KMType.RSA || alg == KMType.EC) && keyFmt != KMType.PKCS8) { + KMException.throwIt(KMError.UNIMPLEMENTED); + } + } + + private void importKey(APDU apdu, short keyFmt, byte[] scratchPad) { + validateImportKey(data[KEY_PARAMETERS], keyFmt); + // Check algorithm and dispatch to appropriate handler. + short alg = KMEnumTag.getValue(KMType.ALGORITHM, data[KEY_PARAMETERS]); + switch (alg) { + case KMType.RSA: + importRSAKey(scratchPad); + break; + case KMType.AES: + importAESKey(scratchPad); + break; + case KMType.DES: + importTDESKey(scratchPad); + break; + case KMType.HMAC: + importHmacKey(scratchPad); + break; + case KMType.EC: + importECKeys(scratchPad); + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + break; + } + makeKeyCharacteristics(scratchPad); + KMAttestationCert cert = + generateAttestation(data[ATTEST_KEY_BLOB], data[ATTEST_KEY_PARAMS], scratchPad); + createEncryptedKeyBlob(scratchPad); + sendOutgoing(apdu, cert, data[CERTIFICATE], data[KEY_BLOB], data[KEY_CHARACTERISTICS]); + } + + private void importECKeys(byte[] scratchPad) { + // Decode key material + KMAsn1Parser pkcs8 = KMAsn1Parser.instance(); + short keyBlob = pkcs8.decodeEc(data[IMPORTED_KEY_BLOB]); + data[PUB_KEY] = KMArray.cast(keyBlob).get((short) 0); + data[SECRET] = KMArray.cast(keyBlob).get((short) 1); + // initialize 256 bit p256 key for given private key and public key. + short index = 0; + // check whether the key size tag is present in key parameters. + short SecretLen = (short) (KMByteBlob.length(data[SECRET]) * 8); + short keySize = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + if (keySize != KMType.INVALID_VALUE) { + // As per NIST.SP.800-186 page 9, secret for 256 curve should be between + // 256-383 + if (((256 <= SecretLen) && (383 >= SecretLen)) ^ keySize == 256) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + if (keySize != 256) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + } else { + if ((256 > SecretLen) || (383 < SecretLen)) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + // add the key size to scratchPad + keySize = KMInteger.uint_16((short) 256); + keySize = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, keySize); + Util.setShort(scratchPad, index, keySize); + index += 2; + } + // check the curve if present in key parameters. + short curve = KMEnumTag.getValue(KMType.ECCURVE, data[KEY_PARAMETERS]); + if (curve != KMType.INVALID_VALUE) { + // As per NIST.SP.800-186 page 9, secret length for 256 curve should be between + // 256-383 + if (((256 <= SecretLen) && (383 >= SecretLen)) ^ curve == KMType.P_256) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + if (curve != KMType.P_256) { + KMException.throwIt(KMError.UNSUPPORTED_EC_CURVE); + } + } else { + if ((256 > SecretLen) || (383 < SecretLen)) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + // add the curve to scratchPad + curve = KMEnumTag.instance(KMType.ECCURVE, KMType.P_256); + Util.setShort(scratchPad, index, curve); + index += 2; + } + + // Check whether key can be created + seProvider.importAsymmetricKey( + KMType.EC, + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + KMByteBlob.cast(data[PUB_KEY]).getBuffer(), + KMByteBlob.cast(data[PUB_KEY]).getStartOff(), + KMByteBlob.cast(data[PUB_KEY]).length()); + + // add scratch pad to key parameters + updateKeyParameters(scratchPad, index); + data[KEY_BLOB] = createKeyBlobInstance(ASYM_KEY_TYPE); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PUB_KEY, data[PUB_KEY]); + } + + private void importHmacKey(byte[] scratchPad) { + // Get Key + data[SECRET] = data[IMPORTED_KEY_BLOB]; + // create HMAC key of up to 512 bit + short index = 0; // index in scratchPad for update params + // check the keysize tag if present in key parameters. + short keysize = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + if (keysize != KMType.INVALID_VALUE) { + if (!(keysize >= 64 && keysize <= 512 && keysize % 8 == 0)) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + if (keysize != (short) (KMByteBlob.length(data[SECRET]) * 8)) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + } else { + // add the key size to scratchPad + keysize = (short) (KMByteBlob.length(data[SECRET]) * 8); + if (!(keysize >= 64 && keysize <= 512 && keysize % 8 == 0)) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + keysize = KMInteger.uint_16(keysize); + short keySizeTag = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, keysize); + Util.setShort(scratchPad, index, keySizeTag); + index += 2; + } + // Check whether key can be created + seProvider.importSymmetricKey( + KMType.HMAC, + keysize, + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length()); + + // update the key parameters list + updateKeyParameters(scratchPad, index); + // validate HMAC Key parameters + validateHmacKey(); + data[KEY_BLOB] = createKeyBlobInstance(SYM_KEY_TYPE); + } + + private void importTDESKey(byte[] scratchPad) { + // Decode Key Material + data[SECRET] = data[IMPORTED_KEY_BLOB]; + short index = 0; // index in scratchPad for update params + // check the keysize tag if present in key parameters. + short keysize = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + if (keysize != KMType.INVALID_VALUE) { + if (keysize != 168) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + if (192 != (short) (8 * KMByteBlob.length(data[SECRET]))) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + } else { + keysize = (short) (KMByteBlob.length(data[SECRET]) * 8); + if (keysize != 192) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + // add the key size to scratchPad + keysize = KMInteger.uint_16((short) 168); + short keysizeTag = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, keysize); + Util.setShort(scratchPad, index, keysizeTag); + index += 2; + } + // Read Minimum Mac length - it must not be present + KMTag.assertAbsence( + data[KEY_PARAMETERS], KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, KMError.INVALID_TAG); + // Check whether key can be created + seProvider.importSymmetricKey( + KMType.DES, + keysize, + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length()); + // update the key parameters list + updateKeyParameters(scratchPad, index); + data[KEY_BLOB] = createKeyBlobInstance(SYM_KEY_TYPE); + } + + private void validateAesKeySize(short keySizeBits) { + if (keySizeBits != 128 && keySizeBits != 256) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + } + + private void importAESKey(byte[] scratchPad) { + // Get Key + data[SECRET] = data[IMPORTED_KEY_BLOB]; + // create 128 or 256 bit AES key + short index = 0; // index in scratchPad for update params + // check the keysize tag if present in key parameters. + short keysize = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + if (keysize != KMType.INVALID_VALUE) { + if (keysize != (short) (8 * KMByteBlob.length(data[SECRET]))) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + validateAesKeySize(keysize); + } else { + // add the key size to scratchPad + keysize = (short) (8 * KMByteBlob.cast(data[SECRET]).length()); + validateAesKeySize(keysize); + keysize = KMInteger.uint_16(keysize); + short keysizeTag = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, keysize); + Util.setShort(scratchPad, index, keysizeTag); + index += 2; + } + // Check whether key can be created + seProvider.importSymmetricKey( + KMType.AES, + keysize, + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length()); + + // update the key parameters list + updateKeyParameters(scratchPad, index); + // validate AES Key parameters + validateAESKey(); + data[KEY_BLOB] = createKeyBlobInstance(SYM_KEY_TYPE); + } + + private void importRSAKey(byte[] scratchPad) { + // Decode key material + KMAsn1Parser pkcs8 = KMAsn1Parser.instance(); + short keyblob = pkcs8.decodeRsa(data[IMPORTED_KEY_BLOB]); + data[PUB_KEY] = KMArray.cast(keyblob).get((short) 0); + short pubKeyExp = KMArray.cast(keyblob).get((short) 1); + data[SECRET] = KMArray.cast(keyblob).get((short) 2); + if (F4.length != KMByteBlob.cast(pubKeyExp).length()) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + if (Util.arrayCompare( + F4, + (short) 0, + KMByteBlob.cast(pubKeyExp).getBuffer(), + KMByteBlob.cast(pubKeyExp).getStartOff(), + (short) F4.length) + != 0) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + short index = 0; // index in scratchPad for update parameters. + // validate public exponent if present in key params - it must be 0x010001 + short len = + KMIntegerTag.getValue( + scratchPad, + (short) 10, // using offset 10 as first 10 bytes reserved for update params + KMType.ULONG_TAG, + KMType.RSA_PUBLIC_EXPONENT, + data[KEY_PARAMETERS]); + if (len != KMTag.INVALID_VALUE) { + if (len != 4 + || Util.getShort(scratchPad, (short) 10) != 0x01 + || Util.getShort(scratchPad, (short) 12) != 0x01) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + } else { + // add public exponent to scratchPad + Util.setShort(scratchPad, (short) 10, (short) 0x01); + Util.setShort(scratchPad, (short) 12, (short) 0x01); + pubKeyExp = KMInteger.uint_32(scratchPad, (short) 10); + pubKeyExp = KMIntegerTag.instance(KMType.ULONG_TAG, KMType.RSA_PUBLIC_EXPONENT, pubKeyExp); + Util.setShort(scratchPad, index, pubKeyExp); + index += 2; + } + + // check the keysize tag if present in key parameters. + short keysize = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + short kSize = (short) (KMByteBlob.length(data[PUB_KEY]) * 8); + if (keysize != KMType.INVALID_VALUE) { + if (keysize != 2048 || (keysize != kSize)) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + } else { + if (2048 != kSize) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + // add the key size to scratchPad + keysize = KMInteger.uint_16((short) 2048); + keysize = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, keysize); + Util.setShort(scratchPad, index, keysize); + index += 2; + } + + // Check whether key can be created + seProvider.importAsymmetricKey( + KMType.RSA, + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + KMByteBlob.cast(data[PUB_KEY]).getBuffer(), + KMByteBlob.cast(data[PUB_KEY]).getStartOff(), + KMByteBlob.cast(data[PUB_KEY]).length()); + + // update the key parameters list + updateKeyParameters(scratchPad, index); + // validate RSA Key parameters + validateRSAKey(scratchPad); + data[KEY_BLOB] = createKeyBlobInstance(ASYM_KEY_TYPE); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PUB_KEY, data[PUB_KEY]); + } + + private void updateKeyParameters(byte[] newParams, short len) { + if (len == 0) { + return; // nothing to update + } + // Create Update Param array and copy current params + short params = KMKeyParameters.cast(data[KEY_PARAMETERS]).getVals(); + len = (short) (KMArray.cast(params).length() + (short) (len / 2)); + short updatedParams = KMArray.instance(len); // update params + + len = KMArray.cast(params).length(); + short index = 0; + + // copy the existing key parameters to updated array + while (index < len) { + short tag = KMArray.cast(params).get(index); + KMArray.cast(updatedParams).add(index, tag); + index++; + } + + // copy new parameters to updated array + len = KMArray.cast(updatedParams).length(); + short newParamIndex = 0; // index in ptrArr + while (index < len) { + short tag = Util.getShort(newParams, newParamIndex); + KMArray.cast(updatedParams).add(index, tag); + index++; + newParamIndex += 2; + } + // replace with updated key parameters. + data[KEY_PARAMETERS] = KMKeyParameters.instance(updatedParams); + } + + private short initStrongBoxCmd(APDU apdu) { + short cmd = KMArray.instance((short) 3); + KMArray.cast(cmd).add((short) 0, KMInteger.exp()); // OS version + KMArray.cast(cmd).add((short) 1, KMInteger.exp()); // OS patch level + KMArray.cast(cmd).add((short) 2, KMInteger.exp()); // Vendor patch level + return receiveIncoming(apdu, cmd); + } + + // This command is executed to set the boot parameters. + // releaseAllOperations has to be called on every boot, so + // it is called from inside initStrongBoxCmd. Later in future if + // initStrongBoxCmd is removed, then make sure that releaseAllOperations + // is moved to a place where it is called on every boot. + private void processInitStrongBoxCmd(APDU apdu) { + short cmd = initStrongBoxCmd(apdu); + + short osVersion = KMArray.cast(cmd).get((short) 0); + short osPatchLevel = KMArray.cast(cmd).get((short) 1); + short vendorPatchLevel = KMArray.cast(cmd).get((short) 2); + setOsVersion(osVersion); + setOsPatchLevel(osPatchLevel); + setVendorPatchLevel(vendorPatchLevel); + kmDataStore.setDeviceBootStatus(KMKeymintDataStore.SET_SYSTEM_PROPERTIES_SUCCESS); + } + + public void reboot() { + // flag to maintain early boot ended state + kmDataStore.setEarlyBootEndedStatus(false); + // Clear all the operation state. + releaseAllOperations(); + // Hmac is cleared, so generate a new Hmac nonce. + initHmacNonceAndSeed(); + // Clear all auth tags. + kmDataStore.removeAllAuthTags(); + } + + protected void initSystemBootParams( + short osVersion, short osPatchLevel, short vendorPatchLevel, short bootPatchLevel) { + osVersion = KMInteger.uint_16(osVersion); + osPatchLevel = KMInteger.uint_16(osPatchLevel); + vendorPatchLevel = KMInteger.uint_16((short) vendorPatchLevel); + setOsVersion(osVersion); + setOsPatchLevel(osPatchLevel); + setVendorPatchLevel(vendorPatchLevel); + } + + protected void setOsVersion(short version) { + kmDataStore.setOsVersion( + KMInteger.cast(version).getBuffer(), + KMInteger.cast(version).getStartOff(), + KMInteger.cast(version).length()); + } + + protected void setOsPatchLevel(short patch) { + kmDataStore.setOsPatch( + KMInteger.cast(patch).getBuffer(), + KMInteger.cast(patch).getStartOff(), + KMInteger.cast(patch).length()); + } + + protected void setVendorPatchLevel(short patch) { + kmDataStore.setVendorPatchLevel( + KMInteger.cast(patch).getBuffer(), + KMInteger.cast(patch).getStartOff(), + KMInteger.cast(patch).length()); + } + + private short generateKeyCmd(APDU apdu) { + short params = KMKeyParameters.expAny(); + short blob = KMByteBlob.exp(); + // Array of expected arguments + short cmd = KMArray.instance((short) 4); + KMArray.cast(cmd).add((short) 0, params); // key params + KMArray.cast(cmd).add((short) 1, blob); // attest key + KMArray.cast(cmd).add((short) 2, params); // attest key params + KMArray.cast(cmd).add((short) 3, blob); // issuer + return receiveIncoming(apdu, cmd); + } + + private void processGenerateKey(APDU apdu) { + // Receive the incoming request fully from the host into buffer. + short cmd = generateKeyCmd(apdu); + // Re-purpose the apdu buffer as scratch pad. + byte[] scratchPad = apdu.getBuffer(); + data[KEY_PARAMETERS] = KMArray.cast(cmd).get((short) 0); + data[ATTEST_KEY_BLOB] = KMArray.cast(cmd).get((short) 1); + data[ATTEST_KEY_PARAMS] = KMArray.cast(cmd).get((short) 2); + data[ATTEST_KEY_ISSUER] = KMArray.cast(cmd).get((short) 3); + data[CERTIFICATE] = KMType.INVALID_VALUE; // by default the cert is empty. + // ROLLBACK_RESISTANCE not supported. + KMTag.assertAbsence( + data[KEY_PARAMETERS], + KMType.BOOL_TAG, + KMType.ROLLBACK_RESISTANCE, + KMError.ROLLBACK_RESISTANCE_UNAVAILABLE); + + // Algorithm must be present + KMTag.assertPresence( + data[KEY_PARAMETERS], KMType.ENUM_TAG, KMType.ALGORITHM, KMError.INVALID_ARGUMENT); + + // Check if the tags are supported. + if (KMKeyParameters.hasUnsupportedTags(data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.UNSUPPORTED_TAG); + } + short attKeyPurpose = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, data[KEY_PARAMETERS]); + // ATTEST_KEY cannot be combined with any other purpose. + if (attKeyPurpose != KMType.INVALID_VALUE + && KMEnumArrayTag.cast(attKeyPurpose).contains(KMType.ATTEST_KEY) + && KMEnumArrayTag.cast(attKeyPurpose).length() > 1) { + KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); + } + short alg = KMEnumTag.getValue(KMType.ALGORITHM, data[KEY_PARAMETERS]); + // Check algorithm and dispatch to appropriate handler. + switch (alg) { + case KMType.RSA: + generateRSAKey(scratchPad); + break; + case KMType.AES: + generateAESKey(scratchPad); + break; + case KMType.DES: + generateTDESKey(scratchPad); + break; + case KMType.HMAC: + generateHmacKey(scratchPad); + break; + case KMType.EC: + generateECKeys(scratchPad); + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + break; + } + // create key blob and associated attestation. + data[ORIGIN] = KMType.GENERATED; + makeKeyCharacteristics(scratchPad); + // construct the certificate and place the encoded data in data[CERTIFICATE] + KMAttestationCert cert = + generateAttestation(data[ATTEST_KEY_BLOB], data[ATTEST_KEY_PARAMS], scratchPad); + createEncryptedKeyBlob(scratchPad); + sendOutgoing(apdu, cert, data[CERTIFICATE], data[KEY_BLOB], data[KEY_CHARACTERISTICS]); + } + + private short getApplicationId(short params) { + short appId = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_ID, params); + if (appId != KMTag.INVALID_VALUE) { + appId = KMByteTag.cast(appId).getValue(); + if (KMByteBlob.cast(appId).length() == 0) { + // Treat empty as INVALID. + return KMType.INVALID_VALUE; + } + } + return appId; + } + + private short getApplicationData(short params) { + short appData = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_DATA, params); + if (appData != KMTag.INVALID_VALUE) { + appData = KMByteTag.cast(appData).getValue(); + if (KMByteBlob.cast(appData).length() == 0) { + // Treat empty as INVALID. + return KMType.INVALID_VALUE; + } + } + return appData; + } + + private short getAttestationMode(short attKeyBlob, short attChallenge) { + short alg = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, data[KEY_PARAMETERS]); + short mode = KMType.NO_CERT; + if (KMEnumTag.cast(alg).getValue() != KMType.RSA + && KMEnumTag.cast(alg).getValue() != KMType.EC) { + return mode; + } + // If attestation keyblob preset + if (attKeyBlob != KMType.INVALID_VALUE && KMByteBlob.cast(attKeyBlob).length() > 0) { + // No attestation challenge present then it is an error + if (attChallenge == KMType.INVALID_VALUE || KMByteBlob.cast(attChallenge).length() <= 0) { + KMException.throwIt(KMError.ATTESTATION_CHALLENGE_MISSING); + } else { + mode = KMType.ATTESTATION_CERT; + } + } else { // no attestation key blob + // Attestation challenge present then it is an error because no factory provisioned attest key + if (attChallenge != KMType.INVALID_VALUE && KMByteBlob.cast(attChallenge).length() > 0) { + KMException.throwIt(KMError.ATTESTATION_KEYS_NOT_PROVISIONED); + } else if (KMEnumArrayTag.contains(KMType.PURPOSE, KMType.ATTEST_KEY, data[HW_PARAMETERS]) + || KMEnumArrayTag.contains(KMType.PURPOSE, KMType.SIGN, data[HW_PARAMETERS])) { + // The Purpose value can be read from either data[HW_PARAMETERS] or data[KEY_PARAMETERS] + // as the values will be same, and they are cryptographically bound. + mode = KMType.SELF_SIGNED_CERT; + } else { + mode = KMType.FAKE_CERT; + } + } + return mode; + } + + private KMAttestationCert generateAttestation( + short attKeyBlob, short attKeyParam, byte[] scratchPad) { + // 1) If attestation key is present and attestation challenge is absent then it is an error. + // 2) If attestation key is absent and attestation challenge is present then it is an error as + // factory provisioned attestation key is not supported. + // 3) If both are present and issuer is absent or attest key purpose is not ATTEST_KEY then it + // is an error. + // 4) If the generated/imported keys are RSA or EC then validity period must be specified. + // Device Unique Attestation is not supported. + short heapStart = repository.getHeapIndex(); + KMTag.assertAbsence( + data[KEY_PARAMETERS], + KMType.BOOL_TAG, + KMType.DEVICE_UNIQUE_ATTESTATION, + KMError.CANNOT_ATTEST_IDS); + // Read attestation challenge if present + short attChallenge = + KMKeyParameters.findTag( + KMType.BYTES_TAG, KMType.ATTESTATION_CHALLENGE, data[KEY_PARAMETERS]); + if (attChallenge != KMType.INVALID_VALUE) { + attChallenge = KMByteTag.cast(attChallenge).getValue(); + } + // No attestation required for symmetric keys + short mode = getAttestationMode(attKeyBlob, attChallenge); + KMAttestationCert cert = null; + + switch (mode) { + case KMType.ATTESTATION_CERT: + cert = + makeAttestationCert( + attKeyBlob, attKeyParam, attChallenge, data[ATTEST_KEY_ISSUER], scratchPad); + break; + case KMType.SELF_SIGNED_CERT: + cert = makeSelfSignedCert(data[SECRET], data[PUB_KEY], mode, scratchPad); + break; + case KMType.FAKE_CERT: + // Generate certificate with no signature. + cert = makeSelfSignedCert(KMType.INVALID_VALUE, data[PUB_KEY], mode, scratchPad); + break; + default: + data[CERTIFICATE] = KMType.INVALID_VALUE; + return null; + } + // Certificate Data is converted to cbor and written to the end of the stack. + short certData = repository.allocReclaimableMemory(MAX_CERT_SIZE); + // Leave first 4 bytes for Array header and ByteBlob header. + cert.buffer(repository.getHeap(), (short) (certData + 4), (short) (MAX_CERT_SIZE - 4)); + // Build the certificate - this will sign the cert + cert.build(); + // Certificate is now built so the data in the heap starting from heapStart to the current + // heap index can be reused. So resetting the heap index to heapStart. + repository.setHeapIndex(heapStart); + data[CERTIFICATE] = certData; + return cert; + } + + // Encodes KeyCharacteristics at the end of the heap + private void encodeKeyCharacteristics(short keyChars) { + byte[] buffer = repository.getHeap(); + short prevReclaimIndex = repository.getHeapReclaimIndex(); + short ptr = repository.allocReclaimableMemory(MAX_KEY_CHARS_SIZE); + short len = encoder.encode(keyChars, buffer, ptr, prevReclaimIndex, MAX_KEY_CHARS_SIZE); + // shift the encoded KeyCharacteristics data towards the right till the data[CERTIFICATE] + // offset. + Util.arrayCopyNonAtomic(buffer, ptr, buffer, (short) (ptr + (MAX_KEY_CHARS_SIZE - len)), len); + // Reclaim the unused memory. + repository.reclaimMemory((short) (MAX_KEY_CHARS_SIZE - len)); + } + + // Encodes KeyBlob at the end of the heap + private void encodeKeyBlob(short keyBlobPtr) { + // allocate reclaimable memory. + byte[] buffer = repository.getHeap(); + short prevReclaimIndex = repository.getHeapReclaimIndex(); + short top = repository.allocReclaimableMemory(MAX_KEYBLOB_SIZE); + short keyBlob = encoder.encode(keyBlobPtr, buffer, top, prevReclaimIndex, MAX_KEYBLOB_SIZE); + Util.arrayCopyNonAtomic( + repository.getHeap(), + top, + repository.getHeap(), + (short) (top + MAX_KEYBLOB_SIZE - keyBlob), + keyBlob); + short newTop = (short) (top + MAX_KEYBLOB_SIZE - keyBlob); + // Encode the KeyBlob array inside a ByteString. Get the length of + // the ByteString header. + short encodedBytesLength = encoder.getEncodedBytesLength(keyBlob); + newTop -= encodedBytesLength; + encoder.encodeByteBlobHeader(keyBlob, buffer, newTop, encodedBytesLength); + // Reclaim unused memory. + repository.reclaimMemory((short) (newTop - top)); + } + + private short readKeyBlobVersion(short keyBlob) { + short version = KMType.INVALID_VALUE; + try { + version = + decoder.readKeyblobVersion( + KMByteBlob.cast(keyBlob).getBuffer(), + KMByteBlob.cast(keyBlob).getStartOff(), + KMByteBlob.cast(keyBlob).length()); + if (version == KMType.INVALID_VALUE) { + // If Version is not present. Then it is either an old KeyBlob or + // corrupted KeyBlob. + version = 0; + } else { + version = KMInteger.cast(version).getShort(); + if (version > KEYBLOB_CURRENT_VERSION || version < 0) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + } + } catch (Exception e) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + return version; + } + + private void readKeyBlobParams(short version, short parsedKeyBlob) { + data[KEY_BLOB] = parsedKeyBlob; + // initialize data + switch (version) { + case (short) 0: + data[SECRET] = KMArray.cast(parsedKeyBlob).get((short) 0); + data[NONCE] = KMArray.cast(parsedKeyBlob).get((short) 1); + data[AUTH_TAG] = KMArray.cast(parsedKeyBlob).get((short) 2); + data[KEY_CHARACTERISTICS] = KMArray.cast(parsedKeyBlob).get((short) 3); + data[PUB_KEY] = KMType.INVALID_VALUE; + if (KMArray.cast(parsedKeyBlob).length() == ASYM_KEY_BLOB_SIZE_V0) { + data[PUB_KEY] = KMArray.cast(parsedKeyBlob).get((short) 4); + } + // Set the data[KEY_BLOB_VERSION_DATA_OFFSET] with integer value of 0 so + // that it will used at later point of time. + data[KEY_BLOB_VERSION_DATA_OFFSET] = KMInteger.uint_8((byte) 0); + break; + case (short) 1: + data[KEY_BLOB_VERSION_DATA_OFFSET] = KMArray.cast(parsedKeyBlob).get((short) 0); + data[SECRET] = KMArray.cast(parsedKeyBlob).get((short) 1); + data[NONCE] = KMArray.cast(parsedKeyBlob).get((short) 2); + data[AUTH_TAG] = KMArray.cast(parsedKeyBlob).get((short) 3); + data[KEY_CHARACTERISTICS] = KMArray.cast(parsedKeyBlob).get((short) 4); + data[PUB_KEY] = KMType.INVALID_VALUE; + if (KMArray.cast(parsedKeyBlob).length() == ASYM_KEY_BLOB_SIZE_V1) { + data[PUB_KEY] = KMArray.cast(parsedKeyBlob).get((short) 5); + } + break; + case (short) 2: + case (short) 3: + data[SECRET] = KMArray.cast(parsedKeyBlob).get(KEY_BLOB_SECRET); + data[NONCE] = KMArray.cast(parsedKeyBlob).get(KEY_BLOB_NONCE); + data[AUTH_TAG] = KMArray.cast(parsedKeyBlob).get(KEY_BLOB_AUTH_TAG); + data[KEY_CHARACTERISTICS] = KMArray.cast(parsedKeyBlob).get(KEY_BLOB_PARAMS); + data[KEY_BLOB_VERSION_DATA_OFFSET] = + KMArray.cast(parsedKeyBlob).get(KEY_BLOB_VERSION_OFFSET); + data[CUSTOM_TAGS] = KMArray.cast(parsedKeyBlob).get(KEY_BLOB_CUSTOM_TAGS); + data[PUB_KEY] = KMType.INVALID_VALUE; + if (KMArray.cast(parsedKeyBlob).length() == ASYM_KEY_BLOB_SIZE_V2_V3) { + data[PUB_KEY] = KMArray.cast(parsedKeyBlob).get(KEY_BLOB_PUB_KEY); + } + break; + default: + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + } + + // Decode the KeyBlob from CBOR structures to the sub types of KMType. + private void decodeKeyBlob(short version, short keyBlob) { + // Decode KeyBlob and read the KeyBlob params based on the version. + short parsedBlob = + decoder.decodeArray( + createKeyBlobExp(version), + KMByteBlob.cast(keyBlob).getBuffer(), + KMByteBlob.cast(keyBlob).getStartOff(), + KMByteBlob.cast(keyBlob).length()); + short minArraySize = 0; + switch (version) { + case 0: + minArraySize = SYM_KEY_BLOB_SIZE_V0; + break; + case 1: + minArraySize = SYM_KEY_BLOB_SIZE_V1; + break; + case 2: + case 3: + minArraySize = SYM_KEY_BLOB_SIZE_V2_V3; + break; + default: + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + ; + // KeyBlob size should not be less than the minimum KeyBlob size. + if (KMArray.cast(parsedBlob).length() < minArraySize) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + readKeyBlobParams(version, parsedBlob); + } + + // Decrypts the secret key in the KeyBlob. The secret can be a Symmetric or Asymmetric key. + private void processDecryptSecret(short version, short appId, short appData, byte[] scratchPad) { + data[TEE_PARAMETERS] = KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getTeeEnforced(); + data[SB_PARAMETERS] = + KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getStrongboxEnforced(); + data[SW_PARAMETERS] = + KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getKeystoreEnforced(); + data[HW_PARAMETERS] = KMKeyParameters.makeHwEnforced(data[SB_PARAMETERS], data[TEE_PARAMETERS]); + + data[HIDDEN_PARAMETERS] = KMKeyParameters.makeHidden(appId, appData, data[ROT], scratchPad); + // Decrypt Secret and verify auth tag + decryptSecret(scratchPad, version); + short keyBlobSecretOff = 0; + switch (version) { + case 0: + // V0 KeyBlob + // KEY_BLOB = [ + // SECRET, + // NONCE, + // AUTH_TAG, + // KEY_CHARACTERISTICS, + // PUBKEY + // ] + keyBlobSecretOff = (short) 0; + break; + case 1: + // V1 KeyBlob + // KEY_BLOB = [ + // VERSION, + // SECRET, + // NONCE, + // AUTH_TAG, + // KEY_CHARACTERISTICS, + // PUBKEY + // ] + keyBlobSecretOff = (short) 1; + break; + case 2: + case 3: + // V2 KeyBlob + // KEY_BLOB = [ + // VERSION, + // SECRET, + // NONCE, + // AUTH_TAG, + // KEY_CHARACTERISTICS, + // CUSTOM_TAGS, + // PUBKEY + // ] + keyBlobSecretOff = KEY_BLOB_SECRET; + break; + default: + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + ; + KMArray.cast(data[KEY_BLOB]).add(keyBlobSecretOff, data[SECRET]); + } + + private void parseEncryptedKeyBlob( + short keyBlob, short appId, short appData, byte[] scratchPad, short version) { + // make root of trust blob + data[ROT] = readROT(scratchPad, version); + if (data[ROT] == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + try { + decodeKeyBlob(version, keyBlob); + processDecryptSecret(version, appId, appData, scratchPad); + } catch (Exception e) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + } + + private void decryptSecret(byte[] scratchPad, short version) { + // derive master key - stored in derivedKey + short len; + short authDataOff = 0; + short authDataLen = 0; + byte[] authDataBuff = null; + switch (version) { + case 3: + len = deriveKey(scratchPad); + break; + + case 2: + case 1: + case 0: + makeAuthData(version, scratchPad); + len = deriveKeyForOldKeyBlobs(scratchPad); + authDataBuff = repository.getHeap(); + authDataOff = data[AUTH_DATA]; + authDataLen = data[AUTH_DATA_LENGTH]; + break; + default: + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + if (!seProvider.aesGCMDecrypt( + KMByteBlob.cast(data[DERIVED_KEY]).getBuffer(), + KMByteBlob.cast(data[DERIVED_KEY]).getStartOff(), + KMByteBlob.cast(data[DERIVED_KEY]).length(), + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + scratchPad, + (short) 0, + KMByteBlob.cast(data[NONCE]).getBuffer(), + KMByteBlob.cast(data[NONCE]).getStartOff(), + KMByteBlob.cast(data[NONCE]).length(), + authDataBuff, + authDataOff, + authDataLen, + KMByteBlob.cast(data[AUTH_TAG]).getBuffer(), + KMByteBlob.cast(data[AUTH_TAG]).getStartOff(), + KMByteBlob.cast(data[AUTH_TAG]).length())) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + // Copy the decrypted secret + data[SECRET] = + KMByteBlob.instance(scratchPad, (short) 0, KMByteBlob.cast(data[SECRET]).length()); + } + + private short addIntegers(short authTime, short timeStamp, byte[] scratchPad) { + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 24, (byte) 0); + Util.arrayCopyNonAtomic( + KMInteger.cast(authTime).getBuffer(), + KMInteger.cast(authTime).getStartOff(), + scratchPad, + (short) (8 - KMInteger.cast(timeStamp).length()), + KMInteger.cast(timeStamp).length()); + + // Copy timestamp to scratchpad + Util.arrayCopyNonAtomic( + KMInteger.cast(timeStamp).getBuffer(), + KMInteger.cast(timeStamp).getStartOff(), + scratchPad, + (short) (16 - KMInteger.cast(timeStamp).length()), + KMInteger.cast(timeStamp).length()); + + // add authTime in millis to timestamp. + KMUtils.add(scratchPad, (short) 0, (short) 8, (short) 16); + return KMInteger.uint_64(scratchPad, (short) 16); + } + + public void powerReset() { + // TODO handle power reset signal. + releaseAllOperations(); + resetWrappingKey(); + } + + private void updateTrustedConfirmationOperation(KMOperationState op) { + if (op.isTrustedConfirmationRequired()) { + op.getTrustedConfirmationSigner() + .update( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length()); + } + } + + private void finishTrustedConfirmationOperation(KMOperationState op) { + // Perform trusted confirmation if required + if (op.isTrustedConfirmationRequired()) { + if (0 == KMByteBlob.cast(data[CONFIRMATION_TOKEN]).length()) { + KMException.throwIt(KMError.NO_USER_CONFIRMATION); + } + + boolean verified = + op.getTrustedConfirmationSigner() + .verify( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length(), + KMByteBlob.cast(data[CONFIRMATION_TOKEN]).getBuffer(), + KMByteBlob.cast(data[CONFIRMATION_TOKEN]).getStartOff(), + KMByteBlob.cast(data[CONFIRMATION_TOKEN]).length()); + if (!verified) { + KMException.throwIt(KMError.NO_USER_CONFIRMATION); + } + } + } + + private boolean isDigestSupported(short alg, short digest) { + switch (alg) { + case KMType.RSA: + case KMType.EC: + if (digest != KMType.DIGEST_NONE && digest != KMType.SHA2_256) { + return false; + } + break; + case KMType.HMAC: + if (digest != KMType.SHA2_256) { + return false; + } + break; + default: + break; + } + return true; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymintDataStore.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymintDataStore.java new file mode 100644 index 0000000..56d99a0 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMKeymintDataStore.java @@ -0,0 +1,1057 @@ +package com.android.javacard.keymaster; + +import com.android.javacard.seprovider.KMDataStoreConstants; +import com.android.javacard.seprovider.KMException; +import com.android.javacard.seprovider.KMKey; +import com.android.javacard.seprovider.KMSEProvider; +import com.android.javacard.seprovider.KMUpgradable; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; +import org.globalplatform.upgrade.Element; + +/** + * This is a storage class which helps in storing the provisioned data, ROT, OS version, Boot patch + * level, Vendor Patchlevel, HMAC nonce, computed shared secret, 8 auth tags, device-locked, + * device-locked timestamp and device-locked password only. Only the provisioned data is restored + * back during applet upgrades and the remaining data is flushed. + */ +public class KMKeymintDataStore implements KMUpgradable { + + // Data table configuration + public static final short KM_APPLET_PACKAGE_VERSION_1 = 0x0100; + public static final short KM_APPLET_PACKAGE_VERSION_2 = 0x0200; + public static final short KM_APPLET_PACKAGE_VERSION_3 = 0x0300; + public static final byte DATA_INDEX_SIZE = 17; + public static final byte DATA_INDEX_ENTRY_SIZE = 4; + public static final byte DATA_INDEX_ENTRY_LENGTH = 0; + public static final byte DATA_INDEX_ENTRY_OFFSET = 2; + public static final short DATA_MEM_SIZE = 300; + // Data table offsets + public static final byte HMAC_NONCE = 0; + public static final byte BOOT_OS_VERSION = 1; + public static final byte BOOT_OS_PATCH_LEVEL = 2; + public static final byte VENDOR_PATCH_LEVEL = 3; + public static final byte DEVICE_LOCKED_TIME = 4; + public static final byte DEVICE_LOCKED = 5; + public static final byte DEVICE_LOCKED_PASSWORD_ONLY = 6; + // Total 8 auth tags, so the next offset is AUTH_TAG_1 + 8 + public static final byte AUTH_TAG_1 = 7; + public static final byte DEVICE_STATUS_FLAG = 15; + public static final byte EARLY_BOOT_ENDED_FLAG = 16; + // Data Item sizes + public static final byte HMAC_SEED_NONCE_SIZE = 32; + public static final byte COMPUTED_HMAC_KEY_SIZE = 32; + public static final byte OS_VERSION_SIZE = 4; + public static final byte OS_PATCH_SIZE = 4; + public static final byte VENDOR_PATCH_SIZE = 4; + public static final byte DEVICE_LOCK_TS_SIZE = 8; + public static final byte MAX_BLOB_STORAGE = 8; + public static final byte AUTH_TAG_LENGTH = 16; + public static final byte AUTH_TAG_COUNTER_SIZE = 4; + public static final byte AUTH_TAG_ENTRY_SIZE = (AUTH_TAG_LENGTH + AUTH_TAG_COUNTER_SIZE + 1); + // Device boot states. Applet starts executing the + // core commands once all the states are set. The commands + // that are allowed irrespective of these states are: + // All the provision commands + // INS_GET_HW_INFO_CMD + // INS_ADD_RNG_ENTROPY_CMD + // INS_COMPUTE_SHARED_HMAC_CMD + // INS_GET_HMAC_SHARING_PARAM_CMD + public static final byte SET_BOOT_PARAMS_SUCCESS = 0x01; + public static final byte SET_SYSTEM_PROPERTIES_SUCCESS = 0x02; + public static final byte NEGOTIATED_SHARED_SECRET_SUCCESS = 0x04; + // Old Data table offsets + private static final byte OLD_PROVISIONED_STATUS_OFFSET = 18; + private static final byte SHARED_SECRET_KEY_SIZE = 32; + private static final byte DEVICE_STATUS_FLAG_SIZE = 1; + private static final short ADDITIONAL_CERT_CHAIN_MAX_SIZE = 2500; // First 2 bytes for length. + private static final short BCC_MAX_SIZE = 512; + private static KMKeymintDataStore kmDataStore; + // Secure Boot Mode + public byte secureBootMode; + // Data - originally was in repository + private byte[] attIdBrand; + private byte[] attIdDevice; + private byte[] attIdProduct; + private byte[] attIdSerial; + private byte[] attIdImei; + private byte[] attIdMeId; + private byte[] attIdManufacturer; + private byte[] attIdModel; + // Boot parameters + private byte[] verifiedHash; + private byte[] bootKey; + private byte[] bootPatchLevel; + private boolean deviceBootLocked; + private short bootState; + // Challenge for Root of trust + private byte[] challenge; + private short dataIndex; + private byte[] dataTable; + private KMSEProvider seProvider; + private KMRepository repository; + private byte[] additionalCertChain; + private byte[] bcc; + private KMKey masterKey; + private KMKey testDeviceUniqueKeyPair; + private KMKey deviceUniqueKeyPair; + private KMKey preSharedKey; + private KMKey computedHmacKey; + private KMKey rkpMacKey; + private byte[] oemRootPublicKey; + private short provisionStatus; + + public KMKeymintDataStore(KMSEProvider provider, KMRepository repo) { + seProvider = provider; + repository = repo; + boolean isUpgrading = provider.isUpgrading(); + initDataTable(); + // Initialize the device locked status + if (!isUpgrading) { + additionalCertChain = new byte[ADDITIONAL_CERT_CHAIN_MAX_SIZE]; + bcc = new byte[BCC_MAX_SIZE]; + oemRootPublicKey = new byte[65]; + } + setDeviceLockPasswordOnly(false); + setDeviceLock(false); + kmDataStore = this; + } + + public static KMKeymintDataStore instance() { + return kmDataStore; + } + + private void initDataTable() { + if (dataTable == null) { + dataTable = new byte[DATA_MEM_SIZE]; + dataIndex = (short) (DATA_INDEX_SIZE * DATA_INDEX_ENTRY_SIZE); + } + } + + private short dataAlloc(short length) { + if (((short) (dataIndex + length)) > dataTable.length) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + dataIndex += length; + return (short) (dataIndex - length); + } + + private void clearDataEntry(short id) { + id = (short) (id * DATA_INDEX_ENTRY_SIZE); + short dataLen = Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_LENGTH)); + if (dataLen != 0) { + short dataPtr = Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_OFFSET)); + JCSystem.beginTransaction(); + Util.arrayFillNonAtomic(dataTable, dataPtr, dataLen, (byte) 0); + JCSystem.commitTransaction(); + } + } + + private void writeDataEntry(short id, byte[] buf, short offset, short len) { + short dataPtr; + id = (short) (id * DATA_INDEX_ENTRY_SIZE); + short dataLen = Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_LENGTH)); + if (dataLen == 0) { + dataPtr = dataAlloc(len); + JCSystem.beginTransaction(); + Util.setShort(dataTable, (short) (id + DATA_INDEX_ENTRY_OFFSET), dataPtr); + Util.setShort(dataTable, (short) (id + DATA_INDEX_ENTRY_LENGTH), len); + Util.arrayCopyNonAtomic(buf, offset, dataTable, dataPtr, len); + JCSystem.commitTransaction(); + } else { + if (len != dataLen) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + dataPtr = Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_OFFSET)); + JCSystem.beginTransaction(); + Util.arrayCopyNonAtomic(buf, offset, dataTable, dataPtr, len); + JCSystem.commitTransaction(); + } + } + + private short readDataEntry(short id, byte[] buf, short offset) { + id = (short) (id * DATA_INDEX_ENTRY_SIZE); + short len = Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_LENGTH)); + if (len != 0) { + Util.arrayCopyNonAtomic( + dataTable, + Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_OFFSET)), + buf, + offset, + len); + } + return len; + } + + private short readDataEntry(byte[] dataTable, short id, byte[] buf, short offset) { + id = (short) (id * DATA_INDEX_ENTRY_SIZE); + short len = Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_LENGTH)); + if (len != 0) { + Util.arrayCopyNonAtomic( + dataTable, + Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_OFFSET)), + buf, + offset, + len); + } + return len; + } + + private short dataLength(short id) { + id = (short) (id * DATA_INDEX_ENTRY_SIZE); + return Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_LENGTH)); + } + + public short readData(short id) { + short len = dataLength(id); + if (len != 0) { + short blob = KMByteBlob.instance(dataLength(id)); + readDataEntry(id, KMByteBlob.cast(blob).getBuffer(), KMByteBlob.cast(blob).getStartOff()); + return blob; + } + return KMType.INVALID_VALUE; + } + + public short getHmacNonce() { + return readData(HMAC_NONCE); + } + + public short getOsVersion() { + short blob = readData(BOOT_OS_VERSION); + if (blob == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_DATA); + } + return KMInteger.uint_32( + KMByteBlob.cast(blob).getBuffer(), KMByteBlob.cast(blob).getStartOff()); + } + + public short getVendorPatchLevel() { + short blob = readData(VENDOR_PATCH_LEVEL); + if (blob == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_DATA); + } + return KMInteger.uint_32( + KMByteBlob.cast(blob).getBuffer(), KMByteBlob.cast(blob).getStartOff()); + } + + public short getOsPatch() { + short blob = readData(BOOT_OS_PATCH_LEVEL); + if (blob == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_DATA); + } + return KMInteger.uint_32( + KMByteBlob.cast(blob).getBuffer(), KMByteBlob.cast(blob).getStartOff()); + } + + private boolean readBoolean(short id) { + short blob = readData(id); + if (blob == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_DATA); + } + return (byte) ((repository.getHeap())[KMByteBlob.cast(blob).getStartOff()]) == 0x01; + } + + public boolean getDeviceLock() { + return readBoolean(DEVICE_LOCKED); + } + + public void setDeviceLock(boolean flag) { + writeBoolean(DEVICE_LOCKED, flag); + } + + public boolean getDeviceLockPasswordOnly() { + return readBoolean(DEVICE_LOCKED_PASSWORD_ONLY); + } + + public void setDeviceLockPasswordOnly(boolean flag) { + writeBoolean(DEVICE_LOCKED_PASSWORD_ONLY, flag); + } + + public boolean getEarlyBootEndedStatus() { + return readBoolean(EARLY_BOOT_ENDED_FLAG); + } + + public void setEarlyBootEndedStatus(boolean flag) { + writeBoolean(EARLY_BOOT_ENDED_FLAG, flag); + } + + public short getDeviceTimeStamp() { + short blob = readData(DEVICE_LOCKED_TIME); + if (blob == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_DATA); + } + return KMInteger.uint_64( + KMByteBlob.cast(blob).getBuffer(), KMByteBlob.cast(blob).getStartOff()); + } + + public void setOsVersion(byte[] buf, short start, short len) { + if (len != OS_VERSION_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + writeDataEntry(BOOT_OS_VERSION, buf, start, len); + } + + public void setVendorPatchLevel(byte[] buf, short start, short len) { + if (len != VENDOR_PATCH_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + writeDataEntry(VENDOR_PATCH_LEVEL, buf, start, len); + } + + private void writeBoolean(short id, boolean flag) { + short start = repository.alloc((short) 1); + if (flag) { + (repository.getHeap())[start] = (byte) 0x01; + } else { + (repository.getHeap())[start] = (byte) 0x00; + } + writeDataEntry(id, repository.getHeap(), start, (short) 1); + } + + public void setDeviceLockTimestamp(byte[] buf, short start, short len) { + if (len != DEVICE_LOCK_TS_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + writeDataEntry(DEVICE_LOCKED_TIME, buf, start, len); + } + + public void clearDeviceBootStatus() { + clearDataEntry(DEVICE_STATUS_FLAG); + } + + public void setDeviceBootStatus(byte initStatus) { + short offset = repository.allocReclaimableMemory(DEVICE_STATUS_FLAG_SIZE); + byte[] buf = repository.getHeap(); + getDeviceBootStatus(buf, offset); + buf[offset] |= initStatus; + writeDataEntry(DEVICE_STATUS_FLAG, buf, offset, DEVICE_STATUS_FLAG_SIZE); + repository.reclaimMemory(DEVICE_STATUS_FLAG_SIZE); + } + + public boolean isDeviceReady() { + boolean result = false; + short offset = repository.allocReclaimableMemory(DEVICE_STATUS_FLAG_SIZE); + byte[] buf = repository.getHeap(); + getDeviceBootStatus(buf, offset); + byte bootCompleteStatus = + (SET_BOOT_PARAMS_SUCCESS + | SET_SYSTEM_PROPERTIES_SUCCESS + | NEGOTIATED_SHARED_SECRET_SUCCESS); + if (bootCompleteStatus == (buf[offset] & bootCompleteStatus)) { + result = true; + } + repository.reclaimMemory(DEVICE_STATUS_FLAG_SIZE); + return result; + } + + public short getDeviceBootStatus(byte[] scratchpad, short offset) { + scratchpad[offset] = 0; + return readDataEntry(DEVICE_STATUS_FLAG, scratchpad, offset); + } + + public void clearDeviceLockTimeStamp() { + clearDataEntry(DEVICE_LOCKED_TIME); + } + + public void setOsPatch(byte[] buf, short start, short len) { + if (len != OS_PATCH_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + writeDataEntry(BOOT_OS_PATCH_LEVEL, buf, start, len); + } + + private boolean isAuthTagSlotAvailable(short tagId, byte[] buf, short offset) { + readDataEntry(tagId, buf, offset); + return (0 == buf[offset]); + } + + public void initHmacNonce(byte[] nonce, short offset, short len) { + if (len != HMAC_SEED_NONCE_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + writeDataEntry(HMAC_NONCE, nonce, offset, len); + } + + public void clearHmacNonce() { + clearDataEntry(HMAC_NONCE); + } + + public boolean persistAuthTag(short authTag) { + + if (KMByteBlob.cast(authTag).length() != AUTH_TAG_LENGTH) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + + short authTagEntry = repository.alloc(AUTH_TAG_ENTRY_SIZE); + short scratchPadOff = repository.alloc(AUTH_TAG_ENTRY_SIZE); + byte[] scratchPad = repository.getHeap(); + writeAuthTagState(repository.getHeap(), authTagEntry, (byte) 1); + Util.arrayCopyNonAtomic( + KMByteBlob.cast(authTag).getBuffer(), + KMByteBlob.cast(authTag).getStartOff(), + repository.getHeap(), + (short) (authTagEntry + 1), + AUTH_TAG_LENGTH); + Util.setShort( + repository.getHeap(), (short) (authTagEntry + AUTH_TAG_LENGTH + 1 + 2), (short) 1); + short index = 0; + while (index < MAX_BLOB_STORAGE) { + if ((dataLength((short) (index + AUTH_TAG_1)) == 0) + || isAuthTagSlotAvailable((short) (index + AUTH_TAG_1), scratchPad, scratchPadOff)) { + + writeDataEntry( + (short) (index + AUTH_TAG_1), repository.getHeap(), authTagEntry, AUTH_TAG_ENTRY_SIZE); + return true; + } + index++; + } + return false; + } + + public void removeAllAuthTags() { + short index = 0; + while (index < MAX_BLOB_STORAGE) { + clearDataEntry((short) (index + AUTH_TAG_1)); + index++; + } + } + + public boolean isAuthTagPersisted(short authTag) { + return (KMType.INVALID_VALUE != findTag(authTag)); + } + + private short findTag(short authTag) { + if (KMByteBlob.cast(authTag).length() != AUTH_TAG_LENGTH) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + short index = 0; + short found; + short offset = repository.alloc(AUTH_TAG_ENTRY_SIZE); + while (index < MAX_BLOB_STORAGE) { + if (dataLength((short) (index + AUTH_TAG_1)) != 0) { + readDataEntry((short) (index + AUTH_TAG_1), repository.getHeap(), offset); + found = + Util.arrayCompare( + repository.getHeap(), + (short) (offset + 1), + KMByteBlob.cast(authTag).getBuffer(), + KMByteBlob.cast(authTag).getStartOff(), + AUTH_TAG_LENGTH); + if (found == 0) { + return (short) (index + AUTH_TAG_1); + } + } + index++; + } + return KMType.INVALID_VALUE; + } + + public short getRateLimitedKeyCount(short authTag, byte[] out, short outOff) { + short tag = findTag(authTag); + short blob; + if (tag != KMType.INVALID_VALUE) { + blob = readData(tag); + Util.arrayCopyNonAtomic( + KMByteBlob.cast(blob).getBuffer(), + (short) (KMByteBlob.cast(blob).getStartOff() + AUTH_TAG_LENGTH + 1), + out, + outOff, + AUTH_TAG_COUNTER_SIZE); + return AUTH_TAG_COUNTER_SIZE; + } + return (short) 0; + } + + public void setRateLimitedKeyCount(short authTag, byte[] buf, short off, short len) { + short tag = findTag(authTag); + if (tag != KMType.INVALID_VALUE) { + short dataPtr = readData(tag); + Util.arrayCopyNonAtomic( + buf, + off, + KMByteBlob.cast(dataPtr).getBuffer(), + (short) (KMByteBlob.cast(dataPtr).getStartOff() + AUTH_TAG_LENGTH + 1), + len); + writeDataEntry( + tag, + KMByteBlob.cast(dataPtr).getBuffer(), + KMByteBlob.cast(dataPtr).getStartOff(), + KMByteBlob.cast(dataPtr).length()); + } + } + + public void persistAdditionalCertChain(byte[] buf, short offset, short len) { + // Input buffer contains encoded additional certificate chain as shown below. + // AdditionalDKSignatures = { + // + SignerName => DKCertChain + // } + // SignerName = tstr + // DKCertChain = [ + // 2* Certificate // Root -> Leaf. Root is the vendo r + // // self-signed cert, leaf contains DK_pu b + // ] + // Certificate = COSE_Sign1 of a public key + if ((short) (len + 2) > ADDITIONAL_CERT_CHAIN_MAX_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + JCSystem.beginTransaction(); + Util.setShort(additionalCertChain, (short) 0, (short) len); + Util.arrayCopyNonAtomic(buf, offset, additionalCertChain, (short) 2, len); + JCSystem.commitTransaction(); + } + + public short getAdditionalCertChainLength() { + return Util.getShort(additionalCertChain, (short) 0); + } + + public byte[] getAdditionalCertChain() { + return additionalCertChain; + } + + public byte[] getBootCertificateChain() { + return bcc; + } + + public void persistBootCertificateChain(byte[] buf, short offset, short len) { + if ((short) (len + 2) > BCC_MAX_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + JCSystem.beginTransaction(); + Util.setShort(bcc, (short) 0, (short) len); + Util.arrayCopyNonAtomic(buf, offset, bcc, (short) 2, len); + JCSystem.commitTransaction(); + } + + private void writeAuthTagState(byte[] buf, short offset, byte state) { + buf[offset] = state; + } + + // The master key should only be generated during applet installation and + // during a device factory reset event. + public KMKey createMasterKey(short keySizeBits) { + if (masterKey == null) { + masterKey = seProvider.createMasterKey(masterKey, keySizeBits); + } + return (KMKey) masterKey; + } + + public KMKey regenerateMasterKey() { + return seProvider.createMasterKey(masterKey, KMKeymasterApplet.MASTER_KEY_SIZE); + } + + public KMKey getMasterKey() { + return masterKey; + } + + public void createPresharedKey(byte[] keyData, short offset, short length) { + if (length != SHARED_SECRET_KEY_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + if (preSharedKey == null) { + preSharedKey = seProvider.createPreSharedKey(preSharedKey, keyData, offset, length); + } + } + + public KMKey getPresharedKey() { + if (preSharedKey == null) { + KMException.throwIt(KMError.INVALID_DATA); + } + return preSharedKey; + } + + public void createComputedHmacKey(byte[] keyData, short offset, short length) { + if (length != COMPUTED_HMAC_KEY_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + if (computedHmacKey == null) { + computedHmacKey = seProvider.createComputedHmacKey(computedHmacKey, keyData, offset, length); + } else { + seProvider.createComputedHmacKey(computedHmacKey, keyData, offset, length); + } + } + + public KMKey getComputedHmacKey() { + if (computedHmacKey == null) { + KMException.throwIt(KMError.INVALID_DATA); + } + return computedHmacKey; + } + + public KMKey createRkpTestDeviceUniqueKeyPair( + byte[] pubKey, + short pubKeyOff, + short pubKeyLen, + byte[] privKey, + short privKeyOff, + short privKeyLen) { + if (testDeviceUniqueKeyPair == null) { + testDeviceUniqueKeyPair = + seProvider.createRkpDeviceUniqueKeyPair( + testDeviceUniqueKeyPair, + pubKey, + pubKeyOff, + pubKeyLen, + privKey, + privKeyOff, + privKeyLen); + } else { + seProvider.createRkpDeviceUniqueKeyPair( + testDeviceUniqueKeyPair, pubKey, pubKeyOff, pubKeyLen, privKey, privKeyOff, privKeyLen); + } + return testDeviceUniqueKeyPair; + } + + public KMKey createRkpDeviceUniqueKeyPair( + byte[] pubKey, + short pubKeyOff, + short pubKeyLen, + byte[] privKey, + short privKeyOff, + short privKeyLen) { + if (deviceUniqueKeyPair == null) { + deviceUniqueKeyPair = + seProvider.createRkpDeviceUniqueKeyPair( + deviceUniqueKeyPair, pubKey, pubKeyOff, pubKeyLen, privKey, privKeyOff, privKeyLen); + } else { + seProvider.createRkpDeviceUniqueKeyPair( + deviceUniqueKeyPair, pubKey, pubKeyOff, pubKeyLen, privKey, privKeyOff, privKeyLen); + } + return deviceUniqueKeyPair; + } + + public KMKey getRkpDeviceUniqueKeyPair(boolean testMode) { + return ((KMKey) (testMode ? testDeviceUniqueKeyPair : deviceUniqueKeyPair)); + } + + public void createRkpMacKey(byte[] keydata, short offset, short length) { + if (rkpMacKey == null) { + rkpMacKey = seProvider.createRkpMacKey(rkpMacKey, keydata, offset, length); + } else { + seProvider.createRkpMacKey(rkpMacKey, keydata, offset, length); + } + } + + public KMKey getRkpMacKey() { + if (rkpMacKey == null) { + KMException.throwIt(KMError.INVALID_DATA); + } + return rkpMacKey; + } + + public short getAttestationId(short tag, byte[] buffer, short start) { + byte[] attestId = null; + switch (tag) { + // Attestation Id Brand + case KMType.ATTESTATION_ID_BRAND: + attestId = attIdBrand; + break; + // Attestation Id Device + case KMType.ATTESTATION_ID_DEVICE: + attestId = attIdDevice; + break; + // Attestation Id Product + case KMType.ATTESTATION_ID_PRODUCT: + attestId = attIdProduct; + break; + // Attestation Id Serial + case KMType.ATTESTATION_ID_SERIAL: + attestId = attIdSerial; + break; + // Attestation Id IMEI + case KMType.ATTESTATION_ID_IMEI: + attestId = attIdImei; + break; + // Attestation Id MEID + case KMType.ATTESTATION_ID_MEID: + attestId = attIdMeId; + break; + // Attestation Id Manufacturer + case KMType.ATTESTATION_ID_MANUFACTURER: + attestId = attIdManufacturer; + break; + // Attestation Id Model + case KMType.ATTESTATION_ID_MODEL: + attestId = attIdModel; + break; + } + if (attestId == null) { + KMException.throwIt(KMError.CANNOT_ATTEST_IDS); + } + Util.arrayCopyNonAtomic(attestId, (short) 0, buffer, start, (short) attestId.length); + return (short) attestId.length; + } + + public void setAttestationId(short tag, byte[] buffer, short start, short length) { + switch (tag) { + // Attestation Id Brand + case KMType.ATTESTATION_ID_BRAND: + JCSystem.beginTransaction(); + attIdBrand = new byte[length]; + Util.arrayCopyNonAtomic(buffer, (short) start, attIdBrand, (short) 0, length); + JCSystem.commitTransaction(); + break; + // Attestation Id Device + case KMType.ATTESTATION_ID_DEVICE: + JCSystem.beginTransaction(); + attIdDevice = new byte[length]; + Util.arrayCopyNonAtomic(buffer, (short) start, attIdDevice, (short) 0, length); + JCSystem.commitTransaction(); + break; + // Attestation Id Product + case KMType.ATTESTATION_ID_PRODUCT: + JCSystem.beginTransaction(); + attIdProduct = new byte[length]; + Util.arrayCopyNonAtomic(buffer, (short) start, attIdProduct, (short) 0, length); + JCSystem.commitTransaction(); + break; + // Attestation Id Serial + case KMType.ATTESTATION_ID_SERIAL: + JCSystem.beginTransaction(); + attIdSerial = new byte[length]; + Util.arrayCopyNonAtomic(buffer, (short) start, attIdSerial, (short) 0, length); + JCSystem.commitTransaction(); + break; + // Attestation Id IMEI + case KMType.ATTESTATION_ID_IMEI: + JCSystem.beginTransaction(); + attIdImei = new byte[length]; + Util.arrayCopyNonAtomic(buffer, (short) start, attIdImei, (short) 0, length); + JCSystem.commitTransaction(); + break; + // Attestation Id MEID + case KMType.ATTESTATION_ID_MEID: + JCSystem.beginTransaction(); + attIdMeId = new byte[length]; + Util.arrayCopyNonAtomic(buffer, (short) start, attIdMeId, (short) 0, length); + JCSystem.commitTransaction(); + break; + // Attestation Id Manufacturer + case KMType.ATTESTATION_ID_MANUFACTURER: + JCSystem.beginTransaction(); + attIdManufacturer = new byte[length]; + Util.arrayCopyNonAtomic(buffer, (short) start, attIdManufacturer, (short) 0, length); + JCSystem.commitTransaction(); + break; + // Attestation Id Model + case KMType.ATTESTATION_ID_MODEL: + JCSystem.beginTransaction(); + attIdModel = new byte[length]; + Util.arrayCopyNonAtomic(buffer, (short) start, attIdModel, (short) 0, length); + JCSystem.commitTransaction(); + break; + } + } + + public void deleteAttestationIds() { + attIdBrand = null; + attIdDevice = null; + attIdProduct = null; + attIdSerial = null; + attIdImei = null; + attIdMeId = null; + attIdManufacturer = null; + attIdModel = null; + // Trigger garbage collection. + JCSystem.requestObjectDeletion(); + } + + public short getVerifiedBootHash(byte[] buffer, short start) { + if (verifiedHash == null) { + KMException.throwIt(KMError.INVALID_DATA); + } + Util.arrayCopyNonAtomic(verifiedHash, (short) 0, buffer, start, (short) verifiedHash.length); + return (short) verifiedHash.length; + } + + public short getBootKey(byte[] buffer, short start) { + if (bootKey == null) { + KMException.throwIt(KMError.INVALID_DATA); + } + Util.arrayCopyNonAtomic(bootKey, (short) 0, buffer, start, (short) bootKey.length); + return (short) bootKey.length; + } + + public short getBootState() { + return bootState; + } + + public void setBootState(short state) { + bootState = state; + } + + public boolean isDeviceBootLocked() { + return deviceBootLocked; + } + + public short getBootPatchLevel() { + if (bootPatchLevel == null) { + KMException.throwIt(KMError.INVALID_DATA); + } + return KMInteger.uint_32(bootPatchLevel, (short) 0); + } + + public void setVerifiedBootHash(byte[] buffer, short start, short length) { + if (verifiedHash == null) { + verifiedHash = new byte[32]; + } + if (length != 32) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + Util.arrayCopy(buffer, start, verifiedHash, (short) 0, (short) 32); + } + + public void setBootKey(byte[] buffer, short start, short length) { + if (bootKey == null) { + bootKey = new byte[32]; + } + if (length != 32) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + Util.arrayCopy(buffer, start, bootKey, (short) 0, (short) 32); + } + + public void setDeviceLocked(boolean state) { + deviceBootLocked = state; + } + + public void setBootPatchLevel(byte[] buffer, short start, short length) { + if (bootPatchLevel == null) { + bootPatchLevel = new byte[4]; + } + if (length > 4 || length < 0) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + Util.arrayCopy(buffer, start, bootPatchLevel, (short) 0, (short) length); + } + + public void setChallenge(byte[] buf, short start, short length) { + if (challenge == null) { + challenge = new byte[16]; + } + if (length != 16) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + Util.arrayCopy(buf, start, challenge, (short) 0, (short) length); + } + + public short getChallenge(byte[] buffer, short start) { + if (challenge == null) { + KMException.throwIt(KMError.INVALID_DATA); + } + Util.arrayCopyNonAtomic(challenge, (short) 0, buffer, start, (short) challenge.length); + return (short) challenge.length; + } + + public boolean isProvisionLocked() { + if (0 != (provisionStatus & KMKeymasterApplet.PROVISION_STATUS_PROVISIONING_LOCKED)) { + return true; + } + return false; + } + + public short getProvisionStatus() { + return provisionStatus; + } + + public void setProvisionStatus(short pStatus) { + JCSystem.beginTransaction(); + provisionStatus |= pStatus; + JCSystem.commitTransaction(); + } + + public void unlockProvision() { + JCSystem.beginTransaction(); + provisionStatus &= ~KMKeymasterApplet.PROVISION_STATUS_PROVISIONING_LOCKED; + JCSystem.commitTransaction(); + } + + public void persistOEMRootPublicKey(byte[] inBuff, short inOffset, short inLength) { + if (inLength != 65) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + if (oemRootPublicKey == null) { + oemRootPublicKey = new byte[65]; + } + Util.arrayCopy(inBuff, inOffset, oemRootPublicKey, (short) 0, inLength); + } + + public byte[] getOEMRootPublicKey() { + if (oemRootPublicKey == null) { + KMException.throwIt(KMError.INVALID_DATA); + } + return oemRootPublicKey; + } + + @Override + public void onSave(Element element) { + // Prmitives + element.write(provisionStatus); + element.write(secureBootMode); + // Objects + element.write(attIdBrand); + element.write(attIdDevice); + element.write(attIdProduct); + element.write(attIdSerial); + element.write(attIdImei); + element.write(attIdMeId); + element.write(attIdManufacturer); + element.write(attIdModel); + element.write(additionalCertChain); + element.write(bcc); + element.write(oemRootPublicKey); + + // Key Objects + seProvider.onSave(element, KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY, masterKey); + seProvider.onSave(element, KMDataStoreConstants.INTERFACE_TYPE_PRE_SHARED_KEY, preSharedKey); + seProvider.onSave( + element, KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR, deviceUniqueKeyPair); + seProvider.onSave(element, KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY, rkpMacKey); + } + + @Override + public void onRestore(Element element, short oldVersion, short currentVersion) { + if (oldVersion <= KM_APPLET_PACKAGE_VERSION_1) { + // 1.0 to 3.1 Upgrade happens here. + handlePreviousVersionUpgrade(element); + return; + } else if (oldVersion == KM_APPLET_PACKAGE_VERSION_2) { + handleUpgrade(element, oldVersion); + JCSystem.beginTransaction(); + // While upgrading Secure Boot Mode flag from 2.0 to 3.0, implementations + // have to update the secureBootMode with the correct input. + secureBootMode = 0; + provisionStatus |= KMKeymasterApplet.PROVISION_STATUS_SECURE_BOOT_MODE; + // Applet package Versions till 2 had CoseSign1 for additionalCertificateChain. + // From package version 3, the additionalCertificateChain is in X.509 format. + // So Unreference the old address and allocate new persistent memory. + additionalCertChain = new byte[ADDITIONAL_CERT_CHAIN_MAX_SIZE]; + JCSystem.commitTransaction(); + // Request for ObjectDeletion for unreferenced address of additionalCertChain. + JCSystem.requestObjectDeletion(); + return; + } + handleUpgrade(element, oldVersion); + } + + private void handlePreviousVersionUpgrade(Element element) { + // Read Primitives + // restore old data table index + short oldDataIndex = element.readShort(); + element.readBoolean(); // pop deviceBootLocked + element.readShort(); // pop bootState + + // Read Objects + // restore old data table + byte[] oldDataTable = (byte[]) element.readObject(); + + attIdBrand = (byte[]) element.readObject(); + attIdDevice = (byte[]) element.readObject(); + attIdProduct = (byte[]) element.readObject(); + attIdSerial = (byte[]) element.readObject(); + attIdImei = (byte[]) element.readObject(); + attIdMeId = (byte[]) element.readObject(); + attIdManufacturer = (byte[]) element.readObject(); + attIdModel = (byte[]) element.readObject(); + element.readObject(); // pop verifiedHash + element.readObject(); // pop bootKey + element.readObject(); // pop bootPatchLevel + additionalCertChain = (byte[]) element.readObject(); + bcc = (byte[]) element.readObject(); + + // Read Key Objects + masterKey = (KMKey) seProvider.onRestore(element); + seProvider.onRestore(element); // pop computedHmacKey + preSharedKey = (KMKey) seProvider.onRestore(element); + deviceUniqueKeyPair = (KMKey) seProvider.onRestore(element); + rkpMacKey = (KMKey) seProvider.onRestore(element); + handleProvisionStatusUpgrade(oldDataTable, oldDataIndex); + } + + private void handleUpgrade(Element element, short oldVersion) { + // Read Primitives + provisionStatus = element.readShort(); + if (oldVersion >= KM_APPLET_PACKAGE_VERSION_3) { + secureBootMode = element.readByte(); + } + // Read Objects + attIdBrand = (byte[]) element.readObject(); + attIdDevice = (byte[]) element.readObject(); + attIdProduct = (byte[]) element.readObject(); + attIdSerial = (byte[]) element.readObject(); + attIdImei = (byte[]) element.readObject(); + attIdMeId = (byte[]) element.readObject(); + attIdManufacturer = (byte[]) element.readObject(); + attIdModel = (byte[]) element.readObject(); + additionalCertChain = (byte[]) element.readObject(); + bcc = (byte[]) element.readObject(); + oemRootPublicKey = (byte[]) element.readObject(); + // Read Key Objects + masterKey = (KMKey) seProvider.onRestore(element); + preSharedKey = (KMKey) seProvider.onRestore(element); + deviceUniqueKeyPair = (KMKey) seProvider.onRestore(element); + rkpMacKey = (KMKey) seProvider.onRestore(element); + } + + public void getProvisionStatus(byte[] dataTable, byte[] scratchpad, short offset) { + Util.setShort(scratchpad, offset, (short) 0); + readDataEntry(dataTable, OLD_PROVISIONED_STATUS_OFFSET, scratchpad, offset); + } + + void handleProvisionStatusUpgrade(byte[] dataTable, short dataTableIndex) { + short dInex = repository.allocReclaimableMemory((short) 2); + byte data[] = repository.getHeap(); + getProvisionStatus(dataTable, data, dInex); + short pStatus = (short) (data[dInex] & 0x00ff); + if (KMKeymasterApplet.PROVISION_STATUS_PROVISIONING_LOCKED + == (pStatus & KMKeymasterApplet.PROVISION_STATUS_PROVISIONING_LOCKED)) { + pStatus |= + KMKeymasterApplet.PROVISION_STATUS_SE_LOCKED + | KMKeymasterApplet.PROVISION_STATUS_SECURE_BOOT_MODE; + } + JCSystem.beginTransaction(); + // While upgrading Secure Boot Mode flag from 1.0 to 3.0, implementations + // have to update the secureBootMode with the correct input. + secureBootMode = 0; + provisionStatus = pStatus; + // Applet package Versions till 2 had CoseSign1 for additionalCertificateChain. + // From package version 3, the additionalCertificateChain is in X.509 format. + // So Unreference the old address and allocate new persistent memory. + additionalCertChain = new byte[ADDITIONAL_CERT_CHAIN_MAX_SIZE]; + JCSystem.commitTransaction(); + repository.reclaimMemory((short) 2); + // Request object deletion for unreferenced address for additionalCertChain + JCSystem.requestObjectDeletion(); + } + + @Override + public short getBackupPrimitiveByteCount() { + // provisionStatus - 2 bytes + // secureBootMode - 1 byte + return (short) + (3 + + seProvider.getBackupPrimitiveByteCount(KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY) + + seProvider.getBackupPrimitiveByteCount( + KMDataStoreConstants.INTERFACE_TYPE_PRE_SHARED_KEY) + + seProvider.getBackupPrimitiveByteCount( + KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR) + + seProvider.getBackupPrimitiveByteCount( + KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY)); + } + + @Override + public short getBackupObjectCount() { + // AttestationIds - 8 + // AdditionalCertificateChain - 1 + // BCC - 1 + // oemRootPublicKey - 1 + return (short) + (11 + + seProvider.getBackupObjectCount(KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY) + + seProvider.getBackupObjectCount(KMDataStoreConstants.INTERFACE_TYPE_PRE_SHARED_KEY) + + seProvider.getBackupObjectCount( + KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR) + + seProvider.getBackupObjectCount(KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY)); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMMap.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMMap.java new file mode 100644 index 0000000..2418204 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMMap.java @@ -0,0 +1,209 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMMap represents an array of a KMType key and a KMType value. Map is the sequence of pairs. Each + * pair is one or more sub-types of KMType. The KMMap instance maps to the CBOR type map. KMMap is a + * KMType and it further extends the value field in TLV_HEADER as MAP_HEADER struct{ short + * subType;short length;} followed by a sequence of pairs. Each pair contains a key and a value as + * short pointers to KMType instances. + */ +public class KMMap extends KMType { + + public static final short ANY_MAP_LENGTH = 0x1000; + private static final byte MAP_HEADER_SIZE = 4; + private static KMMap prototype; + + private KMMap() {} + + private static KMMap proto(short ptr) { + if (prototype == null) { + prototype = new KMMap(); + } + instanceTable[KM_MAP_OFFSET] = ptr; + return prototype; + } + + public static short exp() { + short ptr = instance(MAP_TYPE, (short) MAP_HEADER_SIZE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), (short) 0); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), ANY_MAP_LENGTH); + return ptr; + } + + public static short instance(short length) { + short ptr = KMType.instance(MAP_TYPE, (short) (MAP_HEADER_SIZE + (length * 4))); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), (short) 0); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), length); + return ptr; + } + + public static short instance(short length, byte type) { + short ptr = instance(length); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), type); + return ptr; + } + + public static KMMap cast(short ptr) { + if (heap[ptr] != MAP_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public void add(short index, short keyPtr, short valPtr) { + short len = length(); + if (index >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + short keyIndex = + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index * 4)); + Util.setShort(heap, keyIndex, keyPtr); + Util.setShort(heap, (short) (keyIndex + 2), valPtr); + } + + public short getKey(short index) { + short len = length(); + if (index >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + return Util.getShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index * 4))); + } + + public short getKeyValue(short index) { + short len = length(); + if (index >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + return Util.getShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index * 4 + 2))); + } + + public void swap(short index1, short index2) { + short len = length(); + if (index1 >= len || index2 >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + // Swap keys + short indexPtr1 = + Util.getShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index1 * 4))); + short indexPtr2 = + Util.getShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index2 * 4))); + Util.setShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index1 * 4)), + indexPtr2); + Util.setShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index2 * 4)), + indexPtr1); + + // Swap Values + indexPtr1 = + Util.getShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index1 * 4 + 2))); + indexPtr2 = + Util.getShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index2 * 4 + 2))); + Util.setShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index1 * 4 + 2)), + indexPtr2); + Util.setShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index2 * 4 + 2)), + indexPtr1); + } + + public void canonicalize() { + KMCoseMap.canonicalize(instanceTable[KM_MAP_OFFSET], length()); + } + + public short containedType() { + return Util.getShort(heap, (short) (instanceTable[KM_MAP_OFFSET] + TLV_HEADER_SIZE)); + } + + public short getStartOff() { + return (short) (instanceTable[KM_MAP_OFFSET] + TLV_HEADER_SIZE + MAP_HEADER_SIZE); + } + + public short length() { + return Util.getShort(heap, (short) (instanceTable[KM_MAP_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public byte[] getBuffer() { + return heap; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMNInteger.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMNInteger.java new file mode 100644 index 0000000..3a6404e --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMNInteger.java @@ -0,0 +1,134 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * Represents 8-bit, 16-bit, 32-bit and 64-bit signed integer. It corresponds to CBOR int type. + * struct{byte NEG_INTEGER_TYPE; short length; 4 or 8 bytes of value} + */ +public class KMNInteger extends KMInteger { + + public static final byte SIGNED_MASK = (byte) 0x80; + private static KMNInteger prototype; + + private KMNInteger() {} + + private static KMNInteger proto(short ptr) { + if (prototype == null) { + prototype = new KMNInteger(); + } + instanceTable[KM_NEG_INTEGER_OFFSET] = ptr; + return prototype; + } + + public static short exp() { + return KMType.exp(NEG_INTEGER_TYPE); + } + + // return an empty integer instance + public static short instance(short length) { + if ((length <= 0) || (length > 8)) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + if (length > 4) { + length = KMInteger.UINT_64; + } else { + length = KMInteger.UINT_32; + } + return KMType.instance(NEG_INTEGER_TYPE, length); + } + + public static short instance(byte[] num, short srcOff, short length) { + if (length > 8) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + if (length == 1) { + return uint_8(num[srcOff]); + } else if (length == 2) { + return uint_16(Util.getShort(num, srcOff)); + } else if (length == 4) { + return uint_32(num, srcOff); + } else { + return uint_64(num, srcOff); + } + } + + public static KMNInteger cast(short ptr) { + byte[] heap = repository.getHeap(); + if (heap[ptr] != NEG_INTEGER_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + 1)) == INVALID_VALUE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + // create integer and copy byte value + public static short uint_8(byte num) { + if (num >= 0) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(KMInteger.UINT_32); + heap[(short) (ptr + TLV_HEADER_SIZE + 3)] = num; + return ptr; + } + + // create integer and copy short value + public static short uint_16(short num) { + if (num >= 0) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(KMInteger.UINT_32); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), num); + return ptr; + } + + // create integer and copy integer value + public static short uint_32(byte[] num, short offset) { + if (!isSignedInteger(num, offset)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(KMInteger.UINT_32); + Util.arrayCopy(num, offset, heap, (short) (ptr + TLV_HEADER_SIZE), KMInteger.UINT_32); + return ptr; + } + + // create integer and copy integer value + public static short uint_64(byte[] num, short offset) { + if (!isSignedInteger(num, offset)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(KMInteger.UINT_64); + Util.arrayCopy(num, offset, heap, (short) (ptr + TLV_HEADER_SIZE), KMInteger.UINT_64); + return ptr; + } + + public static boolean isSignedInteger(byte[] num, short offset) { + byte val = num[offset]; + return SIGNED_MASK == (val & SIGNED_MASK); + } + + @Override + protected short getBaseOffset() { + return instanceTable[KM_NEG_INTEGER_OFFSET]; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMOperationState.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMOperationState.java new file mode 100644 index 0000000..2a53acd --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMOperationState.java @@ -0,0 +1,354 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import com.android.javacard.seprovider.KMException; +import com.android.javacard.seprovider.KMOperation; +import javacard.framework.JCSystem; +import javacard.framework.Util; + +/** + * KMOperationState is the container of an active operation started by beginOperation function. This + * operation state is persisted by the applet in non volatile memory. However, this state is not + * retained if applet is upgraded. There will be four operation state records maintained i.e. only + * four active operations are supported at any given time. + */ +public class KMOperationState { + + // sizes + public static final byte OPERATION_HANDLE_SIZE = 8; + public static final byte DATA_SIZE = 11; + public static final byte AUTH_TIME_SIZE = 8; + // Secure user ids 5 * 8 = 40 bytes ( Considering Maximum 5 SECURE USER IDs) + // First two bytes are reserved to store number of secure ids. So total 42 bytes. + public static final byte USER_SECURE_IDS_SIZE = 42; + // byte type + private static final byte ALG = 0; + private static final byte PURPOSE = 1; + private static final byte PADDING = 2; + private static final byte BLOCK_MODE = 3; + private static final byte DIGEST = 4; + private static final byte FLAGS = 5; + private static final byte KEY_SIZE = 6; + private static final byte MAC_LENGTH = 7; + private static final byte MGF_DIGEST = 8; + private static final byte AUTH_TYPE = 9; + private static final byte MIN_MAC_LENGTH = 10; + private static final byte OPERATION = 0; + private static final byte HMAC_SIGNER_OPERATION = 1; + // Flag masks + private static final byte AUTH_PER_OP_REQD = 1; + private static final byte SECURE_USER_ID_REQD = 2; + private static final byte AUTH_TIMEOUT_VALIDATED = 4; + private static final byte AES_GCM_UPDATE_ALLOWED = 8; + private static final byte PROCESSED_INPUT_MSG = 16; + // Max user secure ids. + private static final byte MAX_SECURE_USER_IDS = 5; + + // Object References + private byte[] opHandle; + private byte[] authTime; + private byte[] userSecureIds; + private short[] data; + private Object[] operations; + + public KMOperationState() { + opHandle = JCSystem.makeTransientByteArray(OPERATION_HANDLE_SIZE, JCSystem.CLEAR_ON_RESET); + authTime = JCSystem.makeTransientByteArray(AUTH_TIME_SIZE, JCSystem.CLEAR_ON_RESET); + data = JCSystem.makeTransientShortArray(DATA_SIZE, JCSystem.CLEAR_ON_RESET); + operations = JCSystem.makeTransientObjectArray((short) 2, JCSystem.CLEAR_ON_RESET); + userSecureIds = JCSystem.makeTransientByteArray(USER_SECURE_IDS_SIZE, JCSystem.CLEAR_ON_RESET); + reset(); + } + + public void reset() { + byte index = 0; + while (index < DATA_SIZE) { + data[index] = KMType.INVALID_VALUE; + index++; + } + Util.arrayFillNonAtomic(opHandle, (short) 0, OPERATION_HANDLE_SIZE, (byte) 0); + Util.arrayFillNonAtomic(authTime, (short) 0, AUTH_TIME_SIZE, (byte) 0); + + if (null != operations[OPERATION]) { + ((KMOperation) operations[OPERATION]).abort(); + } + operations[OPERATION] = null; + + if (null != operations[HMAC_SIGNER_OPERATION]) { + ((KMOperation) operations[HMAC_SIGNER_OPERATION]).abort(); + } + operations[HMAC_SIGNER_OPERATION] = null; + } + + public short compare(byte[] handle, short start, short len) { + return Util.arrayCompare(handle, start, opHandle, (short) 0, (short) opHandle.length); + } + + public short getKeySize() { + return data[KEY_SIZE]; + } + + public void setKeySize(short keySize) { + data[KEY_SIZE] = keySize; + } + + public short getHandle() { + return KMInteger.uint_64(opHandle, (short) 0); + } + + public void setHandle(byte[] buf, short start, short len) { + Util.arrayCopyNonAtomic(buf, start, opHandle, (short) 0, (short) opHandle.length); + } + + public short getPurpose() { + return data[PURPOSE]; + } + + public void setPurpose(short purpose) { + data[PURPOSE] = purpose; + } + + public boolean isInputMsgProcessed() { + return (data[FLAGS] & PROCESSED_INPUT_MSG) != 0; + } + + public KMOperation getOperation() { + return (KMOperation) operations[OPERATION]; + } + + public void setOperation(KMOperation op) { + operations[OPERATION] = op; + } + + public boolean isAuthPerOperationReqd() { + return (data[FLAGS] & AUTH_PER_OP_REQD) != 0; + } + + public void setAuthPerOperationReqd(boolean flag) { + if (flag) { + data[FLAGS] = (short) (data[FLAGS] | AUTH_PER_OP_REQD); + } else { + data[FLAGS] = (short) (data[FLAGS] & (~AUTH_PER_OP_REQD)); + } + } + + public boolean isAuthTimeoutValidated() { + return (data[FLAGS] & AUTH_TIMEOUT_VALIDATED) != 0; + } + + public void setAuthTimeoutValidated(boolean flag) { + if (flag) { + data[FLAGS] = (byte) (data[FLAGS] | AUTH_TIMEOUT_VALIDATED); + } else { + data[FLAGS] = (byte) (data[FLAGS] & (~AUTH_TIMEOUT_VALIDATED)); + } + } + + public boolean isSecureUserIdReqd() { + return (data[FLAGS] & SECURE_USER_ID_REQD) != 0; + } + + public short getAuthTime() { + return KMInteger.uint_64(authTime, (short) 0); + } + + public void setAuthTime(byte[] timeBuf, short start) { + Util.arrayCopyNonAtomic(timeBuf, start, authTime, (short) 0, AUTH_TIME_SIZE); + } + + public void setProcessedInputMsg(boolean flag) { + if (flag) { + data[FLAGS] = (byte) (data[FLAGS] | PROCESSED_INPUT_MSG); + } else { + data[FLAGS] = (byte) (data[FLAGS] & (~PROCESSED_INPUT_MSG)); + } + } + + public void setOneTimeAuthReqd(boolean flag) { + if (flag) { + data[FLAGS] = (short) (data[FLAGS] | SECURE_USER_ID_REQD); + } else { + data[FLAGS] = (short) (data[FLAGS] & (~SECURE_USER_ID_REQD)); + } + } + + public short getAuthType() { + return data[AUTH_TYPE]; + } + + public void setAuthType(byte authType) { + data[AUTH_TYPE] = authType; + } + + public short getUserSecureId() { + short offset = 0; + short length = Util.getShort(userSecureIds, offset); + offset += 2; + if (length == 0) { + return KMType.INVALID_VALUE; + } + short arrObj = KMArray.instance(length); + short index = 0; + short obj; + while (index < length) { + obj = KMInteger.instance(userSecureIds, (short) (offset + index * 8), (short) 8); + KMArray.cast(arrObj).add(index, obj); + index++; + } + return KMIntegerArrayTag.instance(KMType.ULONG_ARRAY_TAG, KMType.USER_SECURE_ID, arrObj); + } + + public void setUserSecureId(short integerArrayPtr) { + short length = KMIntegerArrayTag.cast(integerArrayPtr).length(); + if (length > MAX_SECURE_USER_IDS) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + Util.arrayFillNonAtomic(userSecureIds, (short) 0, USER_SECURE_IDS_SIZE, (byte) 0); + short index = 0; + short obj; + short offset = 0; + offset = Util.setShort(userSecureIds, offset, length); + while (index < length) { + obj = KMIntegerArrayTag.cast(integerArrayPtr).get(index); + Util.arrayCopyNonAtomic( + KMInteger.cast(obj).getBuffer(), + KMInteger.cast(obj).getStartOff(), + userSecureIds, + (short) (8 - KMInteger.cast(obj).length() + offset + 8 * index), + KMInteger.cast(obj).length()); + index++; + } + } + + public short getAlgorithm() { + return data[ALG]; + } + + public void setAlgorithm(short algorithm) { + data[ALG] = algorithm; + } + + public short getPadding() { + return data[PADDING]; + } + + public void setPadding(short padding) { + data[PADDING] = padding; + } + + public short getBlockMode() { + return data[BLOCK_MODE]; + } + + public void setBlockMode(short blockMode) { + data[BLOCK_MODE] = blockMode; + } + + public short getDigest() { + return data[DIGEST]; + } + + public void setDigest(byte digest) { + data[DIGEST] = digest; + } + + public short getMgfDigest() { + return data[MGF_DIGEST]; + } + + public void setMgfDigest(byte mgfDigest) { + data[MGF_DIGEST] = mgfDigest; + } + + public boolean isAesGcmUpdateAllowed() { + return (data[FLAGS] & AES_GCM_UPDATE_ALLOWED) != 0; + } + + public void setAesGcmUpdateComplete() { + data[FLAGS] = (byte) (data[FLAGS] & (~AES_GCM_UPDATE_ALLOWED)); + } + + public void setAesGcmUpdateStart() { + data[FLAGS] = (byte) (data[FLAGS] | AES_GCM_UPDATE_ALLOWED); + } + + public short getMinMacLength() { + return data[MIN_MAC_LENGTH]; + } + + public void setMinMacLength(short length) { + data[MIN_MAC_LENGTH] = length; + } + + public short getMacLength() { + return data[MAC_LENGTH]; + } + + public void setMacLength(short length) { + data[MAC_LENGTH] = length; + } + + public byte getBufferingMode() { + short alg = getAlgorithm(); + short purpose = getPurpose(); + short digest = getDigest(); + short padding = getPadding(); + short blockMode = getBlockMode(); + + if (alg == KMType.RSA + && ((digest == KMType.DIGEST_NONE && purpose == KMType.SIGN) + || purpose == KMType.DECRYPT)) { + return KMType.BUF_RSA_DECRYPT_OR_NO_DIGEST; + } + + if (alg == KMType.EC && digest == KMType.DIGEST_NONE && purpose == KMType.SIGN) { + return KMType.BUF_EC_NO_DIGEST; + } + + switch (alg) { + case KMType.AES: + if (purpose == KMType.ENCRYPT && padding == KMType.PKCS7) { + return KMType.BUF_AES_ENCRYPT_PKCS7_BLOCK_ALIGN; + } else if (purpose == KMType.DECRYPT && padding == KMType.PKCS7) { + return KMType.BUF_AES_DECRYPT_PKCS7_BLOCK_ALIGN; + } else if (purpose == KMType.DECRYPT && blockMode == KMType.GCM) { + return KMType.BUF_AES_GCM_DECRYPT_BLOCK_ALIGN; + } + break; + case KMType.DES: + if (purpose == KMType.ENCRYPT && padding == KMType.PKCS7) { + return KMType.BUF_DES_ENCRYPT_PKCS7_BLOCK_ALIGN; + } else if (purpose == KMType.DECRYPT && padding == KMType.PKCS7) { + return KMType.BUF_DES_DECRYPT_PKCS7_BLOCK_ALIGN; + } + } + return KMType.BUF_NONE; + } + + public KMOperation getTrustedConfirmationSigner() { + return (KMOperation) operations[HMAC_SIGNER_OPERATION]; + } + + public void setTrustedConfirmationSigner(KMOperation hmacSignerOp) { + operations[HMAC_SIGNER_OPERATION] = hmacSignerOp; + } + + public boolean isTrustedConfirmationRequired() { + return operations[HMAC_SIGNER_OPERATION] != null; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMRemotelyProvisionedComponentDevice.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMRemotelyProvisionedComponentDevice.java new file mode 100644 index 0000000..8ba0e2f --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMRemotelyProvisionedComponentDevice.java @@ -0,0 +1,1758 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.keymaster; + +import com.android.javacard.seprovider.KMException; +import com.android.javacard.seprovider.KMKey; +import com.android.javacard.seprovider.KMOperation; +import com.android.javacard.seprovider.KMSEProvider; +import javacard.framework.APDU; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; + +/** + * This class handles remote key provisioning. Generates an RKP key and generates a certificate + * signing request(CSR). The generation of CSR is divided among multiple functions to the save the + * memory inside the Applet. The set of functions to be called sequentially in the order to complete + * the process of generating the CSR are processBeginSendData, processUpdateKey, + * processUpdateEekChain, processUpdateChallenge, processFinishSendData, and getResponse. + * ProcessUpdateKey is called Ntimes, where N is the number of keys. Similarly, getResponse is + * called multiple times till the client receives the response completely. + */ +public class KMRemotelyProvisionedComponentDevice { + + // Below are the device info labels + // The string "brand" in hex + public static final byte[] BRAND = {0x62, 0x72, 0x61, 0x6E, 0x64}; + // The string "manufacturer" in hex + public static final byte[] MANUFACTURER = { + 0x6D, 0x61, 0x6E, 0x75, 0x66, 0x61, 0x63, 0x74, 0x75, 0x72, 0x65, 0x72 + }; + // The string "product" in hex + public static final byte[] PRODUCT = {0x70, 0x72, 0x6F, 0x64, 0x75, 0x63, 0x74}; + // The string "model" in hex + public static final byte[] MODEL = {0x6D, 0x6F, 0x64, 0x65, 0x6C}; + // // The string "device" in hex + public static final byte[] DEVICE = {0x64, 0x65, 0x76, 0x69, 0x63, 0x65}; + // The string "vb_state" in hex + public static final byte[] VB_STATE = {0x76, 0x62, 0x5F, 0x73, 0x74, 0x61, 0x74, 0x65}; + // The string "bootloader_state" in hex. + public static final byte[] BOOTLOADER_STATE = { + 0x62, 0x6F, 0x6F, 0x74, 0x6C, 0x6F, 0x61, 0x64, 0x65, 0x72, 0x5F, 0x73, 0x74, 0x61, 0x74, 0x65 + }; + // The string "vb_meta_digest" in hdex. + public static final byte[] VB_META_DIGEST = { + 0X76, 0X62, 0X6D, 0X65, 0X74, 0X61, 0X5F, 0X64, 0X69, 0X67, 0X65, 0X73, 0X74 + }; + // The string "os_version" in hex. + public static final byte[] OS_VERSION = { + 0x6F, 0x73, 0x5F, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E + }; + // The string "system_patch_level" in hex. + public static final byte[] SYSTEM_PATCH_LEVEL = { + 0x73, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x5F, 0x70, 0x61, 0x74, 0x63, 0x68, 0x5F, 0x6C, 0x65, 0x76, + 0x65, 0x6C + }; + // The string "boot_patch_level" in hex. + public static final byte[] BOOT_PATCH_LEVEL = { + 0x62, 0x6F, 0x6F, 0x74, 0x5F, 0x70, 0x61, 0x74, 0x63, 0x68, 0x5F, 0x6C, 0x65, 0x76, 0x65, 0x6C + }; + // The string "vendor_patch_level" in hex. + public static final byte[] VENDOR_PATCH_LEVEL = { + 0x76, 0x65, 0x6E, 0x64, 0x6F, 0x72, 0x5F, 0x70, 0x61, 0x74, 0x63, 0x68, 0x5F, 0x6C, 0x65, 0x76, + 0x65, 0x6C + }; + // The string "version" in hex. + public static final byte[] DEVICE_INFO_VERSION = {0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E}; + // The string "security_level" in hex. + public static final byte[] SECURITY_LEVEL = { + 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x5F, 0x6C, 0x65, 0x76, 0x65, 0x6C + }; + // The string "fused" in hex. + public static final byte[] FUSED = {0x66, 0x75, 0x73, 0x65, 0x64}; + // Below are the Verified boot state values + // The string "green" in hex. + public static final byte[] VB_STATE_GREEN = {0x67, 0x72, 0x65, 0x65, 0x6E}; + // The string "yellow" in hex. + public static final byte[] VB_STATE_YELLOW = {0x79, 0x65, 0x6C, 0x6C, 0x6F, 0x77}; + // The string "orange" in hex. + public static final byte[] VB_STATE_ORANGE = {0x6F, 0x72, 0x61, 0x6E, 0x67, 0x65}; + // The string "red" in hex. + public static final byte[] VB_STATE_RED = {0x72, 0x65, 0x64}; + // Below are the boot loader state values + // The string "unlocked" in hex. + public static final byte[] UNLOCKED = {0x75, 0x6E, 0x6C, 0x6F, 0x63, 0x6B, 0x65, 0x64}; + // The string "locked" in hex. + public static final byte[] LOCKED = {0x6C, 0x6F, 0x63, 0x6B, 0x65, 0x64}; + // Device info CDDL schema version + public static final byte DI_SCHEMA_VERSION = 2; + // The string "strongbox" in hex. + public static final byte[] DI_SECURITY_LEVEL = { + 0x73, 0x74, 0x72, 0x6F, 0x6E, 0x67, 0x62, 0x6F, 0x78 + }; + // Represents each element size inside the data buffer. Each element has two entries + // 1) Length of the element and 2) offset of the element in the data buffer. + public static final byte DATA_INDEX_ENTRY_SIZE = 4; + // It is the offset, which represents the position where the element is present + // in the data buffer. + public static final byte DATA_INDEX_ENTRY_OFFSET = 2; + // Flag to denote TRUE + private static final byte TRUE = 0x01; + // Flag to denote FALSE + private static final byte FALSE = 0x00; + // RKP hardware info Version + private static final short RKP_VERSION = (short) 0x02; + // Below constants used to denote the type of the boot parameters. Note that these + // constants are only defined to be used internally. + private static final byte OS_VERSION_ID = 0x00; + private static final byte SYSTEM_PATCH_LEVEL_ID = 0x01; + private static final byte BOOT_PATCH_LEVEL_ID = 0x02; + private static final byte VENDOR_PATCH_LEVEL_ID = 0x03; + // Configurable flag to denote if Additional certificate chain is supported in the + // RKP server. + private static final boolean IS_ACC_SUPPORTED_IN_RKP_SERVER = false; + // The maximum possible output buffer. + private static final short MAX_SEND_DATA = 512; + // The string "Google Strongbox KeyMint 2" in hex. + private static final byte[] uniqueId = { + 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x20, 0x53, 0x74, 0x72, 0x6f, 0x6e, 0x67, 0x62, 0x6f, 0x78, + 0x20, 0x4b, 0x65, 0x79, 0x4d, 0x69, 0x6e, 0x74, 0x20, 0x32 + }; + // Flag to denote more response is available to the clients. + private static final byte MORE_DATA = 0x01; + // Flag to denote no response is available to the clients. + private static final byte NO_DATA = 0x00; + // Below are the response processing states. As the protected data response is huge it is + // sent back incrementally and the clients are responsible to call get response multiple times + // based on MORE_DATA or NO_DATA flags. BCC - Boot Certificate Chain. + // ACC - Additional Certificate Chain. + private static final byte START_PROCESSING = 0x00; + private static final byte PROCESSING_BCC_IN_PROGRESS = 0x02; + private static final byte PROCESSING_BCC_COMPLETE = 0x04; + private static final byte PROCESSING_ACC_IN_PROGRESS = 0x08; + private static final byte PROCESSING_ACC_COMPLETE = 0x0A; + // The data table size. + private static final short DATA_SIZE = 512; + // Number of entries in the data table. + private static final byte DATA_INDEX_SIZE = 11; + // Below are the data table offsets. + private static final byte EPHEMERAL_MAC_KEY = 0; + private static final byte TOTAL_KEYS_TO_SIGN = 1; + private static final byte KEYS_TO_SIGN_COUNT = 2; + private static final byte TEST_MODE = 3; + private static final byte EEK_KEY = 4; + private static final byte EEK_KEY_ID = 5; + private static final byte CHALLENGE = 6; + private static final byte GENERATE_CSR_PHASE = 7; + private static final byte EPHEMERAL_PUB_KEY = 8; + private static final byte RESPONSE_PROCESSING_STATE = 9; + private static final byte ACC_PROCESSED_LENGTH = 10; + + // Below are some of the sizes defined in the data table. + // The size of the Ephemeral Mac key used to sign the rkp public key. + private static final byte EPHEMERAL_MAC_KEY_SIZE = 32; + // The size of short types. + private static final byte SHORT_SIZE = 2; + // The size of byte types + private static final byte BYTE_SIZE = 1; + // The size of the test mode flag. + private static final byte TEST_MODE_SIZE = 1; + // Below are the different processing stages for generateCSR. + // BEGIN - It is the initial stage where the process is initialized and construction of + // MacedPublickeys are initiated. + // UPDATE - Challenge, EEK and RKP keys are sent to the applet for further process. + // FINISH - MacedPublicKeys are constructed and construction of protected data is initiated. + // GET_RESPONSE - Called multiple times by the client till all the protected data is received. + private static final byte BEGIN = 0x01; + private static final byte UPDATE = 0x02; + private static final byte FINISH = 0x04; + private static final byte GET_RESPONSE = 0x06; + + // RKP mac key size + private static final byte RKP_MAC_KEY_SIZE = 32; + // This holds the Google ECDSA P256 root key for the Endpoint Encryption Key. + public static Object[] authorizedEekRoots; + // Used to hold the temporary results. + public short[] rkpTmpVariables; + // Data table to hold the entries at the initial stages of generateCSR and which are used + // at later stages to construct the response data. + private byte[] data; + // Instance of the CBOR encoder. + private KMEncoder encoder; + // Instance of the CBOR decoder. + private KMDecoder decoder; + // Instance of the KMRepository for memory management. + private KMRepository repository; + // Instance of the provider for cyrpto operations. + private KMSEProvider seProvider; + // Instance of the KMKeymintDataStore to save or retrieve the data. + private KMKeymintDataStore storeDataInst; + // Holds the KMOperation instance. This is used to do multi part update operations. + private Object[] operation; + // Holds the current index in the data table. + private short[] dataIndex; + + public KMRemotelyProvisionedComponentDevice( + KMEncoder encoder, + KMDecoder decoder, + KMRepository repository, + KMSEProvider seProvider, + KMKeymintDataStore storeDInst) { + this.encoder = encoder; + this.decoder = decoder; + this.repository = repository; + this.seProvider = seProvider; + this.storeDataInst = storeDInst; + rkpTmpVariables = JCSystem.makeTransientShortArray((short) 32, JCSystem.CLEAR_ON_RESET); + data = JCSystem.makeTransientByteArray(DATA_SIZE, JCSystem.CLEAR_ON_RESET); + operation = JCSystem.makeTransientObjectArray((short) 1, JCSystem.CLEAR_ON_RESET); + dataIndex = JCSystem.makeTransientShortArray((short) 1, JCSystem.CLEAR_ON_RESET); + // Initialize RKP mac key + if (!seProvider.isUpgrading()) { + short offset = repository.allocReclaimableMemory((short) RKP_MAC_KEY_SIZE); + byte[] buffer = repository.getHeap(); + seProvider.getTrueRandomNumber(buffer, offset, RKP_MAC_KEY_SIZE); + storeDataInst.createRkpMacKey(buffer, offset, RKP_MAC_KEY_SIZE); + repository.reclaimMemory(RKP_MAC_KEY_SIZE); + } + operation[0] = null; + createAuthorizedEEKRoot(); + } + + private void createAuthorizedEEKRoot() { + if (authorizedEekRoots == null) { + authorizedEekRoots = + new Object[] { + new byte[] { + (byte) 0x04, (byte) 0xf7, (byte) 0x14, (byte) 0x8a, (byte) 0xdb, (byte) 0x97, + (byte) 0xf4, (byte) 0xcc, (byte) 0x53, (byte) 0xef, (byte) 0xd2, (byte) 0x64, + (byte) 0x11, (byte) 0xc4, (byte) 0xe3, (byte) 0x75, (byte) 0x1f, (byte) 0x66, + (byte) 0x1f, (byte) 0xa4, (byte) 0x71, (byte) 0x0c, (byte) 0x6c, (byte) 0xcf, + (byte) 0xfa, (byte) 0x09, (byte) 0x46, (byte) 0x80, (byte) 0x74, (byte) 0x87, + (byte) 0x54, (byte) 0xf2, (byte) 0xad, (byte) 0x5e, (byte) 0x7f, (byte) 0x5b, + (byte) 0xf6, (byte) 0xec, (byte) 0xe4, (byte) 0xf6, (byte) 0x19, (byte) 0xcc, + (byte) 0xff, (byte) 0x13, (byte) 0x37, (byte) 0xfd, (byte) 0x0f, (byte) 0xa1, + (byte) 0xc8, (byte) 0x93, (byte) 0xdb, (byte) 0x18, (byte) 0x06, (byte) 0x76, + (byte) 0xc4, (byte) 0x5d, (byte) 0xe6, (byte) 0xd7, (byte) 0x6a, (byte) 0x77, + (byte) 0x86, (byte) 0xc3, (byte) 0x2d, (byte) 0xaf, (byte) 0x8f + }, + }; + } + } + + private void initializeDataTable() { + clearDataTable(); + releaseOperation(); + dataIndex[0] = (short) (DATA_INDEX_SIZE * DATA_INDEX_ENTRY_SIZE); + } + + private short dataAlloc(short length) { + if ((short) (dataIndex[0] + length) > (short) data.length) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + dataIndex[0] += length; + return (short) (dataIndex[0] - length); + } + + private void clearDataTable() { + Util.arrayFillNonAtomic(data, (short) 0, (short) data.length, (byte) 0x00); + dataIndex[0] = 0x00; + } + + private void releaseOperation() { + if (operation[0] != null) { + ((KMOperation) operation[0]).abort(); + operation[0] = null; + } + } + + private short createEntry(short index, short length) { + index = (short) (index * DATA_INDEX_ENTRY_SIZE); + short ptr = dataAlloc(length); + Util.setShort(data, index, length); + Util.setShort(data, (short) (index + DATA_INDEX_ENTRY_OFFSET), ptr); + return ptr; + } + + private short getEntry(short index) { + index = (short) (index * DATA_INDEX_ENTRY_SIZE); + return Util.getShort(data, (short) (index + DATA_INDEX_ENTRY_OFFSET)); + } + + private short getEntryLength(short index) { + index = (short) (index * DATA_INDEX_ENTRY_SIZE); + return Util.getShort(data, index); + } + + private void processGetRkpHwInfoCmd(APDU apdu) { + // Make the response + // Author name - Google. + short respPtr = KMArray.instance((short) 5); + KMArray resp = KMArray.cast(respPtr); + resp.add((short) 0, KMInteger.uint_16(KMError.OK)); + resp.add((short) 1, KMInteger.uint_16(RKP_VERSION)); + resp.add( + (short) 2, + KMByteBlob.instance( + KMKeymasterApplet.Google, (short) 0, (short) KMKeymasterApplet.Google.length)); + resp.add((short) 3, KMInteger.uint_8(KMType.RKP_CURVE_P256)); + resp.add((short) 4, KMByteBlob.instance(uniqueId, (short) 0, (short) uniqueId.length)); + KMKeymasterApplet.sendOutgoing(apdu, respPtr); + } + + /** + * This function generates an EC key pair with attest key as purpose and creates an encrypted key + * blob. It then generates a COSEMac message which includes the ECDSA public key. + */ + public void processGenerateRkpKey(APDU apdu) { + short arr = KMArray.instance((short) 1); + KMArray.cast(arr).add((short) 0, KMSimpleValue.exp()); + arr = KMKeymasterApplet.receiveIncoming(apdu, arr); + // Re-purpose the apdu buffer as scratch pad. + byte[] scratchPad = apdu.getBuffer(); + // test mode flag. + boolean testMode = + (KMSimpleValue.TRUE == KMSimpleValue.cast(KMArray.cast(arr).get((short) 0)).getValue()); + KMKeymasterApplet.generateRkpKey(scratchPad, getEcAttestKeyParameters()); + short pubKey = KMKeymasterApplet.getPubKey(); + short coseMac0 = constructCoseMacForRkpKey(testMode, scratchPad, pubKey); + // Encode the COSE_MAC0 object + arr = KMArray.instance((short) 3); + KMArray.cast(arr).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(arr).add((short) 1, coseMac0); + KMArray.cast(arr).add((short) 2, KMKeymasterApplet.getPivateKey()); + KMKeymasterApplet.sendOutgoing(apdu, arr); + } + + /** + * This is the first command of the generateCSR. + * Input: + * 1) Number of RKP keys. + * 2) Total length of the encoded CoseKeys (Each RKP key is represented in CoseKey) + * 3) Flag which represents Test mode or Production mode. + * Process: + * 1) Generate Ephemeral mac key and store in the temporary data buffer. This key is + * used to sign Mac_Structure, which contains array of encoded RKP keys, + * 2) Initialize the HMAC operation with the ephemeral mac key and do partial sign of the + * Mac_Structure with the input initial data received. A Multipart update on HMAC is + * called on each updateKey command in the second stage. + * 3) Store the number of RKP keys and the test mode flag in the temporary data buffer. + * 4) Update the phase of the generateCSR function to BEGIN. + * Response: + * Send OK response. + * + * @param apdu Input apdu + */ + public void processBeginSendData(APDU apdu) throws Exception { + try { + initializeDataTable(); + short arr = KMArray.instance((short) 3); + KMArray.cast(arr).add((short) 0, KMInteger.exp()); // Array length + KMArray.cast(arr).add((short) 1, KMInteger.exp()); // Total length of the encoded CoseKeys. + KMArray.cast(arr).add((short) 2, KMSimpleValue.exp()); + arr = KMKeymasterApplet.receiveIncoming(apdu, arr); + // Re-purpose the apdu buffer as scratch pad. + byte[] scratchPad = apdu.getBuffer(); + // Generate ephemeral mac key. + short dataEntryIndex = createEntry(EPHEMERAL_MAC_KEY, EPHEMERAL_MAC_KEY_SIZE); + seProvider.newRandomNumber(data, dataEntryIndex, EPHEMERAL_MAC_KEY_SIZE); + // Initialize hmac operation. + initHmacOperation(); + // Partially encode CoseMac structure with partial payload. + constructPartialPubKeysToSignMac( + scratchPad, + KMInteger.cast(KMArray.cast(arr).get((short) 0)).getShort(), + KMInteger.cast(KMArray.cast(arr).get((short) 1)).getShort()); + // Store the total keys in data table. + dataEntryIndex = createEntry(TOTAL_KEYS_TO_SIGN, SHORT_SIZE); + Util.setShort( + data, dataEntryIndex, KMInteger.cast(KMArray.cast(arr).get((short) 0)).getShort()); + // Store the test mode value in data table. + dataEntryIndex = createEntry(TEST_MODE, TEST_MODE_SIZE); + data[dataEntryIndex] = + (KMSimpleValue.TRUE == KMSimpleValue.cast(KMArray.cast(arr).get((short) 2)).getValue()) + ? TRUE + : FALSE; + // Store the current csr status, which is BEGIN. + createEntry(GENERATE_CSR_PHASE, BYTE_SIZE); + updateState(BEGIN); + // Send response. + KMKeymasterApplet.sendResponse(apdu, KMError.OK); + } catch (Exception e) { + clearDataTable(); + releaseOperation(); + throw e; + } + } + + /** + * This is the second command of the generateCSR. + * Input: + * CoseMac0 containing the RKP Key + * Process: + * 1) Validate the phase of generateCSR. Prior state should be either BEGIN or UPDATE. + * 2) Validate the number of RKP Keys received against the value received in first command. + * 3) Validate the CoseMac0 structure and extract the RKP Key. + * 4) Do Multipart HMAC update operation with the input as RKP key. + * 5) Update the number of keys received count into the data buffer. + * 6) Update the phase of the generateCSR function to UPDATE. + * Response: + * Send OK response. + * @param apdu Input apdu + */ + public void processUpdateKey(APDU apdu) throws Exception { + try { + // The prior state can be BEGIN or UPDATE + validateState((byte) (BEGIN | UPDATE)); + validateKeysToSignCount(); + short headers = KMCoseHeaders.exp(); + short arrInst = KMArray.instance((short) 4); + KMArray.cast(arrInst).add((short) 0, KMByteBlob.exp()); + KMArray.cast(arrInst).add((short) 1, headers); + KMArray.cast(arrInst).add((short) 2, KMByteBlob.exp()); + KMArray.cast(arrInst).add((short) 3, KMByteBlob.exp()); + short arr = KMArray.exp(arrInst); + arr = KMKeymasterApplet.receiveIncoming(apdu, arr); + arrInst = KMArray.cast(arr).get((short) 0); + // Re-purpose the apdu buffer as scratch pad. + byte[] scratchPad = apdu.getBuffer(); + + // Validate and extract the CoseKey from CoseMac0 message. + short coseKey = validateAndExtractPublicKey(arrInst, scratchPad); + // Encode CoseKey + short length = + KMKeymasterApplet.encodeToApduBuffer( + coseKey, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + // Do Hmac update with input as encoded CoseKey. + ((KMOperation) operation[0]).update(scratchPad, (short) 0, length); + // Increment the count each time this function gets executed. + // Store the count in data table. + short dataEntryIndex = getEntry(KEYS_TO_SIGN_COUNT); + if (dataEntryIndex == 0) { + dataEntryIndex = createEntry(KEYS_TO_SIGN_COUNT, SHORT_SIZE); + } + length = Util.getShort(data, dataEntryIndex); + Util.setShort(data, dataEntryIndex, ++length); + // Update the csr state + updateState(UPDATE); + // Send response. + KMKeymasterApplet.sendResponse(apdu, KMError.OK); + } catch (Exception e) { + clearDataTable(); + releaseOperation(); + throw e; + } + } + + /** + * This is the third command of generateCSR. + * Input: + * EEK chain ordered from Root to Leaf in CoseSign1 format. + * Process: + * 1) Validate the phase of generateCSR. Prior state should be UPDATE. + * 2) Validate the EEK chain and extract the Leaf EEK + * 3) Retrieve the EEK Id and Public key of Leaf EEK and store in the data buffer. + * 4) Update the phase of the generateCSR function to UPDATE. + * Response: + * Send OK response. + * @param apdu Input apdu. + */ + public void processUpdateEekChain(APDU apdu) throws Exception { + try { + // The prior state can be BEGIN or UPDATE + validateState((byte) (BEGIN | UPDATE)); + short headers = KMCoseHeaders.exp(); + short arrInst = KMArray.instance((short) 4); + KMArray.cast(arrInst).add((short) 0, KMByteBlob.exp()); + KMArray.cast(arrInst).add((short) 1, headers); + KMArray.cast(arrInst).add((short) 2, KMByteBlob.exp()); + KMArray.cast(arrInst).add((short) 3, KMByteBlob.exp()); + short arrSignPtr = KMArray.exp(arrInst); + arrInst = KMKeymasterApplet.receiveIncoming(apdu, arrSignPtr); + if (KMArray.cast(arrInst).length() == 0) { + KMException.throwIt(KMError.STATUS_INVALID_EEK); + } + // Re-purpose the apdu buffer as scratch pad. + byte[] scratchPad = apdu.getBuffer(); + // Validate eek chain. + short eekKey = validateAndExtractEekPub(arrInst, scratchPad); + // Store eek public key and eek id in the data table. + short eekKeyId = KMCoseKey.cast(eekKey).getKeyIdentifier(); + short dataEntryIndex = createEntry(EEK_KEY_ID, KMByteBlob.cast(eekKeyId).length()); + Util.arrayCopyNonAtomic( + KMByteBlob.cast(eekKeyId).getBuffer(), + KMByteBlob.cast(eekKeyId).getStartOff(), + data, + dataEntryIndex, + KMByteBlob.cast(eekKeyId).length()); + // Convert the coseKey to a public key. + short len = KMCoseKey.cast(eekKey).getEcdsa256PublicKey(scratchPad, (short) 0); + dataEntryIndex = createEntry(EEK_KEY, len); + Util.arrayCopyNonAtomic(scratchPad, (short) 0, data, dataEntryIndex, len); + // Update the state + updateState(UPDATE); + KMKeymasterApplet.sendResponse(apdu, KMError.OK); + } catch (Exception e) { + clearDataTable(); + releaseOperation(); + throw e; + } + } + + /** + * This is the fourth command of generateCSR. + * Input: + * Challenge + * Process: + * 1) Validate the phase of generateCSR. Prior state should be either UPDATE or BEGIN. + * 2) Store the challenge in the data buffer. + * 3) Update the phase of the generateCSR function to UPDATE. + * Response: + * Send OK response. + * @param apdu Input apdu. + */ + public void processUpdateChallenge(APDU apdu) throws Exception { + try { + // The prior state can be BEGIN or UPDATE + validateState((byte) (BEGIN | UPDATE)); + short arr = KMArray.instance((short) 1); + KMArray.cast(arr).add((short) 0, KMByteBlob.exp()); + arr = KMKeymasterApplet.receiveIncoming(apdu, arr); + // Store the challenge in the data table. + short challenge = KMArray.cast(arr).get((short) 0); + short challengeLen = KMByteBlob.cast(challenge).length(); + if (challengeLen > 64) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + short dataEntryIndex = createEntry(CHALLENGE, challengeLen); + Util.arrayCopyNonAtomic( + KMByteBlob.cast(challenge).getBuffer(), + KMByteBlob.cast(challenge).getStartOff(), + data, + dataEntryIndex, + challengeLen); + // Update the state + updateState(UPDATE); + KMKeymasterApplet.sendResponse(apdu, KMError.OK); + } catch (Exception e) { + clearDataTable(); + releaseOperation(); + throw e; + } + } + + /** + * This is the fifth command of generateCSR. + * Input: + * No input data. + * Process: + * 1) Validate the phase of generateCSR. Prior state should be UPDATE. + * 2) Check if all the RKP keys are received if not throw exception. + * 3) Finalize the HMAC operation and get the signed Mac_Structure which is called as + * pubKeysToSignMac. + * 4) Start constructing the partial protected data. Create a random + * nonce, initialize the AESGCM operation with the session key derived from + * HKDF(ECDH(EPHEMERAL_EC_KEY, EEK_KEY), KdfContext) + * 5) Construct Encrypt_Structure which acts as AAD for AES-GCM operation. + * 6) The payload for the Protected Data is [SignedMac, BCC, ACC]. Construct partial + * SignedMac structure. + * 7) Note that the HAL has to construct the CoseEncrypt structure by collecting all + * the pieces returned from Applet. + * Response: + * OK + * pubKeysToSignMac - Containes the maced RKP public keys. + * deviceInfo - CBOR encoded device info + * protectedHeader - CoseEncrypt protected header + * unProtectedHeader - CoseEncrypt unprotected header. + * ParitalCipherText - partial encrypted payload of CoseEncrypt structure. + * Flag to represent there is more data to retrieve. + * @param apdu Input apdu. + */ + public void processFinishSendData(APDU apdu) throws Exception { + try { + // The prior state should be UPDATE. + validateState(UPDATE); + byte[] scratchPad = apdu.getBuffer(); + if (data[getEntry(TOTAL_KEYS_TO_SIGN)] != data[getEntry(KEYS_TO_SIGN_COUNT)]) { + // Mismatch in the number of keys sent. + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + // PubKeysToSignMac + short empty = repository.alloc((short) 0); + short len = + ((KMOperation) operation[0]) + .sign(repository.getHeap(), (short) empty, (short) 0, scratchPad, (short) 0); + // release operation + releaseOperation(); + short pubKeysToSignMac = KMByteBlob.instance(scratchPad, (short) 0, len); + // Create DeviceInfo + short deviceInfo = createDeviceInfo(scratchPad); + // Generate Nonce for AES-GCM + seProvider.newRandomNumber(scratchPad, (short) 0, KMKeymasterApplet.AES_GCM_NONCE_LENGTH); + short nonce = + KMByteBlob.instance(scratchPad, (short) 0, KMKeymasterApplet.AES_GCM_NONCE_LENGTH); + // Initializes cipher instance. + initAesGcmOperation(scratchPad, nonce); + // Encode Enc_Structure as additional data for AES-GCM. + processAesGcmUpdateAad(scratchPad); + short partialPayloadLen = processSignedMac(scratchPad, pubKeysToSignMac, deviceInfo); + short partialCipherText = KMByteBlob.instance(scratchPad, (short) 0, partialPayloadLen); + short coseEncryptProtectedHeader = getCoseEncryptProtectedHeader(scratchPad); + short coseEncryptUnProtectedHeader = getCoseEncryptUnprotectedHeader(scratchPad, nonce); + len = + KMKeymasterApplet.encodeToApduBuffer( + deviceInfo, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + short encodedDeviceInfo = KMByteBlob.instance(scratchPad, (short) 0, len); + updateState(FINISH); + short arr = KMArray.instance((short) 7); + KMArray.cast(arr).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(arr).add((short) 1, pubKeysToSignMac); + KMArray.cast(arr).add((short) 2, encodedDeviceInfo); + KMArray.cast(arr).add((short) 3, coseEncryptProtectedHeader); + KMArray.cast(arr).add((short) 4, coseEncryptUnProtectedHeader); + KMArray.cast(arr).add((short) 5, partialCipherText); + KMArray.cast(arr).add((short) 6, KMInteger.uint_8(MORE_DATA)); + KMKeymasterApplet.sendOutgoing(apdu, arr); + } catch (Exception e) { + clearDataTable(); + releaseOperation(); + throw e; + } + } + + /** + * This is the sixth and the last command of generateCSR. This command is called multiple + * times by the HAL until all the cipher data and receipient structure is received. + * Input: + * No input data. + * Process: + * First the BootCertificateChain is processed: Encrypt the boot certificate chain and return + * the BCC. Mark the state as PROCESSING_BCC_COMPLETE. + * Next the AdditionalCertificateChain is processed: Incrementally encrypt ACC and send back + * a chunks of data. Each chunk is 512 bytes. if the processing of ACC is still in progress + * mark the state as PROCESSING_ACC_IN_PROGRESS otherwise mark the state as + * PROCESSING_ACC_COMPLETE. + * Finally construct and return the CoseReceipient structure. + * Response: + * OK + * cipher text: It can be either encrypted bcc or encrypted acc + * Receipient structure: This will be empty will returning the encrypted acc or bcc. + * Flag to represent there is more data to retrieve. + * @param apdu Input apdu. + */ + public void processGetResponse(APDU apdu) throws Exception { + try { + // The prior state should be FINISH. + validateState((byte) (FINISH | GET_RESPONSE)); + byte[] scratchPad = apdu.getBuffer(); + short len = 0; + short recipientStructure = KMArray.instance((short) 0); + byte moreData = MORE_DATA; + byte state = getCurrentOutputProcessingState(); + switch (state) { + case START_PROCESSING: + case PROCESSING_BCC_IN_PROGRESS: + len = processBcc(scratchPad); + updateState(GET_RESPONSE); + break; + case PROCESSING_BCC_COMPLETE: + case PROCESSING_ACC_IN_PROGRESS: + len = processAdditionalCertificateChain(scratchPad); + updateState(GET_RESPONSE); + break; + case PROCESSING_ACC_COMPLETE: + recipientStructure = processRecipientStructure(scratchPad); + len = processFinalData(scratchPad); + moreData = NO_DATA; + releaseOperation(); + clearDataTable(); + break; + default: + KMException.throwIt(KMError.INVALID_STATE); + } + short data = KMByteBlob.instance(scratchPad, (short) 0, len); + short arr = KMArray.instance((short) 4); + KMArray.cast(arr).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(arr).add((short) 1, data); + KMArray.cast(arr).add((short) 2, recipientStructure); + // represents there is more output to retrieve + KMArray.cast(arr).add((short) 3, KMInteger.uint_8(moreData)); + KMKeymasterApplet.sendOutgoing(apdu, arr); + } catch (Exception e) { + clearDataTable(); + releaseOperation(); + throw e; + } + } + + public void process(short ins, APDU apdu) throws Exception { + switch (ins) { + case KMKeymasterApplet.INS_GET_RKP_HARDWARE_INFO: + processGetRkpHwInfoCmd(apdu); + break; + case KMKeymasterApplet.INS_GENERATE_RKP_KEY_CMD: + processGenerateRkpKey(apdu); + break; + case KMKeymasterApplet.INS_BEGIN_SEND_DATA_CMD: + processBeginSendData(apdu); + break; + case KMKeymasterApplet.INS_UPDATE_KEY_CMD: + processUpdateKey(apdu); + break; + case KMKeymasterApplet.INS_UPDATE_EEK_CHAIN_CMD: + processUpdateEekChain(apdu); + break; + case KMKeymasterApplet.INS_UPDATE_CHALLENGE_CMD: + processUpdateChallenge(apdu); + break; + case KMKeymasterApplet.INS_FINISH_SEND_DATA_CMD: + processFinishSendData(apdu); + break; + case KMKeymasterApplet.INS_GET_RESPONSE_CMD: + processGetResponse(apdu); + break; + default: + ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); + } + } + + private boolean isAdditionalCertificateChainPresent() { + if (!IS_ACC_SUPPORTED_IN_RKP_SERVER || (TRUE == data[getEntry(TEST_MODE)])) { + // Don't include AdditionalCertificateChain in ProtectedData if either + // 1. RKP server does not support processing of X.509 Additional Certificate Chain. + // 2. Requested CSR for test mode. + return false; + } + return (storeDataInst.getAdditionalCertChainLength() == 0 ? false : true); + } + + private short processFinalData(byte[] scratchPad) { + // Call finish on AES GCM Cipher + short empty = repository.alloc((short) 0); + short len = + ((KMOperation) operation[0]) + .finish(repository.getHeap(), (short) empty, (short) 0, scratchPad, (short) 0); + return len; + } + + private byte getCurrentOutputProcessingState() { + short index = getEntry(RESPONSE_PROCESSING_STATE); + if (index == 0) { + return START_PROCESSING; + } + return data[index]; + } + + private void updateOutputProcessingState(byte state) { + short dataEntryIndex = getEntry(RESPONSE_PROCESSING_STATE); + data[dataEntryIndex] = state; + } + + /** + * Validates the CoseMac message and extracts the CoseKey from it. + * + * @param coseMacPtr CoseMac instance to be validated. + * @param scratchPad Scratch buffer used to store temp results. + * @return CoseKey instance. + */ + private short validateAndExtractPublicKey(short coseMacPtr, byte[] scratchPad) { + boolean testMode = (TRUE == data[getEntry(TEST_MODE)]) ? true : false; + // Exp for KMCoseHeaders + short coseHeadersExp = KMCoseHeaders.exp(); + // Exp for coseky + short coseKeyExp = KMCoseKey.exp(); + + // validate protected Headers + short ptr = KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_PROTECTED_PARAMS_OFFSET); + ptr = + decoder.decode( + coseHeadersExp, + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()); + + if (!KMCoseHeaders.cast(ptr) + .isDataValid(rkpTmpVariables, KMCose.COSE_ALG_HMAC_256, KMType.INVALID_VALUE)) { + KMException.throwIt(KMError.STATUS_FAILED); + } + + // Validate payload. + ptr = KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_PAYLOAD_OFFSET); + ptr = + decoder.decode( + coseKeyExp, + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()); + + if (!KMCoseKey.cast(ptr) + .isDataValid( + rkpTmpVariables, + KMCose.COSE_KEY_TYPE_EC2, + KMType.INVALID_VALUE, + KMCose.COSE_ALG_ES256, + KMCose.COSE_ECCURVE_256)) { + KMException.throwIt(KMError.STATUS_FAILED); + } + + boolean isTestKey = KMCoseKey.cast(ptr).isTestKey(); + if (isTestKey && !testMode) { + KMException.throwIt(KMError.STATUS_TEST_KEY_IN_PRODUCTION_REQUEST); + } else if (!isTestKey && testMode) { + KMException.throwIt(KMError.STATUS_PRODUCTION_KEY_IN_TEST_REQUEST); + } + + // Compute CoseMac Structure and compare the macs. + short macStructure = + KMCose.constructCoseMacStructure( + KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_PROTECTED_PARAMS_OFFSET), + KMByteBlob.instance((short) 0), + KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_PAYLOAD_OFFSET)); + short encodedLen = + KMKeymasterApplet.encodeToApduBuffer( + macStructure, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + + short hmacLen = + rkpHmacSign(testMode, scratchPad, (short) 0, encodedLen, scratchPad, encodedLen); + + if (hmacLen + != KMByteBlob.cast(KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_TAG_OFFSET)).length()) { + KMException.throwIt(KMError.STATUS_INVALID_MAC); + } + + if (0 + != Util.arrayCompare( + scratchPad, + encodedLen, + KMByteBlob.cast(KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_TAG_OFFSET)).getBuffer(), + KMByteBlob.cast(KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_TAG_OFFSET)) + .getStartOff(), + hmacLen)) { + KMException.throwIt(KMError.STATUS_INVALID_MAC); + } + return ptr; + } + + /** + * This function validates the EEK Chain and extracts the leaf public key, which is used to + * generate shared secret using ECDH. + * + * @param eekArr EEK cert chain array pointer. + * @param scratchPad Scratch buffer used to store temp results. + * @return CoseKey instance. + */ + private short validateAndExtractEekPub(short eekArr, byte[] scratchPad) { + short leafPubKey = 0; + try { + leafPubKey = + KMKeymasterApplet.validateCertChain( + (TRUE == data[getEntry(TEST_MODE)]) ? false : true, // validate EEK root + KMCose.COSE_ALG_ES256, + KMCose.COSE_ALG_ECDH_ES_HKDF_256, + eekArr, + scratchPad, + authorizedEekRoots); + } catch (KMException e) { + KMException.throwIt(KMError.STATUS_INVALID_EEK); + } + return leafPubKey; + } + + private void validateKeysToSignCount() { + short index = getEntry(KEYS_TO_SIGN_COUNT); + short keysToSignCount = 0; + if (index != 0) { + keysToSignCount = Util.getShort(data, index); + } + if (Util.getShort(data, getEntry(TOTAL_KEYS_TO_SIGN)) <= keysToSignCount) { + // Mismatch in the number of keys sent. + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + } + + private void validateState(byte expectedState) { + short dataEntryIndex = getEntry(GENERATE_CSR_PHASE); + if (0 == (data[dataEntryIndex] & expectedState)) { + KMException.throwIt(KMError.INVALID_STATE); + } + } + + private void updateState(byte state) { + short dataEntryIndex = getEntry(GENERATE_CSR_PHASE); + if (dataEntryIndex == 0) { + KMException.throwIt(KMError.INVALID_STATE); + } + data[dataEntryIndex] = state; + } + + /** + * This function constructs a Mac Structure, encode it and signs the encoded buffer with the + * ephemeral mac key. + */ + private void constructPartialPubKeysToSignMac( + byte[] scratchPad, short arrayLength, short encodedCoseKeysLen) { + short ptr; + short len; + short headerPtr = + KMCose.constructHeaders( + rkpTmpVariables, + KMInteger.uint_8(KMCose.COSE_ALG_HMAC_256), + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE); + // Encode the protected header as byte blob. + len = + KMKeymasterApplet.encodeToApduBuffer( + headerPtr, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + short protectedHeader = KMByteBlob.instance(scratchPad, (short) 0, len); + // create MAC_Structure + ptr = + KMCose.constructCoseMacStructure( + protectedHeader, KMByteBlob.instance((short) 0), KMType.INVALID_VALUE); + // Encode the Mac_structure and do HMAC_Sign to produce the tag for COSE_MAC0 + len = + KMKeymasterApplet.encodeToApduBuffer( + ptr, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + // Construct partial payload - Bstr Header + Array Header + // The maximum combined length of bstr header and array header length is 6 bytes. + // The lengths will never exceed Max SHORT value. + short arrPtr = KMArray.instance(arrayLength); + for (short i = 0; i < arrayLength; i++) { + KMArray.cast(arrPtr).add(i, KMType.INVALID_VALUE); + } + arrayLength = encoder.getEncodedLength(arrPtr); + short bufIndex = repository.alloc((short) 6); + short partialPayloadLen = + encoder.encodeByteBlobHeader( + (short) (arrayLength + encodedCoseKeysLen), repository.getHeap(), bufIndex, (short) 3); + + partialPayloadLen += + encoder.encode( + arrPtr, + repository.getHeap(), + (short) (bufIndex + partialPayloadLen), + repository.getHeapReclaimIndex()); + Util.arrayCopyNonAtomic(repository.getHeap(), bufIndex, scratchPad, len, partialPayloadLen); + ((KMOperation) operation[0]).update(scratchPad, (short) 0, (short) (len + partialPayloadLen)); + } + + private short createSignedMac( + KMKey deviceUniqueKeyPair, byte[] scratchPad, short deviceMapPtr, short pubKeysToSign) { + // Challenge + short dataEntryIndex = getEntry(CHALLENGE); + short challengePtr = KMByteBlob.instance(data, dataEntryIndex, getEntryLength(CHALLENGE)); + // Ephemeral mac key + dataEntryIndex = getEntry(EPHEMERAL_MAC_KEY); + short ephmeralMacKey = + KMByteBlob.instance(data, dataEntryIndex, getEntryLength(EPHEMERAL_MAC_KEY)); + + /* Prepare AAD */ + short aad = KMArray.instance((short) 3); + KMArray.cast(aad).add((short) 0, challengePtr); + KMArray.cast(aad).add((short) 1, deviceMapPtr); + KMArray.cast(aad).add((short) 2, pubKeysToSign); + aad = + KMKeymasterApplet.encodeToApduBuffer( + aad, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + aad = KMByteBlob.instance(scratchPad, (short) 0, aad); + + /* construct protected header */ + short protectedHeaders = + KMCose.constructHeaders( + rkpTmpVariables, + KMNInteger.uint_8(KMCose.COSE_ALG_ES256), + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE); + protectedHeaders = + KMKeymasterApplet.encodeToApduBuffer( + protectedHeaders, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + protectedHeaders = KMByteBlob.instance(scratchPad, (short) 0, protectedHeaders); + + /* construct cose sign structure */ + short signStructure = KMCose.constructCoseSignStructure(protectedHeaders, aad, ephmeralMacKey); + signStructure = + KMKeymasterApplet.encodeToApduBuffer( + signStructure, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + // 72 is the maximum ECDSA Signature length. + short maxEcdsaSignLen = 72; + short reclaimIndex = repository.allocReclaimableMemory(maxEcdsaSignLen); + short len = + seProvider.signWithDeviceUniqueKey( + deviceUniqueKeyPair, + scratchPad, + (short) 0, + signStructure, + repository.getHeap(), + reclaimIndex); + Util.arrayCopyNonAtomic(repository.getHeap(), reclaimIndex, scratchPad, (short) 0, len); + repository.reclaimMemory(maxEcdsaSignLen); + len = + KMAsn1Parser.instance() + .decodeEcdsa256Signature( + KMByteBlob.instance(scratchPad, (short) 0, len), scratchPad, (short) 0); + signStructure = KMByteBlob.instance(scratchPad, (short) 0, len); + + /* Construct unprotected headers */ + short unprotectedHeader = KMArray.instance((short) 0); + unprotectedHeader = KMCoseHeaders.instance(unprotectedHeader); + + /* construct Cose_Sign1 */ + return KMCose.constructCoseSign1( + protectedHeaders, unprotectedHeader, ephmeralMacKey, signStructure); + } + + private KMKey createDeviceUniqueKeyPair(boolean testMode, byte[] scratchPad) { + KMKey deviceUniqueKeyPair; + rkpTmpVariables[0] = 0; + rkpTmpVariables[1] = 0; + if (testMode) { + seProvider.createAsymmetricKey( + KMType.EC, + scratchPad, + (short) 0, + (short) 128, + scratchPad, + (short) 128, + (short) 128, + rkpTmpVariables); + deviceUniqueKeyPair = + storeDataInst.createRkpTestDeviceUniqueKeyPair( + scratchPad, + (short) 128, + rkpTmpVariables[1], + scratchPad, + (short) 0, + rkpTmpVariables[0]); + } else { + deviceUniqueKeyPair = storeDataInst.getRkpDeviceUniqueKeyPair(false); + } + return deviceUniqueKeyPair; + } + + /** + * DeviceInfo is a CBOR Map structure described by the following CDDL. + * + * <p>DeviceInfo = { "brand" : tstr, "manufacturer" : tstr, "product" : tstr, "model" : tstr, + * "device" : tstr, "vb_state" : "green" / "yellow" / "orange", // Taken from the AVB values + * "bootloader_state" : "locked" / "unlocked", // Taken from the AVB values "vbmeta_digest": bstr, + * // Taken from the AVB values ? "os_version" : tstr, // Same as android.os.Build.VERSION.release + * "system_patch_level" : uint, // YYYYMMDD "boot_patch_level" : uint, //YYYYMMDD + * "vendor_patch_level" : uint, // YYYYMMDD "version" : 2, // TheCDDL schema version + * "security_level" : "tee" / "strongbox" "fused": 1 / 0, } + */ + private short createDeviceInfo(byte[] scratchpad) { + // Device Info Key Value pairs. + for (short i = 0; i < 32; i++) { + rkpTmpVariables[i] = KMType.INVALID_VALUE; + } + short dataOffset = 2; + rkpTmpVariables[0] = dataOffset; + rkpTmpVariables[1] = 0; + short metaOffset = 0; + updateItem( + rkpTmpVariables, + metaOffset, + BRAND, + getAttestationId(KMType.ATTESTATION_ID_BRAND, scratchpad)); + updateItem( + rkpTmpVariables, + metaOffset, + MANUFACTURER, + getAttestationId(KMType.ATTESTATION_ID_MANUFACTURER, scratchpad)); + updateItem( + rkpTmpVariables, + metaOffset, + PRODUCT, + getAttestationId(KMType.ATTESTATION_ID_PRODUCT, scratchpad)); + updateItem( + rkpTmpVariables, + metaOffset, + MODEL, + getAttestationId(KMType.ATTESTATION_ID_MODEL, scratchpad)); + updateItem( + rkpTmpVariables, + metaOffset, + DEVICE, + getAttestationId(KMType.ATTESTATION_ID_DEVICE, scratchpad)); + updateItem(rkpTmpVariables, metaOffset, VB_STATE, getVbState()); + updateItem(rkpTmpVariables, metaOffset, BOOTLOADER_STATE, getBootloaderState()); + updateItem(rkpTmpVariables, metaOffset, VB_META_DIGEST, getVerifiedBootHash(scratchpad)); + updateItem(rkpTmpVariables, metaOffset, OS_VERSION, getBootParams(OS_VERSION_ID, scratchpad)); + updateItem( + rkpTmpVariables, + metaOffset, + SYSTEM_PATCH_LEVEL, + getBootParams(SYSTEM_PATCH_LEVEL_ID, scratchpad)); + updateItem( + rkpTmpVariables, + metaOffset, + BOOT_PATCH_LEVEL, + getBootParams(BOOT_PATCH_LEVEL_ID, scratchpad)); + updateItem( + rkpTmpVariables, + metaOffset, + VENDOR_PATCH_LEVEL, + getBootParams(VENDOR_PATCH_LEVEL_ID, scratchpad)); + updateItem( + rkpTmpVariables, metaOffset, DEVICE_INFO_VERSION, KMInteger.uint_8(DI_SCHEMA_VERSION)); + updateItem( + rkpTmpVariables, + metaOffset, + SECURITY_LEVEL, + KMTextString.instance(DI_SECURITY_LEVEL, (short) 0, (short) DI_SECURITY_LEVEL.length)); + updateItem(rkpTmpVariables, metaOffset, FUSED, KMInteger.uint_8(storeDataInst.secureBootMode)); + // Create device info map. + short map = KMMap.instance(rkpTmpVariables[1]); + short mapIndex = 0; + short index = 2; + while (index < (short) 32) { + if (rkpTmpVariables[index] != KMType.INVALID_VALUE) { + KMMap.cast(map) + .add(mapIndex++, rkpTmpVariables[index], rkpTmpVariables[(short) (index + 1)]); + } + index += 2; + } + KMMap.cast(map).canonicalize(); + return map; + } + + // Below 6 methods are helper methods to create device info structure. + // ---------------------------------------------------------------------------- + + /** + * Update the item inside the device info structure. + * + * @param deviceIds Device Info structure to be updated. + * @param metaOffset Out parameter meta information. Offset 0 is index and Offset 1 is length. + * @param item Key info to be updated. + * @param value value to be updated. + */ + private void updateItem(short[] deviceIds, short metaOffset, byte[] item, short value) { + if (KMType.INVALID_VALUE != value) { + deviceIds[deviceIds[metaOffset]++] = + KMTextString.instance(item, (short) 0, (short) item.length); + deviceIds[deviceIds[metaOffset]++] = value; + deviceIds[(short) (metaOffset + 1)]++; + } + } + + private short getAttestationId(short attestId, byte[] scratchpad) { + short attIdTagLen = storeDataInst.getAttestationId(attestId, scratchpad, (short) 0); + if (attIdTagLen == 0) { + KMException.throwIt(KMError.INVALID_STATE); + } + return KMTextString.instance(scratchpad, (short) 0, attIdTagLen); + } + + private short getVerifiedBootHash(byte[] scratchPad) { + short len = storeDataInst.getVerifiedBootHash(scratchPad, (short) 0); + if (len == 0) { + KMException.throwIt(KMError.INVALID_STATE); + } + return KMByteBlob.instance(scratchPad, (short) 0, len); + } + + private short getBootloaderState() { + short bootloaderState; + if (storeDataInst.isDeviceBootLocked()) { + bootloaderState = KMTextString.instance(LOCKED, (short) 0, (short) LOCKED.length); + } else { + bootloaderState = KMTextString.instance(UNLOCKED, (short) 0, (short) UNLOCKED.length); + } + return bootloaderState; + } + + private short getVbState() { + short state = storeDataInst.getBootState(); + short vbState = KMType.INVALID_VALUE; + if (state == KMType.VERIFIED_BOOT) { + vbState = KMTextString.instance(VB_STATE_GREEN, (short) 0, (short) VB_STATE_GREEN.length); + } else if (state == KMType.SELF_SIGNED_BOOT) { + vbState = KMTextString.instance(VB_STATE_YELLOW, (short) 0, (short) VB_STATE_YELLOW.length); + } else if (state == KMType.UNVERIFIED_BOOT) { + vbState = KMTextString.instance(VB_STATE_ORANGE, (short) 0, (short) VB_STATE_ORANGE.length); + } else if (state == KMType.FAILED_BOOT) { + vbState = KMTextString.instance(VB_STATE_RED, (short) 0, (short) VB_STATE_RED.length); + } + return vbState; + } + + private short converIntegerToTextString(short intPtr, byte[] scratchPad) { + // Prepare Hex Values + short index = 1; + scratchPad[0] = 0x30; // Ascii 0 + while (index < 10) { + scratchPad[index] = (byte) (scratchPad[(short) (index - 1)] + 1); + index++; + } + scratchPad[index++] = 0x41; // Ascii 'A' + while (index < 16) { + scratchPad[index] = (byte) (scratchPad[(short) (index - 1)] + 1); + index++; + } + + short intLen = KMInteger.cast(intPtr).length(); + short intOffset = KMInteger.cast(intPtr).getStartOff(); + byte[] buf = repository.getHeap(); + short tsPtr = KMTextString.instance((short) (intLen * 2)); + short tsStartOff = KMTextString.cast(tsPtr).getStartOff(); + index = 0; + byte nibble; + while (index < intLen) { + nibble = (byte) ((byte) (buf[intOffset] >> 4) & (byte) 0x0F); + buf[tsStartOff] = scratchPad[nibble]; + nibble = (byte) (buf[intOffset] & 0x0F); + buf[(short) (tsStartOff + 1)] = scratchPad[nibble]; + index++; + intOffset++; + tsStartOff += 2; + } + return tsPtr; + } + + private short getBootParams(byte bootParam, byte[] scratchPad) { + short value = KMType.INVALID_VALUE; + switch (bootParam) { + case OS_VERSION_ID: + value = storeDataInst.getOsVersion(); + break; + case SYSTEM_PATCH_LEVEL_ID: + value = storeDataInst.getOsPatch(); + break; + case BOOT_PATCH_LEVEL_ID: + value = storeDataInst.getBootPatchLevel(); + break; + case VENDOR_PATCH_LEVEL_ID: + value = storeDataInst.getVendorPatchLevel(); + break; + default: + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // Convert Integer to Text String for OS_VERSION. + if (bootParam == OS_VERSION_ID) { + value = converIntegerToTextString(value, scratchPad); + } + return value; + } + // ---------------------------------------------------------------------------- + + // ---------------------------------------------------------------------------- + // ECDH HKDF + private short ecdhHkdfDeriveKey( + byte[] privKeyA, + short privKeyAOff, + short privKeyALen, + byte[] pubKeyA, + short pubKeyAOff, + short pubKeyALen, + byte[] pubKeyB, + short pubKeyBOff, + short pubKeyBLen, + byte[] scratchPad) { + short key = + seProvider.ecdhKeyAgreement( + privKeyA, + privKeyAOff, + privKeyALen, + pubKeyB, + pubKeyBOff, + pubKeyBLen, + scratchPad, + (short) 0); + key = KMByteBlob.instance(scratchPad, (short) 0, key); + + // ignore 0x04 for ephemerical public key as kdfContext should not include 0x04. + pubKeyAOff += 1; + pubKeyALen -= 1; + pubKeyBOff += 1; + pubKeyBLen -= 1; + short kdfContext = + KMCose.constructKdfContext( + pubKeyA, pubKeyAOff, pubKeyALen, pubKeyB, pubKeyBOff, pubKeyBLen, true); + kdfContext = + KMKeymasterApplet.encodeToApduBuffer( + kdfContext, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + kdfContext = KMByteBlob.instance(scratchPad, (short) 0, kdfContext); + + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 32, (byte) 0); + seProvider.hkdf( + KMByteBlob.cast(key).getBuffer(), + KMByteBlob.cast(key).getStartOff(), + KMByteBlob.cast(key).length(), + scratchPad, + (short) 0, + (short) 32, + KMByteBlob.cast(kdfContext).getBuffer(), + KMByteBlob.cast(kdfContext).getStartOff(), + KMByteBlob.cast(kdfContext).length(), + scratchPad, + (short) 32, // offset + (short) 32 // Length of expected output. + ); + Util.arrayCopy(scratchPad, (short) 32, scratchPad, (short) 0, (short) 32); + return (short) 32; + } + + // ---------------------------------------------------------------------------- + // This function returns the instance of private key and It stores the public key in the + // data table for later usage. + private short generateEphemeralEcKey(byte[] scratchPad) { + // Generate ephemeral ec key. + rkpTmpVariables[0] = 0; + rkpTmpVariables[1] = 0; + seProvider.createAsymmetricKey( + KMType.EC, + scratchPad, + (short) 0, + (short) 128, + scratchPad, + (short) 128, + (short) 128, + rkpTmpVariables); + // Copy the ephemeral private key from scratch pad + short ptr = KMByteBlob.instance(rkpTmpVariables[0]); + Util.arrayCopyNonAtomic( + scratchPad, + (short) 0, + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + rkpTmpVariables[0]); + // Store ephemeral public key in data table for later usage. + short dataEntryIndex = createEntry(EPHEMERAL_PUB_KEY, rkpTmpVariables[1]); + Util.arrayCopyNonAtomic(scratchPad, (short) 128, data, dataEntryIndex, rkpTmpVariables[1]); + return ptr; + } + + private void initHmacOperation() { + short dataEntryIndex = getEntry(EPHEMERAL_MAC_KEY); + operation[0] = + seProvider.getRkpOperation( + KMType.SIGN, + KMType.HMAC, + KMType.SHA2_256, + KMType.PADDING_NONE, + (byte) 0, + data, + dataEntryIndex, + getEntryLength(EPHEMERAL_MAC_KEY), + null, + (short) 0, + (short) 0, + (short) 0); + if (operation[0] == null) { + KMException.throwIt(KMError.STATUS_FAILED); + } + } + + private void initAesGcmOperation(byte[] scratchPad, short nonce) { + // Generate Ephemeral mac key + short privKey = generateEphemeralEcKey(scratchPad); + short pubKeyIndex = getEntry(EPHEMERAL_PUB_KEY); + // Generate session key + short eekIndex = getEntry(EEK_KEY); + // Generate session key + short sessionKeyLen = + ecdhHkdfDeriveKey( + KMByteBlob.cast(privKey).getBuffer(), /* Ephemeral Private Key */ + KMByteBlob.cast(privKey).getStartOff(), + KMByteBlob.cast(privKey).length(), + data, /* Ephemeral Public key */ + pubKeyIndex, + getEntryLength(EPHEMERAL_PUB_KEY), + data, /* EEK Public key */ + eekIndex, + getEntryLength(EEK_KEY), + scratchPad /* scratchpad */); + // Initialize the Cipher object. + operation[0] = + seProvider.getRkpOperation( + KMType.ENCRYPT, + KMType.AES, + (byte) 0, + KMType.PADDING_NONE, + KMType.GCM, + scratchPad, /* key */ + (short) 0, + sessionKeyLen, + KMByteBlob.cast(nonce).getBuffer(), /* nonce */ + KMByteBlob.cast(nonce).getStartOff(), + KMByteBlob.cast(nonce).length(), + (short) (KMKeymasterApplet.AES_GCM_AUTH_TAG_LENGTH * 8)); + if (operation[0] == null) { + KMException.throwIt(KMError.STATUS_FAILED); + } + } + + private short processRecipientStructure(byte[] scratchPad) { + short protectedHeaderRecipient = + KMCose.constructHeaders( + rkpTmpVariables, + KMNInteger.uint_8(KMCose.COSE_ALG_ECDH_ES_HKDF_256), + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE); + // Encode the protected header as byte blob. + protectedHeaderRecipient = + KMKeymasterApplet.encodeToApduBuffer( + protectedHeaderRecipient, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + protectedHeaderRecipient = KMByteBlob.instance(scratchPad, (short) 0, protectedHeaderRecipient); + + /* Construct unprotected headers */ + short pubKeyIndex = getEntry(EPHEMERAL_PUB_KEY); + // prepare cosekey + short coseKey = + KMCose.constructCoseKey( + rkpTmpVariables, + KMInteger.uint_8(KMCose.COSE_KEY_TYPE_EC2), + KMType.INVALID_VALUE, + KMNInteger.uint_8(KMCose.COSE_ALG_ES256), + KMInteger.uint_8(KMCose.COSE_ECCURVE_256), + data, + pubKeyIndex, + getEntryLength(EPHEMERAL_PUB_KEY), + KMType.INVALID_VALUE, + false); + short keyIdentifierPtr = + KMByteBlob.instance(data, getEntry(EEK_KEY_ID), getEntryLength(EEK_KEY_ID)); + short unprotectedHeaderRecipient = + KMCose.constructHeaders( + rkpTmpVariables, KMType.INVALID_VALUE, keyIdentifierPtr, KMType.INVALID_VALUE, coseKey); + + // Construct recipients structure. + return KMCose.constructRecipientsStructure( + protectedHeaderRecipient, + unprotectedHeaderRecipient, + KMSimpleValue.instance(KMSimpleValue.NULL)); + } + + private short getAdditionalCertChainProcessedLength() { + short dataEntryIndex = getEntry(ACC_PROCESSED_LENGTH); + if (dataEntryIndex == 0) { + dataEntryIndex = createEntry(ACC_PROCESSED_LENGTH, SHORT_SIZE); + Util.setShort(data, dataEntryIndex, (short) 0); + return (short) 0; + } + return Util.getShort(data, dataEntryIndex); + } + + private void updateAdditionalCertChainProcessedLength(short processedLen) { + short dataEntryIndex = getEntry(ACC_PROCESSED_LENGTH); + Util.setShort(data, dataEntryIndex, processedLen); + } + + private short processAdditionalCertificateChain(byte[] scratchPad) { + byte[] persistedData = storeDataInst.getAdditionalCertChain(); + short totalAccLen = Util.getShort(persistedData, (short) 0); + if (totalAccLen == 0) { + // No Additional certificate chain present. + return 0; + } + short processedLen = getAdditionalCertChainProcessedLength(); + short lengthToSend = (short) (totalAccLen - processedLen); + if (lengthToSend > MAX_SEND_DATA) { + lengthToSend = MAX_SEND_DATA; + } + short cipherTextLen = + ((KMOperation) operation[0]) + .update(persistedData, (short) (2 + processedLen), lengthToSend, scratchPad, (short) 0); + processedLen += lengthToSend; + updateAdditionalCertChainProcessedLength(processedLen); + // Update the output processing state. + updateOutputProcessingState( + (processedLen == totalAccLen) ? PROCESSING_ACC_COMPLETE : PROCESSING_ACC_IN_PROGRESS); + return cipherTextLen; + } + + // BCC for STRONGBOX has chain length of 2. So it can be returned in a single go. + private short processBcc(byte[] scratchPad) { + // Construct BCC + boolean testMode = (TRUE == data[getEntry(TEST_MODE)]) ? true : false; + short len; + if (testMode) { + short bcc = KMKeymasterApplet.generateBcc(true, scratchPad); + len = + KMKeymasterApplet.encodeToApduBuffer( + bcc, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + } else { + byte[] bcc = storeDataInst.getBootCertificateChain(); + len = Util.getShort(bcc, (short) 0); + Util.arrayCopyNonAtomic(bcc, (short) 2, scratchPad, (short) 0, len); + } + short cipherTextLen = + ((KMOperation) operation[0]).update(scratchPad, (short) 0, len, scratchPad, len); + // move cipher text on scratch pad from starting position. + Util.arrayCopyNonAtomic(scratchPad, len, scratchPad, (short) 0, cipherTextLen); + createEntry(RESPONSE_PROCESSING_STATE, BYTE_SIZE); + // If there is no additional certificate chain present then put the state to + // PROCESSING_ACC_COMPLETE. + updateOutputProcessingState( + isAdditionalCertificateChainPresent() ? PROCESSING_BCC_COMPLETE : PROCESSING_ACC_COMPLETE); + return cipherTextLen; + } + + // AAD is the CoseEncrypt structure + private void processAesGcmUpdateAad(byte[] scratchPad) { + short protectedHeader = + KMCose.constructHeaders( + rkpTmpVariables, + KMInteger.uint_8(KMCose.COSE_ALG_AES_GCM_256), + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE); + // Encode the protected header as byte blob. + protectedHeader = + KMKeymasterApplet.encodeToApduBuffer( + protectedHeader, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + protectedHeader = KMByteBlob.instance(scratchPad, (short) 0, protectedHeader); + short coseEncryptStr = + KMCose.constructCoseEncryptStructure(protectedHeader, KMByteBlob.instance((short) 0)); + coseEncryptStr = + KMKeymasterApplet.encodeToApduBuffer( + coseEncryptStr, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + ((KMOperation) operation[0]).updateAAD(scratchPad, (short) 0, coseEncryptStr); + } + + private short processSignedMac(byte[] scratchPad, short pubKeysToSignMac, short deviceInfo) { + // Construct SignedMac + KMKey deviceUniqueKeyPair = + createDeviceUniqueKeyPair((TRUE == data[getEntry(TEST_MODE)]) ? true : false, scratchPad); + // Create signedMac + short signedMac = + createSignedMac(deviceUniqueKeyPair, scratchPad, deviceInfo, pubKeysToSignMac); + // Prepare partial data for encryption. + short arrLength = (short) (isAdditionalCertificateChainPresent() ? 3 : 2); + short arr = KMArray.instance(arrLength); + KMArray.cast(arr).add((short) 0, signedMac); + KMArray.cast(arr).add((short) 1, KMType.INVALID_VALUE); + if (arrLength == 3) { + KMArray.cast(arr).add((short) 2, KMType.INVALID_VALUE); + } + short len = + KMKeymasterApplet.encodeToApduBuffer( + arr, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + short cipherTextLen = + ((KMOperation) operation[0]).update(scratchPad, (short) 0, len, scratchPad, len); + Util.arrayCopyNonAtomic(scratchPad, len, scratchPad, (short) 0, cipherTextLen); + return cipherTextLen; + } + + private short getCoseEncryptProtectedHeader(byte[] scratchPad) { + // CoseEncrypt protected headers. + short protectedHeader = + KMCose.constructHeaders( + rkpTmpVariables, + KMInteger.uint_8(KMCose.COSE_ALG_AES_GCM_256), + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE); + // Encode the protected header as byte blob. + protectedHeader = + KMKeymasterApplet.encodeToApduBuffer( + protectedHeader, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + return KMByteBlob.instance(scratchPad, (short) 0, protectedHeader); + } + + private short getCoseEncryptUnprotectedHeader(byte[] scratchPad, short nonce) { + /* CoseEncrypt unprotected headers */ + return KMCose.constructHeaders( + rkpTmpVariables, KMType.INVALID_VALUE, KMType.INVALID_VALUE, nonce, KMType.INVALID_VALUE); + } + + private short constructCoseMacForRkpKey(boolean testMode, byte[] scratchPad, short pubKey) { + // prepare cosekey + short coseKey = + KMCose.constructCoseKey( + rkpTmpVariables, + KMInteger.uint_8(KMCose.COSE_KEY_TYPE_EC2), + KMType.INVALID_VALUE, + KMNInteger.uint_8(KMCose.COSE_ALG_ES256), + KMInteger.uint_8(KMCose.COSE_ECCURVE_256), + KMByteBlob.cast(pubKey).getBuffer(), + KMByteBlob.cast(pubKey).getStartOff(), + KMByteBlob.cast(pubKey).length(), + KMType.INVALID_VALUE, + testMode); + // Encode the cose key and make it as payload. + short len = + KMKeymasterApplet.encodeToApduBuffer( + coseKey, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + short payload = KMByteBlob.instance(scratchPad, (short) 0, len); + // Prepare protected header, which is required to construct the COSE_MAC0 + short headerPtr = + KMCose.constructHeaders( + rkpTmpVariables, + KMInteger.uint_8(KMCose.COSE_ALG_HMAC_256), + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE); + // Encode the protected header as byte blob. + len = + KMKeymasterApplet.encodeToApduBuffer( + headerPtr, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + short protectedHeader = KMByteBlob.instance(scratchPad, (short) 0, len); + // create MAC_Structure + short macStructure = + KMCose.constructCoseMacStructure(protectedHeader, KMByteBlob.instance((short) 0), payload); + // Encode the Mac_structure and do HMAC_Sign to produce the tag for COSE_MAC0 + len = + KMKeymasterApplet.encodeToApduBuffer( + macStructure, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + // HMAC Sign. + short hmacLen = rkpHmacSign(testMode, scratchPad, (short) 0, len, scratchPad, len); + // Create COSE_MAC0 object + short coseMac0 = + KMCose.constructCoseMac0( + protectedHeader, + KMCoseHeaders.instance(KMArray.instance((short) 0)), + payload, + KMByteBlob.instance(scratchPad, len, hmacLen)); + len = + KMKeymasterApplet.encodeToApduBuffer( + coseMac0, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + return KMByteBlob.instance(scratchPad, (short) 0, len); + } + + private short getEcAttestKeyParameters() { + short tagIndex = 0; + short arrPtr = KMArray.instance((short) 6); + // Key size - 256 + short keySize = + KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, KMInteger.uint_16((short) 256)); + // Digest - SHA256 + short byteBlob = KMByteBlob.instance((short) 1); + KMByteBlob.cast(byteBlob).add((short) 0, KMType.SHA2_256); + short digest = KMEnumArrayTag.instance(KMType.DIGEST, byteBlob); + // Purpose - Attest + byteBlob = KMByteBlob.instance((short) 1); + KMByteBlob.cast(byteBlob).add((short) 0, KMType.ATTEST_KEY); + short purpose = KMEnumArrayTag.instance(KMType.PURPOSE, byteBlob); + + KMArray.cast(arrPtr).add(tagIndex++, purpose); + // Algorithm - EC + KMArray.cast(arrPtr).add(tagIndex++, KMEnumTag.instance(KMType.ALGORITHM, KMType.EC)); + KMArray.cast(arrPtr).add(tagIndex++, keySize); + KMArray.cast(arrPtr).add(tagIndex++, digest); + // Curve - P256 + KMArray.cast(arrPtr).add(tagIndex++, KMEnumTag.instance(KMType.ECCURVE, KMType.P_256)); + // No Authentication is required to use this key. + KMArray.cast(arrPtr).add(tagIndex, KMBoolTag.instance(KMType.NO_AUTH_REQUIRED)); + return KMKeyParameters.instance(arrPtr); + } + + private boolean isSignedByte(byte b) { + return ((b & 0x0080) != 0); + } + + private short writeIntegerHeader(short valueLen, byte[] data, short offset) { + // write length + data[offset] = (byte) valueLen; + // write INTEGER tag + offset--; + data[offset] = 0x02; + return offset; + } + + private short writeSequenceHeader(short valueLen, byte[] data, short offset) { + // write length + data[offset] = (byte) valueLen; + // write INTEGER tag + offset--; + data[offset] = 0x30; + return offset; + } + + private short writeSignatureData( + byte[] input, short inputOff, short inputlen, byte[] output, short offset) { + Util.arrayCopyNonAtomic(input, inputOff, output, offset, inputlen); + if (isSignedByte(input[inputOff])) { + offset--; + output[offset] = (byte) 0; + } + return offset; + } + + public short encodeES256CoseSignSignature( + byte[] input, short offset, short len, byte[] scratchPad, short scratchPadOff) { + // SEQ [ INTEGER(r), INTEGER(s)] + // write from bottom to the top + if (len != 64) { + KMException.throwIt(KMError.INVALID_DATA); + } + short maxTotalLen = 72; + short end = (short) (scratchPadOff + maxTotalLen); + // write s. + short start = (short) (end - 32); + start = writeSignatureData(input, (short) (offset + 32), (short) 32, scratchPad, start); + // write length and header + short length = (short) (end - start); + start--; + start = writeIntegerHeader(length, scratchPad, start); + // write r + short rEnd = start; + start = (short) (start - 32); + start = writeSignatureData(input, offset, (short) 32, scratchPad, start); + // write length and header + length = (short) (rEnd - start); + start--; + start = writeIntegerHeader(length, scratchPad, start); + // write length and sequence header + length = (short) (end - start); + start--; + start = writeSequenceHeader(length, scratchPad, start); + length = (short) (end - start); + if (start > scratchPadOff) { + // re adjust the buffer + Util.arrayCopyNonAtomic(scratchPad, start, scratchPad, scratchPadOff, length); + } + return length; + } + + private short rkpHmacSign( + boolean testMode, + byte[] data, + short dataStart, + short dataLength, + byte[] signature, + short signatureStart) { + short result; + if (testMode) { + short macKey = KMByteBlob.instance(EPHEMERAL_MAC_KEY_SIZE); + Util.arrayFillNonAtomic( + KMByteBlob.cast(macKey).getBuffer(), + KMByteBlob.cast(macKey).getStartOff(), + EPHEMERAL_MAC_KEY_SIZE, + (byte) 0); + result = + seProvider.hmacSign( + KMByteBlob.cast(macKey).getBuffer(), + KMByteBlob.cast(macKey).getStartOff(), + EPHEMERAL_MAC_KEY_SIZE, + data, + dataStart, + dataLength, + signature, + signatureStart); + } else { + result = + seProvider.hmacSign( + storeDataInst.getRkpMacKey(), data, dataStart, dataLength, signature, signatureStart); + } + return result; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMRepository.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMRepository.java new file mode 100644 index 0000000..9fd2406 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMRepository.java @@ -0,0 +1,130 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; + +/** + * KMRepository class manages volatile memory usage by the applet. Note the repository is only used + * by applet and it is not intended to be used by seProvider. + */ +public class KMRepository { + + // The maximum available heap memory. + public static final short HEAP_SIZE = 10000; + // Index pointing from the back of heap. + private static short[] reclaimIndex; + // Singleton instance + private static KMRepository repository; + // Heap buffer + private byte[] heap; + // Index to the heap buffer. + private short[] heapIndex; + + public KMRepository(boolean isUpgrading) { + heap = JCSystem.makeTransientByteArray(HEAP_SIZE, JCSystem.CLEAR_ON_RESET); + heapIndex = JCSystem.makeTransientShortArray((short) 1, JCSystem.CLEAR_ON_RESET); + reclaimIndex = JCSystem.makeTransientShortArray((short) 1, JCSystem.CLEAR_ON_RESET); + reclaimIndex[0] = HEAP_SIZE; + repository = this; + } + + public static KMRepository instance() { + return repository; + } + + public void onUninstall() { + // Javacard Runtime environment cleans up the data. + + } + + public void onProcess() {} + + public void clean() { + Util.arrayFillNonAtomic(heap, (short) 0, HEAP_SIZE, (byte) 0); + heapIndex[0] = 0; + reclaimIndex[0] = HEAP_SIZE; + } + + public void onDeselect() {} + + public void onSelect() { + // If write through caching is implemented then this method will restore the data into cache + } + + // This function uses memory from the back of the heap(transient memory). Call + // reclaimMemory function immediately after the use. + public short allocReclaimableMemory(short length) { + if ((((short) (reclaimIndex[0] - length)) <= heapIndex[0]) || (length >= HEAP_SIZE / 2)) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + reclaimIndex[0] -= length; + return reclaimIndex[0]; + } + + // Reclaims the memory back. + public void reclaimMemory(short length) { + if (reclaimIndex[0] < heapIndex[0]) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + Util.arrayFillNonAtomic(heap, reclaimIndex[0], length, (byte) 0); + reclaimIndex[0] += length; + } + + public short allocAvailableMemory() { + if (heapIndex[0] >= heap.length) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short index = heapIndex[0]; + heapIndex[0] = reclaimIndex[0]; + return index; + } + + public short alloc(short length) { + if ((((short) (heapIndex[0] + length)) > heap.length) + || (((short) (heapIndex[0] + length)) > reclaimIndex[0])) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + heapIndex[0] += length; + return (short) (heapIndex[0] - length); + } + + public byte[] getHeap() { + return heap; + } + + public short getHeapIndex() { + return heapIndex[0]; + } + + // Use this function to reset the heapIndex to its previous state. + // Some of the data might be lost so use it carefully. + public void setHeapIndex(short offset) { + if (offset > heapIndex[0] || offset < 0) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + Util.arrayFillNonAtomic(heap, offset, (short) (heapIndex[0] - offset), (byte) 0); + heapIndex[0] = offset; + } + + public short getHeapReclaimIndex() { + return reclaimIndex[0]; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMSemanticTag.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMSemanticTag.java new file mode 100644 index 0000000..07b2675 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMSemanticTag.java @@ -0,0 +1,80 @@ +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMSemanticTag corresponds to CBOR type of tagged item. The structure is defined as struct{byte + * SEMANTIC_TAG_TYPE; short length; tag, short ptr }. Tag is INTEGER_TYPE and the possible values + * are defined here https://www.rfc-editor.org/rfc/rfc7049#section-2.4 + */ +public class KMSemanticTag extends KMType { + + public static final short COSE_MAC_SEMANTIC_TAG = (short) 0x0011; + public static final short ROT_SEMANTIC_TAG = (short) 0x9C41; + private static KMSemanticTag prototype; + + private KMSemanticTag() {} + + private static KMSemanticTag proto(short ptr) { + if (prototype == null) { + prototype = new KMSemanticTag(); + } + instanceTable[KM_SEMANTIC_TAG_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp(short valuePtr) { + short ptr = KMType.instance(SEMANTIC_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), KMInteger.exp()); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), valuePtr); + return ptr; + } + + public static KMSemanticTag cast(short ptr) { + if (heap[ptr] != SEMANTIC_TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static short instance(short tag, short value) { + if (!isSemanticTagSupported(tag)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + // The maximum tag size can be UINT32. Currently, we support + // only two tags which are short. + short ptr = KMType.instance(SEMANTIC_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), tag); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), value); + return ptr; + } + + private static boolean isSemanticTagSupported(short tag) { + tag = KMInteger.cast(tag).getShort(); + switch (tag) { + case COSE_MAC_SEMANTIC_TAG: + case ROT_SEMANTIC_TAG: + break; + default: + return false; + } + return true; + } + + public short length() { + return Util.getShort(heap, (short) (instanceTable[KM_SEMANTIC_TAG_OFFSET] + 1)); + } + + public short getKeyPtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_SEMANTIC_TAG_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short getValuePtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_SEMANTIC_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMSimpleValue.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMSimpleValue.java new file mode 100644 index 0000000..6dffd73 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMSimpleValue.java @@ -0,0 +1,71 @@ +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMSimpleValue corresponds to CBOR type of Simple value. It holds either true, false or NULL + * values. The structure is defined as struct{byte SIMPLE_VALUE_TYPE; short length; simple value } + */ +public class KMSimpleValue extends KMType { + + public static final byte FALSE = (byte) 20; + public static final byte TRUE = (byte) 21; + public static final byte NULL = (byte) 22; + private static KMSimpleValue prototype; + + private KMSimpleValue() {} + + private static KMSimpleValue proto(short ptr) { + if (prototype == null) { + prototype = new KMSimpleValue(); + } + instanceTable[KM_SIMPLE_VALUE_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + return KMType.exp(SIMPLE_VALUE_TYPE); + } + + public static KMSimpleValue cast(short ptr) { + if (heap[ptr] != SIMPLE_VALUE_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (!isSimpleValueValid(heap[(short) (ptr + 3)])) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static short instance(byte value) { + if (!isSimpleValueValid(value)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = KMType.instance(SIMPLE_VALUE_TYPE, (short) 1); + heap[(short) (ptr + 3)] = value; + return ptr; + } + + private static boolean isSimpleValueValid(byte value) { + switch (value) { + case TRUE: + case FALSE: + case NULL: + break; + default: + return false; + } + return true; + } + + public short length() { + return Util.getShort(heap, (short) (instanceTable[KM_SIMPLE_VALUE_OFFSET] + 1)); + } + + public byte getValue() { + return heap[(short) (instanceTable[KM_SIMPLE_VALUE_OFFSET] + 3)]; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMTag.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMTag.java new file mode 100644 index 0000000..3033a70 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMTag.java @@ -0,0 +1,102 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import com.android.javacard.seprovider.KMException; +import javacard.framework.Util; + +/** + * This class represents a tag as defined by keymaster hal specifications. It is composed of key + * value pair. The key consists of short tag type e.g. KMType.ENUM and short tag key e.g. + * KMType.ALGORITHM. The key is encoded as uint CBOR type with 4 bytes. This is followed by value + * which can be any CBOR type based on key. struct{byte tag=KMType.TAG_TYPE, short length, value) + * where value is subtype of KMTag i.e. struct{short tagType=one of tag types declared in KMType , + * short tagKey=one of the tag keys declared in KMType, value} where value is one of the sub-types + * of KMType. + */ +public class KMTag extends KMType { + + public static short getTagType(short ptr) { + return Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + } + + public static short getKey(short ptr) { + return Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2)); + } + + public static void assertPresence(short params, short tagType, short tagKey, short error) { + if (!isPresent(params, tagType, tagKey)) { + KMException.throwIt(error); + } + } + + public static void assertAbsence(short params, short tagType, short tagKey, short error) { + if (isPresent(params, tagType, tagKey)) { + KMException.throwIt(error); + } + } + + public static boolean isPresent(short params, short tagType, short tagKey) { + short tag = KMKeyParameters.findTag(tagType, tagKey, params); + return tag != KMType.INVALID_VALUE; + } + + public static boolean isEqual(short params, short tagType, short tagKey, short value) { + switch (tagType) { + case KMType.ENUM_TAG: + return KMEnumTag.getValue(tagKey, params) == value; + case KMType.UINT_TAG: + case KMType.DATE_TAG: + case KMType.ULONG_TAG: + return KMIntegerTag.isEqual(params, tagType, tagKey, value); + case KMType.ENUM_ARRAY_TAG: + return KMEnumArrayTag.contains(tagKey, value, params); + case KMType.UINT_ARRAY_TAG: + case KMType.ULONG_ARRAY_TAG: + return KMIntegerArrayTag.contains(tagKey, value, params); + } + return false; + } + + public static void assertTrue(boolean condition, short error) { + if (!condition) { + KMException.throwIt(error); + } + } + + public static boolean isValidPublicExponent(short params) { + short pubExp = KMKeyParameters.findTag(KMType.ULONG_TAG, KMType.RSA_PUBLIC_EXPONENT, params); + if (pubExp == KMType.INVALID_VALUE) { + return false; + } + pubExp = KMIntegerTag.cast(pubExp).getValue(); + if (!(KMInteger.cast(pubExp).getShort() == 0x01 + && KMInteger.cast(pubExp).getSignificantShort() == 0x01)) { + return false; + } + return true; + } + + public static boolean isValidKeySize(short params) { + short keysize = KMKeyParameters.findTag(KMType.UINT_TAG, KMType.KEYSIZE, params); + if (keysize == KMType.INVALID_VALUE) { + return false; + } + short alg = KMEnumTag.getValue(KMType.ALGORITHM, params); + return KMIntegerTag.cast(keysize).isValidKeySize((byte) alg); + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMTextString.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMTextString.java new file mode 100644 index 0000000..80aebd2 --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMTextString.java @@ -0,0 +1,80 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMTextString represents contiguous block of bytes. It corresponds to CBOR type of Text String. It + * extends KMByteBlob by specifying value field as zero or more sequence of bytes. struct{ byte + * TEXT_STR_TYPE; short length; sequence of bytes} + */ +public class KMTextString extends KMByteBlob { + + private static byte OFFSET_SIZE = 2; + + private static KMTextString prototype; + + private KMTextString() {} + + private static KMTextString proto(short ptr) { + if (prototype == null) { + prototype = new KMTextString(); + } + instanceTable[KM_TEXT_STRING_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + return KMType.exp(TEXT_STRING_TYPE); + } + + // return an empty byte blob instance + public static short instance(short length) { + short ptr = KMType.instance(TEXT_STRING_TYPE, (short) (length + OFFSET_SIZE)); + Util.setShort( + heap, (short) (ptr + TLV_HEADER_SIZE), (short) (ptr + TLV_HEADER_SIZE + OFFSET_SIZE)); + Util.setShort(heap, (short) (ptr + 1), length); + return ptr; + } + + // byte blob from existing buf + public static short instance(byte[] buf, short startOff, short length) { + short ptr = instance(length); + Util.arrayCopyNonAtomic( + buf, startOff, heap, (short) (ptr + TLV_HEADER_SIZE + OFFSET_SIZE), length); + return ptr; + } + + // cast the ptr to KMTextString + public static KMTextString cast(short ptr) { + if (heap[ptr] != TEXT_STRING_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + 1)) == INVALID_VALUE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + protected short getBaseOffset() { + return instanceTable[KM_TEXT_STRING_OFFSET]; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMType.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMType.java new file mode 100644 index 0000000..a18cb7f --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMType.java @@ -0,0 +1,407 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; + +/** + * This class declares all types, tag types, and tag keys. It also establishes basic structure of + * any KMType i.e. struct{byte type, short length, value} where value can any of the KMType. Also, + * KMType refers to transient memory heap in the repository. Finally KMType's subtypes are singleton + * prototype objects which just cast the structure over contiguous memory buffer. + */ +public abstract class KMType { + + public static final short INVALID_VALUE = (short) 0x8000; + // Types + public static final byte BYTE_BLOB_TYPE = 0x01; + public static final byte INTEGER_TYPE = 0x02; + public static final byte ENUM_TYPE = 0x03; + public static final byte TAG_TYPE = 0x04; + public static final byte ARRAY_TYPE = 0x05; + public static final byte KEY_PARAM_TYPE = 0x06; + public static final byte KEY_CHAR_TYPE = 0x07; + public static final byte HW_AUTH_TOKEN_TYPE = 0x08; + public static final byte VERIFICATION_TOKEN_TYPE = 0x09; + public static final byte HMAC_SHARING_PARAM_TYPE = 0x0A; + public static final byte X509_CERT = 0x0B; + public static final byte NEG_INTEGER_TYPE = 0x0C; + public static final byte TEXT_STRING_TYPE = 0x0D; + public static final byte MAP_TYPE = 0x0E; + public static final byte COSE_KEY_TYPE = 0x0F; + public static final byte COSE_PAIR_TAG_TYPE = 0x10; + public static final byte COSE_PAIR_INT_TAG_TYPE = 0x20; + public static final byte COSE_PAIR_NEG_INT_TAG_TYPE = 0x30; + public static final byte COSE_PAIR_BYTE_BLOB_TAG_TYPE = 0x40; + public static final byte COSE_PAIR_COSE_KEY_TAG_TYPE = 0x60; + public static final byte COSE_PAIR_SIMPLE_VALUE_TAG_TYPE = 0x70; + public static final byte COSE_PAIR_TEXT_STR_TAG_TYPE = (byte) 0x80; + public static final byte SIMPLE_VALUE_TYPE = (byte) 0x90; + public static final byte COSE_HEADERS_TYPE = (byte) 0xA0; + public static final byte COSE_CERT_PAYLOAD_TYPE = (byte) 0xB0; + public static final byte SEMANTIC_TAG_TYPE = (byte) 0xC0; + // Tag Types + public static final short INVALID_TAG = 0x0000; + public static final short ENUM_TAG = 0x1000; + public static final short ENUM_ARRAY_TAG = 0x2000; + public static final short UINT_TAG = 0x3000; + public static final short UINT_ARRAY_TAG = 0x4000; + public static final short ULONG_TAG = 0x5000; + public static final short DATE_TAG = 0x6000; + public static final short BOOL_TAG = 0x7000; + public static final short BIGNUM_TAG = (short) 0x8000; + public static final short BYTES_TAG = (short) 0x9000; + public static final short ULONG_ARRAY_TAG = (short) 0xA000; + public static final short TAG_TYPE_MASK = (short) 0xF000; + + // Enum Tag + // Internal tags + public static final short RULE = 0x7FFF; + public static final byte IGNORE_INVALID_TAGS = 0x00; + public static final byte FAIL_ON_INVALID_TAGS = 0x01; + + // Algorithm Enum Tag key and values + public static final short ALGORITHM = 0x0002; + public static final byte RSA = 0x01; + public static final byte DES = 0x21; + public static final byte EC = 0x03; + public static final byte AES = 0x20; + public static final byte HMAC = (byte) 0x80; + + // EcCurve Enum Tag key and values. + public static final short ECCURVE = 0x000A; + public static final byte P_224 = 0x00; + public static final byte P_256 = 0x01; + public static final byte P_384 = 0x02; + public static final byte P_521 = 0x03; + public static final byte CURVE_25519 = 0x04; + + // KeyBlobUsageRequirements Enum Tag key and values. + public static final short BLOB_USAGE_REQ = 0x012D; + public static final byte STANDALONE = 0x00; + public static final byte REQUIRES_FILE_SYSTEM = 0x01; + + // HardwareAuthenticatorType Enum Tag key and values. + public static final short USER_AUTH_TYPE = 0x01F8; + public static final byte USER_AUTH_NONE = 0x00; + public static final byte PASSWORD = 0x01; + public static final byte FINGERPRINT = 0x02; + public static final byte BOTH = 0x03; + // have to be power of 2 + public static final byte ANY = (byte) 0xFF; + + // Origin Enum Tag key and values. + public static final short ORIGIN = 0x02BE; + public static final byte GENERATED = 0x00; + public static final byte DERIVED = 0x01; + public static final byte IMPORTED = 0x02; + public static final byte UNKNOWN = 0x03; + public static final byte SECURELY_IMPORTED = 0x04; + + // Hardware Type tag key and values + public static final short HARDWARE_TYPE = 0x0130; + public static final byte SOFTWARE = 0x00; + public static final byte TRUSTED_ENVIRONMENT = 0x01; + public static final byte STRONGBOX = 0x02; + + // No Tag + // Derivation Function - No Tag defined + public static final short KEY_DERIVATION_FUNCTION = (short) 0xF001; + public static final byte DERIVATION_NONE = 0x00; + public static final byte RFC5869_SHA256 = 0x01; + public static final byte ISO18033_2_KDF1_SHA1 = 0x02; + public static final byte ISO18033_2_KDF1_SHA256 = 0x03; + public static final byte ISO18033_2_KDF2_SHA1 = 0x04; + public static final byte ISO18033_2_KDF2_SHA256 = 0x05; + + // KeyFormat - No Tag defined. + public static final short KEY_FORMAT = (short) 0xF002; + public static final byte X509 = 0x00; + public static final byte PKCS8 = 0x01; + public static final byte RAW = 0x03; + + // Verified Boot State + public static final short VERIFIED_BOOT_STATE = (short) 0xF003; + public static final byte VERIFIED_BOOT = 0x00; + public static final byte SELF_SIGNED_BOOT = 0x01; + public static final byte UNVERIFIED_BOOT = 0x02; + public static final byte FAILED_BOOT = 0x03; + + // Device Locked + public static final short DEVICE_LOCKED = (short) 0xF006; + public static final byte DEVICE_LOCKED_TRUE = 0x01; + public static final byte DEVICE_LOCKED_FALSE = 0x00; + + // Enum Array Tag + // Purpose + public static final short PURPOSE = 0x0001; + public static final byte ENCRYPT = 0x00; + public static final byte DECRYPT = 0x01; + public static final byte SIGN = 0x02; + public static final byte VERIFY = 0x03; + public static final byte DERIVE_KEY = 0x04; + public static final byte WRAP_KEY = 0x05; + public static final byte AGREE_KEY = 0x06; + public static final byte ATTEST_KEY = (byte) 0x07; + // Block mode + public static final short BLOCK_MODE = 0x0004; + public static final byte ECB = 0x01; + public static final byte CBC = 0x02; + public static final byte CTR = 0x03; + public static final byte GCM = 0x20; + + // Digest + public static final short DIGEST = 0x0005; + public static final byte DIGEST_NONE = 0x00; + public static final byte MD5 = 0x01; + public static final byte SHA1 = 0x02; + public static final byte SHA2_224 = 0x03; + public static final byte SHA2_256 = 0x04; + public static final byte SHA2_384 = 0x05; + public static final byte SHA2_512 = 0x06; + + // Padding mode + public static final short PADDING = 0x0006; + public static final byte PADDING_NONE = 0x01; + public static final byte RSA_OAEP = 0x02; + public static final byte RSA_PSS = 0x03; + public static final byte RSA_PKCS1_1_5_ENCRYPT = 0x04; + public static final byte RSA_PKCS1_1_5_SIGN = 0x05; + public static final byte PKCS7 = 0x40; + + // OAEP MGF Digests - only SHA-1 is supported in Javacard + public static final short RSA_OAEP_MGF_DIGEST = 0xCB; + + // Integer Tag - UINT, ULONG and DATE + // UINT tags + // Keysize + public static final short KEYSIZE = 0x0003; + // Min Mac Length + public static final short MIN_MAC_LENGTH = 0x0008; + // Min Seconds between OPS + public static final short MIN_SEC_BETWEEN_OPS = 0x0193; + // Max Uses per Boot + public static final short MAX_USES_PER_BOOT = 0x0194; + // UserId + public static final short USERID = 0x01F5; + // Auth Timeout + public static final short AUTH_TIMEOUT = 0x01F9; + // Auth Timeout in Milliseconds + public static final short AUTH_TIMEOUT_MILLIS = 0x7FFF; + // OS Version + public static final short OS_VERSION = 0x02C1; + // OS Patch Level + public static final short OS_PATCH_LEVEL = 0x02C2; + // Vendor Patch Level + public static final short VENDOR_PATCH_LEVEL = 0x02CE; + // Boot Patch Level + public static final short BOOT_PATCH_LEVEL = 0x02CF; + // Mac Length + public static final short MAC_LENGTH = 0x03EB; + // Usage Count Limit + public static final short USAGE_COUNT_LIMIT = 0x195; + + // ULONG tags + // RSA Public Exponent + public static final short RSA_PUBLIC_EXPONENT = 0x00C8; + + // DATE tags + public static final short ACTIVE_DATETIME = 0x0190; + public static final short ORIGINATION_EXPIRE_DATETIME = 0x0191; + public static final short USAGE_EXPIRE_DATETIME = 0x0192; + public static final short CREATION_DATETIME = 0x02BD; + ; + public static final short CERTIFICATE_NOT_BEFORE = 0x03F0; + public static final short CERTIFICATE_NOT_AFTER = 0x03F1; + // Integer Array Tags - ULONG_REP and UINT_REP. + // User Secure Id + public static final short USER_SECURE_ID = (short) 0x01F6; + + // Boolean Tag + // Caller Nonce + public static final short CALLER_NONCE = (short) 0x0007; + // Include Unique Id + public static final short INCLUDE_UNIQUE_ID = (short) 0x00CA; + // Bootloader Only + public static final short BOOTLOADER_ONLY = (short) 0x012E; + // Rollback Resistance + public static final short ROLLBACK_RESISTANCE = (short) 0x012F; + // No Auth Required + public static final short NO_AUTH_REQUIRED = (short) 0x01F7; + // Allow While On Body + public static final short ALLOW_WHILE_ON_BODY = (short) 0x01FA; + // Max Boot Level + public static final short MAX_BOOT_LEVEL = (short) 0x03F2; + // Trusted User Presence Required + public static final short TRUSTED_USER_PRESENCE_REQUIRED = (short) 0x01FB; + // Trusted Confirmation Required + public static final short TRUSTED_CONFIRMATION_REQUIRED = (short) 0x01FC; + // Unlocked Device Required + public static final short UNLOCKED_DEVICE_REQUIRED = (short) 0x01FD; + // Reset Since Id Rotation + public static final short RESET_SINCE_ID_ROTATION = (short) 0x03EC; + // Early boot ended. + public static final short EARLY_BOOT_ONLY = (short) 0x0131; + // Device unique attestation. + public static final short DEVICE_UNIQUE_ATTESTATION = (short) 0x02D0; + + // Byte Tag + // Application Id + public static final short APPLICATION_ID = (short) 0x0259; + // Application Data + public static final short APPLICATION_DATA = (short) 0x02BC; + // Root Of Trust + public static final short ROOT_OF_TRUST = (short) 0x02C0; + // Unique Id + public static final short UNIQUE_ID = (short) 0x02C3; + // Attestation Challenge + public static final short ATTESTATION_CHALLENGE = (short) 0x02C4; + // Attestation Application Id + public static final short ATTESTATION_APPLICATION_ID = (short) 0x02C5; + // Attestation Id Brand + public static final short ATTESTATION_ID_BRAND = (short) 0x02C6; + // Attestation Id Device + public static final short ATTESTATION_ID_DEVICE = (short) 0x02C7; + // Attestation Id Product + public static final short ATTESTATION_ID_PRODUCT = (short) 0x02C8; + // Attestation Id Serial + public static final short ATTESTATION_ID_SERIAL = (short) 0x02C9; + // Attestation Id IMEI + public static final short ATTESTATION_ID_IMEI = (short) 0x02CA; + // Attestation Id MEID + public static final short ATTESTATION_ID_MEID = (short) 0x02CB; + // Attestation Id Manufacturer + public static final short ATTESTATION_ID_MANUFACTURER = (short) 0x02CC; + // Attestation Id Model + public static final short ATTESTATION_ID_MODEL = (short) 0x02CD; + // Associated Data + public static final short ASSOCIATED_DATA = (short) 0x03E8; + // Nonce + public static final short NONCE = (short) 0x03E9; + // Confirmation Token + public static final short CONFIRMATION_TOKEN = (short) 0x03ED; + // Serial Number - this is a big num but in applet we handle it as byte blob + public static final short CERTIFICATE_SERIAL_NUM = (short) 0x03EE; + // Subject Name + public static final short CERTIFICATE_SUBJECT_NAME = (short) 0x03EF; + + public static final short LENGTH_FROM_PDU = (short) 0xFFFF; + + public static final byte NO_VALUE = (byte) 0xff; + // Support Curves for Eek Chain validation. + public static final byte RKP_CURVE_P256 = 1; + // Type offsets. + public static final byte KM_TYPE_BASE_OFFSET = 0; + public static final byte KM_ARRAY_OFFSET = KM_TYPE_BASE_OFFSET; + public static final byte KM_BOOL_TAG_OFFSET = KM_TYPE_BASE_OFFSET + 1; + public static final byte KM_BYTE_BLOB_OFFSET = KM_TYPE_BASE_OFFSET + 2; + public static final byte KM_BYTE_TAG_OFFSET = KM_TYPE_BASE_OFFSET + 3; + public static final byte KM_ENUM_OFFSET = KM_TYPE_BASE_OFFSET + 4; + public static final byte KM_ENUM_ARRAY_TAG_OFFSET = KM_TYPE_BASE_OFFSET + 5; + public static final byte KM_ENUM_TAG_OFFSET = KM_TYPE_BASE_OFFSET + 6; + public static final byte KM_HARDWARE_AUTH_TOKEN_OFFSET = KM_TYPE_BASE_OFFSET + 7; + public static final byte KM_HMAC_SHARING_PARAMETERS_OFFSET = KM_TYPE_BASE_OFFSET + 8; + public static final byte KM_INTEGER_OFFSET = KM_TYPE_BASE_OFFSET + 9; + public static final byte KM_INTEGER_ARRAY_TAG_OFFSET = KM_TYPE_BASE_OFFSET + 10; + public static final byte KM_INTEGER_TAG_OFFSET = KM_TYPE_BASE_OFFSET + 11; + public static final byte KM_KEY_CHARACTERISTICS_OFFSET = KM_TYPE_BASE_OFFSET + 12; + public static final byte KM_KEY_PARAMETERS_OFFSET = KM_TYPE_BASE_OFFSET + 13; + public static final byte KM_VERIFICATION_TOKEN_OFFSET = KM_TYPE_BASE_OFFSET + 14; + public static final byte KM_NEG_INTEGER_OFFSET = KM_TYPE_BASE_OFFSET + 15; + public static final byte KM_TEXT_STRING_OFFSET = KM_TYPE_BASE_OFFSET + 16; + public static final byte KM_MAP_OFFSET = KM_TYPE_BASE_OFFSET + 17; + public static final byte KM_COSE_KEY_OFFSET = KM_TYPE_BASE_OFFSET + 18; + public static final byte KM_COSE_KEY_INT_VAL_OFFSET = KM_TYPE_BASE_OFFSET + 19; + public static final byte KM_COSE_KEY_NINT_VAL_OFFSET = KM_TYPE_BASE_OFFSET + 20; + public static final byte KM_COSE_KEY_BYTE_BLOB_VAL_OFFSET = KM_TYPE_BASE_OFFSET + 21; + public static final byte KM_COSE_KEY_COSE_KEY_VAL_OFFSET = KM_TYPE_BASE_OFFSET + 22; + public static final byte KM_COSE_KEY_SIMPLE_VAL_OFFSET = KM_TYPE_BASE_OFFSET + 23; + public static final byte KM_SIMPLE_VALUE_OFFSET = KM_TYPE_BASE_OFFSET + 24; + public static final byte KM_COSE_HEADERS_OFFSET = KM_TYPE_BASE_OFFSET + 25; + public static final byte KM_COSE_KEY_TXT_STR_VAL_OFFSET = KM_TYPE_BASE_OFFSET + 26; + public static final byte KM_COSE_CERT_PAYLOAD_OFFSET = KM_TYPE_BASE_OFFSET + 27; + public static final byte KM_BIGNUM_TAG_OFFSET = KM_TYPE_BASE_OFFSET + 28; + public static final byte KM_SEMANTIC_TAG_OFFSET = KM_TYPE_BASE_OFFSET + 29; + + // Attestation types + public static final byte NO_CERT = 0; + public static final byte ATTESTATION_CERT = 1; + public static final byte SELF_SIGNED_CERT = 2; + public static final byte FAKE_CERT = 3; + // Buffering Mode + public static final byte BUF_NONE = 0; + public static final byte BUF_RSA_DECRYPT_OR_NO_DIGEST = 1; + public static final byte BUF_EC_NO_DIGEST = 2; + public static final byte BUF_AES_ENCRYPT_PKCS7_BLOCK_ALIGN = 3; + public static final byte BUF_AES_DECRYPT_PKCS7_BLOCK_ALIGN = 4; + public static final byte BUF_DES_ENCRYPT_PKCS7_BLOCK_ALIGN = 5; + public static final byte BUF_DES_DECRYPT_PKCS7_BLOCK_ALIGN = 6; + public static final byte BUF_AES_GCM_DECRYPT_BLOCK_ALIGN = 7; + + // MAX ApplicationID or Application Data size + public static final byte MAX_APP_ID_APP_DATA_SIZE = 64; + // Max attestation challenge size. + public static final short MAX_ATTESTATION_CHALLENGE_SIZE = 128; + // Max certificate serial size. + public static final byte MAX_CERTIFICATE_SERIAL_SIZE = 20; + // Attestation Application ID + public static final short MAX_ATTESTATION_APP_ID_SIZE = 1024; + // Instance table + public static final byte INSTANCE_TABLE_SIZE = 30; + protected static final byte TLV_HEADER_SIZE = 3; + protected static KMRepository repository; + protected static byte[] heap; + protected static short[] instanceTable; + + public static void initialize() { + instanceTable = JCSystem.makeTransientShortArray(INSTANCE_TABLE_SIZE, JCSystem.CLEAR_ON_RESET); + KMType.repository = KMRepository.instance(); + KMType.heap = repository.getHeap(); + } + + public static byte getType(short ptr) { + return heap[ptr]; + } + + public static short length(short ptr) { + return Util.getShort(heap, (short) (ptr + 1)); + } + + public static short getValue(short ptr) { + return Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + } + + protected static short instance(byte type, short length) { + if (length < 0) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + short ptr = repository.alloc((short) (length + TLV_HEADER_SIZE)); + heap[ptr] = type; + Util.setShort(heap, (short) (ptr + 1), length); + return ptr; + } + + protected static short exp(byte type) { + short ptr = repository.alloc(TLV_HEADER_SIZE); + heap[ptr] = type; + Util.setShort(heap, (short) (ptr + 1), INVALID_VALUE); + return ptr; + } +} diff --git a/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMVerificationToken.java b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMVerificationToken.java new file mode 100644 index 0000000..590e73a --- /dev/null +++ b/ready_se/google/keymint/KM200/Applet/src/com/android/javacard/keymaster/KMVerificationToken.java @@ -0,0 +1,129 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMVerificationToken represents VerificationToken structure from android keymaster hal + * specifications. It corresponds to CBOR array type. struct{byte type=VERIFICATION_TOKEN_TYPE; + * short length=2; short arrayPtr} where arrayPtr is a pointer to ordered array with following + * elements: {KMInteger Challenge; KMInteger Timestamp; KMByteBlob PARAMETERS_VERIFIED; + * SecurityLevel level; KMByteBlob Mac}. + */ +public class KMVerificationToken extends KMType { + + public static final byte CHALLENGE = 0x00; + public static final byte TIMESTAMP = 0x01; + public static final byte MAC = 0x02; + + private static KMVerificationToken prototype; + + private KMVerificationToken() {} + + public static short exp() { + short arrPtr = KMArray.instance((short) 3); + KMArray arr = KMArray.cast(arrPtr); + arr.add(CHALLENGE, KMInteger.exp()); + arr.add(TIMESTAMP, KMInteger.exp()); + arr.add(MAC, KMByteBlob.exp()); + return instance(arrPtr); + } + + private static KMVerificationToken proto(short ptr) { + if (prototype == null) { + prototype = new KMVerificationToken(); + } + KMType.instanceTable[KM_VERIFICATION_TOKEN_OFFSET] = ptr; + return prototype; + } + + public static short instance() { + short arrPtr = KMArray.instance((short) 3); + KMArray arr = KMArray.cast(arrPtr); + arr.add(CHALLENGE, KMInteger.uint_16((short) 0)); + arr.add(TIMESTAMP, KMInteger.uint_16((short) 0)); + arr.add(MAC, KMByteBlob.instance((short) 0)); + return instance(arrPtr); + } + + public static short instance(short vals) { + KMArray arr = KMArray.cast(vals); + if (arr.length() != 3) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + short ptr = KMType.instance(VERIFICATION_TOKEN_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMVerificationToken cast(short ptr) { + if (heap[ptr] != VERIFICATION_TOKEN_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (heap[arrPtr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public short getVals() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_VERIFICATION_TOKEN_OFFSET] + TLV_HEADER_SIZE)); + } + + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); + } + + public short getChallenge() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(CHALLENGE); + } + + public void setChallenge(short vals) { + KMInteger.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(CHALLENGE, vals); + } + + public short getTimestamp() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(TIMESTAMP); + } + + public void setTimestamp(short vals) { + KMInteger.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(TIMESTAMP, vals); + } + + public short getMac() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(MAC); + } + + public void setMac(short vals) { + KMByteBlob.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(MAC, vals); + } +} diff --git a/ready_se/google/keymint/KM200/HAL/.clang-format b/ready_se/google/keymint/KM200/HAL/.clang-format new file mode 100644 index 0000000..b0dc94c --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/.clang-format @@ -0,0 +1,10 @@ +BasedOnStyle: LLVM +IndentWidth: 4 +UseTab: Never +BreakBeforeBraces: Attach +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: true +IndentCaseLabels: false +ColumnLimit: 100 +PointerBindsToType: true +SpacesBeforeTrailingComments: 2 diff --git a/ready_se/google/keymint/KM200/HAL/Android.bp b/ready_se/google/keymint/KM200/HAL/Android.bp new file mode 100644 index 0000000..b05bb3e --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/Android.bp @@ -0,0 +1,140 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package { + default_applicable_licenses: [ + "external_libese_ready_se_google_keymint_KM200_HAL_license", + ], +} + +// Added automatically by a large-scale-change +// See: http://go/android-license-faq +license { + name: "external_libese_ready_se_google_keymint_KM200_HAL_license", + visibility: [":__subpackages__"], + license_kinds: [ + "SPDX-license-identifier-Apache-2.0", + ], + license_text: [ + "LICENSE", + ], +} + +cc_library { + name: "libjc_keymint", + defaults: [ + "keymaster_defaults", + "keymint_use_latest_hal_aidl_ndk_shared", + ], + srcs: [ + "CborConverter.cpp", + "JavacardKeyMintDevice.cpp", + "JavacardKeyMintOperation.cpp", + "JavacardRemotelyProvisionedComponentDevice.cpp", + "JavacardSecureElement.cpp", + "JavacardSharedSecret.cpp", + "keymint_utils.cpp", + ], + cflags: ["-O0"], + shared_libs: [ + "android.hardware.security.secureclock-V1-ndk", + "android.hardware.security.sharedsecret-V1-ndk", + "android.hardware.security.rkp-V3-ndk", + "lib_android_keymaster_keymint_utils", + "libbase", + "libcppbor_external", + "libkeymaster_portable", + "libkeymaster_messages", + "libsoft_attestation_cert", + "liblog", + "libcrypto", + "libcutils", + "libjc_keymint_transport", + "libbinder_ndk", + ], + export_include_dirs: [ + ".", + ], + vendor_available: true, +} + +cc_library { + name: "libjc_keymint_transport", + vendor_available: true, + defaults: [ + "keymint_use_latest_hal_aidl_ndk_shared", + ], + srcs: [ + "SocketTransport.cpp", + "OmapiTransport.cpp", + ], + export_include_dirs: [ + ".", + ], + shared_libs: [ + "libbinder", + "libbase", + "liblog", + "libbinder_ndk", + "android.se.omapi-V1-ndk", + "libhardware", + ], +} + +cc_binary { + name: "android.hardware.security.keymint-service.strongbox", + relative_install_path: "hw", + init_rc: ["android.hardware.security.keymint-service.strongbox.rc"], + vintf_fragments: [ + "android.hardware.security.keymint-service.strongbox.xml", + "android.hardware.security.sharedsecret-service.strongbox.xml", + ], + vendor: true, + cflags: [ + "-Wall", + "-Wextra", + ], + defaults: [ + "keymint_use_latest_hal_aidl_ndk_shared", + ], + shared_libs: [ + "android.hardware.security.sharedsecret-V1-ndk", + "lib_android_keymaster_keymint_utils", + "android.hardware.security.rkp-V3-ndk", + "libbase", + "libbinder_ndk", + "libcppbor_external", + "libcrypto", + "libkeymaster_portable", + "libjc_keymint", + "libjc_keymint_transport", + "liblog", + "libutils", + "android.se.omapi-V1-ndk", + ], + srcs: [ + "service.cpp", + ], + required: [ + "android.hardware.hardware_keystore.jc-strongbox-keymint.xml", + ], +} + +prebuilt_etc { + name: "android.hardware.hardware_keystore.jc-strongbox-keymint.xml", + sub_dir: "permissions", + vendor: true, + src: "android.hardware.hardware_keystore.jc-strongbox-keymint.xml", +} diff --git a/ready_se/google/keymint/KM200/HAL/CborConverter.cpp b/ready_se/google/keymint/KM200/HAL/CborConverter.cpp new file mode 100644 index 0000000..d7e6c11 --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/CborConverter.cpp @@ -0,0 +1,512 @@ +/* + ** + ** Copyright 2020, The Android Open Source Project + ** + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** + ** http://www.apache.org/licenses/LICENSE-2.0 + ** + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + */ + +#include "CborConverter.h" + +#include <map> +#include <string> + +#include <android-base/logging.h> + +#include <KeyMintUtils.h> + +namespace keymint::javacard { +using ::aidl::android::hardware::security::keymint::KeyParameterValue; +using ::aidl::android::hardware::security::keymint::SecurityLevel; +using ::aidl::android::hardware::security::keymint::km_utils::kmParam2Aidl; +using ::aidl::android::hardware::security::keymint::km_utils::legacy_enum_conversion; +using ::aidl::android::hardware::security::keymint::km_utils::typeFromTag; + +constexpr int SB_ENFORCED = 0; +constexpr int TEE_ENFORCED = 1; +constexpr int SW_ENFORCED = 2; + +namespace { + +template <KeyParameterValue::Tag aidl_tag> +std::optional<uint32_t> aidlEnumVal2Uint32(const KeyParameterValue& value) { + return (value.getTag() == aidl_tag) + ? std::optional(static_cast<uint32_t>(value.get<aidl_tag>())) + : std::nullopt; +} + +std::optional<uint32_t> aidlEnumParam2Uint32(const KeyParameter& param) { + auto tag = legacy_enum_conversion(param.tag); + switch (tag) { + case KM_TAG_PURPOSE: + return aidlEnumVal2Uint32<KeyParameterValue::keyPurpose>(param.value); + case KM_TAG_ALGORITHM: + return aidlEnumVal2Uint32<KeyParameterValue::algorithm>(param.value); + case KM_TAG_BLOCK_MODE: + return aidlEnumVal2Uint32<KeyParameterValue::blockMode>(param.value); + case KM_TAG_DIGEST: + case KM_TAG_RSA_OAEP_MGF_DIGEST: + return aidlEnumVal2Uint32<KeyParameterValue::digest>(param.value); + case KM_TAG_PADDING: + return aidlEnumVal2Uint32<KeyParameterValue::paddingMode>(param.value); + case KM_TAG_EC_CURVE: + return aidlEnumVal2Uint32<KeyParameterValue::ecCurve>(param.value); + case KM_TAG_USER_AUTH_TYPE: + return aidlEnumVal2Uint32<KeyParameterValue::hardwareAuthenticatorType>(param.value); + case KM_TAG_ORIGIN: + return aidlEnumVal2Uint32<KeyParameterValue::origin>(param.value); + case KM_TAG_BLOB_USAGE_REQUIREMENTS: + case KM_TAG_KDF: + default: + CHECK(false) << "Unknown or unused enum tag: Something is broken"; + return std::nullopt; + } +} + +} // namespace + +bool CborConverter::addAttestationKey(Array& array, + const std::optional<AttestationKey>& attestationKey) { + if (attestationKey.has_value()) { + array.add(Bstr(attestationKey->keyBlob)); + addKeyparameters(array, attestationKey->attestKeyParams); + array.add(Bstr(attestationKey->issuerSubjectName)); + } else { + array.add(std::move(Bstr(vector<uint8_t>(0)))); + array.add(std::move(Map())); + array.add(std::move(Bstr(vector<uint8_t>(0)))); + } + return true; +} + +bool CborConverter::addKeyparameters(Array& array, const vector<KeyParameter>& keyParams) { + Map map; + std::map<uint64_t, vector<uint8_t>> enum_repetition; + std::map<uint64_t, Array> uint_repetition; + for (auto& param : keyParams) { + auto tag = legacy_enum_conversion(param.tag); + switch (typeFromTag(tag)) { + case KM_ENUM: { + auto paramEnum = aidlEnumParam2Uint32(param); + if (paramEnum.has_value()) { + map.add(static_cast<uint64_t>(tag), *paramEnum); + } + break; + } + case KM_UINT: + if (param.value.getTag() == KeyParameterValue::integer) { + auto intVal = param.value.get<KeyParameterValue::integer>(); + map.add(static_cast<uint64_t>(tag), intVal); + } + break; + case KM_UINT_REP: + if (param.value.getTag() == KeyParameterValue::integer) { + auto intVal = param.value.get<KeyParameterValue::integer>(); + uint_repetition[static_cast<uint64_t>(tag)].add(intVal); + } + break; + case KM_ENUM_REP: { + auto paramEnumRep = aidlEnumParam2Uint32(param); + if (paramEnumRep.has_value()) { + enum_repetition[static_cast<uint64_t>(tag)].push_back(*paramEnumRep); + } + break; + } + case KM_ULONG: + if (param.value.getTag() == KeyParameterValue::longInteger) { + auto longVal = param.value.get<KeyParameterValue::longInteger>(); + map.add(static_cast<uint64_t>(tag), longVal); + } + break; + case KM_ULONG_REP: + if (param.value.getTag() == KeyParameterValue::longInteger) { + auto longVal = param.value.get<KeyParameterValue::longInteger>(); + uint_repetition[static_cast<uint64_t>(tag & 0x00000000ffffffff)].add(longVal); + } + break; + case KM_DATE: + if (param.value.getTag() == KeyParameterValue::dateTime) { + auto dateVal = param.value.get<KeyParameterValue::dateTime>(); + map.add(static_cast<uint64_t>(tag), dateVal); + } + break; + case KM_BOOL: + map.add(static_cast<uint64_t>(tag), 1 /* true */); + break; + case KM_BIGNUM: + case KM_BYTES: + if (param.value.getTag() == KeyParameterValue::blob) { + const auto& value = param.value.get<KeyParameterValue::blob>(); + map.add(static_cast<uint64_t>(tag & 0x00000000ffffffff), value); + } + break; + case KM_INVALID: + break; + } + } + + for (auto const& [key, val] : enum_repetition) { + Bstr bstr(val); + map.add(key, std::move(bstr)); + } + + for (auto& [key, val] : uint_repetition) { + map.add(key, std::move(val)); + } + array.add(std::move(map)); + return true; +} + +// Array of three maps +std::optional<vector<KeyCharacteristics>> +CborConverter::getKeyCharacteristics(const unique_ptr<Item>& item, const uint32_t pos) { + vector<KeyCharacteristics> keyCharacteristics; + auto arrayItem = getItemAtPos(item, pos); + if (!arrayItem || (MajorType::ARRAY != getType(arrayItem.value()))) { + return std::nullopt; + } + KeyCharacteristics swEnf{SecurityLevel::KEYSTORE, {}}; + KeyCharacteristics teeEnf{SecurityLevel::TRUSTED_ENVIRONMENT, {}}; + KeyCharacteristics sbEnf{SecurityLevel::STRONGBOX, {}}; + + auto optSbEnf = getKeyParameters(arrayItem.value(), SB_ENFORCED); + if (!optSbEnf) { + return std::nullopt; + } + sbEnf.authorizations = std::move(optSbEnf.value()); + auto optTeeEnf = getKeyParameters(arrayItem.value(), TEE_ENFORCED); + if (!optTeeEnf) { + return std::nullopt; + } + teeEnf.authorizations = std::move(optTeeEnf.value()); + auto optSwEnf = getKeyParameters(arrayItem.value(), SW_ENFORCED); + if (!optSwEnf) { + return std::nullopt; + } + swEnf.authorizations = std::move(optSwEnf.value()); + // VTS will fail if the authorizations list is empty. + if (!sbEnf.authorizations.empty()) keyCharacteristics.push_back(std::move(sbEnf)); + if (!teeEnf.authorizations.empty()) keyCharacteristics.push_back(std::move(teeEnf)); + if (!swEnf.authorizations.empty()) keyCharacteristics.push_back(std::move(swEnf)); + return keyCharacteristics; +} + +std::optional<std::vector<KeyParameter>> CborConverter::getKeyParameter( + const std::pair<const std::unique_ptr<Item>&, const std::unique_ptr<Item>&> pair) { + std::vector<KeyParameter> keyParams; + keymaster_tag_t key; + auto optValue = getUint64(pair.first); + if (!optValue) { + return std::nullopt; + } + key = static_cast<keymaster_tag_t>(optValue.value()); + switch (keymaster_tag_get_type(key)) { + case KM_ENUM_REP: { + /* ENUM_REP contains values encoded in a Byte string */ + const Bstr* bstr = pair.second.get()->asBstr(); + if (bstr == nullptr) { + return std::nullopt; + } + for (auto bchar : bstr->value()) { + keymaster_key_param_t keyParam; + keyParam.tag = key; + keyParam.enumerated = bchar; + keyParams.push_back(kmParam2Aidl(keyParam)); + } + return keyParams; + } + case KM_ENUM: { + keymaster_key_param_t keyParam; + keyParam.tag = key; + if (!(optValue = getUint64(pair.second))) { + return std::nullopt; + } + keyParam.enumerated = static_cast<uint32_t>(optValue.value()); + keyParams.push_back(kmParam2Aidl(keyParam)); + return keyParams; + } + case KM_UINT: { + keymaster_key_param_t keyParam; + keyParam.tag = key; + if (!(optValue = getUint64(pair.second))) { + return std::nullopt; + } + keyParam.integer = static_cast<uint32_t>(optValue.value()); + keyParams.push_back(kmParam2Aidl(keyParam)); + return keyParams; + } + case KM_ULONG: { + keymaster_key_param_t keyParam; + keyParam.tag = key; + if (!(optValue = getUint64(pair.second))) { + return std::nullopt; + } + keyParam.long_integer = optValue.value(); + keyParams.push_back(kmParam2Aidl(keyParam)); + return keyParams; + } + case KM_UINT_REP: { + /* UINT_REP contains values encoded in a Array */ + Array* array = const_cast<Array*>(pair.second.get()->asArray()); + if (array == nullptr) return std::nullopt; + for (int i = 0; i < array->size(); i++) { + keymaster_key_param_t keyParam; + keyParam.tag = key; + const std::unique_ptr<Item>& item = array->get(i); + if (!(optValue = getUint64(item))) { + return std::nullopt; + } + keyParam.integer = static_cast<uint32_t>(optValue.value()); + keyParams.push_back(kmParam2Aidl(keyParam)); + } + return keyParams; + } + case KM_ULONG_REP: { + /* ULONG_REP contains values encoded in a Array */ + Array* array = const_cast<Array*>(pair.second.get()->asArray()); + if (array == nullptr) return std::nullopt; + for (int i = 0; i < array->size(); i++) { + keymaster_key_param_t keyParam; + keyParam.tag = key; + const std::unique_ptr<Item>& item = array->get(i); + if (!(optValue = getUint64(item))) { + return std::nullopt; + } + keyParam.long_integer = optValue.value(); + keyParams.push_back(kmParam2Aidl(keyParam)); + } + return keyParams; + } + case KM_DATE: { + keymaster_key_param_t keyParam; + keyParam.tag = key; + if (!(optValue = getUint64(pair.second))) { + return std::nullopt; + } + keyParam.date_time = optValue.value(); + keyParams.push_back(kmParam2Aidl(keyParam)); + return keyParams; + } + case KM_BOOL: { + keymaster_key_param_t keyParam; + keyParam.tag = key; + if (!(optValue = getUint64(pair.second))) { + return std::nullopt; + } + // If a tag with this type is present, the value is true. If absent, false. + keyParam.boolean = true; + keyParams.push_back(kmParam2Aidl(keyParam)); + return keyParams; + } + case KM_BIGNUM: + case KM_BYTES: { + keymaster_key_param_t keyParam; + keyParam.tag = key; + const Bstr* bstr = pair.second.get()->asBstr(); + if (bstr == nullptr) return std::nullopt; + keyParam.blob.data = bstr->value().data(); + keyParam.blob.data_length = bstr->value().size(); + keyParams.push_back(kmParam2Aidl(keyParam)); + return keyParams; + } + case KM_INVALID: + break; + } + return std::nullopt; +} + +// array of a blobs +std::optional<vector<Certificate>> +CborConverter::getCertificateChain(const std::unique_ptr<Item>& item, const uint32_t pos) { + vector<Certificate> certChain; + auto arrayItem = getItemAtPos(item, pos); + if (!arrayItem || (MajorType::ARRAY != getType(arrayItem.value()))) return std::nullopt; + + const Array* arr = arrayItem.value().get()->asArray(); + for (int i = 0; i < arr->size(); i++) { + Certificate cert; + auto optTemp = getByteArrayVec(arrayItem.value(), i); + if (!optTemp) return std::nullopt; + cert.encodedCertificate = std::move(optTemp.value()); + certChain.push_back(std::move(cert)); + } + return certChain; +} + +std::optional<string> CborConverter::getByteArrayStr(const unique_ptr<Item>& item, + const uint32_t pos) { + auto optTemp = getByteArrayVec(item, pos); + if (!optTemp) { + return std::nullopt; + } + std::string str(optTemp->begin(), optTemp->end()); + return str; +} + +std::optional<std::vector<uint8_t>> CborConverter::getByteArrayVec(const unique_ptr<Item>& item, + const uint32_t pos) { + auto strItem = getItemAtPos(item, pos); + if (!strItem || (MajorType::BSTR != getType(strItem.value()))) { + return std::nullopt; + } + const Bstr* bstr = strItem.value().get()->asBstr(); + return bstr->value(); +} + +std::optional<SharedSecretParameters> +CborConverter::getSharedSecretParameters(const unique_ptr<Item>& item, const uint32_t pos) { + SharedSecretParameters params; + // Array [seed, nonce] + auto arrayItem = getItemAtPos(item, pos); + if (!arrayItem || (MajorType::ARRAY != getType(arrayItem.value()))) { + return std::nullopt; + } + auto optSeed = getByteArrayVec(arrayItem.value(), 0); + auto optNonce = getByteArrayVec(arrayItem.value(), 1); + if (!optSeed || !optNonce) { + return std::nullopt; + } + params.seed = std::move(optSeed.value()); + params.nonce = std::move(optNonce.value()); + return params; +} + +bool CborConverter::addSharedSecretParameters(Array& array, + const vector<SharedSecretParameters>& params) { + Array cborParamsVec; + for (auto param : params) { + Array cborParam; + cborParam.add(Bstr(param.seed)); + cborParam.add(Bstr(param.nonce)); + cborParamsVec.add(std::move(cborParam)); + } + array.add(std::move(cborParamsVec)); + return true; +} + +bool CborConverter::addTimeStampToken(Array& array, const TimeStampToken& token) { + Array vToken; + vToken.add(static_cast<uint64_t>(token.challenge)); + vToken.add(static_cast<uint64_t>(token.timestamp.milliSeconds)); + vToken.add((std::vector<uint8_t>(token.mac))); + array.add(std::move(vToken)); + return true; +} + +bool CborConverter::addHardwareAuthToken(Array& array, const HardwareAuthToken& authToken) { + + Array hwAuthToken; + hwAuthToken.add(static_cast<uint64_t>(authToken.challenge)); + hwAuthToken.add(static_cast<uint64_t>(authToken.userId)); + hwAuthToken.add(static_cast<uint64_t>(authToken.authenticatorId)); + hwAuthToken.add(static_cast<uint64_t>(authToken.authenticatorType)); + hwAuthToken.add(static_cast<uint64_t>(authToken.timestamp.milliSeconds)); + hwAuthToken.add((std::vector<uint8_t>(authToken.mac))); + array.add(std::move(hwAuthToken)); + return true; +} + +std::optional<TimeStampToken> CborConverter::getTimeStampToken(const unique_ptr<Item>& item, + const uint32_t pos) { + TimeStampToken token; + // {challenge, timestamp, Mac} + auto optChallenge = getUint64(item, pos); + auto optTimestampMillis = getUint64(item, pos + 1); + auto optTemp = getByteArrayVec(item, pos + 2); + if (!optChallenge || !optTimestampMillis || !optTemp) { + return std::nullopt; + } + token.mac = std::move(optTemp.value()); + token.challenge = static_cast<long>(std::move(optChallenge.value())); + token.timestamp.milliSeconds = static_cast<long>(std::move(optTimestampMillis.value())); + return token; +} + +std::optional<Array> CborConverter::getArrayItem(const std::unique_ptr<Item>& item, + const uint32_t pos) { + Array array; + auto arrayItem = getItemAtPos(item, pos); + if (!arrayItem || (MajorType::ARRAY != getType(arrayItem.value()))) { + return std::nullopt; + } + array = std::move(*(arrayItem.value().get()->asArray())); + return array; +} + +std::optional<Map> CborConverter::getMapItem(const std::unique_ptr<Item>& item, + const uint32_t pos) { + Map map; + auto mapItem = getItemAtPos(item, pos); + if (!mapItem || (MajorType::MAP != getType(mapItem.value()))) { + return std::nullopt; + } + map = std::move(*(mapItem.value().get()->asMap())); + return map; +} + +std::optional<vector<KeyParameter>> CborConverter::getKeyParameters(const unique_ptr<Item>& item, + const uint32_t pos) { + vector<KeyParameter> params; + auto mapItem = getItemAtPos(item, pos); + if (!mapItem || (MajorType::MAP != getType(mapItem.value()))) return std::nullopt; + const Map* map = mapItem.value().get()->asMap(); + size_t mapSize = map->size(); + for (int i = 0; i < mapSize; i++) { + auto optKeyParams = getKeyParameter((*map)[i]); + if (optKeyParams) { + params.insert(params.end(), optKeyParams->begin(), optKeyParams->end()); + } else { + return std::nullopt; + } + } + return params; +} + +std::tuple<std::unique_ptr<Item>, keymaster_error_t> +CborConverter::decodeData(const std::vector<uint8_t>& response) { + auto [item, pos, message] = cppbor::parse(response); + if (!item || MajorType::ARRAY != getType(item)) { + return {nullptr, KM_ERROR_UNKNOWN_ERROR}; + } + auto optErrorCode = getErrorCode(item, 0); + if (!optErrorCode) { + return {nullptr, KM_ERROR_UNKNOWN_ERROR}; + } + return {std::move(item), optErrorCode.value()}; +} + +std::optional<keymaster_error_t> +CborConverter::getErrorCode(const std::unique_ptr<cppbor::Item>& item, const uint32_t pos) { + auto optErrorVal = getUint64(item, pos); + if (!optErrorVal) { + return std::nullopt; + } + return static_cast<keymaster_error_t>(0 - optErrorVal.value()); +} + +std::optional<uint64_t> CborConverter::getUint64(const unique_ptr<Item>& item) { + if ((item == nullptr) || (MajorType::UINT != getType(item))) { + return std::nullopt; + } + const Uint* uintVal = item.get()->asUint(); + return uintVal->unsignedValue(); +} + +std::optional<uint64_t> CborConverter::getUint64(const unique_ptr<Item>& item, const uint32_t pos) { + auto intItem = getItemAtPos(item, pos); + if (!intItem) { + return std::nullopt; + } + return getUint64(intItem.value()); +} + +} // namespace keymint::javacard diff --git a/ready_se/google/keymint/KM200/HAL/CborConverter.h b/ready_se/google/keymint/KM200/HAL/CborConverter.h new file mode 100644 index 0000000..b49273b --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/CborConverter.h @@ -0,0 +1,139 @@ +/* + ** + ** Copyright 2020, The Android Open Source Project + ** + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** + ** http://www.apache.org/licenses/LICENSE-2.0 + ** + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + */ +#pragma once + +#include <iostream> +#include <memory> +#include <numeric> +#include <vector> + +#include <cppbor.h> +#include <cppbor_parse.h> + +#include <aidl/android/hardware/security/keymint/Certificate.h> +#include <aidl/android/hardware/security/keymint/IKeyMintDevice.h> +#include <aidl/android/hardware/security/secureclock/TimeStampToken.h> +#include <aidl/android/hardware/security/sharedsecret/ISharedSecret.h> + +#include <keymaster/android_keymaster_messages.h> + +namespace keymint::javacard { +using aidl::android::hardware::security::keymint::AttestationKey; +using aidl::android::hardware::security::keymint::Certificate; +using aidl::android::hardware::security::keymint::HardwareAuthToken; +using aidl::android::hardware::security::keymint::KeyCharacteristics; +using aidl::android::hardware::security::keymint::KeyParameter; +using aidl::android::hardware::security::secureclock::TimeStampToken; +using aidl::android::hardware::security::sharedsecret::SharedSecretParameters; +using cppbor::Array; +using cppbor::Bstr; +using cppbor::EncodedItem; +using cppbor::Item; +using cppbor::MajorType; +using cppbor::Map; +using cppbor::Nint; +using cppbor::Uint; +using std::string; +using std::unique_ptr; +using std::vector; + +class CborConverter { + public: + CborConverter() = default; + + ~CborConverter() = default; + + std::tuple<std::unique_ptr<Item>, keymaster_error_t> + decodeData(const std::vector<uint8_t>& response); + + std::optional<uint64_t> getUint64(const unique_ptr<Item>& item); + + std::optional<uint64_t> getUint64(const unique_ptr<Item>& item, const uint32_t pos); + + std::optional<SharedSecretParameters> + getSharedSecretParameters(const std::unique_ptr<Item>& item, const uint32_t pos); + + std::optional<string> getByteArrayStr(const unique_ptr<Item>& item, const uint32_t pos); + + std::optional<std::vector<uint8_t>> getByteArrayVec(const unique_ptr<Item>& item, + const uint32_t pos); + + std::optional<vector<KeyParameter>> getKeyParameters(const unique_ptr<Item>& item, + const uint32_t pos); + + bool addKeyparameters(Array& array, const vector<KeyParameter>& keyParams); + + bool addAttestationKey(Array& array, const std::optional<AttestationKey>& attestationKey); + + bool addHardwareAuthToken(Array& array, const HardwareAuthToken& authToken); + + bool addSharedSecretParameters(Array& array, const vector<SharedSecretParameters>& params); + + std::optional<TimeStampToken> getTimeStampToken(const std::unique_ptr<Item>& item, + const uint32_t pos); + + std::optional<vector<KeyCharacteristics>> + getKeyCharacteristics(const std::unique_ptr<Item>& item, const uint32_t pos); + + std::optional<vector<Certificate>> getCertificateChain(const std::unique_ptr<Item>& item, + const uint32_t pos); + + std::optional<vector<vector<uint8_t>>> getMultiByteArray(const unique_ptr<Item>& item, + const uint32_t pos); + + bool addTimeStampToken(Array& array, const TimeStampToken& token); + + std::optional<Map> getMapItem(const std::unique_ptr<Item>& item, const uint32_t pos); + + std::optional<Array> getArrayItem(const std::unique_ptr<Item>& item, const uint32_t pos); + + std::optional<keymaster_error_t> getErrorCode(const std::unique_ptr<Item>& item, + const uint32_t pos); + + private: + /** + * Get the type of the Item pointer. + */ + inline MajorType getType(const unique_ptr<Item>& item) { return item.get()->type(); } + + /** + * Construct Keyparameter structure from the pair of key and value. If TagType is ENUM_REP the + * value contains binary string. If TagType is UINT_REP or ULONG_REP the value contains Array of + * unsigned integers. + */ + std::optional<std::vector<KeyParameter>> getKeyParameter( + const std::pair<const std::unique_ptr<Item>&, const std::unique_ptr<Item>&> pair); + + /** + * Get the sub item pointer from the root item pointer at the given position. + */ + inline std::optional<unique_ptr<Item>> getItemAtPos(const unique_ptr<Item>& item, + const uint32_t pos) { + Array* arr = nullptr; + + if (MajorType::ARRAY != getType(item)) { + return std::nullopt; + } + arr = const_cast<Array*>(item.get()->asArray()); + if (arr->size() < (pos + 1)) { + return std::nullopt; + } + return std::move((*arr)[pos]); + } +}; + +} // namespace keymint::javacard diff --git a/ready_se/google/keymint/KM200/HAL/ITransport.h b/ready_se/google/keymint/KM200/HAL/ITransport.h new file mode 100644 index 0000000..ca100be --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/ITransport.h @@ -0,0 +1,55 @@ +/* + ** + ** Copyright 2020, The Android Open Source Project + ** + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** + ** http://www.apache.org/licenses/LICENSE-2.0 + ** + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + */ +#pragma once + +#include <memory> +#include <vector> + +#include <hardware/keymaster_defs.h> + +namespace keymint::javacard { +using std::shared_ptr; +using std::vector; + +/** + * ITransport is an interface with a set of virtual methods that allow communication between the + * HAL and the applet on the secure element. + */ +class ITransport { + public: + virtual ~ITransport() {} + + /** + * Opens connection. + */ + virtual keymaster_error_t openConnection() = 0; + /** + * Send data over communication channel and receives data back from the remote end. + */ + virtual keymaster_error_t sendData(const vector<uint8_t>& inData, vector<uint8_t>& output) = 0; + /** + * Closes the connection. + */ + virtual keymaster_error_t closeConnection() = 0; + /** + * Returns the state of the connection status. Returns true if the connection is active, false + * if connection is broken. + */ + virtual bool isConnected() = 0; +}; + +} // namespace keymint::javacard diff --git a/ready_se/google/keymint/KM200/HAL/JavacardKeyMintDevice.cpp b/ready_se/google/keymint/KM200/HAL/JavacardKeyMintDevice.cpp new file mode 100644 index 0000000..bd68b48 --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/JavacardKeyMintDevice.cpp @@ -0,0 +1,454 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "javacard.keymint.device.strongbox-impl" + +#include "JavacardKeyMintDevice.h" + +#include <regex.h> + +#include <algorithm> +#include <iostream> +#include <iterator> +#include <memory> +#include <string> +#include <vector> + +#include <KeyMintUtils.h> +#include <android-base/logging.h> +#include <android-base/properties.h> +#include <hardware/hw_auth_token.h> +#include <keymaster/android_keymaster_messages.h> +#include <keymaster/wrapped_key.h> + +#include "JavacardKeyMintOperation.h" +#include "JavacardSharedSecret.h" + +namespace aidl::android::hardware::security::keymint { +using cppbor::Bstr; +using cppbor::EncodedItem; +using cppbor::Uint; +using ::keymaster::AuthorizationSet; +using ::keymaster::dup_buffer; +using ::keymaster::KeymasterBlob; +using ::keymaster::KeymasterKeyBlob; +using ::keymint::javacard::Instruction; +using std::string; + +ScopedAStatus JavacardKeyMintDevice::defaultHwInfo(KeyMintHardwareInfo* info) { + info->versionNumber = 2; + info->keyMintAuthorName = "Google"; + info->keyMintName = "JavacardKeymintDevice"; + info->securityLevel = securitylevel_; + info->timestampTokenRequired = true; + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::getHardwareInfo(KeyMintHardwareInfo* info) { + auto [item, err] = card_->sendRequest(Instruction::INS_GET_HW_INFO_CMD); + std::optional<string> optKeyMintName; + std::optional<string> optKeyMintAuthorName; + std::optional<uint64_t> optSecLevel; + std::optional<uint64_t> optVersion; + std::optional<uint64_t> optTsRequired; + if (err != KM_ERROR_OK || !(optVersion = cbor_.getUint64(item, 1)) || + !(optSecLevel = cbor_.getUint64(item, 2)) || + !(optKeyMintName = cbor_.getByteArrayStr(item, 3)) || + !(optKeyMintAuthorName = cbor_.getByteArrayStr(item, 4)) || + !(optTsRequired = cbor_.getUint64(item, 5))) { + LOG(ERROR) << "Error in response of getHardwareInfo."; + LOG(INFO) << "Returning defaultHwInfo in getHardwareInfo."; + return defaultHwInfo(info); + } + card_->initializeJavacard(); + info->keyMintName = std::move(optKeyMintName.value()); + info->keyMintAuthorName = std::move(optKeyMintAuthorName.value()); + info->timestampTokenRequired = (optTsRequired.value() == 1); + info->securityLevel = static_cast<SecurityLevel>(std::move(optSecLevel.value())); + info->versionNumber = static_cast<int32_t>(std::move(optVersion.value())); + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::generateKey(const vector<KeyParameter>& keyParams, + const optional<AttestationKey>& attestationKey, + KeyCreationResult* creationResult) { + cppbor::Array array; + // add key params + cbor_.addKeyparameters(array, keyParams); + // add attestation key if any + cbor_.addAttestationKey(array, attestationKey); + auto [item, err] = card_->sendRequest(Instruction::INS_GENERATE_KEY_CMD, array); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending generateKey."; + return km_utils::kmError2ScopedAStatus(err); + } + auto optKeyBlob = cbor_.getByteArrayVec(item, 1); + auto optKeyChars = cbor_.getKeyCharacteristics(item, 2); + auto optCertChain = cbor_.getCertificateChain(item, 3); + if (!optKeyBlob || !optKeyChars || !optCertChain) { + LOG(ERROR) << "Error in decoding og response in generateKey."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + creationResult->keyCharacteristics = std::move(optKeyChars.value()); + creationResult->certificateChain = std::move(optCertChain.value()); + creationResult->keyBlob = std::move(optKeyBlob.value()); + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::addRngEntropy(const vector<uint8_t>& data) { + cppbor::Array request; + // add key data + request.add(Bstr(data)); + auto [item, err] = card_->sendRequest(Instruction::INS_ADD_RNG_ENTROPY_CMD, request); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending addRngEntropy."; + return km_utils::kmError2ScopedAStatus(err); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::importKey(const vector<KeyParameter>& keyParams, + KeyFormat keyFormat, const vector<uint8_t>& keyData, + const optional<AttestationKey>& attestationKey, + KeyCreationResult* creationResult) { + + cppbor::Array request; + // add key params + cbor_.addKeyparameters(request, keyParams); + // add key format + request.add(Uint(static_cast<uint8_t>(keyFormat))); + // add key data + request.add(Bstr(keyData)); + // add attestation key if any + cbor_.addAttestationKey(request, attestationKey); + + auto [item, err] = card_->sendRequest(Instruction::INS_IMPORT_KEY_CMD, request); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending data in importKey."; + return km_utils::kmError2ScopedAStatus(err); + } + auto optKeyBlob = cbor_.getByteArrayVec(item, 1); + auto optKeyChars = cbor_.getKeyCharacteristics(item, 2); + auto optCertChain = cbor_.getCertificateChain(item, 3); + if (!optKeyBlob || !optKeyChars || !optCertChain) { + LOG(ERROR) << "Error in decoding response in importKey."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + creationResult->keyCharacteristics = std::move(optKeyChars.value()); + creationResult->certificateChain = std::move(optCertChain.value()); + creationResult->keyBlob = std::move(optKeyBlob.value()); + return ScopedAStatus::ok(); +} + +// import wrapped key is divided into 2 stage operation. +ScopedAStatus JavacardKeyMintDevice::importWrappedKey(const vector<uint8_t>& wrappedKeyData, + const vector<uint8_t>& wrappingKeyBlob, + const vector<uint8_t>& maskingKey, + const vector<KeyParameter>& unwrappingParams, + int64_t passwordSid, int64_t biometricSid, + KeyCreationResult* creationResult) { + cppbor::Array request; + std::unique_ptr<Item> item; + vector<uint8_t> keyBlob; + std::vector<uint8_t> response; + vector<KeyCharacteristics> keyCharacteristics; + std::vector<uint8_t> iv; + std::vector<uint8_t> transitKey; + std::vector<uint8_t> secureKey; + std::vector<uint8_t> tag; + vector<KeyParameter> authList; + KeyFormat keyFormat; + std::vector<uint8_t> wrappedKeyDescription; + keymaster_error_t errorCode = parseWrappedKey(wrappedKeyData, iv, transitKey, secureKey, tag, + authList, keyFormat, wrappedKeyDescription); + if (errorCode != KM_ERROR_OK) { + LOG(ERROR) << "Error in parse wrapped key in importWrappedKey."; + return km_utils::kmError2ScopedAStatus(errorCode); + } + + // begin import + std::tie(item, errorCode) = + sendBeginImportWrappedKeyCmd(transitKey, wrappingKeyBlob, maskingKey, unwrappingParams); + if (errorCode != KM_ERROR_OK) { + LOG(ERROR) << "Error in send begin import wrapped key in importWrappedKey."; + return km_utils::kmError2ScopedAStatus(errorCode); + } + // Finish the import + std::tie(item, errorCode) = sendFinishImportWrappedKeyCmd( + authList, keyFormat, secureKey, tag, iv, wrappedKeyDescription, passwordSid, biometricSid); + if (errorCode != KM_ERROR_OK) { + LOG(ERROR) << "Error in send finish import wrapped key in importWrappedKey."; + return km_utils::kmError2ScopedAStatus(errorCode); + } + auto optKeyBlob = cbor_.getByteArrayVec(item, 1); + auto optKeyChars = cbor_.getKeyCharacteristics(item, 2); + auto optCertChain = cbor_.getCertificateChain(item, 3); + if (!optKeyBlob || !optKeyChars || !optCertChain) { + LOG(ERROR) << "Error in decoding the response in importWrappedKey."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + creationResult->keyCharacteristics = std::move(optKeyChars.value()); + creationResult->certificateChain = std::move(optCertChain.value()); + creationResult->keyBlob = std::move(optKeyBlob.value()); + return ScopedAStatus::ok(); +} + +std::tuple<std::unique_ptr<Item>, keymaster_error_t> +JavacardKeyMintDevice::sendBeginImportWrappedKeyCmd(const std::vector<uint8_t>& transitKey, + const std::vector<uint8_t>& wrappingKeyBlob, + const std::vector<uint8_t>& maskingKey, + const vector<KeyParameter>& unwrappingParams) { + Array request; + request.add(std::vector<uint8_t>(transitKey)); + request.add(std::vector<uint8_t>(wrappingKeyBlob)); + request.add(std::vector<uint8_t>(maskingKey)); + cbor_.addKeyparameters(request, unwrappingParams); + return card_->sendRequest(Instruction::INS_BEGIN_IMPORT_WRAPPED_KEY_CMD, request); +} + +std::tuple<std::unique_ptr<Item>, keymaster_error_t> +JavacardKeyMintDevice::sendFinishImportWrappedKeyCmd( + const vector<KeyParameter>& keyParams, KeyFormat keyFormat, + const std::vector<uint8_t>& secureKey, const std::vector<uint8_t>& tag, + const std::vector<uint8_t>& iv, const std::vector<uint8_t>& wrappedKeyDescription, + int64_t passwordSid, int64_t biometricSid) { + Array request; + cbor_.addKeyparameters(request, keyParams); + request.add(static_cast<uint64_t>(keyFormat)); + request.add(std::vector<uint8_t>(secureKey)); + request.add(std::vector<uint8_t>(tag)); + request.add(std::vector<uint8_t>(iv)); + request.add(std::vector<uint8_t>(wrappedKeyDescription)); + request.add(Uint(passwordSid)); + request.add(Uint(biometricSid)); + return card_->sendRequest(Instruction::INS_FINISH_IMPORT_WRAPPED_KEY_CMD, request); +} + +ScopedAStatus JavacardKeyMintDevice::upgradeKey(const vector<uint8_t>& keyBlobToUpgrade, + const vector<KeyParameter>& upgradeParams, + vector<uint8_t>* keyBlob) { + cppbor::Array request; + // add key blob + request.add(Bstr(keyBlobToUpgrade)); + // add key params + cbor_.addKeyparameters(request, upgradeParams); + auto [item, err] = card_->sendRequest(Instruction::INS_UPGRADE_KEY_CMD, request); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in upgradeKey."; + return km_utils::kmError2ScopedAStatus(err); + } + auto optKeyBlob = cbor_.getByteArrayVec(item, 1); + if (!optKeyBlob) { + LOG(ERROR) << "Error in decoding the response in upgradeKey."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + *keyBlob = std::move(optKeyBlob.value()); + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::deleteKey(const vector<uint8_t>& keyBlob) { + Array request; + request.add(Bstr(keyBlob)); + auto [item, err] = card_->sendRequest(Instruction::INS_DELETE_KEY_CMD, request); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in deleteKey."; + return km_utils::kmError2ScopedAStatus(err); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::deleteAllKeys() { + auto [item, err] = card_->sendRequest(Instruction::INS_DELETE_ALL_KEYS_CMD); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in deleteAllKeys."; + return km_utils::kmError2ScopedAStatus(err); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::destroyAttestationIds() { + auto [item, err] = card_->sendRequest(Instruction::INS_DESTROY_ATT_IDS_CMD); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in destroyAttestationIds."; + return km_utils::kmError2ScopedAStatus(err); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::begin(KeyPurpose purpose, const std::vector<uint8_t>& keyBlob, + const std::vector<KeyParameter>& params, + const std::optional<HardwareAuthToken>& authToken, + BeginResult* result) { + + cppbor::Array array; + std::vector<uint8_t> response; + // make request + array.add(Uint(static_cast<uint64_t>(purpose))); + array.add(Bstr(keyBlob)); + cbor_.addKeyparameters(array, params); + HardwareAuthToken token = authToken.value_or(HardwareAuthToken()); + cbor_.addHardwareAuthToken(array, token); + + // Send earlyBootEnded if there is any pending earlybootEnded event. + auto retErr = card_->sendEarlyBootEndedEvent(false); + if (retErr != KM_ERROR_OK) { + return km_utils::kmError2ScopedAStatus(retErr); + ; + } + + auto [item, err] = card_->sendRequest(Instruction::INS_BEGIN_OPERATION_CMD, array); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in begin."; + return km_utils::kmError2ScopedAStatus(err); + } + // return the result + auto keyParams = cbor_.getKeyParameters(item, 1); + auto optOpHandle = cbor_.getUint64(item, 2); + auto optBufMode = cbor_.getUint64(item, 3); + auto optMacLength = cbor_.getUint64(item, 4); + + if (!keyParams || !optOpHandle || !optBufMode || !optMacLength) { + LOG(ERROR) << "Error in decoding the response in begin."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + result->params = std::move(keyParams.value()); + result->challenge = optOpHandle.value(); + result->operation = ndk::SharedRefBase::make<JavacardKeyMintOperation>( + static_cast<keymaster_operation_handle_t>(optOpHandle.value()), + static_cast<BufferingMode>(optBufMode.value()), optMacLength.value(), card_); + return ScopedAStatus::ok(); +} + +ScopedAStatus +JavacardKeyMintDevice::deviceLocked(bool passwordOnly, + const std::optional<TimeStampToken>& timestampToken) { + Array request; + int8_t password = 1; + if (!passwordOnly) { + password = 0; + } + request.add(Uint(password)); + cbor_.addTimeStampToken(request, timestampToken.value_or(TimeStampToken())); + auto [item, err] = card_->sendRequest(Instruction::INS_DEVICE_LOCKED_CMD, request); + if (err != KM_ERROR_OK) { + return km_utils::kmError2ScopedAStatus(err); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::earlyBootEnded() { + auto err = card_->sendEarlyBootEndedEvent(true); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending earlyBootEndedEvent."; + return km_utils::kmError2ScopedAStatus(err); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::getKeyCharacteristics( + const std::vector<uint8_t>& keyBlob, const std::vector<uint8_t>& appId, + const std::vector<uint8_t>& appData, std::vector<KeyCharacteristics>* result) { + cppbor::Array request; + request.add(vector<uint8_t>(keyBlob)); + request.add(vector<uint8_t>(appId)); + request.add(vector<uint8_t>(appData)); + auto [item, err] = card_->sendRequest(Instruction::INS_GET_KEY_CHARACTERISTICS_CMD, request); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in getKeyCharacteristics."; + return km_utils::kmError2ScopedAStatus(err); + } + auto optKeyChars = cbor_.getKeyCharacteristics(item, 1); + if (!optKeyChars) { + LOG(ERROR) << "Error in sending in upgradeKey."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + *result = std::move(optKeyChars.value()); + return ScopedAStatus::ok(); +} + +keymaster_error_t +JavacardKeyMintDevice::parseWrappedKey(const vector<uint8_t>& wrappedKeyData, + std::vector<uint8_t>& iv, std::vector<uint8_t>& transitKey, + std::vector<uint8_t>& secureKey, std::vector<uint8_t>& tag, + vector<KeyParameter>& authList, KeyFormat& keyFormat, + std::vector<uint8_t>& wrappedKeyDescription) { + KeymasterBlob kmIv; + KeymasterKeyBlob kmTransitKey; + KeymasterKeyBlob kmSecureKey; + KeymasterBlob kmTag; + AuthorizationSet authSet; + keymaster_key_format_t kmKeyFormat; + KeymasterBlob kmWrappedKeyDescription; + + size_t keyDataLen = wrappedKeyData.size(); + uint8_t* keyData = dup_buffer(wrappedKeyData.data(), keyDataLen); + keymaster_key_blob_t keyMaterial = {keyData, keyDataLen}; + keymaster_error_t error = + parse_wrapped_key(KeymasterKeyBlob(keyMaterial), &kmIv, &kmTransitKey, &kmSecureKey, &kmTag, + &authSet, &kmKeyFormat, &kmWrappedKeyDescription); + if (error != KM_ERROR_OK) { + LOG(ERROR) << "Error parsing wrapped key."; + return error; + } + iv = km_utils::kmBlob2vector(kmIv); + transitKey = km_utils::kmBlob2vector(kmTransitKey); + secureKey = km_utils::kmBlob2vector(kmSecureKey); + tag = km_utils::kmBlob2vector(kmTag); + authList = km_utils::kmParamSet2Aidl(authSet); + keyFormat = static_cast<KeyFormat>(kmKeyFormat); + wrappedKeyDescription = km_utils::kmBlob2vector(kmWrappedKeyDescription); + return KM_ERROR_OK; +} + +ScopedAStatus JavacardKeyMintDevice::convertStorageKeyToEphemeral( + const std::vector<uint8_t>& /* storageKeyBlob */, + std::vector<uint8_t>* /* ephemeralKeyBlob */) { + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNIMPLEMENTED); +} + +ScopedAStatus JavacardKeyMintDevice::getRootOfTrustChallenge(array<uint8_t, 16>* challenge) { + auto [item, err] = card_->sendRequest(Instruction::INS_GET_ROT_CHALLENGE_CMD); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in getRootOfTrustChallenge."; + return km_utils::kmError2ScopedAStatus(err); + } + auto optChallenge = cbor_.getByteArrayVec(item, 1); + if (!optChallenge) { + LOG(ERROR) << "Error in sending in upgradeKey."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + std::move(optChallenge->begin(), optChallenge->begin() + 16, challenge->begin()); + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::getRootOfTrust(const array<uint8_t, 16>& /*challenge*/, + vector<uint8_t>* /*rootOfTrust*/) { + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNIMPLEMENTED); +} + +ScopedAStatus JavacardKeyMintDevice::sendRootOfTrust(const vector<uint8_t>& rootOfTrust) { + cppbor::Array request; + request.add(EncodedItem(rootOfTrust)); // taggedItem. + auto [item, err] = card_->sendRequest(Instruction::INS_SEND_ROT_DATA_CMD, request); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in sendRootOfTrust."; + return km_utils::kmError2ScopedAStatus(err); + } + LOG(INFO) << "JavacardKeyMintDevice::sendRootOfTrust success"; + return ScopedAStatus::ok(); +} + +} // namespace aidl::android::hardware::security::keymint diff --git a/ready_se/google/keymint/KM200/HAL/JavacardKeyMintDevice.h b/ready_se/google/keymint/KM200/HAL/JavacardKeyMintDevice.h new file mode 100644 index 0000000..adf0f7d --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/JavacardKeyMintDevice.h @@ -0,0 +1,124 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <aidl/android/hardware/security/keymint/BnKeyMintDevice.h> +#include <aidl/android/hardware/security/keymint/BnKeyMintOperation.h> +#include <aidl/android/hardware/security/keymint/HardwareAuthToken.h> +#include <aidl/android/hardware/security/sharedsecret/SharedSecretParameters.h> + +#include "CborConverter.h" +#include "JavacardSecureElement.h" + +namespace aidl::android::hardware::security::keymint { +using cppbor::Item; +using ::keymint::javacard::CborConverter; +using ::keymint::javacard::JavacardSecureElement; +using ndk::ScopedAStatus; +using secureclock::TimeStampToken; +using std::array; +using std::optional; +using std::shared_ptr; +using std::vector; + +class JavacardKeyMintDevice : public BnKeyMintDevice { + public: + explicit JavacardKeyMintDevice(shared_ptr<JavacardSecureElement> card) + : securitylevel_(SecurityLevel::STRONGBOX), card_(card) { + card_->initializeJavacard(); + } + virtual ~JavacardKeyMintDevice() {} + + ScopedAStatus getHardwareInfo(KeyMintHardwareInfo* info) override; + + ScopedAStatus addRngEntropy(const vector<uint8_t>& data) override; + + ScopedAStatus generateKey(const vector<KeyParameter>& keyParams, + const optional<AttestationKey>& attestationKey, + KeyCreationResult* creationResult) override; + + ScopedAStatus importKey(const vector<KeyParameter>& keyParams, KeyFormat keyFormat, + const vector<uint8_t>& keyData, + const optional<AttestationKey>& attestationKey, + KeyCreationResult* creationResult) override; + + ScopedAStatus importWrappedKey(const vector<uint8_t>& wrappedKeyData, + const vector<uint8_t>& wrappingKeyBlob, + const vector<uint8_t>& maskingKey, + const vector<KeyParameter>& unwrappingParams, + int64_t passwordSid, int64_t biometricSid, + KeyCreationResult* creationResult) override; + + ScopedAStatus upgradeKey(const vector<uint8_t>& keyBlobToUpgrade, + const vector<KeyParameter>& upgradeParams, + vector<uint8_t>* keyBlob) override; + + ScopedAStatus deleteKey(const vector<uint8_t>& keyBlob) override; + ScopedAStatus deleteAllKeys() override; + ScopedAStatus destroyAttestationIds() override; + + virtual ScopedAStatus begin(KeyPurpose in_purpose, const std::vector<uint8_t>& in_keyBlob, + const std::vector<KeyParameter>& in_params, + const std::optional<HardwareAuthToken>& in_authToken, + BeginResult* _aidl_return) override; + + ScopedAStatus deviceLocked(bool passwordOnly, + const optional<TimeStampToken>& timestampToken) override; + + ScopedAStatus earlyBootEnded() override; + + ScopedAStatus getKeyCharacteristics(const std::vector<uint8_t>& in_keyBlob, + const std::vector<uint8_t>& in_appId, + const std::vector<uint8_t>& in_appData, + std::vector<KeyCharacteristics>* _aidl_return) override; + + ScopedAStatus convertStorageKeyToEphemeral(const std::vector<uint8_t>& storageKeyBlob, + std::vector<uint8_t>* ephemeralKeyBlob) override; + + ScopedAStatus getRootOfTrustChallenge(array<uint8_t, 16>* challenge) override; + + ScopedAStatus getRootOfTrust(const array<uint8_t, 16>& challenge, + vector<uint8_t>* rootOfTrust) override; + + ScopedAStatus sendRootOfTrust(const vector<uint8_t>& rootOfTrust) override; + + private: + keymaster_error_t parseWrappedKey(const vector<uint8_t>& wrappedKeyData, + std::vector<uint8_t>& iv, std::vector<uint8_t>& transitKey, + std::vector<uint8_t>& secureKey, std::vector<uint8_t>& tag, + vector<KeyParameter>& authList, KeyFormat& keyFormat, + std::vector<uint8_t>& wrappedKeyDescription); + + std::tuple<std::unique_ptr<Item>, keymaster_error_t> sendBeginImportWrappedKeyCmd( + const std::vector<uint8_t>& transitKey, const std::vector<uint8_t>& wrappingKeyBlob, + const std::vector<uint8_t>& maskingKey, const vector<KeyParameter>& unwrappingParams); + + std::tuple<std::unique_ptr<Item>, keymaster_error_t> + sendFinishImportWrappedKeyCmd(const vector<KeyParameter>& keyParams, KeyFormat keyFormat, + const std::vector<uint8_t>& secureKey, + const std::vector<uint8_t>& tag, const std::vector<uint8_t>& iv, + const std::vector<uint8_t>& wrappedKeyDescription, + int64_t passwordSid, int64_t biometricSid); + + ScopedAStatus defaultHwInfo(KeyMintHardwareInfo* info); + + const SecurityLevel securitylevel_; + const shared_ptr<JavacardSecureElement> card_; + CborConverter cbor_; +}; + +} // namespace aidl::android::hardware::security::keymint diff --git a/ready_se/google/keymint/KM200/HAL/JavacardKeyMintOperation.cpp b/ready_se/google/keymint/KM200/HAL/JavacardKeyMintOperation.cpp new file mode 100644 index 0000000..a46f066 --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/JavacardKeyMintOperation.cpp @@ -0,0 +1,300 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "javacard.strongbox.keymint.operation-impl" + +#include "JavacardKeyMintOperation.h" + +#include <KeyMintUtils.h> +#include <aidl/android/hardware/security/keymint/ErrorCode.h> +#include <aidl/android/hardware/security/secureclock/ISecureClock.h> +#include <android-base/logging.h> + +#include "CborConverter.h" + +namespace aidl::android::hardware::security::keymint { +using cppbor::Bstr; +using cppbor::Uint; +using secureclock::TimeStampToken; + +JavacardKeyMintOperation::~JavacardKeyMintOperation() { + if (opHandle_ != 0) { + JavacardKeyMintOperation::abort(); + } +} + +ScopedAStatus JavacardKeyMintOperation::updateAad(const vector<uint8_t>& input, + const optional<HardwareAuthToken>& authToken, + const optional<TimeStampToken>& timestampToken) { + cppbor::Array request; + request.add(Uint(opHandle_)); + request.add(Bstr(input)); + cbor_.addHardwareAuthToken(request, authToken.value_or(HardwareAuthToken())); + cbor_.addTimeStampToken(request, timestampToken.value_or(TimeStampToken())); + auto [item, err] = card_->sendRequest(Instruction::INS_UPDATE_AAD_OPERATION_CMD, request); + if (err != KM_ERROR_OK) { + return km_utils::kmError2ScopedAStatus(err); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintOperation::update(const vector<uint8_t>& input, + const optional<HardwareAuthToken>& authToken, + const optional<TimeStampToken>& timestampToken, + vector<uint8_t>* output) { + HardwareAuthToken aToken = authToken.value_or(HardwareAuthToken()); + TimeStampToken tToken = timestampToken.value_or(TimeStampToken()); + DataView view = {.buffer = {}, .data = input, .start = 0, .length = input.size()}; + keymaster_error_t err = bufferData(view); + if (err != KM_ERROR_OK) { + return km_utils::kmError2ScopedAStatus(err); + } + if (!(bufferingMode_ == BufferingMode::EC_NO_DIGEST || + bufferingMode_ == BufferingMode::RSA_DECRYPT_OR_NO_DIGEST)) { + if (view.length > MAX_CHUNK_SIZE) { + err = updateInChunks(view, aToken, tToken, output); + if (err != KM_ERROR_OK) { + return km_utils::kmError2ScopedAStatus(err); + } + } + vector<uint8_t> remaining = popNextChunk(view, view.length); + err = sendUpdate(remaining, aToken, tToken, *output); + } + return km_utils::kmError2ScopedAStatus(err); +} + +ScopedAStatus JavacardKeyMintOperation::finish(const optional<vector<uint8_t>>& input, + const optional<vector<uint8_t>>& signature, + const optional<HardwareAuthToken>& authToken, + const optional<TimeStampToken>& timestampToken, + const optional<vector<uint8_t>>& confirmationToken, + vector<uint8_t>* output) { + HardwareAuthToken aToken = authToken.value_or(HardwareAuthToken()); + TimeStampToken tToken = timestampToken.value_or(TimeStampToken()); + const vector<uint8_t> confToken = confirmationToken.value_or(vector<uint8_t>()); + const vector<uint8_t> inData = input.value_or(vector<uint8_t>()); + DataView view = {.buffer = {}, .data = inData, .start = 0, .length = inData.size()}; + const vector<uint8_t> sign = signature.value_or(vector<uint8_t>()); + if (!(bufferingMode_ == BufferingMode::EC_NO_DIGEST || + bufferingMode_ == BufferingMode::RSA_DECRYPT_OR_NO_DIGEST)) { + appendBufferedData(view); + if (view.length > MAX_CHUNK_SIZE) { + auto err = updateInChunks(view, aToken, tToken, output); + if (err != KM_ERROR_OK) { + return km_utils::kmError2ScopedAStatus(err); + } + } + } else { + keymaster_error_t err = bufferData(view); + if (err != KM_ERROR_OK) { + return km_utils::kmError2ScopedAStatus(err); + } + appendBufferedData(view); + } + vector<uint8_t> remaining = popNextChunk(view, view.length); + return km_utils::kmError2ScopedAStatus( + sendFinish(remaining, sign, aToken, tToken, confToken, *output)); +} + +ScopedAStatus JavacardKeyMintOperation::abort() { + Array request; + request.add(Uint(opHandle_)); + auto [item, err] = card_->sendRequest(Instruction::INS_ABORT_OPERATION_CMD, request); + opHandle_ = 0; + buffer_.clear(); + return km_utils::kmError2ScopedAStatus(err); +} + +void JavacardKeyMintOperation::blockAlign(DataView& view, uint16_t blockSize) { + appendBufferedData(view); + uint16_t offset = getDataViewOffset(view, blockSize); + if (view.buffer.empty() && !view.data.empty()) { + buffer_.insert(buffer_.end(), view.data.begin() + offset, view.data.end()); + } else if (view.data.empty() && !view.buffer.empty()) { + buffer_.insert(buffer_.end(), view.buffer.begin() + offset, view.buffer.end()); + } else { + if (offset < view.buffer.size()) { + buffer_.insert(buffer_.end(), view.buffer.begin() + offset, view.buffer.end()); + buffer_.insert(buffer_.end(), view.data.begin(), view.data.end()); + } else { + offset = offset - view.buffer.size(); + buffer_.insert(buffer_.end(), view.data.begin() + offset, view.data.end()); + } + } + // adjust the view length by removing the buffered data size from it. + view.length = view.length - buffer_.size(); +} + +uint16_t JavacardKeyMintOperation::getDataViewOffset(DataView& view, uint16_t blockSize) { + uint16_t offset = 0; + uint16_t remaining = 0; + switch (bufferingMode_) { + case BufferingMode::BUF_DES_DECRYPT_PKCS7_BLOCK_ALIGNED: + case BufferingMode::BUF_AES_DECRYPT_PKCS7_BLOCK_ALIGNED: + offset = ((view.length / blockSize)) * blockSize; + remaining = (view.length % blockSize); + if (offset >= blockSize && remaining == 0) { + offset -= blockSize; + } + break; + case BufferingMode::BUF_DES_ENCRYPT_PKCS7_BLOCK_ALIGNED: + case BufferingMode::BUF_AES_ENCRYPT_PKCS7_BLOCK_ALIGNED: + offset = ((view.length / blockSize)) * blockSize; + break; + case BufferingMode::BUF_AES_GCM_DECRYPT_BLOCK_ALIGNED: + if (view.length > macLength_) { + offset = (view.length - macLength_); + } + break; + default: + break; + } + return offset; +} + +keymaster_error_t JavacardKeyMintOperation::bufferData(DataView& view) { + if (view.data.empty()) return KM_ERROR_OK; // nothing to buffer + switch (bufferingMode_) { + case BufferingMode::RSA_DECRYPT_OR_NO_DIGEST: + buffer_.insert(buffer_.end(), view.data.begin(), view.data.end()); + if (buffer_.size() > RSA_BUFFER_SIZE) { + abort(); + return KM_ERROR_INVALID_INPUT_LENGTH; + } + view.start = 0; + view.length = 0; + break; + case BufferingMode::EC_NO_DIGEST: + if (buffer_.size() < EC_BUFFER_SIZE) { + buffer_.insert(buffer_.end(), view.data.begin(), view.data.end()); + // Truncate the buffered data if greater then allowed EC buffer size. + if (buffer_.size() > EC_BUFFER_SIZE) { + buffer_.erase(buffer_.begin() + EC_BUFFER_SIZE, buffer_.end()); + } + } + view.start = 0; + view.length = 0; + break; + case BufferingMode::BUF_AES_ENCRYPT_PKCS7_BLOCK_ALIGNED: + case BufferingMode::BUF_AES_DECRYPT_PKCS7_BLOCK_ALIGNED: + blockAlign(view, AES_BLOCK_SIZE); + break; + case BufferingMode::BUF_AES_GCM_DECRYPT_BLOCK_ALIGNED: + blockAlign(view, macLength_); + break; + case BufferingMode::BUF_DES_ENCRYPT_PKCS7_BLOCK_ALIGNED: + case BufferingMode::BUF_DES_DECRYPT_PKCS7_BLOCK_ALIGNED: + blockAlign(view, DES_BLOCK_SIZE); + break; + case BufferingMode::NONE: + break; + } + return KM_ERROR_OK; +} + +// Incrementally send the request using multiple updates. +keymaster_error_t JavacardKeyMintOperation::updateInChunks(DataView& view, + HardwareAuthToken& authToken, + TimeStampToken& timestampToken, + vector<uint8_t>* output) { + keymaster_error_t sendError = KM_ERROR_UNKNOWN_ERROR; + while (view.length > MAX_CHUNK_SIZE) { + vector<uint8_t> chunk = popNextChunk(view, MAX_CHUNK_SIZE); + sendError = sendUpdate(chunk, authToken, timestampToken, *output); + if (sendError != KM_ERROR_OK) { + return sendError; + } + // Clear tokens + if (!authToken.mac.empty()) authToken = HardwareAuthToken(); + if (!timestampToken.mac.empty()) timestampToken = TimeStampToken(); + } + return KM_ERROR_OK; +} + +vector<uint8_t> JavacardKeyMintOperation::popNextChunk(DataView& view, uint32_t chunkSize) { + uint32_t start = view.start; + uint32_t end = start + ((view.length < chunkSize) ? view.length : chunkSize); + vector<uint8_t> chunk; + if (start < view.buffer.size()) { + if (end < view.buffer.size()) { + chunk = {view.buffer.begin() + start, view.buffer.begin() + end}; + } else { + end = end - view.buffer.size(); + chunk = {view.buffer.begin() + start, view.buffer.end()}; + chunk.insert(chunk.end(), view.data.begin(), view.data.begin() + end); + } + } else { + start = start - view.buffer.size(); + end = end - view.buffer.size(); + chunk = {view.data.begin() + start, view.data.begin() + end}; + } + view.start = view.start + chunk.size(); + view.length = view.length - chunk.size(); + return chunk; +} + +keymaster_error_t JavacardKeyMintOperation::sendUpdate(const vector<uint8_t>& input, + const HardwareAuthToken& authToken, + const TimeStampToken& timestampToken, + vector<uint8_t>& output) { + if (input.empty()) { + return KM_ERROR_OK; + } + cppbor::Array request; + request.add(Uint(opHandle_)); + request.add(Bstr(input)); + cbor_.addHardwareAuthToken(request, authToken); + cbor_.addTimeStampToken(request, timestampToken); + auto [item, error] = card_->sendRequest(Instruction::INS_UPDATE_OPERATION_CMD, request); + if (error != KM_ERROR_OK) { + return error; + } + auto optTemp = cbor_.getByteArrayVec(item, 1); + if (!optTemp) { + return KM_ERROR_UNKNOWN_ERROR; + } + output.insert(output.end(), optTemp.value().begin(), optTemp.value().end()); + return KM_ERROR_OK; +} + +keymaster_error_t JavacardKeyMintOperation::sendFinish(const vector<uint8_t>& data, + const vector<uint8_t>& sign, + const HardwareAuthToken& authToken, + const TimeStampToken& timestampToken, + const vector<uint8_t>& confToken, + vector<uint8_t>& output) { + cppbor::Array request; + request.add(Uint(opHandle_)); + request.add(Bstr(data)); + request.add(Bstr(sign)); + cbor_.addHardwareAuthToken(request, authToken); + cbor_.addTimeStampToken(request, timestampToken); + request.add(Bstr(confToken)); + + auto [item, err] = card_->sendRequest(Instruction::INS_FINISH_OPERATION_CMD, request); + if (err != KM_ERROR_OK) { + return err; + } + auto optTemp = cbor_.getByteArrayVec(item, 1); + if (!optTemp) { + return KM_ERROR_UNKNOWN_ERROR; + } + opHandle_ = 0; + output.insert(output.end(), optTemp.value().begin(), optTemp.value().end()); + return KM_ERROR_OK; +} + +} // namespace aidl::android::hardware::security::keymint diff --git a/ready_se/google/keymint/KM200/HAL/JavacardKeyMintOperation.h b/ready_se/google/keymint/KM200/HAL/JavacardKeyMintOperation.h new file mode 100644 index 0000000..c1d967a --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/JavacardKeyMintOperation.h @@ -0,0 +1,136 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <vector> + +#include <aidl/android/hardware/security/keymint/BnKeyMintOperation.h> +#include <aidl/android/hardware/security/secureclock/ISecureClock.h> +#include <hardware/keymaster_defs.h> + +#include "CborConverter.h" +#include "JavacardSecureElement.h" + +#define AES_BLOCK_SIZE 16 +#define DES_BLOCK_SIZE 8 +#define RSA_BUFFER_SIZE 256 +#define EC_BUFFER_SIZE 32 +#define MAX_CHUNK_SIZE 256 + +namespace aidl::android::hardware::security::keymint { +using cppbor::Array; +using cppbor::Item; +using ::keymint::javacard::CborConverter; +using ::keymint::javacard::Instruction; +using ::keymint::javacard::JavacardSecureElement; +using ::ndk::ScopedAStatus; +using secureclock::TimeStampToken; +using std::optional; +using std::shared_ptr; +using std::vector; + +// Bufferig modes for update +enum class BufferingMode : int32_t { + NONE = 0, // Send everything to javacard - most of the assymteric operations + RSA_DECRYPT_OR_NO_DIGEST = + 1, // Buffer everything in update upto 256 bytes and send in finish. If + // input data is greater then 256 bytes then it is an error. Javacard + // will further check according to exact key size and crypto provider. + EC_NO_DIGEST = 2, // Buffer upto 65 bytes and then truncate. Javacard will further truncate + // upto exact keysize. + BUF_AES_ENCRYPT_PKCS7_BLOCK_ALIGNED = 3, // Buffer 16 bytes. + BUF_AES_DECRYPT_PKCS7_BLOCK_ALIGNED = 4, // Buffer 16 bytes. + BUF_DES_ENCRYPT_PKCS7_BLOCK_ALIGNED = 5, // Buffer 8 bytes. + BUF_DES_DECRYPT_PKCS7_BLOCK_ALIGNED = 6, // Buffer 8 bytes. + BUF_AES_GCM_DECRYPT_BLOCK_ALIGNED = 7, // Buffer 16 bytes. + +}; + +// The is the view in the input data being processed by update/finish funcion. + +struct DataView { + vector<uint8_t> buffer; // previously buffered data from cycle n-1 + const vector<uint8_t>& data; // current data in cycle n. + uint32_t start; // start of the view + size_t length; // length of the view +}; + +class JavacardKeyMintOperation : public BnKeyMintOperation { + public: + explicit JavacardKeyMintOperation(keymaster_operation_handle_t opHandle, + BufferingMode bufferingMode, uint16_t macLength, + shared_ptr<JavacardSecureElement> card) + : buffer_(vector<uint8_t>()), bufferingMode_(bufferingMode), macLength_(macLength), + card_(card), opHandle_(opHandle) {} + virtual ~JavacardKeyMintOperation(); + + ScopedAStatus updateAad(const vector<uint8_t>& input, + const optional<HardwareAuthToken>& authToken, + const optional<TimeStampToken>& timestampToken) override; + + ScopedAStatus update(const vector<uint8_t>& input, const optional<HardwareAuthToken>& authToken, + const optional<TimeStampToken>& timestampToken, + vector<uint8_t>* output) override; + + ScopedAStatus finish(const optional<vector<uint8_t>>& input, + const optional<vector<uint8_t>>& signature, + const optional<HardwareAuthToken>& authToken, + const optional<TimeStampToken>& timestampToken, + const optional<vector<uint8_t>>& confirmationToken, + vector<uint8_t>* output) override; + + ScopedAStatus abort() override; + + private: + vector<uint8_t> popNextChunk(DataView& view, uint32_t chunkSize); + + keymaster_error_t updateInChunks(DataView& data, HardwareAuthToken& authToken, + TimeStampToken& timestampToken, vector<uint8_t>* output); + + keymaster_error_t sendFinish(const vector<uint8_t>& data, const vector<uint8_t>& signature, + const HardwareAuthToken& authToken, + const TimeStampToken& timestampToken, + const vector<uint8_t>& confToken, vector<uint8_t>& output); + + keymaster_error_t sendUpdate(const vector<uint8_t>& data, const HardwareAuthToken& authToken, + const TimeStampToken& timestampToken, vector<uint8_t>& output); + + inline void appendBufferedData(DataView& view) { + if (!buffer_.empty()) { + view.buffer = buffer_; + view.length = view.length + buffer_.size(); + view.start = 0; + // view.buffer = insert(data.begin(), buffer_.begin(), buffer_.end()); + buffer_.clear(); + } + } + + std::tuple<std::unique_ptr<Item>, keymaster_error_t> sendRequest(Instruction ins, + Array& request); + keymaster_error_t bufferData(DataView& data); + void blockAlign(DataView& data, uint16_t blockSize); + uint16_t getDataViewOffset(DataView& view, uint16_t blockSize); + + vector<uint8_t> buffer_; + BufferingMode bufferingMode_; + uint16_t macLength_; + const shared_ptr<JavacardSecureElement> card_; + keymaster_operation_handle_t opHandle_; + CborConverter cbor_; +}; + +} // namespace aidl::android::hardware::security::keymint diff --git a/ready_se/google/keymint/KM200/HAL/JavacardRemotelyProvisionedComponentDevice.cpp b/ready_se/google/keymint/KM200/HAL/JavacardRemotelyProvisionedComponentDevice.cpp new file mode 100644 index 0000000..fe9821b --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/JavacardRemotelyProvisionedComponentDevice.cpp @@ -0,0 +1,284 @@ +/* + * Copyright 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "javacard.keymint.device.rkp.strongbox-impl" + +#include "JavacardRemotelyProvisionedComponentDevice.h" + +#include <aidl/android/hardware/security/keymint/MacedPublicKey.h> + +#include <KeyMintUtils.h> +#include <android-base/logging.h> +#include <keymaster/cppcose/cppcose.h> +#include <keymaster/remote_provisioning_utils.h> + +namespace aidl::android::hardware::security::keymint { +using cppbor::Array; +using cppbor::EncodedItem; +using cppcose::kCoseMac0EntryCount; +using cppcose::kCoseMac0Payload; +using ::keymint::javacard::Instruction; +using std::string; + +// RKP error codes defined in keymint applet. +constexpr int32_t kStatusFailed = 32000; +constexpr int32_t kStatusInvalidMac = 32001; +constexpr int32_t kStatusProductionKeyInTestRequest = 32002; +constexpr int32_t kStatusTestKeyInProductionRequest = 32003; +constexpr int32_t kStatusInvalidEek = 32004; +constexpr int32_t kStatusInvalidState = 32005; + +namespace { + +keymaster_error_t translateRkpErrorCode(int32_t error) { + switch (-error) { + case kStatusFailed: + case kStatusInvalidState: + return static_cast<keymaster_error_t>(BnRemotelyProvisionedComponent::STATUS_FAILED); + case kStatusInvalidMac: + return static_cast<keymaster_error_t>(BnRemotelyProvisionedComponent::STATUS_INVALID_MAC); + case kStatusProductionKeyInTestRequest: + return static_cast<keymaster_error_t>( + BnRemotelyProvisionedComponent::STATUS_PRODUCTION_KEY_IN_TEST_REQUEST); + case kStatusTestKeyInProductionRequest: + return static_cast<keymaster_error_t>( + BnRemotelyProvisionedComponent::STATUS_TEST_KEY_IN_PRODUCTION_REQUEST); + case kStatusInvalidEek: + return static_cast<keymaster_error_t>(BnRemotelyProvisionedComponent::STATUS_INVALID_EEK); + } + return static_cast<keymaster_error_t>(error); +} + +ScopedAStatus defaultHwInfo(RpcHardwareInfo* info) { + info->versionNumber = 2; + info->rpcAuthorName = "Google"; + info->supportedEekCurve = RpcHardwareInfo::CURVE_P256; + info->uniqueId = "strongbox keymint"; + return ScopedAStatus::ok(); +} + +uint32_t coseKeyEncodedSize(const std::vector<MacedPublicKey>& keysToSign) { + uint32_t size = 0; + for (auto& macKey : keysToSign) { + auto [macedKeyItem, _, coseMacErrMsg] = cppbor::parse(macKey.macedKey); + if (!macedKeyItem || !macedKeyItem->asArray() || + macedKeyItem->asArray()->size() != kCoseMac0EntryCount) { + LOG(ERROR) << "Invalid COSE_Mac0 structure"; + return 0; + } + auto payload = macedKeyItem->asArray()->get(kCoseMac0Payload)->asBstr(); + if (!payload) return 0; + size += payload->value().size(); + } + return size; +} + +} // namespace + +ScopedAStatus JavacardRemotelyProvisionedComponentDevice::getHardwareInfo(RpcHardwareInfo* info) { + auto [item, err] = card_->sendRequest(Instruction::INS_GET_RKP_HARDWARE_INFO); + std::optional<uint64_t> optVersionNumber; + std::optional<uint64_t> optSupportedEekCurve; + std::optional<string> optRpcAuthorName; + std::optional<string> optUniqueId; + if (err != KM_ERROR_OK || !(optVersionNumber = cbor_.getUint64(item, 1)) || + !(optRpcAuthorName = cbor_.getByteArrayStr(item, 2)) || + !(optSupportedEekCurve = cbor_.getUint64(item, 3)) || + !(optUniqueId = cbor_.getByteArrayStr(item, 4))) { + LOG(ERROR) << "Error in response of getHardwareInfo."; + LOG(INFO) << "Returning defaultHwInfo in getHardwareInfo."; + return defaultHwInfo(info); + } + info->rpcAuthorName = std::move(optRpcAuthorName.value()); + info->versionNumber = static_cast<int32_t>(std::move(optVersionNumber.value())); + info->supportedEekCurve = static_cast<int32_t>(std::move(optSupportedEekCurve.value())); + info->uniqueId = std::move(optUniqueId.value()); + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardRemotelyProvisionedComponentDevice::generateEcdsaP256KeyPair( + bool testMode, MacedPublicKey* macedPublicKey, std::vector<uint8_t>* privateKeyHandle) { + cppbor::Array array; + array.add(testMode); + auto [item, err] = card_->sendRequest(Instruction::INS_GENERATE_RKP_KEY_CMD, array); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending generateEcdsaP256KeyPair."; + return km_utils::kmError2ScopedAStatus(translateRkpErrorCode(err)); + } + std::optional<std::vector<uint8_t>> optMacedKey; + std::optional<std::vector<uint8_t>> optPKeyHandle; + if (!(optMacedKey = cbor_.getByteArrayVec(item, 1)) || + !(optPKeyHandle = cbor_.getByteArrayVec(item, 2))) { + LOG(ERROR) << "Error in decoding og response in generateEcdsaP256KeyPair."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + *privateKeyHandle = std::move(optPKeyHandle.value()); + macedPublicKey->macedKey = std::move(optMacedKey.value()); + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardRemotelyProvisionedComponentDevice::beginSendData( + bool testMode, const std::vector<MacedPublicKey>& keysToSign) { + uint32_t totalEncodedSize = coseKeyEncodedSize(keysToSign); + cppbor::Array array; + array.add(keysToSign.size()); + array.add(totalEncodedSize); + array.add(testMode); + auto [_, err] = card_->sendRequest(Instruction::INS_BEGIN_SEND_DATA_CMD, array); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in beginSendData."; + return km_utils::kmError2ScopedAStatus(translateRkpErrorCode(err)); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardRemotelyProvisionedComponentDevice::updateMacedKey( + const std::vector<MacedPublicKey>& keysToSign) { + for (auto& macedPublicKey : keysToSign) { + cppbor::Array array; + array.add(EncodedItem(macedPublicKey.macedKey)); + auto [_, err] = card_->sendRequest(Instruction::INS_UPDATE_KEY_CMD, array); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in updateMacedKey."; + return km_utils::kmError2ScopedAStatus(translateRkpErrorCode(err)); + } + } + return ScopedAStatus::ok(); +} + +ScopedAStatus +JavacardRemotelyProvisionedComponentDevice::updateChallenge(const std::vector<uint8_t>& challenge) { + Array array; + array.add(challenge); + auto [_, err] = card_->sendRequest(Instruction::INS_UPDATE_CHALLENGE_CMD, array); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in updateChallenge."; + return km_utils::kmError2ScopedAStatus(translateRkpErrorCode(err)); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardRemotelyProvisionedComponentDevice::updateEEK( + const std::vector<uint8_t>& endpointEncCertChain) { + std::vector<uint8_t> eekChain = endpointEncCertChain; + auto [_, err] = card_->sendRequest(Instruction::INS_UPDATE_EEK_CHAIN_CMD, eekChain); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in updateEEK."; + return km_utils::kmError2ScopedAStatus(translateRkpErrorCode(err)); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardRemotelyProvisionedComponentDevice::finishSendData( + std::vector<uint8_t>* keysToSignMac, DeviceInfo* deviceInfo, + std::vector<uint8_t>& coseEncryptProtectedHeader, cppbor::Map& coseEncryptUnProtectedHeader, + std::vector<uint8_t>& partialCipheredData, uint32_t& respFlag) { + + auto [item, err] = card_->sendRequest(Instruction::INS_FINISH_SEND_DATA_CMD); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in finishSendData."; + return km_utils::kmError2ScopedAStatus(translateRkpErrorCode(err)); + } + auto optDecodedKeysToSignMac = cbor_.getByteArrayVec(item, 1); + auto optDecodedDeviceInfo = cbor_.getByteArrayVec(item, 2); + auto optCEncryptProtectedHeader = cbor_.getByteArrayVec(item, 3); + auto optCEncryptUnProtectedHeader = cbor_.getMapItem(item, 4); + auto optPCipheredData = cbor_.getByteArrayVec(item, 5); + auto optRespFlag = cbor_.getUint64(item, 6); + if (!optDecodedKeysToSignMac || !optDecodedDeviceInfo || !optCEncryptProtectedHeader || + !optCEncryptUnProtectedHeader || !optPCipheredData || !optRespFlag) { + LOG(ERROR) << "Error in decoding og response in finishSendData."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + *keysToSignMac = std::move(optDecodedKeysToSignMac.value()); + deviceInfo->deviceInfo = std::move(optDecodedDeviceInfo.value()); + coseEncryptProtectedHeader = std::move(optCEncryptProtectedHeader.value()); + coseEncryptUnProtectedHeader = std::move(optCEncryptUnProtectedHeader.value()); + partialCipheredData.insert(partialCipheredData.end(), optPCipheredData->begin(), + optPCipheredData->end()); + respFlag = std::move(optRespFlag.value()); + return ScopedAStatus::ok(); +} + +ScopedAStatus +JavacardRemotelyProvisionedComponentDevice::getResponse(std::vector<uint8_t>& partialCipheredData, + cppbor::Array& recepientStructure, + uint32_t& respFlag) { + auto [item, err] = card_->sendRequest(Instruction::INS_GET_RESPONSE_CMD); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in getResponse."; + return km_utils::kmError2ScopedAStatus(translateRkpErrorCode(err)); + } + auto optPCipheredData = cbor_.getByteArrayVec(item, 1); + auto optArray = cbor_.getArrayItem(item, 2); + auto optRespFlag = cbor_.getUint64(item, 3); + if (!optPCipheredData || !optArray || !optRespFlag) { + LOG(ERROR) << "Error in decoding og response in getResponse."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + recepientStructure = std::move(optArray.value()); + partialCipheredData.insert(partialCipheredData.end(), optPCipheredData->begin(), + optPCipheredData->end()); + respFlag = std::move(optRespFlag.value()); + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardRemotelyProvisionedComponentDevice::generateCertificateRequest( + bool testMode, const std::vector<MacedPublicKey>& keysToSign, + const std::vector<uint8_t>& endpointEncCertChain, const std::vector<uint8_t>& challenge, + DeviceInfo* deviceInfo, ProtectedData* protectedData, std::vector<uint8_t>* keysToSignMac) { + std::vector<uint8_t> coseEncryptProtectedHeader; + cppbor::Map coseEncryptUnProtectedHeader; + cppbor::Array recipients; + std::vector<uint8_t> cipheredData; + uint32_t respFlag; + auto ret = beginSendData(testMode, keysToSign); + if (!ret.isOk()) return ret; + + ret = updateMacedKey(keysToSign); + if (!ret.isOk()) return ret; + + ret = updateChallenge(challenge); + if (!ret.isOk()) return ret; + + ret = updateEEK(endpointEncCertChain); + if (!ret.isOk()) return ret; + + ret = finishSendData(keysToSignMac, deviceInfo, coseEncryptProtectedHeader, + coseEncryptUnProtectedHeader, cipheredData, respFlag); + if (!ret.isOk()) return ret; + + while (respFlag != 0) { // more data is pending to receive + ret = getResponse(cipheredData, recipients, respFlag); + if (!ret.isOk()) return ret; + } + // Create ConseEncrypt structure. + protectedData->protectedData = cppbor::Array() + .add(coseEncryptProtectedHeader) // Protected + .add(std::move(coseEncryptUnProtectedHeader)) // Unprotected + .add(cipheredData) // Payload + .add(std::move(recipients)) + .encode(); + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardRemotelyProvisionedComponentDevice::generateCertificateRequestV2( + const std::vector<MacedPublicKey>& /*keysToSign*/, const std::vector<uint8_t>& /*challenge*/, + std::vector<uint8_t>* /*csr*/) { + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNIMPLEMENTED); +} + +} // namespace aidl::android::hardware::security::keymint diff --git a/ready_se/google/keymint/KM200/HAL/JavacardRemotelyProvisionedComponentDevice.h b/ready_se/google/keymint/KM200/HAL/JavacardRemotelyProvisionedComponentDevice.h new file mode 100644 index 0000000..7f41891 --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/JavacardRemotelyProvisionedComponentDevice.h @@ -0,0 +1,80 @@ +/* + * Copyright 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <cppbor.h> + +#include <aidl/android/hardware/security/keymint/BnRemotelyProvisionedComponent.h> +#include <aidl/android/hardware/security/keymint/RpcHardwareInfo.h> +#include <aidl/android/hardware/security/keymint/SecurityLevel.h> + +#include <keymaster/UniquePtr.h> +#include <keymaster/android_keymaster.h> + +#include "CborConverter.h" +#include "JavacardSecureElement.h" + +namespace aidl::android::hardware::security::keymint { +using ::keymint::javacard::CborConverter; +using ::keymint::javacard::JavacardSecureElement; +using ndk::ScopedAStatus; +using std::shared_ptr; + +class JavacardRemotelyProvisionedComponentDevice : public BnRemotelyProvisionedComponent { + public: + explicit JavacardRemotelyProvisionedComponentDevice(shared_ptr<JavacardSecureElement> card) + : card_(card) {} + + virtual ~JavacardRemotelyProvisionedComponentDevice() = default; + + ScopedAStatus getHardwareInfo(RpcHardwareInfo* info) override; + + ScopedAStatus generateEcdsaP256KeyPair(bool testMode, MacedPublicKey* macedPublicKey, + std::vector<uint8_t>* privateKeyHandle) override; + + ScopedAStatus generateCertificateRequest(bool testMode, + const std::vector<MacedPublicKey>& keysToSign, + const std::vector<uint8_t>& endpointEncCertChain, + const std::vector<uint8_t>& challenge, + DeviceInfo* deviceInfo, ProtectedData* protectedData, + std::vector<uint8_t>* keysToSignMac) override; + + ScopedAStatus generateCertificateRequestV2(const std::vector<MacedPublicKey>& keysToSign, + const std::vector<uint8_t>& challenge, + std::vector<uint8_t>* csr) override; + + private: + ScopedAStatus beginSendData(bool testMode, const std::vector<MacedPublicKey>& keysToSign); + + ScopedAStatus updateMacedKey(const std::vector<MacedPublicKey>& keysToSign); + + ScopedAStatus updateChallenge(const std::vector<uint8_t>& challenge); + + ScopedAStatus updateEEK(const std::vector<uint8_t>& endpointEncCertChain); + + ScopedAStatus finishSendData(std::vector<uint8_t>* keysToSignMac, DeviceInfo* deviceInfo, + std::vector<uint8_t>& coseEncryptProtectedHeader, + cppbor::Map& coseEncryptUnProtectedHeader, + std::vector<uint8_t>& partialCipheredData, uint32_t& respFlag); + + ScopedAStatus getResponse(std::vector<uint8_t>& partialCipheredData, + cppbor::Array& recepientStructure, uint32_t& respFlag); + std::shared_ptr<JavacardSecureElement> card_; + CborConverter cbor_; +}; + +} // namespace aidl::android::hardware::security::keymint diff --git a/ready_se/google/keymint/KM200/HAL/JavacardSecureElement.cpp b/ready_se/google/keymint/KM200/HAL/JavacardSecureElement.cpp new file mode 100644 index 0000000..7c4f038 --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/JavacardSecureElement.cpp @@ -0,0 +1,157 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "javacard.keymint.device.strongbox-impl" +#include "JavacardSecureElement.h" + +#include <algorithm> +#include <iostream> +#include <iterator> +#include <memory> +#include <regex.h> +#include <string> +#include <vector> + +#include <android-base/logging.h> +#include <android-base/properties.h> +#include <keymaster/android_keymaster_messages.h> + +#include "keymint_utils.h" + +namespace keymint::javacard { + +keymaster_error_t JavacardSecureElement::initializeJavacard() { + Array request; + request.add(Uint(getOsVersion())); + request.add(Uint(getOsPatchlevel())); + request.add(Uint(getVendorPatchlevel())); + auto [item, err] = sendRequest(Instruction::INS_INIT_STRONGBOX_CMD, request); + return err; +} + +keymaster_error_t JavacardSecureElement::sendEarlyBootEndedEvent(bool eventTriggered) { + isEarlyBootEventPending |= eventTriggered; + if (!isEarlyBootEventPending) { + return KM_ERROR_OK; + } + auto [item, err] = sendRequest(Instruction::INS_EARLY_BOOT_ENDED_CMD); + if (err != KM_ERROR_OK) { + // Incase of failure cache the event and send in the next immediate request to Applet. + isEarlyBootEventPending = true; + return err; + } + isEarlyBootEventPending = false; + return KM_ERROR_OK; +} + +keymaster_error_t JavacardSecureElement::constructApduMessage(Instruction& ins, + std::vector<uint8_t>& inputData, + std::vector<uint8_t>& apduOut) { + apduOut.push_back(static_cast<uint8_t>(APDU_CLS)); // CLS + apduOut.push_back(static_cast<uint8_t>(ins)); // INS + apduOut.push_back(static_cast<uint8_t>(APDU_P1)); // P1 + apduOut.push_back(static_cast<uint8_t>(APDU_P2)); // P2 + + if (USHRT_MAX >= inputData.size()) { + // Send extended length APDU always as response size is not known to HAL. + // Case 1: Lc > 0 CLS | INS | P1 | P2 | 00 | 2 bytes of Lc | CommandData | 2 bytes of Le + // all set to 00. Case 2: Lc = 0 CLS | INS | P1 | P2 | 3 bytes of Le all set to 00. + // Extended length 3 bytes, starts with 0x00 + apduOut.push_back(static_cast<uint8_t>(0x00)); + if (inputData.size() > 0) { + apduOut.push_back(static_cast<uint8_t>(inputData.size() >> 8)); + apduOut.push_back(static_cast<uint8_t>(inputData.size() & 0xFF)); + // Data + apduOut.insert(apduOut.end(), inputData.begin(), inputData.end()); + } + // Expected length of output. + // Accepting complete length of output every time. + apduOut.push_back(static_cast<uint8_t>(0x00)); + apduOut.push_back(static_cast<uint8_t>(0x00)); + } else { + LOG(ERROR) << "Error in constructApduMessage."; + return (KM_ERROR_INVALID_INPUT_LENGTH); + } + return (KM_ERROR_OK); // success +} + +keymaster_error_t JavacardSecureElement::sendData(Instruction ins, std::vector<uint8_t>& inData, + std::vector<uint8_t>& response) { + keymaster_error_t ret = KM_ERROR_UNKNOWN_ERROR; + std::vector<uint8_t> apdu; + + ret = constructApduMessage(ins, inData, apdu); + + if (ret != KM_ERROR_OK) { + return ret; + } + + ret = transport_->sendData(apdu, response); + if (ret != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending data in sendData. " << static_cast<int>(ret); + return ret; + } + + // Response size should be greater than 2. Cbor output data followed by two bytes of APDU + // status. + if ((response.size() <= 2) || (getApduStatus(response) != APDU_RESP_STATUS_OK)) { + LOG(ERROR) << "Response of the sendData is wrong: response size = " << response.size() + << " apdu status = " << getApduStatus(response); + return (KM_ERROR_UNKNOWN_ERROR); + } + // remove the status bytes + response.pop_back(); + response.pop_back(); + return (KM_ERROR_OK); // success +} + +std::tuple<std::unique_ptr<Item>, keymaster_error_t> +JavacardSecureElement::sendRequest(Instruction ins, Array& request) { + vector<uint8_t> response; + // encode request + std::vector<uint8_t> command = request.encode(); + auto sendError = sendData(ins, command, response); + if (sendError != KM_ERROR_OK) { + return {unique_ptr<Item>(nullptr), sendError}; + } + // decode the response and send that back + return cbor_.decodeData(response); +} + +std::tuple<std::unique_ptr<Item>, keymaster_error_t> +JavacardSecureElement::sendRequest(Instruction ins, std::vector<uint8_t>& command) { + vector<uint8_t> response; + auto sendError = sendData(ins, command, response); + if (sendError != KM_ERROR_OK) { + return {unique_ptr<Item>(nullptr), sendError}; + } + // decode the response and send that back + return cbor_.decodeData(response); +} + +std::tuple<std::unique_ptr<Item>, keymaster_error_t> +JavacardSecureElement::sendRequest(Instruction ins) { + vector<uint8_t> response; + vector<uint8_t> emptyRequest; + auto sendError = sendData(ins, emptyRequest, response); + if (sendError != KM_ERROR_OK) { + return {unique_ptr<Item>(nullptr), sendError}; + } + // decode the response and send that back + return cbor_.decodeData(response); +} + +} // namespace keymint::javacard diff --git a/ready_se/google/keymint/KM200/HAL/JavacardSecureElement.h b/ready_se/google/keymint/KM200/HAL/JavacardSecureElement.h new file mode 100644 index 0000000..2ea5fe4 --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/JavacardSecureElement.h @@ -0,0 +1,114 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <ITransport.h> + +#include "CborConverter.h" + +#define APDU_CLS 0x80 +#define APDU_P1 0x50 +#define APDU_P2 0x00 +#define APDU_RESP_STATUS_OK 0x9000 + +#define KEYMINT_CMD_APDU_START 0x20 + +namespace keymint::javacard { +using std::shared_ptr; +using std::vector; + +enum class Instruction { + // Keymaster commands + INS_GENERATE_KEY_CMD = KEYMINT_CMD_APDU_START + 1, + INS_IMPORT_KEY_CMD = KEYMINT_CMD_APDU_START + 2, + INS_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 3, + INS_EXPORT_KEY_CMD = KEYMINT_CMD_APDU_START + 4, + INS_ATTEST_KEY_CMD = KEYMINT_CMD_APDU_START + 5, + INS_UPGRADE_KEY_CMD = KEYMINT_CMD_APDU_START + 6, + INS_DELETE_KEY_CMD = KEYMINT_CMD_APDU_START + 7, + INS_DELETE_ALL_KEYS_CMD = KEYMINT_CMD_APDU_START + 8, + INS_ADD_RNG_ENTROPY_CMD = KEYMINT_CMD_APDU_START + 9, + INS_COMPUTE_SHARED_SECRET_CMD = KEYMINT_CMD_APDU_START + 10, + INS_DESTROY_ATT_IDS_CMD = KEYMINT_CMD_APDU_START + 11, + INS_VERIFY_AUTHORIZATION_CMD = KEYMINT_CMD_APDU_START + 12, + INS_GET_SHARED_SECRET_PARAM_CMD = KEYMINT_CMD_APDU_START + 13, + INS_GET_KEY_CHARACTERISTICS_CMD = KEYMINT_CMD_APDU_START + 14, + INS_GET_HW_INFO_CMD = KEYMINT_CMD_APDU_START + 15, + INS_BEGIN_OPERATION_CMD = KEYMINT_CMD_APDU_START + 16, + INS_UPDATE_OPERATION_CMD = KEYMINT_CMD_APDU_START + 17, + INS_FINISH_OPERATION_CMD = KEYMINT_CMD_APDU_START + 18, + INS_ABORT_OPERATION_CMD = KEYMINT_CMD_APDU_START + 19, + INS_DEVICE_LOCKED_CMD = KEYMINT_CMD_APDU_START + 20, + INS_EARLY_BOOT_ENDED_CMD = KEYMINT_CMD_APDU_START + 21, + INS_GET_CERT_CHAIN_CMD = KEYMINT_CMD_APDU_START + 22, + INS_UPDATE_AAD_OPERATION_CMD = KEYMINT_CMD_APDU_START + 23, + INS_BEGIN_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 24, + INS_FINISH_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 25, + INS_INIT_STRONGBOX_CMD = KEYMINT_CMD_APDU_START + 26, + // RKP Commands + INS_GET_RKP_HARDWARE_INFO = KEYMINT_CMD_APDU_START + 27, + INS_GENERATE_RKP_KEY_CMD = KEYMINT_CMD_APDU_START + 28, + INS_BEGIN_SEND_DATA_CMD = KEYMINT_CMD_APDU_START + 29, + INS_UPDATE_KEY_CMD = KEYMINT_CMD_APDU_START + 30, + INS_UPDATE_EEK_CHAIN_CMD = KEYMINT_CMD_APDU_START + 31, + INS_UPDATE_CHALLENGE_CMD = KEYMINT_CMD_APDU_START + 32, + INS_FINISH_SEND_DATA_CMD = KEYMINT_CMD_APDU_START + 33, + INS_GET_RESPONSE_CMD = KEYMINT_CMD_APDU_START + 34, + // SE ROT Commands + INS_GET_ROT_CHALLENGE_CMD = KEYMINT_CMD_APDU_START + 45, + INS_GET_ROT_DATA_CMD = KEYMINT_CMD_APDU_START + 46, + INS_SEND_ROT_DATA_CMD = KEYMINT_CMD_APDU_START + 47, +}; + +class JavacardSecureElement { + public: + explicit JavacardSecureElement(shared_ptr<ITransport> transport, uint32_t osVersion, + uint32_t osPatchLevel, uint32_t vendorPatchLevel) + : transport_(transport), osVersion_(osVersion), osPatchLevel_(osPatchLevel), + vendorPatchLevel_(vendorPatchLevel), isEarlyBootEventPending(false) { + transport_->openConnection(); + } + virtual ~JavacardSecureElement() { transport_->closeConnection(); } + + std::tuple<std::unique_ptr<Item>, keymaster_error_t> sendRequest(Instruction ins, + Array& request); + std::tuple<std::unique_ptr<Item>, keymaster_error_t> sendRequest(Instruction ins); + std::tuple<std::unique_ptr<Item>, keymaster_error_t> sendRequest(Instruction ins, + std::vector<uint8_t>& command); + + keymaster_error_t sendData(Instruction ins, std::vector<uint8_t>& inData, + std::vector<uint8_t>& response); + + keymaster_error_t constructApduMessage(Instruction& ins, std::vector<uint8_t>& inputData, + std::vector<uint8_t>& apduOut); + keymaster_error_t initializeJavacard(); + keymaster_error_t sendEarlyBootEndedEvent(bool eventTriggered); + inline uint16_t getApduStatus(std::vector<uint8_t>& inputData) { + // Last two bytes are the status SW0SW1 + uint8_t SW0 = inputData.at(inputData.size() - 2); + uint8_t SW1 = inputData.at(inputData.size() - 1); + return (SW0 << 8 | SW1); + } + + shared_ptr<ITransport> transport_; + uint32_t osVersion_; + uint32_t osPatchLevel_; + uint32_t vendorPatchLevel_; + bool isEarlyBootEventPending; + CborConverter cbor_; +}; +} // namespace keymint::javacard diff --git a/ready_se/google/keymint/KM200/HAL/JavacardSharedSecret.cpp b/ready_se/google/keymint/KM200/HAL/JavacardSharedSecret.cpp new file mode 100644 index 0000000..c5cf9a2 --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/JavacardSharedSecret.cpp @@ -0,0 +1,61 @@ +#define LOG_TAG "javacard.strongbox.keymint.operation-impl" +#include "JavacardSharedSecret.h" + +#include <android-base/logging.h> + +#include <KeyMintUtils.h> + +namespace aidl::android::hardware::security::sharedsecret { +using ::keymint::javacard::Instruction; + +ScopedAStatus JavacardSharedSecret::getSharedSecretParameters(SharedSecretParameters* params) { + auto error = card_->initializeJavacard(); + if (error != KM_ERROR_OK) { + LOG(ERROR) << "Error in initializing javacard."; + return keymint::km_utils::kmError2ScopedAStatus(error); + } + auto [item, err] = card_->sendRequest(Instruction::INS_GET_SHARED_SECRET_PARAM_CMD); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in getSharedSecretParameters."; + return keymint::km_utils::kmError2ScopedAStatus(err); + } + auto optSSParams = cbor_.getSharedSecretParameters(item, 1); + if (!optSSParams) { + LOG(ERROR) << "Error in sending in getSharedSecretParameters."; + return keymint::km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + *params = std::move(optSSParams.value()); + return ScopedAStatus::ok(); +} + +ScopedAStatus +JavacardSharedSecret::computeSharedSecret(const std::vector<SharedSecretParameters>& params, + std::vector<uint8_t>* secret) { + + auto error = card_->sendEarlyBootEndedEvent(false); + if (error != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending earlyBoot event javacard."; + return keymint::km_utils::kmError2ScopedAStatus(error); + } + error = card_->initializeJavacard(); + if (error != KM_ERROR_OK) { + LOG(ERROR) << "Error in initializing javacard."; + return keymint::km_utils::kmError2ScopedAStatus(error); + } + cppbor::Array request; + cbor_.addSharedSecretParameters(request, params); + auto [item, err] = card_->sendRequest(Instruction::INS_COMPUTE_SHARED_SECRET_CMD, request); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in computeSharedSecret."; + return keymint::km_utils::kmError2ScopedAStatus(err); + } + auto optSecret = cbor_.getByteArrayVec(item, 1); + if (!optSecret) { + LOG(ERROR) << "Error in decoding the response in computeSharedSecret."; + return keymint::km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + *secret = std::move(optSecret.value()); + return ScopedAStatus::ok(); +} + +} // namespace aidl::android::hardware::security::sharedsecret diff --git a/ready_se/google/keymint/KM200/HAL/JavacardSharedSecret.h b/ready_se/google/keymint/KM200/HAL/JavacardSharedSecret.h new file mode 100644 index 0000000..340853a --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/JavacardSharedSecret.h @@ -0,0 +1,34 @@ +#pragma once + +#include <memory> +#include <vector> + +#include <aidl/android/hardware/security/sharedsecret/BnSharedSecret.h> +#include <aidl/android/hardware/security/sharedsecret/SharedSecretParameters.h> + +#include "CborConverter.h" +#include "JavacardSecureElement.h" + +namespace aidl::android::hardware::security::sharedsecret { +using ::keymint::javacard::CborConverter; +using ::keymint::javacard::JavacardSecureElement; +using ndk::ScopedAStatus; +using std::shared_ptr; +using std::vector; + +class JavacardSharedSecret : public BnSharedSecret { + public: + explicit JavacardSharedSecret(shared_ptr<JavacardSecureElement> card) : card_(card) {} + virtual ~JavacardSharedSecret() {} + + ScopedAStatus getSharedSecretParameters(SharedSecretParameters* params) override; + + ScopedAStatus computeSharedSecret(const std::vector<SharedSecretParameters>& params, + std::vector<uint8_t>* secret) override; + + private: + shared_ptr<JavacardSecureElement> card_; + CborConverter cbor_; +}; + +} // namespace aidl::android::hardware::security::sharedsecret diff --git a/ready_se/google/keymint/KM200/HAL/LICENSE b/ready_se/google/keymint/KM200/HAL/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/ready_se/google/keymint/KM200/HAL/METADATA b/ready_se/google/keymint/KM200/HAL/METADATA new file mode 100644 index 0000000..d97975c --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/METADATA @@ -0,0 +1,3 @@ +third_party { + license_type: NOTICE +} diff --git a/ready_se/google/keymint/KM200/HAL/OWNERS b/ready_se/google/keymint/KM200/HAL/OWNERS new file mode 100644 index 0000000..0bd972b --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/OWNERS @@ -0,0 +1,3 @@ +pathakc@google.com +subrahmanyaman@google.com +avinashh@google.com diff --git a/ready_se/google/keymint/KM200/HAL/OmapiTransport.cpp b/ready_se/google/keymint/KM200/HAL/OmapiTransport.cpp new file mode 100644 index 0000000..3fd5e43 --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/OmapiTransport.cpp @@ -0,0 +1,276 @@ +/* + ** + ** Copyright 2020, The Android Open Source Project + ** + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** + ** http://www.apache.org/licenses/LICENSE-2.0 + ** + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + */ +#include "OmapiTransport.h" + +#include <arpa/inet.h> +#include <stdio.h> +#include <string.h> +#include <sys/socket.h> +#include <unistd.h> +#include <vector> + +#include <aidl/android/hardware/security/keymint/ErrorCode.h> +#include <android-base/logging.h> + +namespace keymint::javacard { +using ::aidl::android::hardware::security::keymint::ErrorCode; + +constexpr uint8_t KEYMINT_APPLET_AID[] = {0xA0, 0x00, 0x00, 0x00, 0x62, 0x03, + 0x02, 0x0C, 0x01, 0x01, 0x01}; +std::string const ESE_READER_PREFIX = "eSE"; +constexpr const char omapiServiceName[] = "android.se.omapi.ISecureElementService/default"; + +class SEListener : public ::aidl::android::se::omapi::BnSecureElementListener {}; + +keymaster_error_t OmapiTransport::initialize() { + + LOG(DEBUG) << "Initialize the secure element connection"; + + // Get OMAPI vendor stable service handler + ::ndk::SpAIBinder ks2Binder(AServiceManager_checkService(omapiServiceName)); + omapiSeService = aidl::android::se::omapi::ISecureElementService::fromBinder(ks2Binder); + + if (omapiSeService == nullptr) { + LOG(ERROR) << "Failed to start omapiSeService null"; + return static_cast<keymaster_error_t>(ErrorCode::HARDWARE_NOT_YET_AVAILABLE); + } + + int size = sizeof(KEYMINT_APPLET_AID) / sizeof(KEYMINT_APPLET_AID[0]); + // reset readers, clear readers if already existing + if (mVSReaders.size() > 0) { + closeConnection(); + } + + std::vector<std::string> readers = {}; + // Get available readers + auto status = omapiSeService->getReaders(&readers); + if (!status.isOk()) { + LOG(ERROR) << "getReaders failed to get available readers: " << status.getMessage(); + return static_cast<keymaster_error_t>(ErrorCode::HARDWARE_TYPE_UNAVAILABLE); + } + + // Get SE readers handlers + for (auto readerName : readers) { + std::shared_ptr<::aidl::android::se::omapi::ISecureElementReader> reader; + status = omapiSeService->getReader(readerName, &reader); + if (!status.isOk()) { + LOG(ERROR) << "getReader for " << readerName.c_str() + << " Failed: " << status.getMessage(); + return static_cast<keymaster_error_t>(ErrorCode::HARDWARE_TYPE_UNAVAILABLE); + } + mVSReaders[readerName] = reader; + } + + // Find eSE reader, as of now assumption is only eSE available on device + LOG(DEBUG) << "Finding eSE reader"; + eSEReader = nullptr; + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + if (name.find(ESE_READER_PREFIX, 0) != std::string::npos) { + LOG(DEBUG) << "eSE reader found: " << name; + eSEReader = reader; + break; + } + } + } + + if (eSEReader == nullptr) { + LOG(ERROR) << "secure element reader " << ESE_READER_PREFIX << " not found"; + return static_cast<keymaster_error_t>(ErrorCode::HARDWARE_TYPE_UNAVAILABLE); + } + + bool isSecureElementPresent = false; + auto res = eSEReader->isSecureElementPresent(&isSecureElementPresent); + if (!res.isOk()) { + eSEReader = nullptr; + LOG(ERROR) << "isSecureElementPresent error: " << res.getMessage(); + return static_cast<keymaster_error_t>(ErrorCode::HARDWARE_TYPE_UNAVAILABLE); + } + if (!isSecureElementPresent) { + LOG(ERROR) << "secure element not found"; + eSEReader = nullptr; + return static_cast<keymaster_error_t>(ErrorCode::HARDWARE_TYPE_UNAVAILABLE); + } + + status = eSEReader->openSession(&session); + if (!status.isOk()) { + LOG(ERROR) << "openSession error: " << status.getMessage(); + return KM_ERROR_SECURE_HW_COMMUNICATION_FAILED; + } + if (session == nullptr) { + LOG(ERROR) << "Could not open session null"; + return KM_ERROR_SECURE_HW_COMMUNICATION_FAILED; + } + + std::vector<uint8_t> aid(KEYMINT_APPLET_AID, KEYMINT_APPLET_AID + size); + auto mSEListener = ndk::SharedRefBase::make<SEListener>(); + status = session->openLogicalChannel(aid, 0x00, mSEListener, &channel); + if (!status.isOk()) { + LOG(ERROR) << "openLogicalChannel error: " << status.getMessage(); + return KM_ERROR_SECURE_HW_COMMUNICATION_FAILED; + } + if (channel == nullptr) { + LOG(ERROR) << "Could not open channel null"; + return KM_ERROR_SECURE_HW_COMMUNICATION_FAILED; + } + + return KM_ERROR_OK; +} + +bool OmapiTransport::internalTransmitApdu( + std::shared_ptr<aidl::android::se::omapi::ISecureElementReader> reader, + std::vector<uint8_t> apdu, std::vector<uint8_t>& transmitResponse) { + + LOG(DEBUG) << "internalTransmitApdu: trasmitting data to secure element"; + if (reader == nullptr) { + LOG(ERROR) << "eSE reader is null"; + return false; + } + + bool result = true; + auto res = ndk::ScopedAStatus::ok(); + if (session != nullptr) { + res = session->isClosed(&result); + if (!res.isOk()) { + LOG(ERROR) << "isClosed error: " << res.getMessage(); + return KM_ERROR_SECURE_HW_COMMUNICATION_FAILED; + } + } + if (result) { + res = reader->openSession(&session); + if (!res.isOk()) { + LOG(ERROR) << "openSession error: " << res.getMessage(); + return false; + } + if (session == nullptr) { + LOG(ERROR) << "Could not open session null"; + return false; + } + } + + result = true; + if (channel != nullptr) { + res = channel->isClosed(&result); + if (!res.isOk()) { + LOG(ERROR) << "isClosed error: " << res.getMessage(); + return KM_ERROR_SECURE_HW_COMMUNICATION_FAILED; + } + } + + int size = sizeof(KEYMINT_APPLET_AID) / sizeof(KEYMINT_APPLET_AID[0]); + std::vector<uint8_t> aid(KEYMINT_APPLET_AID, KEYMINT_APPLET_AID + size); + if (result) { + auto mSEListener = ndk::SharedRefBase::make<SEListener>(); + res = session->openLogicalChannel(aid, 0x00, mSEListener, &channel); + if (!res.isOk()) { + LOG(ERROR) << "openLogicalChannel error: " << res.getMessage(); + return false; + } + if (channel == nullptr) { + LOG(ERROR) << "Could not open channel null"; + return false; + } + } + + std::vector<uint8_t> selectResponse = {}; + res = channel->getSelectResponse(&selectResponse); + if (!res.isOk()) { + LOG(ERROR) << "getSelectResponse error: " << res.getMessage(); + return false; + } + + if ((selectResponse.size() < 2) || + ((selectResponse[selectResponse.size() - 1] & 0xFF) != 0x00) || + ((selectResponse[selectResponse.size() - 2] & 0xFF) != 0x90)) { + LOG(ERROR) << "Failed to select the Applet."; + return false; + } + + res = channel->transmit(apdu, &transmitResponse); + + LOG(INFO) << "STATUS OF TRNSMIT: " << res.getExceptionCode() + << " Message: " << res.getMessage(); + if (!res.isOk()) { + LOG(ERROR) << "transmit error: " << res.getMessage(); + return false; + } + + return true; +} + +keymaster_error_t OmapiTransport::openConnection() { + + // if already conection setup done, no need to initialise it again. + if (isConnected()) { + return KM_ERROR_OK; + } + return initialize(); +} + +keymaster_error_t OmapiTransport::sendData(const vector<uint8_t>& inData, vector<uint8_t>& output) { + + if (!isConnected()) { + // Try to initialize connection to eSE + LOG(INFO) << "Failed to send data, try to initialize connection SE connection"; + auto res = initialize(); + if (res != KM_ERROR_OK) { + LOG(ERROR) << "Failed to send data, initialization not completed"; + closeConnection(); + return res; + } + } + + if (eSEReader != nullptr) { + LOG(DEBUG) << "Sending apdu data to secure element: " << ESE_READER_PREFIX; + if (internalTransmitApdu(eSEReader, inData, output)) { + return KM_ERROR_OK; + } else { + return KM_ERROR_SECURE_HW_COMMUNICATION_FAILED; + } + } else { + LOG(ERROR) << "secure element reader " << ESE_READER_PREFIX << " not found"; + return KM_ERROR_SECURE_HW_COMMUNICATION_FAILED; + } +} + +keymaster_error_t OmapiTransport::closeConnection() { + LOG(DEBUG) << "Closing all connections"; + if (omapiSeService != nullptr) { + if (mVSReaders.size() > 0) { + for (const auto& [name, reader] : mVSReaders) { + reader->closeSessions(); + } + mVSReaders.clear(); + } + } + if (channel != nullptr) channel->close(); + if (session != nullptr) session->close(); + return KM_ERROR_OK; +} + +bool OmapiTransport::isConnected() { + // Check already initialization completed or not + if (omapiSeService != nullptr && eSEReader != nullptr) { + LOG(DEBUG) << "Connection initialization already completed"; + return true; + } + + LOG(DEBUG) << "Connection initialization not completed"; + return false; +} + +} // namespace keymint::javacard diff --git a/ready_se/google/keymint/KM200/HAL/OmapiTransport.h b/ready_se/google/keymint/KM200/HAL/OmapiTransport.h new file mode 100644 index 0000000..a199bbb --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/OmapiTransport.h @@ -0,0 +1,65 @@ +#pragma once + +#include <map> +#include <memory> +#include <vector> + +#include <aidl/android/se/omapi/BnSecureElementListener.h> +#include <aidl/android/se/omapi/ISecureElementChannel.h> +#include <aidl/android/se/omapi/ISecureElementListener.h> +#include <aidl/android/se/omapi/ISecureElementReader.h> +#include <aidl/android/se/omapi/ISecureElementService.h> +#include <aidl/android/se/omapi/ISecureElementSession.h> + +#include <android/binder_manager.h> + +#include "ITransport.h" + +namespace keymint::javacard { +using std::vector; + +/** + * OmapiTransport is derived from ITransport. This class gets the OMAPI service binder instance and + * uses IPC to communicate with OMAPI service. OMAPI inturn communicates with hardware via + * ISecureElement. + */ +class OmapiTransport : public ITransport { + + public: + OmapiTransport() + : omapiSeService(nullptr), eSEReader(nullptr), session(nullptr), channel(nullptr), + mVSReaders({}) {} + /** + * Gets the binder instance of ISEService, gets te reader corresponding to secure element, + * establishes a session and opens a basic channel. + */ + keymaster_error_t openConnection() override; + /** + * Transmists the data over the opened basic channel and receives the data back. + */ + keymaster_error_t sendData(const vector<uint8_t>& inData, vector<uint8_t>& output) override; + + /** + * Closes the connection. + */ + keymaster_error_t closeConnection() override; + /** + * Returns the state of the connection status. Returns true if the connection is active, false + * if connection is broken. + */ + bool isConnected() override; + + private: + std::shared_ptr<aidl::android::se::omapi::ISecureElementService> omapiSeService; + std::shared_ptr<aidl::android::se::omapi::ISecureElementReader> eSEReader; + std::shared_ptr<aidl::android::se::omapi::ISecureElementSession> session; + std::shared_ptr<aidl::android::se::omapi::ISecureElementChannel> channel; + std::map<std::string, std::shared_ptr<aidl::android::se::omapi::ISecureElementReader>> + mVSReaders; + keymaster_error_t initialize(); + bool + internalTransmitApdu(std::shared_ptr<aidl::android::se::omapi::ISecureElementReader> reader, + std::vector<uint8_t> apdu, std::vector<uint8_t>& transmitResponse); +}; + +} // namespace keymint::javacard diff --git a/ready_se/google/keymint/KM200/HAL/SocketTransport.cpp b/ready_se/google/keymint/KM200/HAL/SocketTransport.cpp new file mode 100644 index 0000000..a3595fe --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/SocketTransport.cpp @@ -0,0 +1,144 @@ +/* + ** + ** Copyright 2020, The Android Open Source Project + ** + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** + ** http://www.apache.org/licenses/LICENSE-2.0 + ** + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + */ +#include "SocketTransport.h" + +#include <arpa/inet.h> +#include <errno.h> + +#include <memory> +#include <vector> + +#include <aidl/android/hardware/security/keymint/ErrorCode.h> +#include <android-base/logging.h> +#include <sys/socket.h> + +#include "ITransport.h" + +#define PORT 8080 +#define IPADDR "192.168.9.112" +#define MAX_RECV_BUFFER_SIZE 2500 + +namespace keymint::javacard { +using ::aidl::android::hardware::security::keymint::ErrorCode; +using std::shared_ptr; +using std::vector; + +keymaster_error_t SocketTransport::openConnection() { + struct sockaddr_in serv_addr; + if ((mSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + LOG(ERROR) << "Socket creation failed" + << " Error: " << strerror(errno); + return static_cast<keymaster_error_t>(ErrorCode::HARDWARE_TYPE_UNAVAILABLE); + } + + serv_addr.sin_family = AF_INET; + serv_addr.sin_port = htons(PORT); + + // Convert IPv4 and IPv6 addresses from text to binary form + if (inet_pton(AF_INET, IPADDR, &serv_addr.sin_addr) <= 0) { + LOG(ERROR) << "Invalid address/ Address not supported."; + return static_cast<keymaster_error_t>(ErrorCode::HARDWARE_TYPE_UNAVAILABLE); + } + + if (connect(mSocket, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) { + close(mSocket); + LOG(ERROR) << "Connection failed. Error: " << strerror(errno); + return KM_ERROR_SECURE_HW_COMMUNICATION_FAILED; + } + socketStatus = true; + return KM_ERROR_OK; +} + +keymaster_error_t SocketTransport::sendData(const vector<uint8_t>& inData, + vector<uint8_t>& output) { + int count = 1; + while (!socketStatus && count++ < 5) { + sleep(1); + LOG(ERROR) << "Trying to open socket connection... count: " << count; + openConnection(); + } + + if (count >= 5) { + LOG(ERROR) << "Failed to open socket connection"; + return KM_ERROR_SECURE_HW_COMMUNICATION_FAILED; + } + // Prepend the input length to the inputData before sending. + vector<uint8_t> inDataPrependedLength; + inDataPrependedLength.push_back(static_cast<uint8_t>(inData.size() >> 8)); + inDataPrependedLength.push_back(static_cast<uint8_t>(inData.size() & 0xFF)); + inDataPrependedLength.insert(inDataPrependedLength.end(), inData.begin(), inData.end()); + + if (0 > + send(mSocket, inDataPrependedLength.data(), inDataPrependedLength.size(), MSG_NOSIGNAL)) { + static int connectionResetCnt = 0; /* To avoid loop */ + if ((ECONNRESET == errno || EPIPE == errno) && connectionResetCnt == 0) { + // Connection reset. Try open socket and then sendData. + socketStatus = false; + connectionResetCnt++; + return sendData(inData, output); + } + LOG(ERROR) << "Failed to send data over socket err: " << errno; + connectionResetCnt = 0; + return KM_ERROR_SECURE_HW_COMMUNICATION_FAILED; + } + + if (!readData(output)) { + return KM_ERROR_SECURE_HW_COMMUNICATION_FAILED; + } + return KM_ERROR_OK; +} + +keymaster_error_t SocketTransport::closeConnection() { + close(mSocket); + socketStatus = false; + return KM_ERROR_OK; +} + +bool SocketTransport::isConnected() { + return socketStatus; +} + +bool SocketTransport::readData(vector<uint8_t>& output) { + uint8_t buffer[MAX_RECV_BUFFER_SIZE]; + ssize_t expectedResponseLen = 0; + ssize_t totalBytesRead = 0; + // The first 2 bytes in the response contains the expected response length. + do { + size_t i = 0; + ssize_t numBytes = read(mSocket, buffer, MAX_RECV_BUFFER_SIZE); + if (0 > numBytes) { + LOG(ERROR) << "Failed to read data from socket."; + return false; + } + totalBytesRead += numBytes; + if (expectedResponseLen == 0) { + // First two bytes in the response contains the expected response length. + expectedResponseLen |= static_cast<ssize_t>(buffer[1] & 0xFF); + expectedResponseLen |= static_cast<ssize_t>((buffer[0] << 8) & 0xFF00); + // 2 bytes for storing the length. + expectedResponseLen += 2; + i = 2; + } + for (; i < numBytes; i++) { + output.push_back(buffer[i]); + } + } while (totalBytesRead < expectedResponseLen); + + return true; +} + +} // namespace keymint::javacard diff --git a/ready_se/google/keymint/KM200/HAL/SocketTransport.h b/ready_se/google/keymint/KM200/HAL/SocketTransport.h new file mode 100644 index 0000000..73725d0 --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/SocketTransport.h @@ -0,0 +1,55 @@ +/* + ** + ** Copyright 2020, The Android Open Source Project + ** + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** + ** http://www.apache.org/licenses/LICENSE-2.0 + ** + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + */ +#pragma once + +#include <memory> +#include <vector> + +#include "ITransport.h" + +namespace keymint::javacard { +using std::shared_ptr; +using std::vector; + +class SocketTransport : public ITransport { + + public: + SocketTransport() : mSocket(-1), socketStatus(false) {} + /** + * Creates a socket instance and connects to the provided server IP and port. + */ + keymaster_error_t openConnection() override; + /** + * Sends data over socket and receives data back. + */ + keymaster_error_t sendData(const vector<uint8_t>& inData, vector<uint8_t>& output) override; + /** + * Closes the connection. + */ + keymaster_error_t closeConnection() override; + /** + * Returns the state of the connection status. Returns true if the connection is active, + * false if connection is broken. + */ + bool isConnected() override; + + private: + bool readData(vector<uint8_t>& output); + int mSocket; + bool socketStatus; +}; +} // namespace keymint::javacard diff --git a/ready_se/google/keymint/KM200/HAL/android.hardware.hardware_keystore.jc-strongbox-keymint.xml b/ready_se/google/keymint/KM200/HAL/android.hardware.hardware_keystore.jc-strongbox-keymint.xml new file mode 100644 index 0000000..c6ca188 --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/android.hardware.hardware_keystore.jc-strongbox-keymint.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 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. +--> +<!-- Feature for devices with Keymaster in StrongBox. --> +<permissions> + <feature name="android.hardware.strongbox_keystore" version="200"/> + <feature name="android.hardware.keystore.app_attest_key" /> +</permissions> diff --git a/ready_se/google/keymint/KM200/HAL/android.hardware.security.keymint-service.strongbox.rc b/ready_se/google/keymint/KM200/HAL/android.hardware.security.keymint-service.strongbox.rc new file mode 100644 index 0000000..7bb96f0 --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/android.hardware.security.keymint-service.strongbox.rc @@ -0,0 +1,3 @@ +service vendor.keymint-strongbox /vendor/bin/hw/android.hardware.security.keymint-service.strongbox + class early_hal + user jc_strongbox diff --git a/ready_se/google/keymint/KM200/HAL/android.hardware.security.keymint-service.strongbox.xml b/ready_se/google/keymint/KM200/HAL/android.hardware.security.keymint-service.strongbox.xml new file mode 100644 index 0000000..0631f12 --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/android.hardware.security.keymint-service.strongbox.xml @@ -0,0 +1,10 @@ +<manifest version="1.0" type="device"> + <hal format="aidl"> + <name>android.hardware.security.keymint</name> + <fqname>IKeyMintDevice/strongbox</fqname> + </hal> + <hal format="aidl"> + <name>android.hardware.security.keymint</name> + <fqname>IRemotelyProvisionedComponent/strongbox</fqname> + </hal> +</manifest> diff --git a/ready_se/google/keymint/KM200/HAL/android.hardware.security.sharedsecret-service.strongbox.xml b/ready_se/google/keymint/KM200/HAL/android.hardware.security.sharedsecret-service.strongbox.xml new file mode 100644 index 0000000..5492100 --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/android.hardware.security.sharedsecret-service.strongbox.xml @@ -0,0 +1,6 @@ +<manifest version="1.0" type="device"> + <hal format="aidl"> + <name>android.hardware.security.sharedsecret</name> + <fqname>ISharedSecret/strongbox</fqname> + </hal> +</manifest> diff --git a/ready_se/google/keymint/KM200/HAL/keymint_utils.cpp b/ready_se/google/keymint/KM200/HAL/keymint_utils.cpp new file mode 100644 index 0000000..f613eda --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/keymint_utils.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "keymint_utils.h" + +#include <regex.h> + +#include <android-base/properties.h> + +namespace keymint::javacard { + +namespace { + +constexpr char kPlatformVersionProp[] = "ro.build.version.release"; +constexpr char kPlatformVersionRegex[] = "^([0-9]{1,2})(\\.([0-9]{1,2}))?(\\.([0-9]{1,2}))?"; +constexpr size_t kMajorVersionMatch = 1; +constexpr size_t kMinorVersionMatch = 3; +constexpr size_t kSubminorVersionMatch = 5; +constexpr size_t kPlatformVersionMatchCount = kSubminorVersionMatch + 1; + +constexpr char kPlatformPatchlevelProp[] = "ro.build.version.security_patch"; +constexpr char kVendorPatchlevelProp[] = "ro.vendor.build.security_patch"; +constexpr char kPatchlevelRegex[] = "^([0-9]{4})-([0-9]{2})-([0-9]{2})$"; +constexpr size_t kYearMatch = 1; +constexpr size_t kMonthMatch = 2; +constexpr size_t kDayMatch = 3; +constexpr size_t kPatchlevelMatchCount = kDayMatch + 1; + +uint32_t match_to_uint32(const char* expression, const regmatch_t& match) { + if (match.rm_so == -1) return 0; + + size_t len = match.rm_eo - match.rm_so; + std::string s(expression + match.rm_so, len); + return std::stoul(s); +} + +std::string wait_and_get_property(const char* prop) { + std::string prop_value; + while (!::android::base::WaitForPropertyCreation(prop)) + ; + prop_value = ::android::base::GetProperty(prop, "" /* default */); + return prop_value; +} + +uint32_t getOsVersion(const char* version_str) { + regex_t regex; + if (regcomp(®ex, kPlatformVersionRegex, REG_EXTENDED)) { + return 0; + } + + regmatch_t matches[kPlatformVersionMatchCount]; + int not_match = + regexec(®ex, version_str, kPlatformVersionMatchCount, matches, 0 /* flags */); + regfree(®ex); + if (not_match) { + return 0; + } + + uint32_t major = match_to_uint32(version_str, matches[kMajorVersionMatch]); + uint32_t minor = match_to_uint32(version_str, matches[kMinorVersionMatch]); + uint32_t subminor = match_to_uint32(version_str, matches[kSubminorVersionMatch]); + + return (major * 100 + minor) * 100 + subminor; +} + +enum class PatchlevelOutput { kYearMonthDay, kYearMonth }; + +uint32_t getPatchlevel(const char* patchlevel_str, PatchlevelOutput detail) { + regex_t regex; + if (regcomp(®ex, kPatchlevelRegex, REG_EXTENDED) != 0) { + return 0; + } + + regmatch_t matches[kPatchlevelMatchCount]; + int not_match = regexec(®ex, patchlevel_str, kPatchlevelMatchCount, matches, 0 /* flags */); + regfree(®ex); + if (not_match) { + return 0; + } + + uint32_t year = match_to_uint32(patchlevel_str, matches[kYearMatch]); + uint32_t month = match_to_uint32(patchlevel_str, matches[kMonthMatch]); + + if (month < 1 || month > 12) { + return 0; + } + + switch (detail) { + case PatchlevelOutput::kYearMonthDay: { + uint32_t day = match_to_uint32(patchlevel_str, matches[kDayMatch]); + if (day < 1 || day > 31) { + return 0; + } + return year * 10000 + month * 100 + day; + } + case PatchlevelOutput::kYearMonth: + return year * 100 + month; + } +} + +} // anonymous namespace + +uint32_t getOsVersion() { + std::string version = wait_and_get_property(kPlatformVersionProp); + return getOsVersion(version.c_str()); +} + +uint32_t getOsPatchlevel() { + std::string patchlevel = wait_and_get_property(kPlatformPatchlevelProp); + return getPatchlevel(patchlevel.c_str(), PatchlevelOutput::kYearMonth); +} + +uint32_t getVendorPatchlevel() { + std::string patchlevel = wait_and_get_property(kVendorPatchlevelProp); + return getPatchlevel(patchlevel.c_str(), PatchlevelOutput::kYearMonthDay); +} + +} // namespace keymint::javacard diff --git a/ready_se/google/keymint/KM200/HAL/keymint_utils.h b/ready_se/google/keymint/KM200/HAL/keymint_utils.h new file mode 100644 index 0000000..65cda63 --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/keymint_utils.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <string> +#include <vector> + +// #include <aidl/android/hardware/security/keymint/HardwareAuthToken.h> + +// namespace aidl::android::hardware::security::keymint { +namespace keymint::javacard { + +using std::vector; + +inline static std::vector<uint8_t> blob2vector(const uint8_t* data, const size_t length) { + std::vector<uint8_t> result(data, data + length); + return result; +} + +inline static std::vector<uint8_t> blob2vector(const std::string& value) { + vector<uint8_t> result(reinterpret_cast<const uint8_t*>(value.data()), + reinterpret_cast<const uint8_t*>(value.data()) + value.size()); + return result; +} + +// HardwareAuthToken vector2AuthToken(const vector<uint8_t>& buffer); +// vector<uint8_t> authToken2vector(const HardwareAuthToken& token); + +uint32_t getOsVersion(); +uint32_t getOsPatchlevel(); +uint32_t getVendorPatchlevel(); + +} // namespace keymint::javacard diff --git a/ready_se/google/keymint/KM200/HAL/service.cpp b/ready_se/google/keymint/KM200/HAL/service.cpp new file mode 100644 index 0000000..e83ee3d --- /dev/null +++ b/ready_se/google/keymint/KM200/HAL/service.cpp @@ -0,0 +1,95 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "javacard.strongbox-service" + +#include <aidl/android/hardware/security/keymint/SecurityLevel.h> + +#include <android-base/logging.h> +#include <android-base/properties.h> +#include <android/binder_manager.h> +#include <android/binder_process.h> + +#include "JavacardKeyMintDevice.h" +#include "JavacardRemotelyProvisionedComponentDevice.h" +#include "JavacardSecureElement.h" +#include "JavacardSharedSecret.h" +#include "OmapiTransport.h" +#include "SocketTransport.h" +#include "keymint_utils.h" + +using aidl::android::hardware::security::keymint::JavacardKeyMintDevice; +using aidl::android::hardware::security::keymint::JavacardRemotelyProvisionedComponentDevice; +using aidl::android::hardware::security::keymint::SecurityLevel; +using aidl::android::hardware::security::sharedsecret::JavacardSharedSecret; +using keymint::javacard::getOsPatchlevel; +using keymint::javacard::getOsVersion; +using keymint::javacard::getVendorPatchlevel; +using keymint::javacard::ITransport; +using keymint::javacard::JavacardSecureElement; +using keymint::javacard::OmapiTransport; +using keymint::javacard::SocketTransport; + +#define PROP_BUILD_QEMU "ro.kernel.qemu" +#define PROP_BUILD_FINGERPRINT "ro.build.fingerprint" +// Cuttlefish build fingerprint substring. +#define CUTTLEFISH_FINGERPRINT_SS "aosp_cf_" + +template <typename T, class... Args> std::shared_ptr<T> addService(Args&&... args) { + std::shared_ptr<T> ser = ndk::SharedRefBase::make<T>(std::forward<Args>(args)...); + auto instanceName = std::string(T::descriptor) + "/strongbox"; + LOG(INFO) << "adding javacard strongbox service instance: " << instanceName; + binder_status_t status = + AServiceManager_addService(ser->asBinder().get(), instanceName.c_str()); + CHECK(status == STATUS_OK); + return ser; +} + +std::shared_ptr<ITransport> getTransportInstance() { + bool isEmulator = false; + // Check if the current build is for emulator or device. + isEmulator = android::base::GetBoolProperty(PROP_BUILD_QEMU, false); + if (!isEmulator) { + std::string fingerprint = android::base::GetProperty(PROP_BUILD_FINGERPRINT, ""); + if (!fingerprint.empty()) { + if (fingerprint.find(CUTTLEFISH_FINGERPRINT_SS, 0) != std::string::npos) { + isEmulator = true; + } + } + } + + if (!isEmulator) { + return std::make_shared<OmapiTransport>(); + } else { + return std::make_shared<SocketTransport>(); + } +} + +int main() { + ABinderProcess_setThreadPoolMaxThreadCount(0); + // Javacard Secure Element + std::shared_ptr<JavacardSecureElement> card = std::make_shared<JavacardSecureElement>( + getTransportInstance(), getOsVersion(), getOsPatchlevel(), getVendorPatchlevel()); + // Add Keymint Service + addService<JavacardKeyMintDevice>(card); + // Add Shared Secret Service + addService<JavacardSharedSecret>(card); + // Add Remotely Provisioned Component Service + addService<JavacardRemotelyProvisionedComponentDevice>(card); + + ABinderProcess_joinThreadPool(); + return EXIT_FAILURE; // should not reach +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java new file mode 100644 index 0000000..018ac02 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java @@ -0,0 +1,622 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.keymaster; + +import com.android.javacard.seprovider.KMAndroidSEProvider; +import com.android.javacard.seprovider.KMException; +import javacard.framework.APDU; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; +import javacard.security.CryptoException; +import org.globalplatform.upgrade.Element; +import org.globalplatform.upgrade.OnUpgradeListener; +import org.globalplatform.upgrade.UpgradeManager; + +/** + * This class extends from KMKeymasterApplet which is main entry point to receive apdu commands. All + * the provision commands are processed here and later the data is handed over to the KMDataStore + * class which stores the data in the flash memory. + */ +public class KMAndroidSEApplet extends KMKeymasterApplet implements OnUpgradeListener { + // Magic number version stored along with provisioned data. This is used to differentiate + // between data before and after the magic number is used. + private static final byte KM_MAGIC_NUMBER = (byte) 0x82; + // MSB byte is for Major version and LSB byte is for Minor version. + public static final short KM_APPLET_PACKAGE_VERSION = 0x0400; + // This flag is used to know if card reset happened. + private static final short POWER_RESET_MASK_FLAG = (short) 0x4000; + + // Provider specific Commands + private static final byte INS_KEYMINT_PROVIDER_APDU_START = 0x00; + private static final byte INS_PROVISION_ATTEST_IDS_CMD = INS_KEYMINT_PROVIDER_APDU_START + 3; + // Commands 4, 5 and 6 are reserved for vendor usage. + private static final byte INS_GET_PROVISION_STATUS_CMD = INS_KEYMINT_PROVIDER_APDU_START + 7; + // 0x08 was reserved for INS_INIT_STRONGBOX_CMD + // 0x09 was reserved for INS_SET_BOOT_ENDED_CMD earlier. it is unused now. + private static final byte INS_SE_FACTORY_PROVISIONING_LOCK_CMD = + INS_KEYMINT_PROVIDER_APDU_START + 10; + private static final byte INS_PROVISION_OEM_ROOT_PUBLIC_KEY_CMD = + INS_KEYMINT_PROVIDER_APDU_START + 11; + private static final byte INS_OEM_UNLOCK_PROVISIONING_CMD = INS_KEYMINT_PROVIDER_APDU_START + 12; + private static final byte INS_PROVISION_RKP_DEVICE_UNIQUE_KEYPAIR_CMD = + INS_KEYMINT_PROVIDER_APDU_START + 13; + private static final byte INS_PROVISION_RKP_UDS_CERT_CHAIN_CMD = + INS_KEYMINT_PROVIDER_APDU_START + 14; + private static final byte INS_PROVISION_PRESHARED_SECRET_CMD = + INS_KEYMINT_PROVIDER_APDU_START + 15; + private static final byte INS_SET_BOOT_PARAMS_CMD = + INS_KEYMINT_PROVIDER_APDU_START + 16; // Unused + private static final byte INS_OEM_LOCK_PROVISIONING_CMD = INS_KEYMINT_PROVIDER_APDU_START + 17; + private static final byte INS_PROVISION_SECURE_BOOT_MODE_CMD = + INS_KEYMINT_PROVIDER_APDU_START + 18; + + private static final byte INS_KEYMINT_PROVIDER_APDU_END = 0x1F; + // The length of the provisioned pre shared key. + public static final byte SHARED_SECRET_KEY_SIZE = 32; + + // Version of the database which is used to differentiate between different version of the + // database. + protected short packageVersion; + + KMAndroidSEApplet() { + super(new KMAndroidSEProvider()); + packageVersion = KM_APPLET_PACKAGE_VERSION; + } + + /** + * Installs this applet. + * + * @param bArray the array containing installation parameters + * @param bOffset the starting offset in bArray + * @param bLength the length in bytes of the parameter data in bArray + */ + public static void install(byte[] bArray, short bOffset, byte bLength) { + new KMAndroidSEApplet().register(bArray, (short) (bOffset + 1), bArray[bOffset]); + } + + public void handleDeviceBooted() { + if (seProvider.isBootSignalEventSupported() && seProvider.isDeviceRebooted()) { + kmDataStore.clearDeviceBootStatus(); + super.reboot(); + seProvider.clearDeviceBooted(true); + } + } + + @Override + public void updateApduStatusFlags(short apduIns) { + apduStatusFlags[APDU_INCOMING_AND_RECEIVE_STATUS_INDEX] = 0; + apduStatusFlags[APDU_CASE4_COMMAND_STATUS_INDEX] = 1; + switch (apduIns) { + case INS_GET_PROVISION_STATUS_CMD: + case INS_SE_FACTORY_PROVISIONING_LOCK_CMD: + apduStatusFlags[APDU_CASE4_COMMAND_STATUS_INDEX] = 0; + break; + default: + super.updateApduStatusFlags(apduIns); + } + } + + @Override + public void process(APDU apdu) { + try { + handleDeviceBooted(); + // If this is select applet apdu which is selecting this applet then return + if (apdu.isISOInterindustryCLA()) { + if (selectingApplet()) { + return; + } + } + short apduIns = validateApdu(apdu); + if (apduIns == KMType.INVALID_VALUE) { + return; + } + updateApduStatusFlags(apduIns); + if (((KMAndroidSEProvider) seProvider).isPowerReset()) { + super.powerReset(); + } + + if (isCommandAllowed(apduIns)) { + switch (apduIns) { + case INS_PROVISION_ATTEST_IDS_CMD: + processProvisionAttestIdsCmd(apdu); + kmDataStore.setProvisionStatus(PROVISION_STATUS_ATTEST_IDS); + sendResponse(apdu, KMError.OK); + break; + + case INS_PROVISION_PRESHARED_SECRET_CMD: + processProvisionPreSharedSecretCmd(apdu); + kmDataStore.setProvisionStatus(PROVISION_STATUS_PRESHARED_SECRET); + sendResponse(apdu, KMError.OK); + break; + + case INS_GET_PROVISION_STATUS_CMD: + processGetProvisionStatusCmd(apdu); + break; + + case INS_PROVISION_RKP_DEVICE_UNIQUE_KEYPAIR_CMD: + processProvisionRkpDeviceUniqueKeyPair(apdu); + break; + + case INS_PROVISION_RKP_UDS_CERT_CHAIN_CMD: + processProvisionRkpUdsCertChain(apdu); + break; + + case INS_SE_FACTORY_PROVISIONING_LOCK_CMD: + kmDataStore.setProvisionStatus(PROVISION_STATUS_SE_LOCKED); + sendResponse(apdu, KMError.OK); + break; + + case INS_PROVISION_OEM_ROOT_PUBLIC_KEY_CMD: + processProvisionOEMRootPublicKeyCmd(apdu); + kmDataStore.setProvisionStatus(PROVISION_STATUS_OEM_PUBLIC_KEY); + sendResponse(apdu, KMError.OK); + break; + + case INS_OEM_LOCK_PROVISIONING_CMD: + processOEMLockProvisionCmd(apdu); + break; + + case INS_OEM_UNLOCK_PROVISIONING_CMD: + processOEMUnlockProvisionCmd(apdu); + break; + + case INS_PROVISION_SECURE_BOOT_MODE_CMD: + processSecureBootCmd(apdu); + break; + + default: + super.process(apdu); + break; + } + } else { + ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED); + } + } catch (KMException exception) { + sendResponse(apdu, KMException.reason()); + } catch (ISOException exp) { + sendResponse(apdu, mapISOErrorToKMError(exp.getReason())); + } catch (CryptoException e) { + sendResponse(apdu, mapCryptoErrorToKMError(e.getReason())); + } catch (Exception e) { + sendResponse(apdu, KMError.GENERIC_UNKNOWN_ERROR); + } finally { + repository.clean(); + } + } + + private boolean isCommandAllowed(short apduIns) { + boolean result = true; + switch (apduIns) { + case INS_PROVISION_ATTEST_IDS_CMD: + case INS_PROVISION_PRESHARED_SECRET_CMD: + case INS_PROVISION_SECURE_BOOT_MODE_CMD: + case INS_PROVISION_OEM_ROOT_PUBLIC_KEY_CMD: + if (kmDataStore.isProvisionLocked()) { + result = false; + } + break; + + case INS_OEM_UNLOCK_PROVISIONING_CMD: + if (!kmDataStore.isProvisionLocked()) { + result = false; + } + break; + + case INS_SE_FACTORY_PROVISIONING_LOCK_CMD: + if (isSeFactoryProvisioningLocked() || !isSeFactoryProvisioningComplete()) { + result = false; + } + break; + + case INS_OEM_LOCK_PROVISIONING_CMD: + // Allow lock only when + // 1. All the necessary provisioning commands are succcessfully executed + // 2. SE provision is locked + // 3. OEM Root Public is provisioned. + if (kmDataStore.isProvisionLocked() + || !(isProvisioningComplete() && isSeFactoryProvisioningLocked())) { + result = false; + } + break; + + case INS_PROVISION_RKP_DEVICE_UNIQUE_KEYPAIR_CMD: + case INS_PROVISION_RKP_UDS_CERT_CHAIN_CMD: + if (isSeFactoryProvisioningLocked()) { + result = false; + } + break; + + case INS_GET_PROVISION_STATUS_CMD: + break; + + default: + // Allow other commands only if provision is completed. + if (!isProvisioningComplete()) { + result = false; + } + } + return result; + } + + private boolean isSeFactoryProvisioningLocked() { + short pStatus = kmDataStore.getProvisionStatus(); + boolean result = false; + if ((0 != (pStatus & PROVISION_STATUS_SE_LOCKED))) { + result = true; + } + return result; + } + + private boolean isSeFactoryProvisioningComplete() { + short pStatus = kmDataStore.getProvisionStatus(); + if (PROVISION_STATUS_DEVICE_UNIQUE_KEYPAIR + == (pStatus & PROVISION_STATUS_DEVICE_UNIQUE_KEYPAIR)) { + return true; + } + return false; + } + + private void processSecureBootCmd(APDU apdu) { + short argsProto = KMArray.instance((short) 1); + KMArray.cast(argsProto).add((short) 0, KMInteger.exp()); + short args = receiveIncoming(apdu, argsProto); + short val = KMInteger.cast(KMArray.cast(args).get((short) 0)).getShort(); + if (val != 1 && val != 0) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // Store secure boot mode value. + JCSystem.beginTransaction(); + kmDataStore.secureBootMode = (byte) val; + JCSystem.commitTransaction(); + kmDataStore.setProvisionStatus(PROVISION_STATUS_SECURE_BOOT_MODE); + sendResponse(apdu, KMError.OK); + } + + private void processOEMUnlockProvisionCmd(APDU apdu) { + authenticateOEM(OEM_UNLOCK_PROVISION_VERIFICATION_LABEL, apdu); + kmDataStore.unlockProvision(); + sendResponse(apdu, KMError.OK); + } + + private void processOEMLockProvisionCmd(APDU apdu) { + authenticateOEM(OEM_LOCK_PROVISION_VERIFICATION_LABEL, apdu); + // Enable the lock bit in provision status. + kmDataStore.setProvisionStatus(PROVISION_STATUS_PROVISIONING_LOCKED); + sendResponse(apdu, KMError.OK); + } + + private void authenticateOEM(byte[] plainMsg, APDU apdu) { + + tmpVariables[0] = KMArray.instance((short) 1); + KMArray.cast(tmpVariables[0]).add((short) 0, KMByteBlob.exp()); + short args = receiveIncoming(apdu, tmpVariables[0]); + // Get the signature input. + short signature = KMArray.cast(args).get((short) 0); + byte[] oemPublicKey = kmDataStore.getOEMRootPublicKey(); + + if (!seProvider.ecVerify256( + oemPublicKey, + (short) 0, + (short) oemPublicKey.length, + plainMsg, + (short) 0, + (short) plainMsg.length, + KMByteBlob.cast(signature).getBuffer(), + KMByteBlob.cast(signature).getStartOff(), + KMByteBlob.cast(signature).length())) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + } + + private void processProvisionOEMRootPublicKeyCmd(APDU apdu) { + // Arguments + short keyparams = KMKeyParameters.exp(); + short keyFormatPtr = KMEnum.instance(KMType.KEY_FORMAT); + short blob = KMByteBlob.exp(); + short argsProto = KMArray.instance((short) 3); + KMArray.cast(argsProto).add((short) 0, keyparams); + KMArray.cast(argsProto).add((short) 1, keyFormatPtr); + KMArray.cast(argsProto).add((short) 2, blob); + short args = receiveIncoming(apdu, argsProto); + + // key params should have os patch, os version and verified root of trust + data[KEY_PARAMETERS] = KMArray.cast(args).get((short) 0); + tmpVariables[0] = KMArray.cast(args).get((short) 1); + // Key format must be RAW format + byte keyFormat = KMEnum.cast(tmpVariables[0]).getVal(); + if (keyFormat != KMType.RAW) { + KMException.throwIt(KMError.UNIMPLEMENTED); + } + + // get algorithm - only EC keys expected + tmpVariables[0] = KMEnumTag.getValue(KMType.ALGORITHM, data[KEY_PARAMETERS]); + if (tmpVariables[0] != KMType.EC) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // get digest - only SHA256 supported + tmpVariables[0] = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.DIGEST, data[KEY_PARAMETERS]); + if (tmpVariables[0] != KMType.INVALID_VALUE) { + if (KMEnumArrayTag.cast(tmpVariables[0]).length() != 1) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + tmpVariables[0] = KMEnumArrayTag.cast(tmpVariables[0]).get((short) 0); + if (tmpVariables[0] != KMType.SHA2_256) { + KMException.throwIt(KMError.INCOMPATIBLE_DIGEST); + } + } else { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // Purpose should be VERIFY + tmpVariables[0] = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, data[KEY_PARAMETERS]); + if (tmpVariables[0] != KMType.INVALID_VALUE) { + if (KMEnumArrayTag.cast(tmpVariables[0]).length() != 1) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + tmpVariables[0] = KMEnumArrayTag.cast(tmpVariables[0]).get((short) 0); + if (tmpVariables[0] != KMType.VERIFY) { + KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); + } + } else { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + + tmpVariables[0] = KMArray.cast(args).get((short) 2); + // persist OEM Root Public Key. + kmDataStore.persistOEMRootPublicKey( + KMByteBlob.cast(tmpVariables[0]).getBuffer(), + KMByteBlob.cast(tmpVariables[0]).getStartOff(), + KMByteBlob.cast(tmpVariables[0]).length()); + } + + private static void processProvisionRkpDeviceUniqueKeyPair(APDU apdu) { + // Re-purpose the apdu buffer as scratch pad. + byte[] scratchPad = apdu.getBuffer(); + short arr = KMArray.instance((short) 1); + short coseKeyExp = KMCoseKey.exp(); + KMArray.cast(arr).add((short) 0, coseKeyExp); // [ CoseKey ] + arr = receiveIncoming(apdu, arr); + // Get cose key. + short coseKey = KMArray.cast(arr).get((short) 0); + short pubKeyLen = KMCoseKey.cast(coseKey).getEcdsa256PublicKey(scratchPad, (short) 0); + short privKeyLen = KMCoseKey.cast(coseKey).getPrivateKey(scratchPad, pubKeyLen); + // Store the Device unique Key. + kmDataStore.createRkpDeviceUniqueKeyPair( + scratchPad, (short) 0, pubKeyLen, scratchPad, pubKeyLen, privKeyLen); + short dcc = generateDiceCertChain(scratchPad); + short len = KMKeymasterApplet.encodeToApduBuffer(dcc, scratchPad, (short) 0, MAX_COSE_BUF_SIZE); + kmDataStore.persistBootCertificateChain(scratchPad, (short) 0, len); + kmDataStore.setProvisionStatus(PROVISION_STATUS_DEVICE_UNIQUE_KEYPAIR); + sendResponse(apdu, KMError.OK); + } + + private void processProvisionRkpUdsCertChain(APDU apdu) { + // X509 certificate chain is received as shown below: + /** + * x509CertChain = bstr .cbor UdsCerts + * + * <p>UdsCerts = { * SignerName => UdsCertChain } + * + * <p>; SignerName is a string identifier that indicates both the signing authority as ; well as + * the format of the UdsCertChain SignerName = tstr + * + * <p>UdsCertChain = [ 2* X509Certificate ; Root -> ... -> Leaf. "Root" is the vendor + * self-signed ; cert, "Leaf" contains UDS_Public. There may also be ; intermediate certificates + * between Root and Leaf. ] + * + * <p>; A bstr containing a DER-encoded X.509 certificate (RSA, NIST P-curve, or EdDSA) + * X509Certificate = bstr + */ + // Store the CBOR encoded UdsCerts as it is in the persistent memory so cbor decoding is + // required here. + byte[] srcBuffer = apdu.getBuffer(); + short recvLen = apdu.setIncomingAndReceive(); + apduStatusFlags[APDU_INCOMING_AND_RECEIVE_STATUS_INDEX] = 1; + short srcOffset = apdu.getOffsetCdata(); + short bufferLength = apdu.getIncomingLength(); + short bufferStartOffset = repository.allocReclaimableMemory(bufferLength); + short index = bufferStartOffset; + byte[] buffer = repository.getHeap(); + while (recvLen > 0 && ((short) (index - bufferStartOffset) < bufferLength)) { + Util.arrayCopyNonAtomic(srcBuffer, srcOffset, buffer, index, recvLen); + index += recvLen; + recvLen = apdu.receiveBytes(srcOffset); + } + short byteHeaderLen = + decoder.readCertificateChainHeaderLen(buffer, bufferStartOffset, bufferLength); + kmDataStore.persistUdsCertChain( + buffer, + (short) (bufferStartOffset + byteHeaderLen), + (short) (bufferLength - byteHeaderLen)); + kmDataStore.setProvisionStatus(PROVISION_STATUS_UDS_CERT_CHAIN); + // reclaim memory + repository.reclaimMemory(bufferLength); + sendResponse(apdu, KMError.OK); + } + + private void processProvisionAttestIdsCmd(APDU apdu) { + short keyparams = KMKeyParameters.exp(); + short cmd = KMArray.instance((short) 1); + KMArray.cast(cmd).add((short) 0, keyparams); + short args = receiveIncoming(apdu, cmd); + + short attData = KMArray.cast(args).get((short) 0); + // persist attestation Ids - if any is missing then exception occurs + setAttestationIds(attData); + } + + public void setAttestationIds(short attIdVals) { + KMKeyParameters instParam = KMKeyParameters.cast(attIdVals); + KMArray vals = KMArray.cast(instParam.getVals()); + short index = 0; + short length = vals.length(); + short key; + short type; + short obj; + while (index < length) { + obj = vals.get(index); + key = KMTag.getKey(obj); + type = KMTag.getTagType(obj); + + if (KMType.BYTES_TAG != type) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + obj = KMByteTag.cast(obj).getValue(); + if (KMByteBlob.cast(obj).length() > KMConfigurations.MAX_ATTESTATION_IDS_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + kmDataStore.setAttestationId( + key, + KMByteBlob.cast(obj).getBuffer(), + KMByteBlob.cast(obj).getStartOff(), + KMByteBlob.cast(obj).length()); + index++; + } + } + + private void processProvisionPreSharedSecretCmd(APDU apdu) { + short blob = KMByteBlob.exp(); + short argsProto = KMArray.instance((short) 1); + KMArray.cast(argsProto).add((short) 0, blob); + short args = receiveIncoming(apdu, argsProto); + + short val = KMArray.cast(args).get((short) 0); + + if (val != KMType.INVALID_VALUE && KMByteBlob.cast(val).length() != SHARED_SECRET_KEY_SIZE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // Persist shared Hmac. + kmDataStore.createPresharedKey( + KMByteBlob.cast(val).getBuffer(), + KMByteBlob.cast(val).getStartOff(), + KMByteBlob.cast(val).length()); + } + + // This function masks the error code with POWER_RESET_MASK_FLAG + // in case if card reset event occurred. The clients of the Applet + // has to extract the power reset status from the error code and + // process accordingly. + private static short buildErrorStatus(short err) { + short int32Ptr = KMInteger.instance((short) 4); + short powerResetStatus = 0; + if (((KMAndroidSEProvider) seProvider).isPowerReset()) { + powerResetStatus = POWER_RESET_MASK_FLAG; + } + + Util.setShort( + KMInteger.cast(int32Ptr).getBuffer(), + KMInteger.cast(int32Ptr).getStartOff(), + powerResetStatus); + + Util.setShort( + KMInteger.cast(int32Ptr).getBuffer(), + (short) (KMInteger.cast(int32Ptr).getStartOff() + 2), + err); + // reset power reset status flag to its default value. + // repository.restorePowerResetStatus(); //TODO + return int32Ptr; + } + + private void processGetProvisionStatusCmd(APDU apdu) { + byte[] scratchpad = apdu.getBuffer(); + short pStatus = kmDataStore.getProvisionStatus(); + Util.setShort(scratchpad, (short) 0, pStatus); + short resp = KMArray.instance((short) 2); + KMArray.cast(resp).add((short) 0, buildErrorStatus(KMError.OK)); + KMArray.cast(resp).add((short) 1, KMInteger.instance(scratchpad, (short) 0, (short) 2)); + sendOutgoing(apdu, resp); + } + + private boolean isProvisioningComplete() { + short pStatus = kmDataStore.getProvisionStatus(); + short pCompleteStatus = + PROVISION_STATUS_DEVICE_UNIQUE_KEYPAIR + | PROVISION_STATUS_PRESHARED_SECRET + | PROVISION_STATUS_ATTEST_IDS + | PROVISION_STATUS_OEM_PUBLIC_KEY + | PROVISION_STATUS_SECURE_BOOT_MODE; + if (kmDataStore.isProvisionLocked() || (pCompleteStatus == (pStatus & pCompleteStatus))) { + return true; + } + return false; + } + + @Override + public void onCleanup() {} + + @Override + public void onConsolidate() {} + + private boolean isUpgradeAllowed(short oldVersion) { + boolean upgradeAllowed = false; + // Downgrade of the Applet is not allowed. + if (KM_APPLET_PACKAGE_VERSION >= oldVersion) { + upgradeAllowed = true; + } + return upgradeAllowed; + } + + @Override + public void onRestore(Element element) { + element.initRead(); + byte magicNumber = element.readByte(); + if (magicNumber != KM_MAGIC_NUMBER) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short oldPackageVersion = element.readShort(); + // Validate version. + if (!isUpgradeAllowed(oldPackageVersion)) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + kmDataStore.onRestore(element, oldPackageVersion, KM_APPLET_PACKAGE_VERSION); + } + + @Override + public Element onSave() { + short primitiveCount = 3; + primitiveCount += kmDataStore.getBackupPrimitiveByteCount(); + short objectCount = kmDataStore.getBackupObjectCount(); + // Create element. + Element element = + UpgradeManager.createElement(Element.TYPE_SIMPLE, primitiveCount, objectCount); + + element.write(KM_MAGIC_NUMBER); + element.write(packageVersion); + kmDataStore.onSave(element); + return element; + } + + private short validateApdu(APDU apdu) { + // Read the apdu header and buffer. + byte[] apduBuffer = apdu.getBuffer(); + short P1P2 = Util.getShort(apduBuffer, ISO7816.OFFSET_P1); + + // Validate CLA + if (!apdu.isValidCLA()) { + ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED); + } + + // Validate P1P2. + if (P1P2 != KMKeymasterApplet.KM_HAL_VERSION) { + sendResponse(apdu, KMError.INVALID_P1P2); + return KMType.INVALID_VALUE; + } + return apduBuffer[ISO7816.OFFSET_INS]; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java new file mode 100644 index 0000000..bf9e7ac --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java @@ -0,0 +1,1056 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.keymaster; + +import com.android.javacard.seprovider.KMAESKey; +import com.android.javacard.seprovider.KMAttestationCert; +import com.android.javacard.seprovider.KMException; +import com.android.javacard.seprovider.KMKey; +import com.android.javacard.seprovider.KMSEProvider; +import javacard.framework.JCSystem; +import javacard.framework.Util; + +/** + * The class encodes strongbox generated and signed attestation certificates. It only encodes the + * required fields of the certificates. This class is not meant to be a generic X509 cert encoder. + * Any fields that are fixed are added as byte arrays. Extensions are encoded as per the values. The + * certificate is assembled with leafs first and then the sequences. + */ +public class KMAttestationCertImpl implements KMAttestationCert { + + // The maximum size of the either software or hardware parameters. + private static final byte MAX_PARAMS = 30; + // DER encoded object identifiers required by the cert. + // rsaEncryption - 1.2.840.113549.1.1.1 + private static final byte[] rsaEncryption = { + 0x06, 0x09, 0x2A, (byte) 0x86, 0x48, (byte) 0x86, (byte) 0xF7, 0x0D, 0x01, 0x01, 0x01 + }; + // ecPublicKey - 1.2.840.10045.2.1 + private static final byte[] eccPubKey = { + 0x06, 0x07, 0x2A, (byte) 0x86, 0x48, (byte) 0xCE, 0x3D, 0x02, 0x01 + }; + // prime256v1 curve - 1.2.840.10045.3.1.7 + private static final byte[] prime256v1 = { + 0x06, 0x08, 0x2A, (byte) 0x86, 0x48, (byte) 0xCE, 0x3D, 0x03, 0x01, 0x07 + }; + // Key Usage Extn - 2.5.29.15 + private static final byte[] keyUsageExtn = {0x06, 0x03, 0x55, 0x1D, 0x0F}; + // Android Extn - 1.3.6.1.4.1.11129.2.1.17 + private static final byte[] androidExtn = { + 0x06, 0x0A, 0X2B, 0X06, 0X01, 0X04, 0X01, (byte) 0XD6, 0X79, 0X02, 0X01, 0X11 + }; + // The length of the RSA signature. + private static final short RSA_SIG_LEN = 256; + // The maximum length of the ECDSA signature. + private static final byte ECDSA_MAX_SIG_LEN = 72; + // Signature algorithm identifier - ecdsaWithSha256 - 1.2.840.10045.4.3.2 + // SEQUENCE of alg OBJ ID and parameters = NULL. + private static final byte[] X509EcdsaSignAlgIdentifier = { + 0x30, 0x0A, 0x06, 0x08, 0x2A, (byte) 0x86, 0x48, (byte) 0xCE, (byte) 0x3D, 0x04, 0x03, 0x02 + }; + // Signature algorithm identifier - sha256WithRSAEncryption - 1.2.840.113549.1.1.11 + // SEQUENCE of alg OBJ ID and parameters = NULL. + private static final byte[] X509RsaSignAlgIdentifier = { + 0x30, + 0x0D, + 0x06, + 0x09, + 0x2A, + (byte) 0x86, + 0x48, + (byte) 0x86, + (byte) 0xF7, + 0x0D, + 0x01, + 0x01, + 0x0B, + 0x05, + 0x00 + }; + + // Below are the allowed softwareEnforced Authorization tags inside the attestation certificate's + // extension. + private static final short[] swTagIds = { + KMType.ATTESTATION_APPLICATION_ID, + KMType.CREATION_DATETIME, + KMType.ALLOW_WHILE_ON_BODY, + KMType.USAGE_COUNT_LIMIT, + KMType.USAGE_EXPIRE_DATETIME, + KMType.ORIGINATION_EXPIRE_DATETIME, + KMType.ACTIVE_DATETIME, + }; + + // Below are the allowed hardwareEnforced Authorization tags inside the attestation certificate's + // extension. + private static final short[] hwTagIds = { + KMType.ATTESTATION_ID_SECOND_IMEI, + KMType.BOOT_PATCH_LEVEL, + KMType.VENDOR_PATCH_LEVEL, + KMType.ATTESTATION_ID_MODEL, + KMType.ATTESTATION_ID_MANUFACTURER, + KMType.ATTESTATION_ID_MEID, + KMType.ATTESTATION_ID_IMEI, + KMType.ATTESTATION_ID_SERIAL, + KMType.ATTESTATION_ID_PRODUCT, + KMType.ATTESTATION_ID_DEVICE, + KMType.ATTESTATION_ID_BRAND, + KMType.OS_PATCH_LEVEL, + KMType.OS_VERSION, + KMType.ROOT_OF_TRUST, + KMType.ORIGIN, + KMType.UNLOCKED_DEVICE_REQUIRED, + KMType.TRUSTED_CONFIRMATION_REQUIRED, + KMType.AUTH_TIMEOUT, + KMType.USER_AUTH_TYPE, + KMType.NO_AUTH_REQUIRED, + KMType.EARLY_BOOT_ONLY, + KMType.ROLLBACK_RESISTANCE, + KMType.RSA_OAEP_MGF_DIGEST, + KMType.RSA_PUBLIC_EXPONENT, + KMType.ECCURVE, + KMType.PADDING, + KMType.DIGEST, + KMType.KEYSIZE, + KMType.ALGORITHM, + KMType.PURPOSE + }; + // Below are the constants for the key usage extension. + private static final byte keyUsageSign = (byte) 0x80; // 0 bit + private static final byte keyUsageKeyEncipher = (byte) 0x20; // 2nd- bit + private static final byte keyUsageDataEncipher = (byte) 0x10; // 3rd- bit + private static final byte keyUsageKeyAgreement = (byte) 0x08; // 4th- bit + private static final byte keyUsageCertSign = (byte) 0x04; // 5th- bit + // KeyMint HAL Version constant. + private static final short KEYMINT_VERSION = 300; + // Attestation version constant. + private static final short ATTESTATION_VERSION = 300; + // The X.509 version as per rfc5280#section-4.1.2.1 + private static final byte X509_VERSION = (byte) 0x02; + + // Buffer indexes in transient array + private static final byte NUM_INDEX_ENTRIES = 21; + private static final byte CERT_START = (byte) 0; + private static final byte CERT_LENGTH = (byte) 1; + private static final byte TBS_START = (byte) 2; + private static final byte TBS_LENGTH = (byte) 3; + private static final byte BUF_START = (byte) 4; + private static final byte BUF_LENGTH = (byte) 5; + private static final byte SW_PARAM_INDEX = (byte) 6; + private static final byte HW_PARAM_INDEX = (byte) 7; + // Data indexes in transient array + private static final byte STACK_PTR = (byte) 8; + private static final byte UNIQUE_ID = (byte) 9; + private static final byte ATT_CHALLENGE = (byte) 10; + private static final byte NOT_BEFORE = (byte) 11; + private static final byte NOT_AFTER = (byte) 12; + private static final byte PUB_KEY = (byte) 13; + private static final byte VERIFIED_BOOT_KEY = (byte) 14; + private static final byte VERIFIED_HASH = (byte) 15; + private static final byte ISSUER = (byte) 16; + private static final byte SUBJECT_NAME = (byte) 17; + private static final byte SERIAL_NUMBER = (byte) 18; + private static final byte CERT_ATT_KEY_SECRET = (byte) 19; + private static final byte CERT_ATT_KEY_RSA_PUB_MOD = (byte) 20; + // State indexes in transient array + private static final byte NUM_STATE_ENTRIES = 7; + private static final byte KEY_USAGE = (byte) 0; + private static final byte UNUSED_BITS = (byte) 1; + private static final byte DEVICE_LOCKED = (byte) 2; + private static final byte VERIFIED_STATE = (byte) 3; + private static final byte CERT_MODE = (byte) 4; + private static final byte RSA_CERT = (byte) 5; + private static final byte CERT_RSA_SIGN = (byte) 6; + + private static KMAttestationCert inst; + private static KMSEProvider seProvider; + + private static short[] indexes; + private static byte[] states; + + private static byte[] stack; + private static short[] swParams; + private static short[] hwParams; + // The maximum size of the serial number. + private static final byte SERIAL_NUM_MAX_LEN = 20; + + private KMAttestationCertImpl() {} + + public static KMAttestationCert instance(boolean rsaCert, KMSEProvider provider) { + if (inst == null) { + inst = new KMAttestationCertImpl(); + seProvider = provider; + + // Allocate transient memory + indexes = JCSystem.makeTransientShortArray(NUM_INDEX_ENTRIES, JCSystem.CLEAR_ON_RESET); + states = JCSystem.makeTransientByteArray(NUM_STATE_ENTRIES, JCSystem.CLEAR_ON_RESET); + swParams = JCSystem.makeTransientShortArray(MAX_PARAMS, JCSystem.CLEAR_ON_RESET); + hwParams = JCSystem.makeTransientShortArray(MAX_PARAMS, JCSystem.CLEAR_ON_RESET); + } + init(rsaCert); + return inst; + } + + private static void init(boolean rsaCert) { + for (short i = 0; i < NUM_INDEX_ENTRIES; i++) { + indexes[i] = 0; + } + Util.arrayFillNonAtomic(states, (short) 0, NUM_STATE_ENTRIES, (byte) 0); + stack = null; + states[CERT_MODE] = KMType.NO_CERT; + states[UNUSED_BITS] = 8; + states[RSA_CERT] = rsaCert ? (byte) 1 : (byte) 0; + states[CERT_RSA_SIGN] = 1; + indexes[CERT_ATT_KEY_SECRET] = KMType.INVALID_VALUE; + indexes[CERT_ATT_KEY_RSA_PUB_MOD] = KMType.INVALID_VALUE; + indexes[ISSUER] = KMType.INVALID_VALUE; + indexes[SUBJECT_NAME] = KMType.INVALID_VALUE; + indexes[SERIAL_NUMBER] = KMType.INVALID_VALUE; + } + + @Override + public KMAttestationCert verifiedBootHash(short obj) { + indexes[VERIFIED_HASH] = obj; + return this; + } + + @Override + public KMAttestationCert verifiedBootKey(short obj) { + indexes[VERIFIED_BOOT_KEY] = obj; + return this; + } + + @Override + public KMAttestationCert verifiedBootState(byte val) { + states[VERIFIED_STATE] = val; + return this; + } + + private KMAttestationCert uniqueId(short obj) { + indexes[UNIQUE_ID] = obj; + return this; + } + + @Override + public KMAttestationCert notBefore(short obj, boolean derEncoded, byte[] scratchpad) { + if (!derEncoded) { + // convert milliseconds to UTC date + indexes[NOT_BEFORE] = KMUtils.convertToDate(obj, scratchpad, true); + } else { + indexes[NOT_BEFORE] = + KMByteBlob.instance( + KMByteBlob.cast(obj).getBuffer(), + KMByteBlob.cast(obj).getStartOff(), + KMByteBlob.cast(obj).length()); + } + return this; + } + + @Override + public KMAttestationCert notAfter( + short usageExpiryTimeObj, boolean derEncoded, byte[] scratchPad) { + if (!derEncoded) { + if (usageExpiryTimeObj != KMType.INVALID_VALUE) { + // compare if the expiry time is greater then 2050 then use generalized + // time format else use utc time format. + short tmpVar = KMInteger.uint_64(KMUtils.firstJan2050, (short) 0); + if (KMInteger.compare(usageExpiryTimeObj, tmpVar) >= 0) { + usageExpiryTimeObj = KMUtils.convertToDate(usageExpiryTimeObj, scratchPad, false); + } else { + usageExpiryTimeObj = KMUtils.convertToDate(usageExpiryTimeObj, scratchPad, true); + } + indexes[NOT_AFTER] = usageExpiryTimeObj; + } else { + // notAfter = certExpirtyTimeObj; + } + } else { + indexes[NOT_AFTER] = usageExpiryTimeObj; + } + return this; + } + + @Override + public KMAttestationCert deviceLocked(boolean val) { + if (val) { + states[DEVICE_LOCKED] = (byte) 0xFF; + } else { + states[DEVICE_LOCKED] = 0; + } + return this; + } + + @Override + public KMAttestationCert publicKey(short obj) { + indexes[PUB_KEY] = obj; + return this; + } + + @Override + public KMAttestationCert attestationChallenge(short obj) { + indexes[ATT_CHALLENGE] = obj; + return this; + } + + @Override + public KMAttestationCert extensionTag(short tag, boolean hwEnforced) { + if (hwEnforced) { + hwParams[indexes[HW_PARAM_INDEX]] = tag; + indexes[HW_PARAM_INDEX]++; + } else { + swParams[indexes[SW_PARAM_INDEX]] = tag; + indexes[SW_PARAM_INDEX]++; + } + if (KMTag.getKey(tag) == KMType.PURPOSE) { + createKeyUsage(tag); + } + return this; + } + + @Override + public KMAttestationCert issuer(short obj) { + indexes[ISSUER] = obj; + return this; + } + + private void createKeyUsage(short tag) { + short len = KMEnumArrayTag.cast(tag).length(); + byte index = 0; + while (index < len) { + if (KMEnumArrayTag.cast(tag).get(index) == KMType.SIGN) { + states[KEY_USAGE] = (byte) (states[KEY_USAGE] | keyUsageSign); + } else if (KMEnumArrayTag.cast(tag).get(index) == KMType.WRAP_KEY) { + states[KEY_USAGE] = (byte) (states[KEY_USAGE] | keyUsageKeyEncipher); + } else if (KMEnumArrayTag.cast(tag).get(index) == KMType.DECRYPT) { + states[KEY_USAGE] = (byte) (states[KEY_USAGE] | keyUsageDataEncipher); + } else if (KMEnumArrayTag.cast(tag).get(index) == KMType.AGREE_KEY) { + states[KEY_USAGE] = (byte) (states[KEY_USAGE] | keyUsageKeyAgreement); + } else if (KMEnumArrayTag.cast(tag).get(index) == KMType.ATTEST_KEY) { + states[KEY_USAGE] = (byte) (states[KEY_USAGE] | keyUsageCertSign); + } + index++; + } + index = states[KEY_USAGE]; + while (index != 0) { + index = (byte) (index << 1); + states[UNUSED_BITS]--; + } + } + + private static void pushTbsCert(boolean rsaCert, boolean rsa) { + short last = indexes[STACK_PTR]; + pushExtensions(); + // subject public key info + if (rsaCert) { + pushRsaSubjectKeyInfo(); + } else { + pushEccSubjectKeyInfo(); + } + // subject + pushBytes( + KMByteBlob.cast(indexes[SUBJECT_NAME]).getBuffer(), + KMByteBlob.cast(indexes[SUBJECT_NAME]).getStartOff(), + KMByteBlob.cast(indexes[SUBJECT_NAME]).length()); + pushValidity(); + // issuer - der encoded + pushBytes( + KMByteBlob.cast(indexes[ISSUER]).getBuffer(), + KMByteBlob.cast(indexes[ISSUER]).getStartOff(), + KMByteBlob.cast(indexes[ISSUER]).length()); + // Algorithm Id + if (rsa) { + pushAlgorithmId(X509RsaSignAlgIdentifier); + } else { + pushAlgorithmId(X509EcdsaSignAlgIdentifier); + } + // Serial Number + pushBytes( + KMByteBlob.cast(indexes[SERIAL_NUMBER]).getBuffer(), + KMByteBlob.cast(indexes[SERIAL_NUMBER]).getStartOff(), + KMByteBlob.cast(indexes[SERIAL_NUMBER]).length()); + pushIntegerHeader(KMByteBlob.cast(indexes[SERIAL_NUMBER]).length()); + // Version + pushByte(X509_VERSION); + pushIntegerHeader((short) 1); + pushByte((byte) 0x03); + pushByte((byte) 0xA0); + // Finally sequence header. + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + private static void pushExtensions() { + short last = indexes[STACK_PTR]; + // Push KeyUsage extension + if (states[KEY_USAGE] != 0) { + pushKeyUsage(states[KEY_USAGE], states[UNUSED_BITS]); + } + if (states[CERT_MODE] == KMType.ATTESTATION_CERT) { + pushKeyDescription(); + } + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + // Extensions have explicit tag of [3] + pushLength((short) (last - indexes[STACK_PTR])); + pushByte((byte) 0xA3); + } + + // Time SEQUENCE{UTCTime, UTC or Generalized Time) + private static void pushValidity() { + short last = indexes[STACK_PTR]; + if (indexes[NOT_AFTER] != 0) { + pushBytes( + KMByteBlob.cast(indexes[NOT_AFTER]).getBuffer(), + KMByteBlob.cast(indexes[NOT_AFTER]).getStartOff(), + KMByteBlob.cast(indexes[NOT_AFTER]).length()); + } else { + KMException.throwIt(KMError.INVALID_DATA); + } + pushTimeHeader(KMByteBlob.cast(indexes[NOT_AFTER]).length()); + pushBytes( + KMByteBlob.cast(indexes[NOT_BEFORE]).getBuffer(), + KMByteBlob.cast(indexes[NOT_BEFORE]).getStartOff(), + KMByteBlob.cast(indexes[NOT_BEFORE]).length()); + pushTimeHeader(KMByteBlob.cast(indexes[NOT_BEFORE]).length()); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + private static void pushTimeHeader(short len) { + if (len == 13) { // UTC Time + pushLength((short) 0x0D); + pushByte((byte) 0x17); + } else if (len == 15) { // Generalized Time + pushLength((short) 0x0F); + pushByte((byte) 0x18); + } else { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + } + + // SEQUENCE{SEQUENCE{algId, NULL}, bitString{SEQUENCE{ modulus as positive integer, public + // exponent + // as positive integer} + private static void pushRsaSubjectKeyInfo() { + short last = indexes[STACK_PTR]; + pushBytes(KMKeymasterApplet.F4, (short) 0, (short) KMKeymasterApplet.F4.length); + pushIntegerHeader((short) KMKeymasterApplet.F4.length); + pushBytes( + KMByteBlob.cast(indexes[PUB_KEY]).getBuffer(), + KMByteBlob.cast(indexes[PUB_KEY]).getStartOff(), + KMByteBlob.cast(indexes[PUB_KEY]).length()); + + // encode modulus as positive if the MSB is 1. + if (KMByteBlob.cast(indexes[PUB_KEY]).get((short) 0) < 0) { + pushByte((byte) 0x00); + pushIntegerHeader((short) (KMByteBlob.cast(indexes[PUB_KEY]).length() + 1)); + } else { + pushIntegerHeader(KMByteBlob.cast(indexes[PUB_KEY]).length()); + } + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + pushBitStringHeader((byte) 0x00, (short) (last - indexes[STACK_PTR])); + pushRsaEncryption(); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + // SEQUENCE{SEQUENCE{ecPubKey, prime256v1}, bitString{pubKey}} + private static void pushEccSubjectKeyInfo() { + short last = indexes[STACK_PTR]; + pushBytes( + KMByteBlob.cast(indexes[PUB_KEY]).getBuffer(), + KMByteBlob.cast(indexes[PUB_KEY]).getStartOff(), + KMByteBlob.cast(indexes[PUB_KEY]).length()); + pushBitStringHeader((byte) 0x00, KMByteBlob.cast(indexes[PUB_KEY]).length()); + pushEcDsa(); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + private static void pushEcDsa() { + short last = indexes[STACK_PTR]; + pushBytes(prime256v1, (short) 0, (short) prime256v1.length); + pushBytes(eccPubKey, (short) 0, (short) eccPubKey.length); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + private static void pushRsaEncryption() { + short last = indexes[STACK_PTR]; + pushNullHeader(); + pushBytes(rsaEncryption, (short) 0, (short) rsaEncryption.length); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + // KeyDescription ::= SEQUENCE { + // attestationVersion INTEGER, # Value 200 + // attestationSecurityLevel SecurityLevel, # See below + // keymasterVersion INTEGER, # Value 200 + // keymasterSecurityLevel SecurityLevel, # See below + // attestationChallenge OCTET_STRING, # Tag::ATTESTATION_CHALLENGE from attestParams + // uniqueId OCTET_STRING, # Empty unless key has Tag::INCLUDE_UNIQUE_ID + // softwareEnforced AuthorizationList, # See below + // hardwareEnforced AuthorizationList, # See below + // } + private static void pushKeyDescription() { + short last = indexes[STACK_PTR]; + pushHWParams(); + pushSWParams(); + if (indexes[UNIQUE_ID] != 0) { + pushOctetString( + KMByteBlob.cast(indexes[UNIQUE_ID]).getBuffer(), + KMByteBlob.cast(indexes[UNIQUE_ID]).getStartOff(), + KMByteBlob.cast(indexes[UNIQUE_ID]).length()); + } else { + pushOctetStringHeader((short) 0); + } + pushOctetString( + KMByteBlob.cast(indexes[ATT_CHALLENGE]).getBuffer(), + KMByteBlob.cast(indexes[ATT_CHALLENGE]).getStartOff(), + KMByteBlob.cast(indexes[ATT_CHALLENGE]).length()); + pushEnumerated(KMType.STRONGBOX); + pushShort(KEYMINT_VERSION); + pushIntegerHeader((short) 2); + pushEnumerated(KMType.STRONGBOX); + pushShort(ATTESTATION_VERSION); + pushIntegerHeader((short) 2); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + pushOctetStringHeader((short) (last - indexes[STACK_PTR])); + pushBytes(androidExtn, (short) 0, (short) androidExtn.length); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + private static void pushSWParams() { + short last = indexes[STACK_PTR]; + byte index = 0; + short length = (short) swTagIds.length; + do { + pushParams(swParams, indexes[SW_PARAM_INDEX], swTagIds[index]); + } while (++index < length); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + private static void pushHWParams() { + short last = indexes[STACK_PTR]; + byte index = 0; + short length = (short) hwTagIds.length; + do { + if (hwTagIds[index] == KMType.ROOT_OF_TRUST) { + pushRoT(); + continue; + } + if (pushParams(hwParams, indexes[HW_PARAM_INDEX], hwTagIds[index])) { + continue; + } + } while (++index < length); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + private static boolean pushParams(short[] params, short len, short tagId) { + short index = 0; + while (index < len) { + if (tagId == KMTag.getKey(params[index])) { + pushTag(params[index]); + return true; + } + index++; + } + return false; + } + + private static void pushTag(short tag) { + short type = KMTag.getTagType(tag); + short tagId = KMTag.getKey(tag); + short val; + switch (type) { + case KMType.BYTES_TAG: + val = KMByteTag.cast(tag).getValue(); + pushBytesTag( + tagId, + KMByteBlob.cast(val).getBuffer(), + KMByteBlob.cast(val).getStartOff(), + KMByteBlob.cast(val).length()); + break; + case KMType.ENUM_TAG: + val = KMEnumTag.cast(tag).getValue(); + pushEnumTag(tagId, (byte) val); + break; + case KMType.ENUM_ARRAY_TAG: + val = KMEnumArrayTag.cast(tag).getValues(); + pushEnumArrayTag( + tagId, + KMByteBlob.cast(val).getBuffer(), + KMByteBlob.cast(val).getStartOff(), + KMByteBlob.cast(val).length()); + break; + case KMType.UINT_TAG: + case KMType.ULONG_TAG: + case KMType.DATE_TAG: + val = KMIntegerTag.cast(tag).getValue(); + pushIntegerTag( + tagId, + KMInteger.cast(val).getBuffer(), + KMInteger.cast(val).getStartOff(), + KMInteger.cast(val).length()); + break; + case KMType.UINT_ARRAY_TAG: + case KMType.ULONG_ARRAY_TAG: + // According to KeyMint hal only one user secure id is used but this conflicts with + // tag type which is ULONG-REP. Currently this is encoded as SET OF INTEGERS + val = KMIntegerArrayTag.cast(tag).getValues(); + pushIntegerArrayTag(tagId, val); + break; + case KMType.BOOL_TAG: + val = KMBoolTag.cast(tag).getVal(); + pushBoolTag(tagId); + break; + default: + KMException.throwIt(KMError.INVALID_TAG); + break; + } + } + + // RootOfTrust ::= SEQUENCE { + // verifiedBootKey OCTET_STRING, + // deviceLocked BOOLEAN, + // verifiedBootState VerifiedBootState, + // verifiedBootHash OCTET_STRING, + // } + // VerifiedBootState ::= ENUMERATED { + // Verified (0), + // SelfSigned (1), + // Unverified (2), + // Failed (3), + // } + private static void pushRoT() { + short last = indexes[STACK_PTR]; + // verified boot hash + pushOctetString( + KMByteBlob.cast(indexes[VERIFIED_HASH]).getBuffer(), + KMByteBlob.cast(indexes[VERIFIED_HASH]).getStartOff(), + KMByteBlob.cast(indexes[VERIFIED_HASH]).length()); + + pushEnumerated(states[VERIFIED_STATE]); + + pushBoolean(states[DEVICE_LOCKED]); + // verified boot Key + pushOctetString( + KMByteBlob.cast(indexes[VERIFIED_BOOT_KEY]).getBuffer(), + KMByteBlob.cast(indexes[VERIFIED_BOOT_KEY]).getStartOff(), + KMByteBlob.cast(indexes[VERIFIED_BOOT_KEY]).length()); + + // Finally sequence header + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + // ... and tag Id + pushTagIdHeader(KMType.ROOT_OF_TRUST, (short) (last - indexes[STACK_PTR])); + } + + private static void pushOctetString(byte[] buf, short start, short len) { + pushBytes(buf, start, len); + pushOctetStringHeader(len); + } + + private static void pushBoolean(byte val) { + pushByte(val); + pushBooleanHeader((short) 1); + } + + private static void pushBooleanHeader(short len) { + pushLength(len); + pushByte((byte) 0x01); + } + + // Only SET of INTEGERS supported are padding, digest, purpose and blockmode + // All of these are enum array tags i.e. byte long values + private static void pushEnumArrayTag(short tagId, byte[] buf, short start, short len) { + short last = indexes[STACK_PTR]; + short index = 0; + while (index < len) { + pushByte(buf[(short) (start + index)]); + pushIntegerHeader((short) 1); + index++; + } + pushSetHeader((short) (last - indexes[STACK_PTR])); + pushTagIdHeader(tagId, (short) (last - indexes[STACK_PTR])); + } + + // Only SET of INTEGERS supported are padding, digest, purpose and blockmode + // All of these are enum array tags i.e. byte long values + private static void pushIntegerArrayTag(short tagId, short arr) { + short last = indexes[STACK_PTR]; + short index = 0; + short len = KMArray.cast(arr).length(); + short ptr; + while (index < len) { + ptr = KMArray.cast(arr).get(index); + pushInteger( + KMInteger.cast(ptr).getBuffer(), + KMInteger.cast(ptr).getStartOff(), + KMInteger.cast(ptr).length()); + index++; + } + pushSetHeader((short) (last - indexes[STACK_PTR])); + pushTagIdHeader(tagId, (short) (last - indexes[STACK_PTR])); + } + + private static void pushSetHeader(short len) { + pushLength(len); + pushByte((byte) 0x31); + } + + private static void pushEnumerated(byte val) { + short last = indexes[STACK_PTR]; + pushByte(val); + pushEnumeratedHeader((short) (last - indexes[STACK_PTR])); + } + + private static void pushEnumeratedHeader(short len) { + pushLength(len); + pushByte((byte) 0x0A); + } + + private static void pushBoolTag(short tagId) { + short last = indexes[STACK_PTR]; + pushNullHeader(); + pushTagIdHeader(tagId, (short) (last - indexes[STACK_PTR])); + } + + private static void pushNullHeader() { + pushByte((byte) 0); + pushByte((byte) 0x05); + } + + private static void pushEnumTag(short tagId, byte val) { + short last = indexes[STACK_PTR]; + pushByte(val); + pushIntegerHeader((short) (last - indexes[STACK_PTR])); + pushTagIdHeader(tagId, (short) (last - indexes[STACK_PTR])); + } + + private static void pushIntegerTag(short tagId, byte[] buf, short start, short len) { + short last = indexes[STACK_PTR]; + pushInteger(buf, start, len); + pushTagIdHeader(tagId, (short) (last - indexes[STACK_PTR])); + } + + // Ignore leading zeros. Only Unsigned Integers are required hence if MSB is set then add 0x00 + // as most significant byte. + private static void pushInteger(byte[] buf, short start, short len) { + short last = indexes[STACK_PTR]; + byte index = 0; + while (index < (byte) len) { + if (buf[(short) (start + index)] != 0) { + break; + } + index++; + } + if (index == (byte) len) { + pushByte((byte) 0x00); + } else { + pushBytes(buf, (short) (start + index), (short) (len - index)); + if (buf[(short) (start + index)] < 0) { // MSB is 1 + pushByte((byte) 0x00); // always unsigned int + } + } + pushIntegerHeader((short) (last - indexes[STACK_PTR])); + } + + // Bytes Tag is a octet string and tag id is added explicitly + private static void pushBytesTag(short tagId, byte[] buf, short start, short len) { + short last = indexes[STACK_PTR]; + pushBytes(buf, start, len); + pushOctetStringHeader((short) (last - indexes[STACK_PTR])); + pushTagIdHeader(tagId, (short) (last - indexes[STACK_PTR])); + } + + // tag id <= 30 ---> 0xA0 | {tagId} + // 30 < tagId < 128 ---> 0xBF 0x{tagId} + // tagId >= 128 ---> 0xBF 0x80+(tagId/128) 0x{tagId - (128*(tagId/128))} + private static void pushTagIdHeader(short tagId, short len) { + pushLength(len); + short count = (short) (tagId / 128); + if (count > 0) { + pushByte((byte) (tagId - (128 * count))); + pushByte((byte) (0x80 + count)); + pushByte((byte) 0xBF); + } else if (tagId > 30) { + pushByte((byte) tagId); + pushByte((byte) 0xBF); + } else { + pushByte((byte) (0xA0 | (byte) tagId)); + } + } + + // SEQUENCE {ObjId, OCTET STRING{BIT STRING{keyUsage}}} + private static void pushKeyUsage(byte keyUsage, byte unusedBits) { + short last = indexes[STACK_PTR]; + pushByte(keyUsage); + pushBitStringHeader(unusedBits, (short) (last - indexes[STACK_PTR])); + pushOctetStringHeader((short) (last - indexes[STACK_PTR])); + pushBytes(keyUsageExtn, (short) 0, (short) keyUsageExtn.length); + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + } + + private static void pushAlgorithmId(byte[] algId) { + pushBytes(algId, (short) 0, (short) algId.length); + } + + private static void pushIntegerHeader(short len) { + pushLength(len); + pushByte((byte) 0x02); + } + + private static void pushOctetStringHeader(short len) { + pushLength(len); + pushByte((byte) 0x04); + } + + private static void pushSequenceHeader(short len) { + pushLength(len); + pushByte((byte) 0x30); + } + + private static void pushBitStringHeader(byte unusedBits, short len) { + pushByte(unusedBits); + pushLength((short) (len + 1)); // 1 extra byte for unused bits byte + pushByte((byte) 0x03); + } + + private static void pushLength(short len) { + if (len < 128) { + pushByte((byte) len); + } else if (len < 256) { + pushByte((byte) len); + pushByte((byte) 0x81); + } else { + pushShort(len); + pushByte((byte) 0x82); + } + } + + private static void pushShort(short val) { + decrementStackPtr((short) 2); + Util.setShort(stack, indexes[STACK_PTR], val); + } + + private static void pushByte(byte val) { + decrementStackPtr((short) 1); + stack[indexes[STACK_PTR]] = val; + } + + private static void pushBytes(byte[] buf, short start, short len) { + decrementStackPtr(len); + if (buf != null) { + Util.arrayCopyNonAtomic(buf, start, stack, indexes[STACK_PTR], len); + } + } + + private static void decrementStackPtr(short cnt) { + indexes[STACK_PTR] = (short) (indexes[STACK_PTR] - cnt); + if (indexes[BUF_START] > indexes[STACK_PTR]) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + } + + @Override + public KMAttestationCert buffer(byte[] buf, short start, short maxLen) { + stack = buf; + indexes[BUF_START] = start; + indexes[BUF_LENGTH] = maxLen; + indexes[STACK_PTR] = (short) (indexes[BUF_START] + indexes[BUF_LENGTH]); + return this; + } + + @Override + public short getCertStart() { + return indexes[CERT_START]; + } + + @Override + public short getCertLength() { + return indexes[CERT_LENGTH]; + } + + public void build(short attSecret, short attMod, boolean rsaSign, boolean fakeCert) { + indexes[STACK_PTR] = (short) (indexes[BUF_START] + indexes[BUF_LENGTH]); + short last = indexes[STACK_PTR]; + short sigLen = 0; + if (fakeCert) { + rsaSign = true; + pushByte((byte) 0); + sigLen = 1; + } + // Push placeholder signature Bit string header + // This will potentially change at the end + else if (rsaSign) { + decrementStackPtr(RSA_SIG_LEN); + } else { + decrementStackPtr(ECDSA_MAX_SIG_LEN); + } + short signatureOffset = indexes[STACK_PTR]; + pushBitStringHeader((byte) 0, (short) (last - indexes[STACK_PTR])); + if (rsaSign) { + pushAlgorithmId(X509RsaSignAlgIdentifier); + } else { + pushAlgorithmId(X509EcdsaSignAlgIdentifier); + } + indexes[TBS_LENGTH] = indexes[STACK_PTR]; + pushTbsCert((states[RSA_CERT] == 0 ? false : true), rsaSign); + indexes[TBS_START] = indexes[STACK_PTR]; + indexes[TBS_LENGTH] = (short) (indexes[TBS_LENGTH] - indexes[TBS_START]); + if (attSecret != KMType.INVALID_VALUE) { + // Sign with the attestation key + // The pubKey is the modulus. + if (rsaSign) { + sigLen = + seProvider.rsaSign256Pkcs1( + KMByteBlob.cast(attSecret).getBuffer(), + KMByteBlob.cast(attSecret).getStartOff(), + KMByteBlob.cast(attSecret).length(), + KMByteBlob.cast(attMod).getBuffer(), + KMByteBlob.cast(attMod).getStartOff(), + KMByteBlob.cast(attMod).length(), + stack, + indexes[TBS_START], + indexes[TBS_LENGTH], + stack, + signatureOffset); + if (sigLen > RSA_SIG_LEN) KMException.throwIt(KMError.UNKNOWN_ERROR); + } else { + sigLen = + seProvider.ecSign256( + KMByteBlob.cast(attSecret).getBuffer(), + KMByteBlob.cast(attSecret).getStartOff(), + KMByteBlob.cast(attSecret).length(), + stack, + indexes[TBS_START], + indexes[TBS_LENGTH], + stack, + signatureOffset); + if (sigLen > ECDSA_MAX_SIG_LEN) KMException.throwIt(KMError.UNKNOWN_ERROR); + } + // Adjust signature length + indexes[STACK_PTR] = signatureOffset; + pushBitStringHeader((byte) 0, sigLen); + } else if (!fakeCert) { // No attestation key provisioned in the factory + KMException.throwIt(KMError.ATTESTATION_KEYS_NOT_PROVISIONED); + } + last = (short) (signatureOffset + sigLen); + // Add certificate sequence header + indexes[STACK_PTR] = indexes[TBS_START]; + pushSequenceHeader((short) (last - indexes[STACK_PTR])); + indexes[CERT_START] = indexes[STACK_PTR]; + indexes[CERT_LENGTH] = (short) (last - indexes[CERT_START]); + } + + @Override + public void build() { + if (states[CERT_MODE] == KMType.FAKE_CERT) { + build(KMType.INVALID_VALUE, KMType.INVALID_VALUE, true, true); + } else { + build( + indexes[CERT_ATT_KEY_SECRET], + indexes[CERT_ATT_KEY_RSA_PUB_MOD], + (states[CERT_RSA_SIGN] == 0 ? false : true), + false); + } + } + + @Override + public KMAttestationCert makeUniqueId( + byte[] scratchPad, + short scratchPadOff, + byte[] creationTime, + short timeOffset, + short creationTimeLen, + byte[] attestAppId, + short appIdOff, + short attestAppIdLen, + byte resetSinceIdRotation, + KMKey masterKey) { + // Concatenate T||C||R + // temporal count T + short temp = + KMUtils.countTemporalCount( + creationTime, timeOffset, creationTimeLen, scratchPad, scratchPadOff); + Util.setShort(scratchPad, (short) scratchPadOff, temp); + temp = scratchPadOff; + scratchPadOff += 2; + + // Application Id C + Util.arrayCopyNonAtomic(attestAppId, appIdOff, scratchPad, scratchPadOff, attestAppIdLen); + scratchPadOff += attestAppIdLen; + + // Reset After Rotation R + scratchPad[scratchPadOff] = resetSinceIdRotation; + scratchPadOff++; + + // Get the key data from the master key + KMAESKey aesKey = (KMAESKey) masterKey; + short mKeyData = KMByteBlob.instance((short) (aesKey.aesKey.getSize() / 8)); + aesKey.aesKey.getKey( + KMByteBlob.cast(mKeyData).getBuffer(), /* Key */ + KMByteBlob.cast(mKeyData).getStartOff()); /* Key start*/ + timeOffset = KMByteBlob.instance((short) 32); + appIdOff = + seProvider.hmacSign( + KMByteBlob.cast(mKeyData).getBuffer(), /* Key */ + KMByteBlob.cast(mKeyData).getStartOff(), /* Key start*/ + KMByteBlob.cast(mKeyData).length(), /* Key length*/ + scratchPad, /* data */ + temp, /* data start */ + scratchPadOff, /* data length */ + KMByteBlob.cast(timeOffset).getBuffer(), /* signature buffer */ + KMByteBlob.cast(timeOffset).getStartOff()); /* signature start */ + if (appIdOff != 32) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + return uniqueId(timeOffset); + } + + @Override + public boolean serialNumber(short number) { + // https://datatracker.ietf.org/doc/html/rfc5280#section-4.1.2.2 + short length = KMByteBlob.cast(number).length(); + if (length > SERIAL_NUM_MAX_LEN) { + return false; + } + // The serial number Must be a positive integer. + byte msb = KMByteBlob.cast(number).get((short) 0); + if (msb < 0 && length > (SERIAL_NUM_MAX_LEN - 1)) { + return false; + } + indexes[SERIAL_NUMBER] = number; + return true; + } + + @Override + public boolean subjectName(short sub) { + if (sub == KMType.INVALID_VALUE || KMByteBlob.cast(sub).length() == 0) return false; + indexes[SUBJECT_NAME] = sub; + return true; + } + + @Override + public KMAttestationCert ecAttestKey(short attestKey, byte mode) { + states[CERT_MODE] = mode; + indexes[CERT_ATT_KEY_SECRET] = attestKey; + indexes[CERT_ATT_KEY_RSA_PUB_MOD] = KMType.INVALID_VALUE; + states[CERT_RSA_SIGN] = 0; + return this; + } + + @Override + public KMAttestationCert rsaAttestKey(short attestPrivExp, short attestMod, byte mode) { + states[CERT_MODE] = mode; + indexes[CERT_ATT_KEY_SECRET] = attestPrivExp; + indexes[CERT_ATT_KEY_RSA_PUB_MOD] = attestMod; + states[CERT_RSA_SIGN] = 1; + return this; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMConfigurations.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMConfigurations.java new file mode 100644 index 0000000..b7a66d2 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMConfigurations.java @@ -0,0 +1,32 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.keymaster; + +/** + * This class contains all the configuration values. Vendors can modify these values accordingly + * based on their environment. + */ +public class KMConfigurations { + // Machine types + public static final byte LITTLE_ENDIAN = 0x00; + public static final byte BIG_ENDIAN = 0x01; + public static final byte TEE_MACHINE_TYPE = LITTLE_ENDIAN; + // If the size of the attestation ids is known and lesser than 64 + // then reduce the size here. It reduces the heap memory usage. + public static final byte MAX_ATTESTATION_IDS_SIZE = 64; + // DER subject max length. + public static final short MAX_SUBJECT_DER_LEN = 1095; +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMUtils.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMUtils.java new file mode 100644 index 0000000..95ee67f --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMUtils.java @@ -0,0 +1,440 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.keymaster; + +import com.android.javacard.seprovider.KMException; +import javacard.framework.Util; + +/** + * This is a utility class which helps in converting date to UTC format and doing some arithmetic + * Operations. + */ +public class KMUtils { + + // 64 bit unsigned calculations for time + public static final byte[] oneSecMsec = {0, 0, 0, 0, 0, 0, 0x03, (byte) 0xE8}; // 1000 msec + public static final byte[] oneMinMsec = {0, 0, 0, 0, 0, 0, (byte) 0xEA, 0x60}; // 60000 msec + public static final byte[] oneHourMsec = { + 0, 0, 0, 0, 0, 0x36, (byte) 0xEE, (byte) 0x80 + }; // 3600000 msec + public static final byte[] oneDayMsec = {0, 0, 0, 0, 0x05, 0x26, 0x5C, 0x00}; // 86400000 msec + public static final byte[] oneMonthMsec = { + 0, 0, 0, 0, (byte) 0x9C, (byte) 0xBE, (byte) 0xBD, 0x50 + }; // 2629746000 msec + public static final byte[] leapYearMsec = { + 0, 0, 0, 0x07, (byte) 0x5C, (byte) 0xD7, (byte) 0x88, 0x00 + }; // 31622400000; + public static final byte[] yearMsec = { + 0, 0, 0, 0x07, 0x57, (byte) 0xB1, 0x2C, 0x00 + }; // 31536000000 + // Leap year(366) + 3 * 365 + public static final byte[] fourYrsMsec = { + 0, 0, 0, 0x1D, 0x63, (byte) 0xEB, 0x0C, 0x00 + }; // 126230400000 + public static final byte[] firstJan2020 = { + 0, 0, 0x01, 0x6F, 0x5E, 0x66, (byte) 0xE8, 0x00 + }; // 1577836800000 msec + public static final byte[] firstJan2050 = { + 0, 0, 0x02, 0x4b, (byte) 0xCE, 0x5C, (byte) 0xF0, 0x00 + }; // 2524608000000 + // msec + public static final byte[] febMonthLeapMSec = { + 0, 0, 0, 0, (byte) 0x95, 0x58, 0x6C, 0x00 + }; // 2505600000 + public static final byte[] febMonthMsec = { + 0, 0, 0, 0, (byte) 0x90, 0x32, 0x10, 0x00 + }; // 2419200000 + public static final byte[] ThirtyOneDaysMonthMsec = { + 0, 0, 0, 0, (byte) 0x9F, (byte) 0xA5, 0x24, 0x00 + }; // 2678400000 + public static final byte[] ThirtDaysMonthMsec = { + 0, 0, 0, 0, (byte) 0x9A, 0x7E, (byte) 0xC8, 0x00 + }; // 2592000000 + public static final short year2051 = 2051; + public static final short year2020 = 2020; + // Convert to milliseconds constants + public static final byte[] SEC_TO_MILLIS_SHIFT_POS = {9, 8, 7, 6, 5, 3}; + + // -------------------------------------- + public static short convertToDate(short time, byte[] scratchPad, boolean utcFlag) { + + short yrsCount = 0; + short monthCount = 1; + short dayCount = 1; + short hhCount = 0; + short mmCount = 0; + short ssCount = 0; + byte Z = 0x5A; + boolean from2020 = true; + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + Util.arrayCopyNonAtomic( + KMInteger.cast(time).getBuffer(), + KMInteger.cast(time).getStartOff(), + scratchPad, + (short) (8 - KMInteger.cast(time).length()), + KMInteger.cast(time).length()); + // If the time is less then 1 Jan 2020 then it is an error + if (KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, firstJan2020, (short) 0, (short) 8) + < 0) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + if (utcFlag + && KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, firstJan2050, (short) 0, (short) 8) + >= 0) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + + if (KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, firstJan2050, (short) 0, (short) 8) + < 0) { + Util.arrayCopyNonAtomic(firstJan2020, (short) 0, scratchPad, (short) 8, (short) 8); + subtract(scratchPad, (short) 0, (short) 8, (short) 16, (byte) 8); + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + } else { + from2020 = false; + Util.arrayCopyNonAtomic(firstJan2050, (short) 0, scratchPad, (short) 8, (short) 8); + subtract(scratchPad, (short) 0, (short) 8, (short) 16, (byte) 8); + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + } + // divide the given time with four yrs msec count + if (KMInteger.unsignedByteArrayCompare(scratchPad, (short) 0, fourYrsMsec, (short) 0, (short) 8) + >= 0) { + Util.arrayCopyNonAtomic(fourYrsMsec, (short) 0, scratchPad, (short) 8, (short) 8); + // quotient is multiple of 4 + yrsCount = divide(scratchPad, (short) 0, (short) 8, (short) 16); + yrsCount = (short) (yrsCount * 4); // number of yrs. + // copy reminder as new dividend + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + } + + // Get the leap year index starting from the (base Year + yrsCount) Year. + short leapYrIdx = getLeapYrIndex(from2020, yrsCount); + + // if leap year index is 0, then the number of days for the 1st year will be 366 days. + // if leap year index is not 0, then the number of days for the 1st year will be 365 days. + if (((leapYrIdx == 0) + && (KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, leapYearMsec, (short) 0, (short) 8) + >= 0)) + || ((leapYrIdx != 0) + && (KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, yearMsec, (short) 0, (short) 8) + >= 0))) { + for (short i = 0; i < 4; i++) { + yrsCount++; + if (i == leapYrIdx) { + Util.arrayCopyNonAtomic(leapYearMsec, (short) 0, scratchPad, (short) 8, (short) 8); + } else { + Util.arrayCopyNonAtomic(yearMsec, (short) 0, scratchPad, (short) 8, (short) 8); + } + subtract(scratchPad, (short) 0, (short) 8, (short) 16, (byte) 8); + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + if (((short) (i + 1) == leapYrIdx)) { + if (KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, leapYearMsec, (short) 0, (short) 8) + < 0) { + break; + } + } else { + if (KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, yearMsec, (short) 0, (short) 8) + < 0) { + break; + } + } + } + } + + // total yrs from 1970 + if (from2020) { + yrsCount = (short) (year2020 + yrsCount); + } else { + yrsCount = (short) (year2051 + yrsCount); + } + + // divide the given time with one month msec count + if (KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, oneMonthMsec, (short) 0, (short) 8) + >= 0) { + for (short i = 0; i < 12; i++) { + if (i == 1) { + // Feb month + if (isLeapYear(yrsCount)) { + // Leap year 29 days + Util.arrayCopyNonAtomic(febMonthLeapMSec, (short) 0, scratchPad, (short) 8, (short) 8); + } else { + // 28 days + Util.arrayCopyNonAtomic(febMonthMsec, (short) 0, scratchPad, (short) 8, (short) 8); + } + } else if (((i <= 6) && ((i % 2 == 0))) || ((i > 6) && ((i % 2 == 1)))) { + Util.arrayCopyNonAtomic( + ThirtyOneDaysMonthMsec, (short) 0, scratchPad, (short) 8, (short) 8); + } else { + // 30 Days + Util.arrayCopyNonAtomic(ThirtDaysMonthMsec, (short) 0, scratchPad, (short) 8, (short) 8); + } + + if (KMInteger.unsignedByteArrayCompare( + scratchPad, (short) 0, scratchPad, (short) 8, (short) 8) + >= 0) { + subtract(scratchPad, (short) 0, (short) 8, (short) 16, (byte) 8); + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + } else { + break; + } + monthCount++; + } + } + + // divide the given time with one day msec count + if (KMInteger.unsignedByteArrayCompare(scratchPad, (short) 0, oneDayMsec, (short) 0, (short) 8) + >= 0) { + Util.arrayCopyNonAtomic(oneDayMsec, (short) 0, scratchPad, (short) 8, (short) 8); + dayCount = divide(scratchPad, (short) 0, (short) 8, (short) 16); + dayCount++; + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + } + + // divide the given time with one hour msec count + if (KMInteger.unsignedByteArrayCompare(scratchPad, (short) 0, oneHourMsec, (short) 0, (short) 8) + >= 0) { + Util.arrayCopyNonAtomic(oneHourMsec, (short) 0, scratchPad, (short) 8, (short) 8); + hhCount = divide(scratchPad, (short) 0, (short) 8, (short) 16); + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + } + + // divide the given time with one minute msec count + if (KMInteger.unsignedByteArrayCompare(scratchPad, (short) 0, oneMinMsec, (short) 0, (short) 8) + >= 0) { + Util.arrayCopyNonAtomic(oneMinMsec, (short) 0, scratchPad, (short) 8, (short) 8); + mmCount = divide(scratchPad, (short) 0, (short) 8, (short) 16); + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + } + + // divide the given time with one second msec count + if (KMInteger.unsignedByteArrayCompare(scratchPad, (short) 0, oneSecMsec, (short) 0, (short) 8) + >= 0) { + Util.arrayCopyNonAtomic(oneSecMsec, (short) 0, scratchPad, (short) 8, (short) 8); + ssCount = divide(scratchPad, (short) 0, (short) 8, (short) 16); + Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); + } + + // Now convert to ascii string YYMMDDhhmmssZ or YYYYMMDDhhmmssZ + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + short len = numberToString(yrsCount, scratchPad, (short) 0); // returns YYYY + len += numberToString(monthCount, scratchPad, len); + len += numberToString(dayCount, scratchPad, len); + len += numberToString(hhCount, scratchPad, len); + len += numberToString(mmCount, scratchPad, len); + len += numberToString(ssCount, scratchPad, len); + scratchPad[len] = Z; + len++; + if (utcFlag) { + return KMByteBlob.instance(scratchPad, (short) 2, (short) (len - 2)); // YY + } else { + return KMByteBlob.instance(scratchPad, (short) 0, len); // YYYY + } + } + + public static short numberToString(short number, byte[] scratchPad, short offset) { + byte zero = 0x30; + byte len = 2; + byte digit; + if (number > 999) { + len = 4; + } + byte index = len; + while (index > 0) { + digit = (byte) (number % 10); + number = (short) (number / 10); + scratchPad[(short) (offset + index - 1)] = (byte) (digit + zero); + index--; + } + return len; + } + + // Use Euclid's formula: dividend = quotient*divisor + remainder + // i.e. dividend - quotient*divisor = remainder where remainder < divisor. + // so this is division by subtraction until remainder remains. + public static short divide(byte[] buf, short dividend, short divisor, short remainder) { + short expCnt = 1; + short q = 0; + // first increase divisor so that it becomes greater then dividend. + while (compare(buf, divisor, dividend) < 0) { + shiftLeft(buf, divisor); + expCnt = (short) (expCnt << 1); + } + // Now subtract divisor from dividend if dividend is greater then divisor. + // Copy remainder in the dividend and repeat. + while (expCnt != 0) { + if (compare(buf, dividend, divisor) >= 0) { + subtract(buf, dividend, divisor, remainder, (byte) 8); + copy(buf, remainder, dividend); + q = (short) (q + expCnt); + } + expCnt = (short) (expCnt >> 1); + shiftRight(buf, divisor); + } + return q; + } + + public static void copy(byte[] buf, short from, short to) { + Util.arrayCopyNonAtomic(buf, from, buf, to, (short) 8); + } + + public static byte compare(byte[] buf, short lhs, short rhs) { + return KMInteger.unsignedByteArrayCompare(buf, lhs, buf, rhs, (short) 8); + } + + public static void shiftLeft(byte[] buf, short start, short count) { + short index = 0; + while (index < count) { + shiftLeft(buf, start); + index++; + } + } + + public static void shiftLeft(byte[] buf, short start) { + byte index = 7; + byte carry = 0; + byte tmp; + while (index >= 0) { + tmp = buf[(short) (start + index)]; + buf[(short) (start + index)] = (byte) (buf[(short) (start + index)] << 1); + buf[(short) (start + index)] = (byte) (buf[(short) (start + index)] + carry); + if (tmp < 0) { + carry = 1; + } else { + carry = 0; + } + index--; + } + } + + public static void shiftRight(byte[] buf, short start) { + byte index = 0; + byte carry = 0; + byte tmp; + while (index < 8) { + tmp = (byte) (buf[(short) (start + index)] & 0x01); + buf[(short) (start + index)] = (byte) (buf[(short) (start + index)] >> 1); + buf[(short) (start + index)] = (byte) (buf[(short) (start + index)] & 0x7F); + buf[(short) (start + index)] = (byte) (buf[(short) (start + index)] | carry); + if (tmp == 1) { + carry = (byte) 0x80; + } else { + carry = 0; + } + index++; + } + } + + public static void add(byte[] buf, short op1, short op2, short result) { + byte index = 7; + byte carry = 0; + short tmp; + short val1 = 0; + short val2 = 0; + while (index >= 0) { + val1 = (short) (buf[(short) (op1 + index)] & 0x00FF); + val2 = (short) (buf[(short) (op2 + index)] & 0x00FF); + tmp = (short) (val1 + val2 + carry); + carry = 0; + if (tmp > 255) { + carry = 1; // max unsigned byte value is 255 + } + buf[(short) (result + index)] = (byte) (tmp & (byte) 0xFF); + index--; + } + } + + // subtraction by borrowing. + public static void subtract(byte[] buf, short op1, short op2, short result, byte sizeBytes) { + byte borrow = 0; + byte index = (byte) (sizeBytes - 1); + short r; + short x; + short y; + while (index >= 0) { + x = (short) (buf[(short) (op1 + index)] & 0xFF); + y = (short) (buf[(short) (op2 + index)] & 0xFF); + r = (short) (x - y - borrow); + borrow = 0; + if (r < 0) { + borrow = 1; + r = (short) (r + 256); // max unsigned byte value is 255 + } + buf[(short) (result + index)] = (byte) (r & 0xFF); + index--; + } + } + + public static short countTemporalCount( + byte[] bufTime, short timeOff, short timeLen, byte[] scratchPad, short offset) { + Util.arrayFillNonAtomic(scratchPad, (short) offset, (short) 24, (byte) 0); + Util.arrayCopyNonAtomic(bufTime, timeOff, scratchPad, (short) (offset + 8 - timeLen), timeLen); + Util.arrayCopyNonAtomic( + ThirtDaysMonthMsec, (short) 0, scratchPad, (short) (offset + 8), (short) 8); + return divide(scratchPad, (short) 0, (short) 8, (short) 16); + } + + public static boolean isLeapYear(short year) { + if ((short) (year % 4) == (short) 0) { + if (((short) (year % 100) == (short) 0) && ((short) (year % 400)) != (short) 0) { + return false; + } + return true; + } + return false; + } + + public static short getLeapYrIndex(boolean from2020, short yrsCount) { + short newBaseYr = (short) (from2020 ? (year2020 + yrsCount) : (year2051 + yrsCount)); + for (short i = 0; i < 4; i++) { + if (isLeapYear((short) (newBaseYr + i))) { + return i; + } + } + return -1; + } + + public static void computeOnesCompliment(byte[] buf, short offset, short len) { + short index = offset; + // Compute 1s compliment + while (index < (short) (len + offset)) { + buf[index] = (byte) ~buf[index]; + index++; + } + } + + // i * 1000 = (i << 9) + (i << 8) + (i << 7) + (i << 6) + (i << 5) + ( i << 3) + public static void convertToMilliseconds( + byte[] buf, short inputOff, short outputOff, short scratchPadOff) { + short index = 0; + short length = (short) SEC_TO_MILLIS_SHIFT_POS.length; + while (index < length) { + Util.arrayCopyNonAtomic(buf, inputOff, buf, scratchPadOff, (short) 8); + shiftLeft(buf, scratchPadOff, SEC_TO_MILLIS_SHIFT_POS[index]); + Util.arrayCopyNonAtomic(buf, outputOff, buf, (short) (scratchPadOff + 8), (short) 8); + add(buf, scratchPadOff, (short) (8 + scratchPadOff), (short) (16 + scratchPadOff)); + Util.arrayCopyNonAtomic(buf, (short) (scratchPadOff + 16), buf, outputOff, (short) 8); + Util.arrayFillNonAtomic(buf, scratchPadOff, (short) 24, (byte) 0); + index++; + } + } +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAESKey.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAESKey.java new file mode 100644 index 0000000..41059bb --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAESKey.java @@ -0,0 +1,53 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.security.AESKey; +import org.globalplatform.upgrade.Element; + +/** This is a wrapper class for AESKey. */ +public class KMAESKey implements KMKey { + + public AESKey aesKey; + + public KMAESKey(AESKey key) { + aesKey = key; + } + + public static void onSave(Element element, KMAESKey kmKey) { + element.write(kmKey.aesKey); + } + + public static KMAESKey onRestore(AESKey aesKey) { + if (aesKey == null) { + return null; + } + return new KMAESKey(aesKey); + } + + public static short getBackupPrimitiveByteCount() { + return (short) 0; + } + + public static short getBackupObjectCount() { + return (short) 1; + } + + @Override + public short getPublicKey(byte[] buf, short offset) { + return (short) 0; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAndroidSEProvider.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAndroidSEProvider.java new file mode 100644 index 0000000..b8e78a0 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAndroidSEProvider.java @@ -0,0 +1,1572 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; +import javacard.security.AESKey; +import javacard.security.CryptoException; +import javacard.security.DESKey; +import javacard.security.ECPrivateKey; +import javacard.security.ECPublicKey; +import javacard.security.HMACKey; +import javacard.security.Key; +import javacard.security.KeyAgreement; +import javacard.security.KeyBuilder; +import javacard.security.KeyPair; +import javacard.security.MessageDigest; +import javacard.security.RSAPrivateKey; +import javacard.security.RandomData; +import javacard.security.Signature; +import javacardx.crypto.AEADCipher; +import javacardx.crypto.Cipher; +import org.globalplatform.upgrade.Element; +import org.globalplatform.upgrade.UpgradeManager; + +/** + * This class implements KMSEProvider and provides all the necessary crypto operations required to + * support the KeyMint specification. This class supports AES, 3DES, HMAC, RSA, ECDSA, ECDH + * algorithms additionally it also supports ECDSA_NO_DIGEST, RSA_NO_DIGEST and RSA_OAEP_MGF1_SHA1 + * and RSA_OAEP_MGF1_SHA256 algorithms. This class follows the pattern of Init-Update-Final for the + * crypto operations. + */ +public class KMAndroidSEProvider implements KMSEProvider { + + // The tag length for AES GCM algorithm. + public static final byte AES_GCM_TAG_LENGTH = 16; + // The nonce length for AES GCM algorithm. + public static final byte AES_GCM_NONCE_LENGTH = 12; + // AES keysize offsets in aesKeys[] for 128 and 256 sizes respectively. + public static final byte KEYSIZE_128_OFFSET = 0x00; + public static final byte KEYSIZE_256_OFFSET = 0x01; + // The size of the temporary buffer. + public static final short TMP_ARRAY_SIZE = 300; + // The length of the rsa key in bytes. + private static final short RSA_KEY_SIZE = 256; + // Below are the flag to denote device reset events + public static final byte POWER_RESET_FALSE = (byte) 0xAA; + public static final byte POWER_RESET_TRUE = (byte) 0x00; + // The computed HMAC key size. + private static final byte COMPUTED_HMAC_KEY_SIZE = 32; + // The constant 'L' as defiend in + // https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-108.pdf, page 12. + private static byte[] CMAC_KDF_CONSTANT_L; + // Constant to represent 0. + private static byte[] CMAC_KDF_CONSTANT_ZERO; + // KeyAgreement instance. + private static KeyAgreement keyAgreement; + + // AESKey + private AESKey aesKeys[]; + // DES3Key + private DESKey triDesKey; + // HMACKey + private HMACKey hmacKey; + // RSA Key Pair + private KeyPair rsaKeyPair; + // EC Key Pair. + private KeyPair ecKeyPair; + // Temporary array. + public byte[] tmpArray; + // This is used for internal encryption/decryption operations. + private static AEADCipher aesGcmCipher; + // Instance of Signature algorithm used in KDF. + private Signature kdf; + // Flag used to denote the power reset event. + public static byte[] resetFlag; + // Instance of HMAC Signature algorithm. + private Signature hmacSignature; + // For ImportwrappedKey operations. + private KMRsaOAEPEncoding rsaOaepDecipher; + // Instance of pool manager. + private KMPoolManager poolMgr; + // Instance of KMOperationImpl used only to encrypt/decrypt the KeyBlobs. + private KMOperationImpl globalOperation; + // Entropy + private RandomData rng; + // Singleton instance. + private static KMAndroidSEProvider androidSEProvider = null; + + public static KMAndroidSEProvider getInstance() { + return androidSEProvider; + } + + public KMAndroidSEProvider() { + initStatics(); + // Re-usable AES,DES and HMAC keys in persisted memory. + aesKeys = new AESKey[2]; + aesKeys[KEYSIZE_128_OFFSET] = + (AESKey) + KeyBuilder.buildKey( + KeyBuilder.TYPE_AES_TRANSIENT_RESET, KeyBuilder.LENGTH_AES_128, false); + aesKeys[KEYSIZE_256_OFFSET] = + (AESKey) + KeyBuilder.buildKey( + KeyBuilder.TYPE_AES_TRANSIENT_RESET, KeyBuilder.LENGTH_AES_256, false); + triDesKey = + (DESKey) + KeyBuilder.buildKey( + KeyBuilder.TYPE_DES_TRANSIENT_RESET, KeyBuilder.LENGTH_DES3_3KEY, false); + hmacKey = + (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC_TRANSIENT_RESET, (short) 512, false); + rsaKeyPair = new KeyPair(KeyPair.ALG_RSA, KeyBuilder.LENGTH_RSA_2048); + ecKeyPair = new KeyPair(KeyPair.ALG_EC_FP, KeyBuilder.LENGTH_EC_FP_256); + keyAgreement = KeyAgreement.getInstance(KeyAgreement.ALG_EC_SVDP_DH_PLAIN, false); + poolMgr = KMPoolManager.getInstance(); + poolMgr.initECKey(ecKeyPair); + // RsaOAEP Decipher + rsaOaepDecipher = new KMRsaOAEPEncoding(KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA1); + + kdf = Signature.getInstance(Signature.ALG_AES_CMAC_128, false); + hmacSignature = Signature.getInstance(Signature.ALG_HMAC_SHA_256, false); + + globalOperation = new KMOperationImpl(); + + // Temporary transient array created to use locally inside functions. + tmpArray = JCSystem.makeTransientByteArray(TMP_ARRAY_SIZE, JCSystem.CLEAR_ON_DESELECT); + Util.arrayFillNonAtomic(tmpArray, (short) 0, TMP_ARRAY_SIZE, (byte) 0); + // Random number generator initialisation. + rng = RandomData.getInstance(RandomData.ALG_KEYGENERATION); + androidSEProvider = this; + resetFlag = JCSystem.makeTransientByteArray((short) 1, JCSystem.CLEAR_ON_RESET); + resetFlag[0] = (byte) POWER_RESET_FALSE; + } + + void initStatics() { + CMAC_KDF_CONSTANT_L = new byte[] {0x00, 0x00, 0x01, 0x00}; + CMAC_KDF_CONSTANT_ZERO = new byte[] {0x00}; + } + + public void clean() { + Util.arrayFillNonAtomic(tmpArray, (short) 0, TMP_ARRAY_SIZE, (byte) 0); + } + + public AESKey createAESKey(short keysize) { + try { + if (keysize > TMP_ARRAY_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + newRandomNumber(tmpArray, (short) 0, (short) (keysize / 8)); + return createAESKey(tmpArray, (short) 0, (short) (keysize / 8)); + } finally { + clean(); + } + } + + public AESKey createAESKey(byte[] buf, short startOff, short length) { + AESKey key = null; + short keysize = (short) (length * 8); + if (keysize == 128) { + key = (AESKey) aesKeys[KEYSIZE_128_OFFSET]; + key.setKey(buf, (short) startOff); + } else if (keysize == 256) { + key = (AESKey) aesKeys[KEYSIZE_256_OFFSET]; + key.setKey(buf, (short) startOff); + } else { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + return key; + } + + public DESKey createTDESKey() { + try { + newRandomNumber(tmpArray, (short) 0, (short) (KeyBuilder.LENGTH_DES3_3KEY / 8)); + return createTDESKey(tmpArray, (short) 0, (short) (KeyBuilder.LENGTH_DES3_3KEY / 8)); + } finally { + clean(); + } + } + + public DESKey createTDESKey(byte[] secretBuffer, short secretOff, short secretLength) { + triDesKey.setKey(secretBuffer, secretOff); + return triDesKey; + } + + public HMACKey createHMACKey(short keysize) { + // As per the KeyMint2.0 specification + // The minimum supported HMAC key size is 64 bits + // The maximum supported HMAC key size is 512 bits + // The keysize should be a multiple of 8. + if ((keysize % 8 != 0) || !(keysize >= 64 && keysize <= 512)) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + try { + newRandomNumber(tmpArray, (short) 0, (short) (keysize / 8)); + return createHMACKey(tmpArray, (short) 0, (short) (keysize / 8)); + } finally { + clean(); + } + } + + public HMACKey createHMACKey(byte[] secretBuffer, short secretOff, short secretLength) { + hmacKey.setKey(secretBuffer, secretOff, secretLength); + return hmacKey; + } + + public KeyPair createRsaKeyPair() { + rsaKeyPair.genKeyPair(); + return rsaKeyPair; + } + + public RSAPrivateKey createRsaKey( + byte[] modBuffer, + short modOff, + short modLength, + byte[] privBuffer, + short privOff, + short privLength) { + RSAPrivateKey privKey = (RSAPrivateKey) rsaKeyPair.getPrivate(); + privKey.setExponent(privBuffer, privOff, privLength); + privKey.setModulus(modBuffer, modOff, modLength); + return privKey; + } + + public KeyPair createECKeyPair() { + ecKeyPair.genKeyPair(); + return ecKeyPair; + } + + public ECPrivateKey createEcKey(byte[] privBuffer, short privOff, short privLength) { + ECPrivateKey privKey = (ECPrivateKey) ecKeyPair.getPrivate(); + privKey.setS(privBuffer, privOff, privLength); + return privKey; + } + + @Override + public short createSymmetricKey(byte alg, short keysize, byte[] buf, short startOff) { + switch (alg) { + case KMType.AES: + AESKey aesKey = createAESKey(keysize); + return aesKey.getKey(buf, startOff); + case KMType.DES: + DESKey desKey = createTDESKey(); + return desKey.getKey(buf, startOff); + case KMType.HMAC: + HMACKey hmacKey = createHMACKey(keysize); + return hmacKey.getKey(buf, startOff); + default: + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + return 0; + } + + @Override + public void createAsymmetricKey( + byte alg, + byte[] privKeyBuf, + short privKeyStart, + short privKeyLength, + byte[] pubModBuf, + short pubModStart, + short pubModLength, + short[] lengths) { + switch (alg) { + case KMType.RSA: + if (RSA_KEY_SIZE != privKeyLength || RSA_KEY_SIZE != pubModLength) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + KeyPair rsaKey = createRsaKeyPair(); + RSAPrivateKey privKey = (RSAPrivateKey) rsaKey.getPrivate(); + // Copy exponent. + Util.arrayFillNonAtomic(tmpArray, (short) 0, RSA_KEY_SIZE, (byte) 0); + lengths[0] = privKey.getExponent(tmpArray, (short) 0); + if (lengths[0] > privKeyLength) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + Util.arrayFillNonAtomic(privKeyBuf, privKeyStart, privKeyLength, (byte) 0); + Util.arrayCopyNonAtomic( + tmpArray, + (short) 0, + privKeyBuf, + (short) (privKeyStart + privKeyLength - lengths[0]), + lengths[0]); + // Copy modulus + Util.arrayFillNonAtomic(tmpArray, (short) 0, RSA_KEY_SIZE, (byte) 0); + lengths[1] = privKey.getModulus(tmpArray, (short) 0); + if (lengths[1] > pubModLength) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + Util.arrayFillNonAtomic(pubModBuf, pubModStart, pubModLength, (byte) 0); + Util.arrayCopyNonAtomic( + tmpArray, + (short) 0, + pubModBuf, + (short) (pubModStart + pubModLength - lengths[1]), + lengths[1]); + break; + case KMType.EC: + KeyPair ecKey = createECKeyPair(); + ECPublicKey ecPubKey = (ECPublicKey) ecKey.getPublic(); + ECPrivateKey ecPrivKey = (ECPrivateKey) ecKey.getPrivate(); + lengths[0] = ecPrivKey.getS(privKeyBuf, privKeyStart); + lengths[1] = ecPubKey.getW(pubModBuf, pubModStart); + if (lengths[0] > privKeyLength || lengths[1] > pubModLength) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + break; + default: + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + } + + @Override + public boolean importSymmetricKey( + byte alg, short keysize, byte[] buf, short startOff, short length) { + switch (alg) { + case KMType.AES: + createAESKey(buf, startOff, length); + break; + case KMType.DES: + createTDESKey(buf, startOff, length); + break; + case KMType.HMAC: + createHMACKey(buf, startOff, length); + break; + default: + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + return true; + } + + @Override + public boolean importAsymmetricKey( + byte alg, + byte[] privKeyBuf, + short privKeyStart, + short privKeyLength, + byte[] pubModBuf, + short pubModStart, + short pubModLength) { + switch (alg) { + case KMType.RSA: + createRsaKey(pubModBuf, pubModStart, pubModLength, privKeyBuf, privKeyStart, privKeyLength); + break; + case KMType.EC: + createEcKey(privKeyBuf, privKeyStart, privKeyLength); + break; + default: + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + return true; + } + + @Override + public void getTrueRandomNumber(byte[] buf, short start, short length) { + newRandomNumber(buf, start, length); + } + + @Override + public void newRandomNumber(byte[] num, short startOff, short length) { + rng.nextBytes(num, startOff, length); + } + + @Override + public void addRngEntropy(byte[] num, short offset, short length) { + rng.setSeed(num, offset, length); + } + + public short aesGCMEncrypt( + AESKey key, + byte[] secret, + short secretStart, + short secretLen, + byte[] encSecret, + short encSecretStart, + byte[] nonce, + short nonceStart, + short nonceLen, + byte[] authData, + short authDataStart, + short authDataLen, + byte[] authTag, + short authTagStart, + short authTagLen) { + if (authTagLen != AES_GCM_TAG_LENGTH) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + if (nonceLen != AES_GCM_NONCE_LENGTH) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + if (aesGcmCipher == null) { + aesGcmCipher = (AEADCipher) Cipher.getInstance(AEADCipher.ALG_AES_GCM, false); + } + aesGcmCipher.init(key, Cipher.MODE_ENCRYPT, nonce, nonceStart, nonceLen); + if (authDataLen != 0) { + aesGcmCipher.updateAAD(authData, authDataStart, authDataLen); + } + short ciphLen = aesGcmCipher.doFinal(secret, secretStart, secretLen, encSecret, encSecretStart); + aesGcmCipher.retrieveTag(authTag, authTagStart, authTagLen); + return ciphLen; + } + + @Override + public short aesGCMEncrypt( + byte[] aesKey, + short aesKeyStart, + short aesKeyLen, + byte[] secret, + short secretStart, + short secretLen, + byte[] encSecret, + short encSecretStart, + byte[] nonce, + short nonceStart, + short nonceLen, + byte[] authData, + short authDataStart, + short authDataLen, + byte[] authTag, + short authTagStart, + short authTagLen) { + + AESKey key = createAESKey(aesKey, aesKeyStart, aesKeyLen); + return aesGCMEncrypt( + key, + secret, + secretStart, + secretLen, + encSecret, + encSecretStart, + nonce, + nonceStart, + nonceLen, + authData, + authDataStart, + authDataLen, + authTag, + authTagStart, + authTagLen); + } + + @Override + public boolean aesGCMDecrypt( + byte[] aesKey, + short aesKeyStart, + short aesKeyLen, + byte[] encSecret, + short encSecretStart, + short encSecretLen, + byte[] secret, + short secretStart, + byte[] nonce, + short nonceStart, + short nonceLen, + byte[] authData, + short authDataStart, + short authDataLen, + byte[] authTag, + short authTagStart, + short authTagLen) { + if (aesGcmCipher == null) { + aesGcmCipher = (AEADCipher) Cipher.getInstance(AEADCipher.ALG_AES_GCM, false); + } + boolean verification = false; + AESKey key = createAESKey(aesKey, aesKeyStart, aesKeyLen); + aesGcmCipher.init(key, Cipher.MODE_DECRYPT, nonce, nonceStart, nonceLen); + if (authDataLen != 0) { + aesGcmCipher.updateAAD(authData, authDataStart, authDataLen); + } + // encrypt the secret + aesGcmCipher.doFinal(encSecret, encSecretStart, encSecretLen, secret, secretStart); + verification = + aesGcmCipher.verifyTag( + authTag, authTagStart, (short) authTagLen, (short) AES_GCM_TAG_LENGTH); + return verification; + } + + public HMACKey cmacKdf( + KMKey preSharedKey, + byte[] label, + short labelStart, + short labelLen, + byte[] context, + short contextStart, + short contextLength) { + // Note: the variables i and L correspond to i and L in the standard. See page 12 of + // http://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-108.pdf. + try { + // This is hardcoded to requirement - 32 byte output with two concatenated + // 16 bytes K1 and K2. + final byte n = 2; // hardcoded + + // [i] counter - 32 bits + short iBufLen = 4; + short keyOutLen = n * 16; + // Convert Hmackey to AES Key as the algorithm is ALG_AES_CMAC_128. + KMHmacKey hmacKey = ((KMHmacKey) preSharedKey); + hmacKey.hmacKey.getKey(tmpArray, (short) 0); + aesKeys[KEYSIZE_256_OFFSET].setKey(tmpArray, (short) 0); + // Initialize the key derivation function. + kdf.init(aesKeys[KEYSIZE_256_OFFSET], Signature.MODE_SIGN); + // Clear the tmpArray buffer. + Util.arrayFillNonAtomic(tmpArray, (short) 0, (short) 256, (byte) 0); + + Util.arrayFillNonAtomic(tmpArray, (short) 0, iBufLen, (byte) 0); + Util.arrayFillNonAtomic(tmpArray, (short) iBufLen, keyOutLen, (byte) 0); + + byte i = 1; + short pos = 0; + while (i <= n) { + tmpArray[3] = i; + // 4 bytes of iBuf with counter in it + kdf.update(tmpArray, (short) 0, (short) iBufLen); + kdf.update(label, labelStart, (short) labelLen); // label + kdf.update( + CMAC_KDF_CONSTANT_ZERO, + (short) 0, + (short) CMAC_KDF_CONSTANT_ZERO.length); // 1 byte of 0x00 + kdf.update(context, contextStart, contextLength); // context + // 4 bytes of L - signature of 16 bytes + pos = + kdf.sign( + CMAC_KDF_CONSTANT_L, + (short) 0, + (short) CMAC_KDF_CONSTANT_L.length, + tmpArray, + (short) (iBufLen + pos)); + i++; + } + return createHMACKey(tmpArray, (short) iBufLen, (short) keyOutLen); + } finally { + clean(); + } + } + + public short hmacSign( + HMACKey key, byte[] data, short dataStart, short dataLength, byte[] mac, short macStart) { + hmacSignature.init(key, Signature.MODE_SIGN); + return hmacSignature.sign(data, dataStart, dataLength, mac, macStart); + } + + @Override + public short hmacSign( + byte[] keyBuf, + short keyStart, + short keyLength, + byte[] data, + short dataStart, + short dataLength, + byte[] mac, + short macStart) { + HMACKey key = createHMACKey(keyBuf, keyStart, keyLength); + return hmacSign(key, data, dataStart, dataLength, mac, macStart); + } + + @Override + public short hmacSign( + Object key, byte[] data, short dataStart, short dataLength, byte[] mac, short macStart) { + if (!(key instanceof KMHmacKey)) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + KMHmacKey hmacKey = (KMHmacKey) key; + return hmacSign(hmacKey.hmacKey, data, dataStart, dataLength, mac, macStart); + } + + @Override + public short hmacKDF( + KMKey masterkey, + byte[] data, + short dataStart, + short dataLength, + byte[] signature, + short signatureStart) { + try { + KMAESKey aesKey = (KMAESKey) masterkey; + short keyLen = (short) (aesKey.aesKey.getSize() / 8); + aesKey.aesKey.getKey(tmpArray, (short) 0); + return hmacSign( + tmpArray, (short) 0, keyLen, data, dataStart, dataLength, signature, signatureStart); + } finally { + clean(); + } + } + + @Override + public boolean hmacVerify( + KMKey key, + byte[] data, + short dataStart, + short dataLength, + byte[] mac, + short macStart, + short macLength) { + KMHmacKey hmacKey = (KMHmacKey) key; + hmacSignature.init(hmacKey.hmacKey, Signature.MODE_VERIFY); + return hmacSignature.verify(data, dataStart, dataLength, mac, macStart, macLength); + } + + @Override + public short rsaDecipherOAEP256( + byte[] secret, + short secretStart, + short secretLength, + byte[] modBuffer, + short modOff, + short modLength, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart) { + RSAPrivateKey key = (RSAPrivateKey) rsaKeyPair.getPrivate(); + key.setExponent(secret, (short) secretStart, (short) secretLength); + key.setModulus(modBuffer, (short) modOff, (short) modLength); + rsaOaepDecipher.init(key, Cipher.MODE_DECRYPT); + return rsaOaepDecipher.doFinal( + inputDataBuf, + (short) inputDataStart, + (short) inputDataLength, + outputDataBuf, + (short) outputDataStart); + } + + private byte mapSignature256Alg(byte alg, byte padding, byte digest) { + switch (alg) { + case KMType.RSA: + switch (padding) { + case KMType.RSA_PKCS1_1_5_SIGN: + { + if (digest == KMType.DIGEST_NONE) { + return KMRsa2048NoDigestSignature.ALG_RSA_PKCS1_NODIGEST; + } else { + return Signature.ALG_RSA_SHA_256_PKCS1; + } + } + case KMType.RSA_PSS: + return Signature.ALG_RSA_SHA_256_PKCS1_PSS; + case KMType.PADDING_NONE: + return KMRsa2048NoDigestSignature.ALG_RSA_SIGN_NOPAD; + } + break; + case KMType.EC: + if (digest == KMType.DIGEST_NONE) { + return KMEcdsa256NoDigestSignature.ALG_ECDSA_NODIGEST; + } else { + return Signature.ALG_ECDSA_SHA_256; + } + case KMType.HMAC: + return Signature.ALG_HMAC_SHA_256; + } + return -1; + } + + private byte mapCipherAlg(byte alg, byte padding, byte blockmode, byte digest) { + switch (alg) { + case KMType.AES: + switch (blockmode) { + case KMType.ECB: + return Cipher.ALG_AES_BLOCK_128_ECB_NOPAD; + case KMType.CBC: + return Cipher.ALG_AES_BLOCK_128_CBC_NOPAD; + case KMType.CTR: + return Cipher.ALG_AES_CTR; + case KMType.GCM: + return AEADCipher.ALG_AES_GCM; + } + break; + case KMType.DES: + switch (blockmode) { + case KMType.ECB: + return Cipher.ALG_DES_ECB_NOPAD; + case KMType.CBC: + return Cipher.ALG_DES_CBC_NOPAD; + } + break; + case KMType.RSA: + switch (padding) { + case KMType.PADDING_NONE: + return Cipher.ALG_RSA_NOPAD; + case KMType.RSA_PKCS1_1_5_ENCRYPT: + return Cipher.ALG_RSA_PKCS1; + case KMType.RSA_OAEP: + { + if (digest == KMType.SHA1) { + /* MGF Digest is SHA1 */ + return KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA1; + } else if (digest == KMType.SHA2_256) { + /* MGF Digest is SHA256 */ + return KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA256; + } else { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + } + } + break; + } + return -1; + } + + public KMOperation createSymmetricCipher( + short alg, + short purpose, + short macLength, + short blockMode, + short padding, + byte[] secret, + short secretStart, + short secretLength, + byte[] ivBuffer, + short ivStart, + short ivLength, + boolean isRkp) { + + short cipherAlg = mapCipherAlg((byte) alg, (byte) padding, (byte) blockMode, (byte) 0); + KMOperation operation = null; + if (isRkp) { + operation = poolMgr.getRKpOperation(purpose, cipherAlg, alg, padding, blockMode, macLength); + } else { + operation = + poolMgr.getOperationImpl( + purpose, cipherAlg, alg, padding, blockMode, macLength, secretLength, false); + } + // Get the KeyObject from the operation and update the key with the secret key material. + KMKeyObject keyObj = operation.getKeyObject(); + Key key = (Key) keyObj.keyObjectInst; + switch (secretLength) { + case 32: + case 16: + ((AESKey) key).setKey(secret, secretStart); + break; + case 24: + ((DESKey) key).setKey(secret, secretStart); + break; + default: + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + break; + } + ((KMOperationImpl) operation).init(key, KMType.INVALID_VALUE, ivBuffer, ivStart, ivLength); + return operation; + } + + public KMOperation createHmacSignerVerifier( + short purpose, + short digest, + byte[] secret, + short secretStart, + short secretLength, + boolean isRkp) { + KMOperation operation = null; + if (digest != KMType.SHA2_256) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + if (isRkp) { + operation = + poolMgr.getRKpOperation( + purpose, + Signature.ALG_HMAC_SHA_256, + KMType.HMAC, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE); + } else { + operation = + poolMgr.getOperationImpl( + purpose, + Signature.ALG_HMAC_SHA_256, + KMType.HMAC, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + (short) 0, + false); + } + // Get the KeyObject from the operation and update the key with the secret key material. + KMKeyObject keyObj = operation.getKeyObject(); + HMACKey key = (HMACKey) keyObj.keyObjectInst; + key.setKey(secret, secretStart, secretLength); + ((KMOperationImpl) operation).init(key, digest, null, (short) 0, (short) 0); + return operation; + } + + private KMOperation createHmacSignerVerifier( + short purpose, short digest, HMACKey hmacKey, boolean isTrustedConf) { + if (digest != KMType.SHA2_256) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + KMOperation operation = + poolMgr.getOperationImpl( + purpose, + Signature.ALG_HMAC_SHA_256, + KMType.HMAC, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + (short) 0, + isTrustedConf); + // Get the KeyObject from the operation and update the key with the secret key material. + KMKeyObject keyObj = operation.getKeyObject(); + HMACKey key = (HMACKey) keyObj.keyObjectInst; + short len = hmacKey.getKey(tmpArray, (short) 0); + key.setKey(tmpArray, (short) 0, len); + ((KMOperationImpl) operation).init(key, digest, null, (short) 0, (short) 0); + return operation; + } + + @Override + public KMOperation getRkpOperation( + byte purpose, + byte alg, + byte digest, + byte padding, + byte blockMode, + KMKey keyPair, + byte[] ivBuf, + short ivStart, + short ivLength, + short macLength) { + KMOperation opr = null; + switch (alg) { + case KMType.EC: + // get EC private key buffer + ECPrivateKey ecPrivKey = + (ECPrivateKey) ((KMECDeviceUniqueKeyPair) keyPair).ecKeyPair.getPrivate(); + short ecPrivKeyLen = ecPrivKey.getS(tmpArray, (short) 0); + opr = createEcSigner(digest, tmpArray, (short) 0, ecPrivKeyLen, true /* isRKP */); + break; + default: + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + return opr; + } + + @Override + public KMOperation initSymmetricOperation( + byte purpose, + byte alg, + byte digest, + byte padding, + byte blockMode, + byte[] keyBuf, + short keyStart, + short keyLength, + byte[] ivBuf, + short ivStart, + short ivLength, + short macLength) { + KMOperation opr = null; + switch (alg) { + case KMType.AES: + case KMType.DES: + // Convert macLength to bytes + macLength = (short) (macLength / 8); + opr = + createSymmetricCipher( + alg, + purpose, + macLength, + blockMode, + padding, + keyBuf, + keyStart, + keyLength, + ivBuf, + ivStart, + ivLength, + false /* isRKP */); + break; + case KMType.HMAC: + opr = + createHmacSignerVerifier( + purpose, digest, keyBuf, keyStart, keyLength, false /* isRKP */); + break; + default: + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + return opr; + } + + @Override + public KMOperation initSymmetricOperation( + byte purpose, + byte alg, + byte digest, + byte padding, + byte blockMode, + Object key, + byte interfaceType, + byte[] ivBuf, + short ivStart, + short ivLength, + short macLength, + boolean oneShot) { + short keyLen = 0; + globalOperation.setPurpose(purpose); + globalOperation.setAlgorithmType(alg); + globalOperation.setPaddingAlgorithm(padding); + globalOperation.setBlockMode(blockMode); + try { + switch (interfaceType) { + case KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY: + KMAESKey aesKey = (KMAESKey) key; + keyLen = (short) (aesKey.aesKey.getSize() / 8); + aesKey.aesKey.getKey(tmpArray, (short) 0); + break; + + default: + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + + switch (alg) { + case KMType.HMAC: + HMACKey hmackey = createHMACKey(tmpArray, (short) 0, keyLen); + globalOperation.setSignature(hmacSignature); + globalOperation.init(hmackey, digest, null, (short) 0, (short) 0); + break; + + default: + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + } finally { + clean(); + } + return globalOperation; + } + + @Override + public KMOperation initTrustedConfirmationSymmetricOperation(KMKey computedHmacKey) { + KMHmacKey key = (KMHmacKey) computedHmacKey; + return createHmacSignerVerifier(KMType.VERIFY, KMType.SHA2_256, key.hmacKey, true); + } + + public KMOperation createRsaSigner( + short digest, + short padding, + byte[] secret, + short secretStart, + short secretLength, + byte[] modBuffer, + short modOff, + short modLength) { + byte alg = mapSignature256Alg(KMType.RSA, (byte) padding, (byte) digest); + KMOperation operation = + poolMgr.getOperationImpl( + KMType.SIGN, + alg, + KMType.RSA, + padding, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + secretLength, + false); + // Get the KeyObject from the operation and update the key with the secret key material. + KMKeyObject keyObj = operation.getKeyObject(); + RSAPrivateKey key = (RSAPrivateKey) ((KeyPair) (keyObj.keyObjectInst)).getPrivate(); + key.setExponent(secret, secretStart, secretLength); + key.setModulus(modBuffer, modOff, modLength); + ((KMOperationImpl) operation).init(key, digest, null, (short) 0, (short) 0); + return operation; + } + + public KMOperation createRsaDecipher( + short padding, + short mgfDigest, + byte[] secret, + short secretStart, + short secretLength, + byte[] modBuffer, + short modOff, + short modLength) { + byte cipherAlg = mapCipherAlg(KMType.RSA, (byte) padding, (byte) 0, (byte) mgfDigest); + KMOperation operation = + poolMgr.getOperationImpl( + KMType.DECRYPT, + cipherAlg, + KMType.RSA, + padding, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + secretLength, + false); + // Get the KeyObject from the operation and update the key with the secret key material. + KMKeyObject keyObj = operation.getKeyObject(); + RSAPrivateKey key = (RSAPrivateKey) ((KeyPair) (keyObj.keyObjectInst)).getPrivate(); + key.setExponent(secret, secretStart, secretLength); + key.setModulus(modBuffer, modOff, modLength); + ((KMOperationImpl) operation).init(key, KMType.INVALID_VALUE, null, (short) 0, (short) 0); + return operation; + } + + public KMOperation createEcSigner( + short digest, byte[] secret, short secretStart, short secretLength, boolean isRkp) { + KMOperation operation = null; + byte alg = mapSignature256Alg(KMType.EC, (byte) 0, (byte) digest); + if (isRkp) { + operation = + poolMgr.getRKpOperation( + KMType.SIGN, + Signature.ALG_ECDSA_SHA_256, + KMType.EC, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE); + } else { + operation = + poolMgr.getOperationImpl( + KMType.SIGN, + alg, + KMType.EC, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + secretLength, + false); + } + KMKeyObject keyObj = operation.getKeyObject(); + ECPrivateKey key = (ECPrivateKey) ((KeyPair) (keyObj.keyObjectInst)).getPrivate(); + key.setS(secret, secretStart, secretLength); + ((KMOperationImpl) operation).init(key, digest, null, (short) 0, (short) 0); + return operation; + } + + public KMOperation createKeyAgreement(byte[] secret, short secretStart, short secretLength) { + KMOperation operation = + poolMgr.getOperationImpl( + KMType.AGREE_KEY, + KeyAgreement.ALG_EC_SVDP_DH_PLAIN, + KMType.EC, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + (short) 0, + false); + KMKeyObject keyObj = operation.getKeyObject(); + ECPrivateKey key = (ECPrivateKey) ((KeyPair) (keyObj.keyObjectInst)).getPrivate(); + key.setS(secret, secretStart, secretLength); + ((KMOperationImpl) operation).init(key, KMType.INVALID_VALUE, null, (short) 0, (short) 0); + return operation; + } + + @Override + public KMOperation initAsymmetricOperation( + byte purpose, + byte alg, + byte padding, + byte digest, + byte mgfDigest, + byte[] privKeyBuf, + short privKeyStart, + short privKeyLength, + byte[] pubModBuf, + short pubModStart, + short pubModLength) { + KMOperation opr = null; + if (alg == KMType.RSA) { + switch (purpose) { + case KMType.SIGN: + opr = + createRsaSigner( + digest, + padding, + privKeyBuf, + privKeyStart, + privKeyLength, + pubModBuf, + pubModStart, + pubModLength); + break; + case KMType.DECRYPT: + opr = + createRsaDecipher( + padding, + mgfDigest, + privKeyBuf, + privKeyStart, + privKeyLength, + pubModBuf, + pubModStart, + pubModLength); + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + break; + } + } else if (alg == KMType.EC) { + switch (purpose) { + case KMType.SIGN: + opr = createEcSigner(digest, privKeyBuf, privKeyStart, privKeyLength, false); + break; + + case KMType.AGREE_KEY: + opr = createKeyAgreement(privKeyBuf, privKeyStart, privKeyLength); + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + break; + } + } else { + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + } + return opr; + } + + @Override + public short cmacKDF( + KMKey pSharedKey, + byte[] label, + short labelStart, + short labelLen, + byte[] context, + short contextStart, + short contextLength, + byte[] keyBuf, + short keyStart) { + HMACKey key = + cmacKdf(pSharedKey, label, labelStart, labelLen, context, contextStart, contextLength); + return key.getKey(keyBuf, keyStart); + } + + @Override + public boolean isUpgrading() { + return UpgradeManager.isUpgrading(); + } + + @Override + public KMKey createMasterKey(KMKey masterKey, short keySizeBits) { + try { + if (masterKey == null) { + AESKey key = (AESKey) KeyBuilder.buildKey(KeyBuilder.TYPE_AES, keySizeBits, false); + masterKey = new KMAESKey(key); + } + short keyLen = (short) (keySizeBits / 8); + Util.arrayFillNonAtomic(tmpArray, (short) 0, keyLen, (byte) 0); + getTrueRandomNumber(tmpArray, (short) 0, keyLen); + ((KMAESKey) masterKey).aesKey.setKey(tmpArray, (short) 0); + return (KMKey) masterKey; + } finally { + clean(); + } + } + + @Override + public KMKey createPreSharedKey(KMKey preSharedKey, byte[] keyData, short offset, short length) { + short lengthInBits = (short) (length * 8); + if ((lengthInBits % 8 != 0) || !(lengthInBits >= 64 && lengthInBits <= 512)) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + if (preSharedKey == null) { + HMACKey key = (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC, lengthInBits, false); + preSharedKey = new KMHmacKey(key); + } + ((KMHmacKey) preSharedKey).hmacKey.setKey(keyData, offset, length); + return (KMKey) preSharedKey; + } + + @Override + public KMKey createComputedHmacKey( + KMKey computedHmacKey, byte[] keyData, short offset, short length) { + if (length != COMPUTED_HMAC_KEY_SIZE) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + if (computedHmacKey == null) { + HMACKey key = + (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC, (short) (length * 8), false); + computedHmacKey = new KMHmacKey(key); + } + ((KMHmacKey) computedHmacKey).hmacKey.setKey(keyData, offset, length); + return (KMKey) computedHmacKey; + } + + @Override + public short ecSign256( + byte[] secret, + short secretStart, + short secretLength, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart) { + + ECPrivateKey key = (ECPrivateKey) ecKeyPair.getPrivate(); + key.setS(secret, secretStart, secretLength); + + Signature.OneShot signer = null; + try { + + signer = + Signature.OneShot.open( + MessageDigest.ALG_SHA_256, Signature.SIG_CIPHER_ECDSA, Cipher.PAD_NULL); + signer.init(key, Signature.MODE_SIGN); + return signer.sign( + inputDataBuf, inputDataStart, inputDataLength, outputDataBuf, outputDataStart); + } finally { + if (signer != null) { + signer.close(); + } + } + } + + @Override + public short rsaSign256Pkcs1( + byte[] secret, + short secretStart, + short secretLength, + byte[] modBuf, + short modStart, + short modLength, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart) { + + Signature.OneShot signer = null; + try { + + signer = + Signature.OneShot.open( + MessageDigest.ALG_SHA_256, Signature.SIG_CIPHER_RSA, Cipher.PAD_PKCS1); + + RSAPrivateKey key = (RSAPrivateKey) rsaKeyPair.getPrivate(); + ; + key.setExponent(secret, secretStart, secretLength); + key.setModulus(modBuf, modStart, modLength); + + signer.init(key, Signature.MODE_SIGN); + return signer.sign( + inputDataBuf, inputDataStart, inputDataLength, outputDataBuf, outputDataStart); + } finally { + if (signer != null) { + signer.close(); + } + } + } + + @Override + public boolean isAttestationKeyProvisioned() { + return false; + } + + @Override + public short getAttestationKeyAlgorithm() { + return KMType.INVALID_VALUE; + } + + @Override + public short hkdf( + byte[] ikm, + short ikmOff, + short ikmLen, + byte[] salt, + short saltOff, + short saltLen, + byte[] info, + short infoOff, + short infoLen, + byte[] out, + short outOff, + short outLen) { + // HMAC_extract + hkdfExtract(ikm, ikmOff, ikmLen, salt, saltOff, saltLen, tmpArray, (short) 0); + // HMAC_expand + return hkdfExpand(tmpArray, (short) 0, (short) 32, info, infoOff, infoLen, out, outOff, outLen); + } + + private short hkdfExtract( + byte[] ikm, + short ikmOff, + short ikmLen, + byte[] salt, + short saltOff, + short saltLen, + byte[] out, + short off) { + // https://tools.ietf.org/html/rfc5869#section-2.2 + HMACKey hmacKey = createHMACKey(salt, saltOff, saltLen); + hmacSignature.init(hmacKey, Signature.MODE_SIGN); + return hmacSignature.sign(ikm, ikmOff, ikmLen, out, off); + } + + private short hkdfExpand( + byte[] prk, + short prkOff, + short prkLen, + byte[] info, + short infoOff, + short infoLen, + byte[] out, + short outOff, + short outLen) { + // https://tools.ietf.org/html/rfc5869#section-2.3 + short digestLen = (short) 32; // SHA256 digest length. + // Calculate no of iterations N. + short n = (short) ((short) (outLen + digestLen - 1) / digestLen); + if (n > 255) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + HMACKey hmacKey = createHMACKey(prk, prkOff, prkLen); + Util.arrayFill(tmpArray, (short) 0, (short) 33, (byte) 0); + short bytesCopied = 0; + short len = 0; + for (short i = 0; i < n; i++) { + tmpArray[0]++; + hmacSignature.init(hmacKey, Signature.MODE_SIGN); + if (i != 0) { + hmacSignature.update(tmpArray, (short) 1, (short) 32); + } + hmacSignature.update(info, infoOff, infoLen); + len = hmacSignature.sign(tmpArray, (short) 0, (short) 1, tmpArray, (short) 1); + if ((short) (bytesCopied + len) > outLen) { + len = (short) (outLen - bytesCopied); + } + Util.arrayCopyNonAtomic(tmpArray, (short) 1, out, (short) (outOff + bytesCopied), len); + bytesCopied += len; + } + return outLen; + } + + @Override + public short ecdhKeyAgreement( + byte[] privKey, + short privKeyOff, + short privKeyLen, + byte[] publicKey, + short publicKeyOff, + short publicKeyLen, + byte[] secret, + short secretOff) { + keyAgreement.init(createEcKey(privKey, privKeyOff, privKeyLen)); + return keyAgreement.generateSecret(publicKey, publicKeyOff, publicKeyLen, secret, secretOff); + } + + @Override + public boolean ecVerify256( + byte[] pubKey, + short pubKeyOffset, + short pubKeyLen, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] signatureDataBuf, + short signatureDataStart, + short signatureDataLen) { + Signature.OneShot signer = null; + try { + signer = + Signature.OneShot.open( + MessageDigest.ALG_SHA_256, Signature.SIG_CIPHER_ECDSA, Cipher.PAD_NULL); + ECPublicKey key = (ECPublicKey) ecKeyPair.getPublic(); + key.setW(pubKey, pubKeyOffset, pubKeyLen); + signer.init(key, Signature.MODE_VERIFY); + return signer.verify( + inputDataBuf, + inputDataStart, + inputDataLength, + signatureDataBuf, + signatureDataStart, + (short) (signatureDataBuf[(short) (signatureDataStart + 1)] + 2)); + } finally { + if (signer != null) { + signer.close(); + } + } + } + + @Override + public short signWithDeviceUniqueKey( + KMKey ecPrivKey, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart) { + Signature.OneShot signer = null; + try { + signer = + Signature.OneShot.open( + MessageDigest.ALG_SHA_256, Signature.SIG_CIPHER_ECDSA, Cipher.PAD_NULL); + signer.init( + ((KMECDeviceUniqueKeyPair) ecPrivKey).ecKeyPair.getPrivate(), Signature.MODE_SIGN); + return signer.sign( + inputDataBuf, inputDataStart, inputDataLength, outputDataBuf, outputDataStart); + } finally { + if (signer != null) { + signer.close(); + } + } + } + + @Override + public KMKey createRkpDeviceUniqueKeyPair( + KMKey key, + byte[] pubKey, + short pubKeyOff, + short pubKeyLen, + byte[] privKey, + short privKeyOff, + short privKeyLen) { + if (key == null) { + KeyPair ecKeyPair = new KeyPair(KeyPair.ALG_EC_FP, KeyBuilder.LENGTH_EC_FP_256); + poolMgr.initECKey(ecKeyPair); + key = new KMECDeviceUniqueKeyPair(ecKeyPair); + } + ECPrivateKey ecKeyPair = (ECPrivateKey) ((KMECDeviceUniqueKeyPair) key).ecKeyPair.getPrivate(); + ECPublicKey ecPublicKey = (ECPublicKey) ((KMECDeviceUniqueKeyPair) key).ecKeyPair.getPublic(); + ecKeyPair.setS(privKey, privKeyOff, privKeyLen); + ecPublicKey.setW(pubKey, pubKeyOff, pubKeyLen); + return (KMKey) key; + } + + @Override + public KMKey createRkpMacKey(KMKey rkpMacKey, byte[] keyData, short offset, short length) { + if (rkpMacKey == null) { + HMACKey key = + (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC, (short) (length * 8), false); + rkpMacKey = new KMHmacKey(key); + } + ((KMHmacKey) rkpMacKey).hmacKey.setKey(keyData, offset, length); + return rkpMacKey; + } + + @Override + public short messageDigest256( + byte[] inBuff, short inOffset, short inLength, byte[] outBuff, short outOffset) { + MessageDigest.OneShot mDigest = null; + short len = 0; + try { + mDigest = MessageDigest.OneShot.open(MessageDigest.ALG_SHA_256); + len = mDigest.doFinal(inBuff, inOffset, inLength, outBuff, outOffset); + } finally { + if (mDigest != null) { + mDigest.close(); + mDigest = null; + } + } + return len; + } + + public boolean isPowerReset() { + boolean flag = false; + if (resetFlag[0] == POWER_RESET_TRUE) { + resetFlag[0] = POWER_RESET_FALSE; + flag = true; + if (poolMgr != null) { + poolMgr.powerReset(); + } + } + return flag; + } + + @Override + public void onSave(Element element, byte interfaceType, Object object) { + element.write(interfaceType); + if (object == null) { + element.write(null); + return; + } + switch (interfaceType) { + case KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY: + KMAESKey.onSave(element, (KMAESKey) object); + break; + case KMDataStoreConstants.INTERFACE_TYPE_PRE_SHARED_KEY: + KMHmacKey.onSave(element, (KMHmacKey) object); + break; + case KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR: + KMECDeviceUniqueKeyPair.onSave(element, (KMECDeviceUniqueKeyPair) object); + break; + case KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY: + KMHmacKey.onSave(element, (KMHmacKey) object); + break; + default: + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + } + + @Override + public Object onRestore(Element element) { + if (element == null) { + return null; + } + byte interfaceType = element.readByte(); + switch (interfaceType) { + case KMDataStoreConstants.INTERFACE_TYPE_COMPUTED_HMAC_KEY: + return KMHmacKey.onRestore((HMACKey) element.readObject()); + case KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY: + return KMAESKey.onRestore((AESKey) element.readObject()); + case KMDataStoreConstants.INTERFACE_TYPE_PRE_SHARED_KEY: + return KMHmacKey.onRestore((HMACKey) element.readObject()); + case KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR: + return KMECDeviceUniqueKeyPair.onRestore((KeyPair) element.readObject()); + case KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY: + return KMHmacKey.onRestore((HMACKey) element.readObject()); + default: + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return null; + } + + @Override + public short getBackupPrimitiveByteCount(byte interfaceType) { + short primitiveCount = 1; // interface type + switch (interfaceType) { + case KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY: + primitiveCount += KMAESKey.getBackupPrimitiveByteCount(); + break; + case KMDataStoreConstants.INTERFACE_TYPE_PRE_SHARED_KEY: + primitiveCount += KMHmacKey.getBackupPrimitiveByteCount(); + break; + case KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR: + primitiveCount += KMECDeviceUniqueKeyPair.getBackupPrimitiveByteCount(); + break; + case KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY: + primitiveCount += KMHmacKey.getBackupPrimitiveByteCount(); + break; + default: + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return primitiveCount; + } + + @Override + public short getBackupObjectCount(byte interfaceType) { + switch (interfaceType) { + case KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY: + return KMAESKey.getBackupObjectCount(); + case KMDataStoreConstants.INTERFACE_TYPE_PRE_SHARED_KEY: + return KMHmacKey.getBackupObjectCount(); + case KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR: + return KMECDeviceUniqueKeyPair.getBackupObjectCount(); + case KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY: + return KMHmacKey.getBackupObjectCount(); + default: + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return 0; + } + + @Override + public boolean isBootSignalEventSupported() { + return false; + } + + @Override + public boolean isDeviceRebooted() { + return false; + } + + @Override + public void clearDeviceBooted(boolean resetBootFlag) { + // To be filled + } +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAttestationCert.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAttestationCert.java new file mode 100644 index 0000000..05801b0 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMAttestationCert.java @@ -0,0 +1,198 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +/** + * The KMAttestationCert interface represents a X509 compliant attestation certificate required to + * support keymaster's attestKey function. This cert will be created according to the specifications + * given in android keymaster hal documentation. KMSeProvider has to provide the instance of this + * certificate. This interface is designed based on builder pattern and hence each method returns + * instance of cert. + */ +public interface KMAttestationCert { + + /** + * Set verified boot hash. + * + * @param obj This is a KMByteBlob containing hash + * @return instance of KMAttestationCert + */ + KMAttestationCert verifiedBootHash(short obj); + + /** + * Set verified boot key received during booting up. + * + * @param obj This is a KMByteBlob containing verified boot key. + * @return instance of KMAttestationCert + */ + KMAttestationCert verifiedBootKey(short obj); + + /** + * Set verified boot state received during booting up. + * + * @param val This is a byte containing verified boot state value. + * @return instance of KMAttestationCert + */ + KMAttestationCert verifiedBootState(byte val); + + /** + * Set uniqueId received from CA certificate during provisioning. + * + * @param scratchpad Buffer to store intermediate results. + * @param scratchPadOff Start offset of the scratchpad buffer. + * @param creationTime This buffer contains the CREATION_TIME value. + * @param creationTimeOff Start offset of creattionTime buffer. + * @param creationTimeLen Length of the creationTime buffer. + * @param attestAppId This buffer contains the ATTESTATION_APPLICATION_ID value. + * @param attestAppIdOff Start offset of the attestAppId buffer. + * @param attestAppIdLen Length of the attestAppId buffer. + * @param resetSinceIdRotation This holds the information of RESET_SINCE_ID_ROTATION. + * @param masterKey + * @return instance of KMAttestationCert. + */ + KMAttestationCert makeUniqueId( + byte[] scratchpad, + short scratchPadOff, + byte[] creationTime, + short creationTimeOff, + short creationTimeLen, + byte[] attestAppId, + short attestAppIdOff, + short attestAppIdLen, + byte resetSinceIdRotation, + KMKey masterKey); + + /** + * Set start time received from creation/activation time tag. Used for certificate's valid period. + * + * @param obj This is a KMByteBlob object containing start time. + * @param scratchpad Buffer to store intermediate results. + * @return instance of KMAttestationCert. + */ + KMAttestationCert notBefore(short obj, boolean derEncoded, byte[] scratchpad); + + /** + * Set expiry time received from expiry time tag or ca certificates expiry time. Used for + * certificate's valid period. + * + * @param usageExpiryTimeObj This is a KMByteBlob containing expiry time. certificate. + * @param scratchPad Buffer to store intermediate results. + * @return instance of KMAttestationCert + */ + KMAttestationCert notAfter(short usageExpiryTimeObj, boolean derEncoded, byte[] scratchPad); + + /** + * Set device lock status received during booting time or due to device lock command. + * + * @param val This is true if device is locked. + * @return instance of KMAttestationCert + */ + KMAttestationCert deviceLocked(boolean val); + + /** + * Set public key to be attested received from attestKey command. + * + * @param obj This is KMByteBlob containing the public key. + * @return instance of KMAttestationCert + */ + KMAttestationCert publicKey(short obj); + + /** + * Set attestation challenge received from attestKey command. + * + * @param obj This is KMByteBlob containing the attestation challenge. + * @return instance of KMAttestationCert + */ + KMAttestationCert attestationChallenge(short obj); + + /** + * Set extension tag received from key characteristics which needs to be added to android + * extension. This method will called once for each tag. + * + * @param tag is the KMByteBlob containing KMTag. + * @param hwEnforced is true if the tag has to be added to hw enforced list or else added to sw + * enforced list. + * @return instance of KMAttestationCert + */ + KMAttestationCert extensionTag(short tag, boolean hwEnforced); + + /** + * Set ASN.1 encoded X509 issuer field received from attestation key CA cert. + * + * @param obj This is KMByteBlob containing the issuer. + * @return instance of KMAttestationCert + */ + KMAttestationCert issuer(short obj); + + /** + * Set byte buffer to be used to generate certificate. + * + * @param buf This is byte[] buffer. + * @param bufStart This is short start offset. + * @param maxLen This is short length of the buffer. + * @return instance of KMAttestationCert + */ + KMAttestationCert buffer(byte[] buf, short bufStart, short maxLen); + + /** + * Get the start of the certificate + * + * @return start of the attestation cert. + */ + short getCertStart(); + + /** + * Get the length of the certificate + * + * @return length of the attestation cert. + */ + short getCertLength(); + + /** + * Build a fake signed certificate. After this method executes the certificate is ready with the + * signature equal to 1 byte which is 0 and with rsa signature algorithm. + */ + void build(); + + /** + * Set the Serial number in the certificate. If no serial number is set then serial number is 1. + * + * @param serialNumber + */ + boolean serialNumber(short serialNumber); + + /** + * Set the Subject Name in the certificate. + * + * @param subject + */ + boolean subjectName(short subject); + + /** + * Set attestation key and mode. + * + * @param attestKey KMByteBlob of the key + * @param mode + */ + KMAttestationCert ecAttestKey(short attestKey, byte mode); + /** + * Set attestation key and mode. + * + * @param attestKey KMByteBlob of the key + * @param mode + */ + KMAttestationCert rsaAttestKey(short attestPrivExp, short attestMod, byte mode); +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMDataStoreConstants.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMDataStoreConstants.java new file mode 100644 index 0000000..61ddb36 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMDataStoreConstants.java @@ -0,0 +1,17 @@ +package com.android.javacard.seprovider; + +/** + * This class holds different interface type constants to differentiate between the instances of + * Computed Hmac key, device unique key pair, RKP Mac key, and master key when passed as generic + * objects. These constants are used in upgrade flow to retrieve the size of the object and + * primitive types saved and restored for respective key types. + */ +public class KMDataStoreConstants { + // INTERFACE Types + public static final byte INTERFACE_TYPE_COMPUTED_HMAC_KEY = 0x01; + // 0x02 reserved for INTERFACE_TYPE_ATTESTATION_KEY + public static final byte INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR = 0x03; + public static final byte INTERFACE_TYPE_MASTER_KEY = 0x04; + public static final byte INTERFACE_TYPE_PRE_SHARED_KEY = 0x05; + public static final byte INTERFACE_TYPE_RKP_MAC_KEY = 0x06; +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMECDeviceUniqueKeyPair.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMECDeviceUniqueKeyPair.java new file mode 100644 index 0000000..0e430a3 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMECDeviceUniqueKeyPair.java @@ -0,0 +1,55 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.security.ECPublicKey; +import javacard.security.KeyPair; +import org.globalplatform.upgrade.Element; + +/** This is a wrapper class for KeyPair. */ +public class KMECDeviceUniqueKeyPair implements KMKey { + + public KeyPair ecKeyPair; + + @Override + public short getPublicKey(byte[] buf, short offset) { + ECPublicKey publicKey = (ECPublicKey) ecKeyPair.getPublic(); + return publicKey.getW(buf, offset); + } + + public KMECDeviceUniqueKeyPair(KeyPair ecPair) { + ecKeyPair = ecPair; + } + + public static void onSave(Element element, KMECDeviceUniqueKeyPair kmKey) { + element.write(kmKey.ecKeyPair); + } + + public static KMECDeviceUniqueKeyPair onRestore(KeyPair ecKey) { + if (ecKey == null) { + return null; + } + return new KMECDeviceUniqueKeyPair(ecKey); + } + + public static short getBackupPrimitiveByteCount() { + return (short) 0; + } + + public static short getBackupObjectCount() { + return (short) 1; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMEcdsa256NoDigestSignature.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMEcdsa256NoDigestSignature.java new file mode 100644 index 0000000..83774ab --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMEcdsa256NoDigestSignature.java @@ -0,0 +1,138 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.framework.Util; +import javacard.security.CryptoException; +import javacard.security.Key; +import javacard.security.MessageDigest; +import javacard.security.Signature; +import javacardx.crypto.Cipher; + +/** + * This class provides support for ECDSA_NO_DIGEST signature algorithm. Added this because javacard + * 3.0.5 does not support this + */ +public class KMEcdsa256NoDigestSignature extends Signature { + + public static final byte ALG_ECDSA_NODIGEST = (byte) 0x67; + public static final byte MAX_NO_DIGEST_MSG_LEN = 32; + private byte algorithm; + private Signature inst; + + public KMEcdsa256NoDigestSignature(byte alg) { + algorithm = alg; + // There is no constant for no digest so ALG_ECDSA_SHA_256 is used. However, + // signPreComputedHash is used for signing which is equivalent to no digest sign. + inst = Signature.getInstance(Signature.ALG_ECDSA_SHA_256, false); + } + + @Override + public void init(Key key, byte b) throws CryptoException { + inst.init(key, b); + } + + @Override + public void init(Key key, byte b, byte[] bytes, short i, short i1) throws CryptoException { + inst.init(key, b, bytes, i, i1); + } + + @Override + public void setInitialDigest(byte[] bytes, short i, short i1, byte[] bytes1, short i2, short i3) + throws CryptoException {} + + @Override + public byte getAlgorithm() { + return algorithm; + } + + @Override + public byte getMessageDigestAlgorithm() { + return MessageDigest.ALG_NULL; + } + + @Override + public byte getCipherAlgorithm() { + return 0; + } + + @Override + public byte getPaddingAlgorithm() { + return Cipher.PAD_NULL; + } + + @Override + public short getLength() throws CryptoException { + return inst.getLength(); + } + + @Override + public void update(byte[] message, short msgStart, short messageLength) throws CryptoException { + // HAL accumulates the data and send it at finish operation. + } + + @Override + public short sign(byte[] bytes, short i, short i1, byte[] bytes1, short i2) + throws CryptoException { + try { + if (i1 > MAX_NO_DIGEST_MSG_LEN) { + CryptoException.throwIt(CryptoException.ILLEGAL_USE); + } + // add zeros to the left + if (i1 < MAX_NO_DIGEST_MSG_LEN) { + Util.arrayFillNonAtomic( + KMAndroidSEProvider.getInstance().tmpArray, + (short) 0, + (short) MAX_NO_DIGEST_MSG_LEN, + (byte) 0); + } + Util.arrayCopyNonAtomic( + bytes, + i, + KMAndroidSEProvider.getInstance().tmpArray, + (short) (MAX_NO_DIGEST_MSG_LEN - i1), + i1); + return inst.signPreComputedHash( + KMAndroidSEProvider.getInstance().tmpArray, + (short) 0, + (short) MAX_NO_DIGEST_MSG_LEN, + bytes1, + i2); + } finally { + KMAndroidSEProvider.getInstance().clean(); + } + } + + @Override + public short signPreComputedHash(byte[] bytes, short i, short i1, byte[] bytes1, short i2) + throws CryptoException { + return inst.sign(bytes, i, i1, bytes1, i2); + } + + @Override + public boolean verify(byte[] bytes, short i, short i1, byte[] bytes1, short i2, short i3) + throws CryptoException { + // Verification is handled inside HAL + return false; + } + + @Override + public boolean verifyPreComputedHash( + byte[] bytes, short i, short i1, byte[] bytes1, short i2, short i3) throws CryptoException { + // Verification is handled inside HAL + return false; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMError.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMError.java new file mode 100644 index 0000000..69cb069 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMError.java @@ -0,0 +1,32 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +/** + * KMError includes all the error codes from android keymaster hal specifications. The values are + * positive unlike negative values in keymaster hal. + */ +public class KMError { + + public static final short OK = 0; + public static final short UNSUPPORTED_PURPOSE = 2; + public static final short UNSUPPORTED_ALGORITHM = 4; + public static final short INVALID_INPUT_LENGTH = 21; + public static final short VERIFICATION_FAILED = 30; + public static final short TOO_MANY_OPERATIONS = 31; + public static final short INVALID_ARGUMENT = 38; + public static final short UNKNOWN_ERROR = 1000; +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMException.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMException.java new file mode 100644 index 0000000..79983a2 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMException.java @@ -0,0 +1,46 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.seprovider; + +import javacard.framework.JCSystem; + +/** + * KMException is shared instance of exception used for all exceptions in the applet. It is used to + * throw EMError errors. + */ +public class KMException extends RuntimeException { + + private static short[] reason; + private static KMException exception; + + private KMException() {} + + public static short reason() { + return reason[0]; + } + + public static void throwIt(short e) { + if (reason == null) { + reason = JCSystem.makeTransientShortArray((short) 1, JCSystem.CLEAR_ON_DESELECT); + } + if (exception == null) { + exception = new KMException(); + } + reason[0] = e; + throw exception; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMHmacKey.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMHmacKey.java new file mode 100644 index 0000000..e938a2b --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMHmacKey.java @@ -0,0 +1,53 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.security.HMACKey; +import org.globalplatform.upgrade.Element; + +/** This is a wrapper class for HMACKey. */ +public class KMHmacKey implements KMKey { + + public HMACKey hmacKey; + + public KMHmacKey(HMACKey key) { + hmacKey = key; + } + + public static void onSave(Element element, KMHmacKey kmKey) { + element.write(kmKey.hmacKey); + } + + public static KMHmacKey onRestore(HMACKey hmacKey) { + if (hmacKey == null) { + return null; + } + return new KMHmacKey(hmacKey); + } + + public static short getBackupPrimitiveByteCount() { + return (short) 0; + } + + public static short getBackupObjectCount() { + return (short) 1; + } + + @Override + public short getPublicKey(byte[] buf, short offset) { + return (short) 0; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMKey.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMKey.java new file mode 100644 index 0000000..9894382 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMKey.java @@ -0,0 +1,10 @@ +package com.android.javacard.seprovider; + +/** + * This interface helps to decouple Javacard internal key objects from the keymaster package. Using + * Javacard key objects provides security by providing protection against side channel attacks. + * KMAESKey, KMECDeviceUniqueKey and KMHmacKey implements this interface. + */ +public interface KMKey { + short getPublicKey(byte[] buf, short offset); +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMKeyObject.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMKeyObject.java new file mode 100644 index 0000000..a37da08 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMKeyObject.java @@ -0,0 +1,10 @@ +package com.android.javacard.seprovider; + +/** + * This class holds the KeyObject and its associated algorithm value. Each KMKeyObject is tied to + * one of the crypto operations. + */ +public class KMKeyObject { + public byte algorithm; + public Object keyObjectInst; +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperation.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperation.java new file mode 100644 index 0000000..12e691e --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperation.java @@ -0,0 +1,75 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +/** + * KMOperation represents a persistent operation started by keymaster hal's beginOperation function. + * This operation is persistent i.e. it will be stored in non volatile memory of se card. It will be + * returned back to KMSEProvider for the reuse when the operation is finished. + */ +public interface KMOperation { + + // Used for cipher operations + short update( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart); + + // Used for signature operations + short update(byte[] inputDataBuf, short inputDataStart, short inputDataLength); + + // Used for finishing cipher operations or ecdh keyAgreement. + short finish( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart); + + // Used for finishing signing operations. + short sign( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] signBuf, + short signStart); + + // Used for finishing verifying operations. + boolean verify( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] signBuf, + short signStart, + short signLength); + + // Used for aborting the ongoing operations. + void abort(); + + // Used for AES GCM cipher operation. + void updateAAD(byte[] dataBuf, short dataStart, short dataLength); + + // Used for getting output size before finishing a AES GCM cipher operation. For encryption this + // will + // include the auth tag which is appended at the end of the encrypted data. For decryption this + // will be + // size of the decrypted data only. + short getAESGCMOutputSize(short dataSize, short macLength); + + KMKeyObject getKeyObject(); +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperationImpl.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperationImpl.java new file mode 100644 index 0000000..8059e44 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMOperationImpl.java @@ -0,0 +1,415 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.framework.JCSystem; +import javacard.framework.Util; +import javacard.security.CryptoException; +import javacard.security.Key; +import javacard.security.KeyAgreement; +import javacard.security.PrivateKey; +import javacard.security.Signature; +import javacardx.crypto.AEADCipher; +import javacardx.crypto.Cipher; + +/** + * This class contains the actual implementation of all the crypto operations. It internally uses + * the Javacard crypto library to perform the operations. + */ +public class KMOperationImpl implements KMOperation { + + private static final byte ALG_TYPE_OFFSET = 0x00; + private static final byte PADDING_OFFSET = 0x01; + private static final byte PURPOSE_OFFSET = 0x02; + private static final byte BLOCK_MODE_OFFSET = 0x03; + private static final byte MAC_LENGTH_OFFSET = 0x04; + private final byte[] EMPTY = {}; + // This will hold the length of the buffer stored inside the + // Java Card after the GCM update operation. + private static final byte AES_GCM_UPDATE_LEN_OFFSET = 0x05; + private static final byte PARAMETERS_LENGTH = 6; + private short[] parameters; + // Either one of Cipher/Signature instance is stored. + private Object[] operationInst; + + public KMOperationImpl() { + parameters = JCSystem.makeTransientShortArray(PARAMETERS_LENGTH, JCSystem.CLEAR_ON_RESET); + operationInst = JCSystem.makeTransientObjectArray((short) 2, JCSystem.CLEAR_ON_RESET); + reset(); + } + + public short getPurpose() { + return parameters[PURPOSE_OFFSET]; + } + + public void setPurpose(short mode) { + parameters[PURPOSE_OFFSET] = mode; + } + + public short getMacLength() { + return parameters[MAC_LENGTH_OFFSET]; + } + + public void setMacLength(short macLength) { + parameters[MAC_LENGTH_OFFSET] = macLength; + } + + public short getPaddingAlgorithm() { + return parameters[PADDING_OFFSET]; + } + + public void setPaddingAlgorithm(short alg) { + parameters[PADDING_OFFSET] = alg; + } + + public void setBlockMode(short mode) { + parameters[BLOCK_MODE_OFFSET] = mode; + } + + public short getBlockMode() { + return parameters[BLOCK_MODE_OFFSET]; + } + + public short getAlgorithmType() { + return parameters[ALG_TYPE_OFFSET]; + } + + public void setAlgorithmType(short cipherAlg) { + parameters[ALG_TYPE_OFFSET] = cipherAlg; + } + + public void setCipher(Cipher cipher) { + operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO] = cipher; + } + + public void setSignature(Signature signer) { + operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO] = signer; + } + + public void setKeyAgreement(KeyAgreement keyAgreement) { + operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO] = keyAgreement; + } + + public boolean isResourceMatches(Object object, byte resourceType) { + return operationInst[resourceType] == object; + } + + public void setKeyObject(KMKeyObject keyObject) { + operationInst[KMPoolManager.RESOURCE_TYPE_KEY] = keyObject; + } + + public KMKeyObject getKeyObject() { + return (KMKeyObject) operationInst[KMPoolManager.RESOURCE_TYPE_KEY]; + } + + private void reset() { + operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO] = null; + operationInst[KMPoolManager.RESOURCE_TYPE_KEY] = null; + parameters[MAC_LENGTH_OFFSET] = KMType.INVALID_VALUE; + parameters[AES_GCM_UPDATE_LEN_OFFSET] = 0; + parameters[BLOCK_MODE_OFFSET] = KMType.INVALID_VALUE; + parameters[PURPOSE_OFFSET] = KMType.INVALID_VALUE; + parameters[ALG_TYPE_OFFSET] = KMType.INVALID_VALUE; + parameters[PADDING_OFFSET] = KMType.INVALID_VALUE; + } + + private byte mapPurpose(short purpose) { + switch (purpose) { + case KMType.ENCRYPT: + return Cipher.MODE_ENCRYPT; + case KMType.DECRYPT: + return Cipher.MODE_DECRYPT; + case KMType.SIGN: + return Signature.MODE_SIGN; + case KMType.VERIFY: + return Signature.MODE_VERIFY; + } + return -1; + } + + private void initSymmetricCipher(Key key, byte[] ivBuffer, short ivStart, short ivLength) { + Cipher symmCipher = (Cipher) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]; + byte cipherAlg = symmCipher.getAlgorithm(); + switch (cipherAlg) { + case Cipher.ALG_AES_BLOCK_128_CBC_NOPAD: + case Cipher.ALG_AES_CTR: + symmCipher.init(key, mapPurpose(getPurpose()), ivBuffer, ivStart, ivLength); + break; + case Cipher.ALG_AES_BLOCK_128_ECB_NOPAD: + case Cipher.ALG_DES_ECB_NOPAD: + symmCipher.init(key, mapPurpose(getPurpose())); + break; + case Cipher.ALG_DES_CBC_NOPAD: + // Consume only 8 bytes of iv. the random number for iv is of 16 bytes. + // While sending back the iv, send only 8 bytes. + symmCipher.init(key, mapPurpose(getPurpose()), ivBuffer, ivStart, (short) 8); + break; + case AEADCipher.ALG_AES_GCM: + ((AEADCipher) symmCipher).init(key, mapPurpose(getPurpose()), ivBuffer, ivStart, ivLength); + break; + default: // This should never happen + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + } + + private void initRsa(Key key, short digest) { + if (KMType.SIGN == getPurpose()) { + byte mode; + if (getPaddingAlgorithm() == KMType.PADDING_NONE + || (getPaddingAlgorithm() == KMType.RSA_PKCS1_1_5_SIGN && digest == KMType.DIGEST_NONE)) { + mode = Cipher.MODE_DECRYPT; + } else { + mode = Signature.MODE_SIGN; + } + ((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]).init((PrivateKey) key, mode); + } else { // RSA Cipher + ((Cipher) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .init((PrivateKey) key, mapPurpose(getPurpose())); + } + } + + private void initEc(Key key) { + if (KMType.AGREE_KEY == getPurpose()) { + ((KeyAgreement) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]).init((PrivateKey) key); + } else { + ((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .init((PrivateKey) key, mapPurpose(getPurpose())); + } + } + + public void init(Key key, short digest, byte[] buf, short start, short length) { + switch (getAlgorithmType()) { + case KMType.AES: + case KMType.DES: + initSymmetricCipher(key, buf, start, length); + break; + case KMType.HMAC: + ((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .init(key, mapPurpose(getPurpose())); + break; + case KMType.RSA: + initRsa(key, digest); + break; + case KMType.EC: + initEc(key); + break; + default: // This should never happen + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + break; + } + } + + @Override + public short update( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart) { + short len = + ((Cipher) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .update(inputDataBuf, inputDataStart, inputDataLength, outputDataBuf, outputDataStart); + if (parameters[ALG_TYPE_OFFSET] == KMType.AES && parameters[BLOCK_MODE_OFFSET] == KMType.GCM) { + // Every time Block size data is stored as intermediate result. + parameters[AES_GCM_UPDATE_LEN_OFFSET] += (short) (inputDataLength - len); + } + return len; + } + + @Override + public short update(byte[] inputDataBuf, short inputDataStart, short inputDataLength) { + ((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .update(inputDataBuf, inputDataStart, inputDataLength); + return 0; + } + + private short finishKeyAgreement( + byte[] publicKey, short start, short len, byte[] output, short outputStart) { + return ((KeyAgreement) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .generateSecret(publicKey, start, len, output, outputStart); + } + + private short finishCipher( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLen, + byte[] outputDataBuf, + short outputDataStart) { + short len = 0; + try { + byte[] tmpArray = KMAndroidSEProvider.getInstance().tmpArray; + Cipher cipher = (Cipher) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]; + short cipherAlg = parameters[ALG_TYPE_OFFSET]; + short blockMode = parameters[BLOCK_MODE_OFFSET]; + short mode = parameters[PURPOSE_OFFSET]; + short macLength = parameters[MAC_LENGTH_OFFSET]; + short padding = parameters[PADDING_OFFSET]; + + if (cipherAlg == KMType.AES && blockMode == KMType.GCM) { + if (mode == KMType.DECRYPT) { + inputDataLen = (short) (inputDataLen - macLength); + } + } else if ((cipherAlg == KMType.DES || cipherAlg == KMType.AES) + && padding == KMType.PKCS7 + && mode == KMType.ENCRYPT) { + byte blkSize = 16; + byte paddingBytes; + short inputlen = inputDataLen; + if (cipherAlg == KMType.DES) { + blkSize = 8; + } + // padding bytes + if (inputlen % blkSize == 0) { + paddingBytes = blkSize; + } else { + paddingBytes = (byte) (blkSize - (inputlen % blkSize)); + } + // final len with padding + inputlen = (short) (inputlen + paddingBytes); + // intermediate buffer to copy input data+padding + // fill in the padding + Util.arrayFillNonAtomic(tmpArray, (short) 0, inputlen, paddingBytes); + // copy the input data + Util.arrayCopyNonAtomic(inputDataBuf, inputDataStart, tmpArray, (short) 0, inputDataLen); + inputDataBuf = tmpArray; + inputDataLen = inputlen; + inputDataStart = 0; + } + len = + cipher.doFinal( + inputDataBuf, inputDataStart, inputDataLen, outputDataBuf, outputDataStart); + if ((cipherAlg == KMType.AES || cipherAlg == KMType.DES) + && padding == KMType.PKCS7 + && mode == KMType.DECRYPT) { + byte blkSize = 16; + if (cipherAlg == KMType.DES) { + blkSize = 8; + } + if (len > 0) { + // verify if padding is corrupted. + byte paddingByte = outputDataBuf[(short) (outputDataStart + len - 1)]; + // padding byte always should be <= block size + if ((short) paddingByte > blkSize || (short) paddingByte <= 0) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + + for (short j = 1; j <= paddingByte; ++j) { + if (outputDataBuf[(short) (outputDataStart + len - j)] != paddingByte) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + } + len = (short) (len - (short) paddingByte); // remove the padding bytes + } + } else if (cipherAlg == KMType.AES && blockMode == KMType.GCM) { + if (mode == KMType.ENCRYPT) { + len += + ((AEADCipher) cipher) + .retrieveTag(outputDataBuf, (short) (outputDataStart + len), macLength); + } else { + boolean verified = + ((AEADCipher) cipher) + .verifyTag( + inputDataBuf, (short) (inputDataStart + inputDataLen), macLength, macLength); + if (!verified) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + } + } + } finally { + KMAndroidSEProvider.getInstance().clean(); + } + return len; + } + + @Override + public short finish( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLen, + byte[] outputDataBuf, + short outputDataStart) { + if (parameters[PURPOSE_OFFSET] == KMType.AGREE_KEY) { + return finishKeyAgreement( + inputDataBuf, inputDataStart, inputDataLen, outputDataBuf, outputDataStart); + } else { + return finishCipher( + inputDataBuf, inputDataStart, inputDataLen, outputDataBuf, outputDataStart); + } + } + + @Override + public short sign( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] signBuf, + short signStart) { + return ((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .sign(inputDataBuf, inputDataStart, inputDataLength, signBuf, signStart); + } + + @Override + public boolean verify( + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] signBuf, + short signStart, + short signLength) { + return ((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .verify(inputDataBuf, inputDataStart, inputDataLength, signBuf, signStart, signLength); + } + + @Override + public void abort() { + // Few simulators does not reset the Hmac signer instance on init so as + // a workaround to reset the hmac signer instance in case of abort/failure of the operation + // the corresponding sign / verify function is called. + if (operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO] != null) { + if ((parameters[PURPOSE_OFFSET] == KMType.SIGN || parameters[PURPOSE_OFFSET] == KMType.VERIFY) + && (((Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]).getAlgorithm() + == Signature.ALG_HMAC_SHA_256)) { + Signature signer = (Signature) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]; + try { + if (parameters[PURPOSE_OFFSET] == KMType.SIGN) { + signer.sign(EMPTY, (short) 0, (short) 0, EMPTY, (short) 0); + } else { + signer.verify(EMPTY, (short) 0, (short) 0, EMPTY, (short) 0, (short) 0); + } + } catch (Exception e) { + // Ignore. + } + } + } + reset(); + } + + @Override + public void updateAAD(byte[] dataBuf, short dataStart, short dataLength) { + ((AEADCipher) operationInst[KMPoolManager.RESOURCE_TYPE_CRYPTO]) + .updateAAD(dataBuf, dataStart, dataLength); + } + + @Override + public short getAESGCMOutputSize(short dataSize, short macLength) { + if (parameters[PURPOSE_OFFSET] == KMType.ENCRYPT) { + return (short) (parameters[AES_GCM_UPDATE_LEN_OFFSET] + dataSize + macLength); + } else { + return (short) (parameters[AES_GCM_UPDATE_LEN_OFFSET] + dataSize - macLength); + } + } +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMPoolManager.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMPoolManager.java new file mode 100644 index 0000000..9bc258c --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMPoolManager.java @@ -0,0 +1,657 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.framework.JCSystem; +import javacard.security.AESKey; +import javacard.security.CryptoException; +import javacard.security.DESKey; +import javacard.security.ECPrivateKey; +import javacard.security.ECPublicKey; +import javacard.security.HMACKey; +import javacard.security.KeyAgreement; +import javacard.security.KeyBuilder; +import javacard.security.KeyPair; +import javacard.security.Signature; +import javacardx.crypto.AEADCipher; +import javacardx.crypto.Cipher; + +/** + * This class creates and manages all the cipher, signer, key agreement, operation and trusted + * confirmation pool instances. Each cipher or signer pool can hold a maximum of 4 instances per + * algorithm; however, only one instance of each algorithm is created initially and if required more + * instances are created dynamically. A maximum of four operations can be performed simultaneously. + * Upon reaching the maximum limit of 4, further operations or crypto instances will throw a + * TOO_MANY_OPERATIONS error. TrustedConfirmation pool is to support any operation which has the + * TRUSTED_CONFIRMATION tag in its key parameters. + */ +public class KMPoolManager { + + public static final byte MAX_OPERATION_INSTANCES = 4; + private static final byte HMAC_MAX_OPERATION_INSTANCES = 8; + public static final byte AES_128 = 0x04; + public static final byte AES_256 = 0x05; + // Resource type constants + public static final byte RESOURCE_TYPE_CRYPTO = 0x00; + public static final byte RESOURCE_TYPE_KEY = 0x01; + // static final variables + // -------------------------------------------------------------- + // P-256 Curve Parameters + static byte[] secp256r1_P; + static byte[] secp256r1_A; + + static byte[] secp256r1_B; + static byte[] secp256r1_S; + + // Uncompressed form + static byte[] secp256r1_UCG; + static byte[] secp256r1_N; + static final byte secp256r1_H = 1; + // -------------------------------------------------------------- + + // Cipher pool + private Object[] cipherPool; + // Signature pool + private Object[] signerPool; + // Keyagreement pool + private Object[] keyAgreementPool; + // KMOperationImpl pool + private Object[] operationPool; + // Hmac signer pool which is used to support TRUSTED_CONFIRMATION_REQUIRED tag. + private Object[] hmacSignOperationPool; + + private Object[] keysPool; + // RKP uses AESGCM and HMAC in generateCSR flow. + KMOperation rkpOPeration; + Signature rkpEc; + KMKeyObject rkpEcKey; + + final byte[] KEY_ALGS = { + AES_128, AES_256, KMType.DES, KMType.RSA, KMType.EC, KMType.HMAC, + }; + + final byte[] CIPHER_ALGS = { + Cipher.ALG_AES_BLOCK_128_CBC_NOPAD, + Cipher.ALG_AES_BLOCK_128_ECB_NOPAD, + Cipher.ALG_DES_CBC_NOPAD, + Cipher.ALG_DES_ECB_NOPAD, + Cipher.ALG_AES_CTR, + Cipher.ALG_RSA_PKCS1, + KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA1, + KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA256, + Cipher.ALG_RSA_NOPAD, + AEADCipher.ALG_AES_GCM + }; + + final byte[] SIG_ALGS = { + Signature.ALG_RSA_SHA_256_PKCS1, + Signature.ALG_RSA_SHA_256_PKCS1_PSS, + Signature.ALG_ECDSA_SHA_256, + Signature.ALG_HMAC_SHA_256, + KMRsa2048NoDigestSignature.ALG_RSA_SIGN_NOPAD, + KMRsa2048NoDigestSignature.ALG_RSA_PKCS1_NODIGEST, + KMEcdsa256NoDigestSignature.ALG_ECDSA_NODIGEST + }; + + final byte[] KEY_AGREE_ALGS = {KeyAgreement.ALG_EC_SVDP_DH_PLAIN}; + + private static KMPoolManager poolManager; + + public static KMPoolManager getInstance() { + if (poolManager == null) { + poolManager = new KMPoolManager(); + } + return poolManager; + } + + public static void initStatics() { + secp256r1_P = + new byte[] { + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF + }; + + secp256r1_A = + new byte[] { + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFC + }; + + secp256r1_B = + new byte[] { + (byte) 0x5A, (byte) 0xC6, (byte) 0x35, (byte) 0xD8, (byte) 0xAA, (byte) 0x3A, + (byte) 0x93, (byte) 0xE7, (byte) 0xB3, (byte) 0xEB, (byte) 0xBD, (byte) 0x55, + (byte) 0x76, (byte) 0x98, (byte) 0x86, (byte) 0xBC, (byte) 0x65, (byte) 0x1D, + (byte) 0x06, (byte) 0xB0, (byte) 0xCC, (byte) 0x53, (byte) 0xB0, (byte) 0xF6, + (byte) 0x3B, (byte) 0xCE, (byte) 0x3C, (byte) 0x3E, (byte) 0x27, (byte) 0xD2, + (byte) 0x60, (byte) 0x4B + }; + + secp256r1_S = + new byte[] { + (byte) 0xC4, (byte) 0x9D, (byte) 0x36, (byte) 0x08, (byte) 0x86, (byte) 0xE7, + (byte) 0x04, (byte) 0x93, (byte) 0x6A, (byte) 0x66, (byte) 0x78, (byte) 0xE1, + (byte) 0x13, (byte) 0x9D, (byte) 0x26, (byte) 0xB7, (byte) 0x81, (byte) 0x9F, + (byte) 0x7E, (byte) 0x90 + }; + + // Uncompressed form + secp256r1_UCG = + new byte[] { + (byte) 0x04, (byte) 0x6B, (byte) 0x17, (byte) 0xD1, (byte) 0xF2, (byte) 0xE1, + (byte) 0x2C, (byte) 0x42, (byte) 0x47, (byte) 0xF8, (byte) 0xBC, (byte) 0xE6, + (byte) 0xE5, (byte) 0x63, (byte) 0xA4, (byte) 0x40, (byte) 0xF2, (byte) 0x77, + (byte) 0x03, (byte) 0x7D, (byte) 0x81, (byte) 0x2D, (byte) 0xEB, (byte) 0x33, + (byte) 0xA0, (byte) 0xF4, (byte) 0xA1, (byte) 0x39, (byte) 0x45, (byte) 0xD8, + (byte) 0x98, (byte) 0xC2, (byte) 0x96, (byte) 0x4F, (byte) 0xE3, (byte) 0x42, + (byte) 0xE2, (byte) 0xFE, (byte) 0x1A, (byte) 0x7F, (byte) 0x9B, (byte) 0x8E, + (byte) 0xE7, (byte) 0xEB, (byte) 0x4A, (byte) 0x7C, (byte) 0x0F, (byte) 0x9E, + (byte) 0x16, (byte) 0x2B, (byte) 0xCE, (byte) 0x33, (byte) 0x57, (byte) 0x6B, + (byte) 0x31, (byte) 0x5E, (byte) 0xCE, (byte) 0xCB, (byte) 0xB6, (byte) 0x40, + (byte) 0x68, (byte) 0x37, (byte) 0xBF, (byte) 0x51, (byte) 0xF5 + }; + + secp256r1_N = + new byte[] { + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x00, (byte) 0x00, + (byte) 0x00, (byte) 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xBC, (byte) 0xE6, + (byte) 0xFA, (byte) 0xAD, (byte) 0xA7, (byte) 0x17, (byte) 0x9E, (byte) 0x84, + (byte) 0xF3, (byte) 0xB9, (byte) 0xCA, (byte) 0xC2, (byte) 0xFC, (byte) 0x63, + (byte) 0x25, (byte) 0x51 + }; + } + + private KMPoolManager() { + initStatics(); + cipherPool = new Object[(short) (CIPHER_ALGS.length * MAX_OPERATION_INSTANCES)]; + // Extra 4 algorithms are used to support TRUSTED_CONFIRMATION_REQUIRED feature. + signerPool = + new Object[(short) ((SIG_ALGS.length * MAX_OPERATION_INSTANCES) + MAX_OPERATION_INSTANCES)]; + keyAgreementPool = new Object[(short) (KEY_AGREE_ALGS.length * MAX_OPERATION_INSTANCES)]; + + keysPool = + new Object[(short) ((KEY_ALGS.length * MAX_OPERATION_INSTANCES) + MAX_OPERATION_INSTANCES)]; + operationPool = new Object[MAX_OPERATION_INSTANCES]; + hmacSignOperationPool = new Object[MAX_OPERATION_INSTANCES]; + /* Initialize pools */ + initializeOperationPool(); + initializeHmacSignOperationPool(); + initializeSignerPool(); + initializeCipherPool(); + initializeKeyAgreementPool(); + initializeKeysPool(); + // Initialize the Crypto and Key objects required for RKP flow. + initializeRKpObjects(); + } + + private void initializeRKpObjects() { + rkpOPeration = new KMOperationImpl(); + rkpEc = Signature.getInstance(Signature.ALG_ECDSA_SHA_256, false); + rkpEcKey = createKeyObjectInstance(KMType.EC); + } + + private void initializeKeysPool() { + for (short index = 0; index < KEY_ALGS.length; index++) { + keysPool[index] = createKeyObjectInstance(KEY_ALGS[index]); + } + } + + private void initializeOperationPool() { + for (short index = 0; index < MAX_OPERATION_INSTANCES; index++) { + operationPool[index] = new KMOperationImpl(); + } + } + + private void initializeHmacSignOperationPool() { + for (short index = 0; index < MAX_OPERATION_INSTANCES; index++) { + hmacSignOperationPool[index] = new KMOperationImpl(); + } + } + + // Create a signature instance of each algorithm once. + private void initializeSignerPool() { + short index; + for (index = 0; index < SIG_ALGS.length; index++) { + signerPool[index] = getSignatureInstance(SIG_ALGS[index]); + } + + // Allocate extra 4 HMAC signer instances required for trusted confirmation + for (short len = (short) (index + 4); index < len; index++) { + signerPool[index] = getSignatureInstance(Signature.ALG_HMAC_SHA_256); + } + } + + // Create a cipher instance of each algorithm once. + private void initializeCipherPool() { + for (short index = 0; index < CIPHER_ALGS.length; index++) { + cipherPool[index] = getCipherInstance(CIPHER_ALGS[index]); + } + } + + private void initializeKeyAgreementPool() { + for (short index = 0; index < KEY_AGREE_ALGS.length; index++) { + keyAgreementPool[index] = getKeyAgreementInstance(KEY_AGREE_ALGS[index]); + } + } + + private Object[] getCryptoPoolInstance(short purpose) { + switch (purpose) { + case KMType.AGREE_KEY: + return keyAgreementPool; + + case KMType.ENCRYPT: + case KMType.DECRYPT: + return cipherPool; + + case KMType.SIGN: + case KMType.VERIFY: + return signerPool; + + default: + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + return null; + } + + private Object createInstance(short purpose, short alg) { + switch (purpose) { + case KMType.AGREE_KEY: + return getKeyAgreementInstance((byte) alg); + + case KMType.ENCRYPT: + case KMType.DECRYPT: + return getCipherInstance((byte) alg); + + case KMType.SIGN: + case KMType.VERIFY: + return getSignatureInstance((byte) alg); + + default: + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + return null; + } + + private KeyAgreement getKeyAgreementInstance(byte alg) { + return KeyAgreement.getInstance(alg, false); + } + + private Signature getSignatureInstance(byte alg) { + if (KMRsa2048NoDigestSignature.ALG_RSA_SIGN_NOPAD == alg + || KMRsa2048NoDigestSignature.ALG_RSA_PKCS1_NODIGEST == alg) { + return new KMRsa2048NoDigestSignature(alg); + } else if (KMEcdsa256NoDigestSignature.ALG_ECDSA_NODIGEST == alg) { + return new KMEcdsa256NoDigestSignature(alg); + } else { + return Signature.getInstance(alg, false); + } + } + + private KMKeyObject createKeyObjectInstance(byte alg) { + Object keyObject = null; + switch (alg) { + case AES_128: + keyObject = + (AESKey) + KeyBuilder.buildKey( + KeyBuilder.TYPE_AES_TRANSIENT_RESET, KeyBuilder.LENGTH_AES_128, false); + break; + case AES_256: + keyObject = + (AESKey) + KeyBuilder.buildKey( + KeyBuilder.TYPE_AES_TRANSIENT_RESET, KeyBuilder.LENGTH_AES_256, false); + break; + case KMType.DES: + keyObject = + (DESKey) + KeyBuilder.buildKey( + KeyBuilder.TYPE_DES_TRANSIENT_RESET, KeyBuilder.LENGTH_DES3_3KEY, false); + break; + case KMType.RSA: + keyObject = new KeyPair(KeyPair.ALG_RSA, KeyBuilder.LENGTH_RSA_2048); + break; + case KMType.EC: + keyObject = new KeyPair(KeyPair.ALG_EC_FP, KeyBuilder.LENGTH_EC_FP_256); + initECKey((KeyPair) keyObject); + break; + case KMType.HMAC: + keyObject = + (HMACKey) KeyBuilder.buildKey(KeyBuilder.TYPE_HMAC_TRANSIENT_RESET, (short) 512, false); + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + KMKeyObject ptr = new KMKeyObject(); + ptr.algorithm = alg; + ptr.keyObjectInst = keyObject; + return ptr; + } + + private Cipher getCipherInstance(byte alg) { + if ((KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA1 == alg) + || (KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA256 == alg)) { + return new KMRsaOAEPEncoding(alg); + } else { + return Cipher.getInstance(alg, false); + } + } + + /** + * Returns the first available resource from operation pool. + * + * @return instance of the available resource or null if no resource is available. + */ + public KMOperation getResourceFromOperationPool(boolean isTrustedConfOpr) { + short index = 0; + KMOperationImpl impl; + Object[] oprPool; + if (isTrustedConfOpr) { + oprPool = hmacSignOperationPool; + } else { + oprPool = operationPool; + } + while (index < oprPool.length) { + impl = (KMOperationImpl) oprPool[index]; + // Mode is always set. so compare using mode value. + if (impl.getPurpose() == KMType.INVALID_VALUE) { + return impl; + } + index++; + } + return null; + } + + private byte getAlgorithm(short purpose, Object object) { + switch (purpose) { + case KMType.AGREE_KEY: + return ((KeyAgreement) object).getAlgorithm(); + + case KMType.ENCRYPT: + case KMType.DECRYPT: + return ((Cipher) object).getAlgorithm(); + + case KMType.SIGN: + case KMType.VERIFY: + return ((Signature) object).getAlgorithm(); + + default: + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + return 0; + } + + private boolean isResourceBusy(Object obj, byte resourceType) { + short index = 0; + while (index < MAX_OPERATION_INSTANCES) { + if (((KMOperationImpl) operationPool[index]).isResourceMatches(obj, resourceType) + || ((KMOperationImpl) hmacSignOperationPool[index]) + .isResourceMatches(obj, resourceType)) { + return true; + } + index++; + } + return false; + } + + private void setObject(short purpose, KMOperation operation, Object obj) { + switch (purpose) { + case KMType.AGREE_KEY: + ((KMOperationImpl) operation).setKeyAgreement((KeyAgreement) obj); + break; + case KMType.ENCRYPT: + case KMType.DECRYPT: + ((KMOperationImpl) operation).setCipher((Cipher) obj); + break; + case KMType.SIGN: + case KMType.VERIFY: + ((KMOperationImpl) operation).setSignature((Signature) obj); + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + } + + private void reserveOperation( + KMOperation operation, + short purpose, + short strongboxAlgType, + short padding, + short blockMode, + short macLength, + Object obj, + KMKeyObject keyObject) { + ((KMOperationImpl) operation).setPurpose(purpose); + ((KMOperationImpl) operation).setAlgorithmType(strongboxAlgType); + ((KMOperationImpl) operation).setPaddingAlgorithm(padding); + ((KMOperationImpl) operation).setBlockMode(blockMode); + ((KMOperationImpl) operation).setMacLength(macLength); + ((KMOperationImpl) operation).setKeyObject(keyObject); + setObject(purpose, operation, obj); + } + + public KMOperation getRKpOperation( + short purpose, + short alg, + short strongboxAlgType, + short padding, + short blockMode, + short macLength) { + if (((KMOperationImpl) rkpOPeration).getPurpose() != KMType.INVALID_VALUE) { + // Should not come here. + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + Object cryptoObj = null; + KMKeyObject keyObject = null; + + switch (alg) { + case Signature.ALG_ECDSA_SHA_256: + cryptoObj = rkpEc; + keyObject = rkpEcKey; + break; + default: + // Should not come here. + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + break; + } + reserveOperation( + rkpOPeration, + purpose, + strongboxAlgType, + padding, + blockMode, + macLength, + cryptoObj, + keyObject); + return rkpOPeration; + } + + public KMOperation getOperationImpl( + short purpose, + short alg, + short strongboxAlgType, + short padding, + short blockMode, + short macLength, + short secretLength, + boolean isTrustedConfOpr) { + KMOperation operation; + // Throw exception if no resource from operation pool is available. + if (null == (operation = getResourceFromOperationPool(isTrustedConfOpr))) { + KMException.throwIt(KMError.TOO_MANY_OPERATIONS); + } + // Get one of the pool instances (cipher / signer / keyAgreement) based on purpose. + Object[] pool = getCryptoPoolInstance(purpose); + short index = 0; + short usageCount = 0; + short maxOperations = MAX_OPERATION_INSTANCES; + if (Signature.ALG_HMAC_SHA_256 == alg) { + maxOperations = HMAC_MAX_OPERATION_INSTANCES; + } + + KMKeyObject keyObject = getKeyObjectFromPool(alg, secretLength, maxOperations); + while (index < pool.length) { + if (usageCount >= maxOperations) { + KMException.throwIt(KMError.TOO_MANY_OPERATIONS); + } + if (pool[index] == null) { + // Create one of the instance (Cipher / Signer / KeyAgreement] based on purpose. + Object cipherObject = createInstance(purpose, alg); + JCSystem.beginTransaction(); + pool[index] = cipherObject; + JCSystem.commitTransaction(); + reserveOperation( + operation, + purpose, + strongboxAlgType, + padding, + blockMode, + macLength, + pool[index], + keyObject); + break; + } + if (alg == getAlgorithm(purpose, pool[index])) { + // Check if the crypto instance is not busy and free to use. + if (!isResourceBusy(pool[index], RESOURCE_TYPE_CRYPTO)) { + reserveOperation( + operation, + purpose, + strongboxAlgType, + padding, + blockMode, + macLength, + pool[index], + keyObject); + break; + } + usageCount++; + } + index++; + } + return operation; + } + + public KMKeyObject getKeyObjectFromPool(short alg, short secretLength, short maxOperations) { + KMKeyObject keyObject = null; + byte algo = mapAlgorithm(alg, secretLength); + short index = 0; + short usageCount = 0; + while (index < keysPool.length) { + if (usageCount >= maxOperations) { + KMException.throwIt(KMError.TOO_MANY_OPERATIONS); + } + if (keysPool[index] == null) { + keyObject = createKeyObjectInstance(algo); + JCSystem.beginTransaction(); + keysPool[index] = keyObject; + JCSystem.commitTransaction(); + break; + } + keyObject = (KMKeyObject) keysPool[index]; + if (algo == keyObject.algorithm) { + // Check if the Object instance is not busy and free to use. + if (!isResourceBusy(keyObject, RESOURCE_TYPE_KEY)) { + break; + } + usageCount++; + } + index++; + } + return keyObject; + } + + private byte mapAlgorithm(short alg, short secretLength) { + byte algo = 0; + switch (alg) { + case Cipher.ALG_AES_BLOCK_128_CBC_NOPAD: + case Cipher.ALG_AES_BLOCK_128_ECB_NOPAD: + case Cipher.ALG_AES_CTR: + case AEADCipher.ALG_AES_GCM: + if (secretLength == 16) { + algo = AES_128; + } else if (secretLength == 32) { + algo = AES_256; + } else { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + break; + case Cipher.ALG_DES_CBC_NOPAD: + case Cipher.ALG_DES_ECB_NOPAD: + algo = KMType.DES; + break; + case Cipher.ALG_RSA_PKCS1: + case KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA1: + case KMRsaOAEPEncoding.ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA256: + case Cipher.ALG_RSA_NOPAD: + case Signature.ALG_RSA_SHA_256_PKCS1: + case Signature.ALG_RSA_SHA_256_PKCS1_PSS: + case KMRsa2048NoDigestSignature.ALG_RSA_SIGN_NOPAD: + case KMRsa2048NoDigestSignature.ALG_RSA_PKCS1_NODIGEST: + algo = KMType.RSA; + break; + case Signature.ALG_ECDSA_SHA_256: + case KMEcdsa256NoDigestSignature.ALG_ECDSA_NODIGEST: + case KeyAgreement.ALG_EC_SVDP_DH_PLAIN: + algo = KMType.EC; + break; + case Signature.ALG_HMAC_SHA_256: + algo = KMType.HMAC; + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + return algo; + } + + public void initECKey(KeyPair ecKeyPair) { + ECPrivateKey privKey = (ECPrivateKey) ecKeyPair.getPrivate(); + ECPublicKey pubkey = (ECPublicKey) ecKeyPair.getPublic(); + pubkey.setFieldFP(secp256r1_P, (short) 0, (short) secp256r1_P.length); + pubkey.setA(secp256r1_A, (short) 0, (short) secp256r1_A.length); + pubkey.setB(secp256r1_B, (short) 0, (short) secp256r1_B.length); + pubkey.setG(secp256r1_UCG, (short) 0, (short) secp256r1_UCG.length); + pubkey.setK(secp256r1_H); + pubkey.setR(secp256r1_N, (short) 0, (short) secp256r1_N.length); + + privKey.setFieldFP(secp256r1_P, (short) 0, (short) secp256r1_P.length); + privKey.setA(secp256r1_A, (short) 0, (short) secp256r1_A.length); + privKey.setB(secp256r1_B, (short) 0, (short) secp256r1_B.length); + privKey.setG(secp256r1_UCG, (short) 0, (short) secp256r1_UCG.length); + privKey.setK(secp256r1_H); + privKey.setR(secp256r1_N, (short) 0, (short) secp256r1_N.length); + } + + public void powerReset() { + short index = 0; + while (index < operationPool.length) { + ((KMOperationImpl) operationPool[index]).abort(); + ((KMOperationImpl) hmacSignOperationPool[index]).abort(); + index++; + } + // release rkp operation + rkpOPeration.abort(); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsa2048NoDigestSignature.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsa2048NoDigestSignature.java new file mode 100644 index 0000000..4890ce2 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsa2048NoDigestSignature.java @@ -0,0 +1,140 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.framework.Util; +import javacard.security.CryptoException; +import javacard.security.Key; +import javacard.security.MessageDigest; +import javacard.security.Signature; +import javacardx.crypto.Cipher; + +/** This class provides support for RSA_NO_DIGEST signature algorithm. */ +public class KMRsa2048NoDigestSignature extends Signature { + + public static final byte ALG_RSA_SIGN_NOPAD = (byte) 0x65; + public static final byte ALG_RSA_PKCS1_NODIGEST = (byte) 0x66; + private byte algorithm; + private Cipher inst; + + public KMRsa2048NoDigestSignature(byte alg) { + algorithm = alg; + inst = Cipher.getInstance(Cipher.ALG_RSA_NOPAD, false); + } + + @Override + public void init(Key key, byte b) throws CryptoException { + inst.init(key, b); + } + + @Override + public void init(Key key, byte b, byte[] bytes, short i, short i1) throws CryptoException { + inst.init(key, b, bytes, i, i1); + } + + @Override + public void setInitialDigest(byte[] bytes, short i, short i1, byte[] bytes1, short i2, short i3) + throws CryptoException {} + + @Override + public byte getAlgorithm() { + return algorithm; + } + + @Override + public byte getMessageDigestAlgorithm() { + return MessageDigest.ALG_NULL; + } + + @Override + public byte getCipherAlgorithm() { + return algorithm; + } + + @Override + public byte getPaddingAlgorithm() { + return Cipher.PAD_NULL; + } + + @Override + public short getLength() throws CryptoException { + return 0; + } + + @Override + public void update(byte[] bytes, short i, short i1) throws CryptoException { + // HAL accumulates the data and send it at finish operation. + } + + @Override + public short sign(byte[] bytes, short i, short i1, byte[] bytes1, short i2) + throws CryptoException { + padData(bytes, i, i1, KMAndroidSEProvider.getInstance().tmpArray, (short) 0); + return inst.doFinal( + KMAndroidSEProvider.getInstance().tmpArray, (short) 0, (short) 256, bytes1, i2); + } + + @Override + public short signPreComputedHash(byte[] bytes, short i, short i1, byte[] bytes1, short i2) + throws CryptoException { + return 0; + } + + @Override + public boolean verify(byte[] bytes, short i, short i1, byte[] bytes1, short i2, short i3) + throws CryptoException { + // Verification is handled inside HAL + return false; + } + + @Override + public boolean verifyPreComputedHash( + byte[] bytes, short i, short i1, byte[] bytes1, short i2, short i3) throws CryptoException { + // Verification is handled inside HAL + return false; + } + + private void padData(byte[] buf, short start, short len, byte[] outBuf, short outBufStart) { + if (!isValidData(buf, start, len)) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + Util.arrayFillNonAtomic(outBuf, (short) outBufStart, (short) 256, (byte) 0x00); + if (algorithm == ALG_RSA_SIGN_NOPAD) { // add zero to right + } else if (algorithm == ALG_RSA_PKCS1_NODIGEST) { // 0x00||0x01||PS||0x00 + outBuf[0] = 0x00; + outBuf[1] = 0x01; + Util.arrayFillNonAtomic(outBuf, (short) 2, (short) (256 - len - 3), (byte) 0xFF); + outBuf[(short) (256 - len - 1)] = 0x00; + } else { + CryptoException.throwIt(CryptoException.ILLEGAL_USE); + } + Util.arrayCopyNonAtomic(buf, start, outBuf, (short) (256 - len), len); + } + + private boolean isValidData(byte[] buf, short start, short len) { + if (algorithm == ALG_RSA_SIGN_NOPAD) { + if (len > 256) { + return false; + } + } else { // ALG_RSA_PKCS1_NODIGEST + if (len > 245) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + return false; + } + } + return true; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsaOAEPEncoding.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsaOAEPEncoding.java new file mode 100644 index 0000000..bf34ea1 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMRsaOAEPEncoding.java @@ -0,0 +1,289 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import javacard.framework.JCSystem; +import javacard.framework.Util; +import javacard.security.CryptoException; +import javacard.security.Key; +import javacard.security.MessageDigest; +import javacardx.crypto.Cipher; + +/** This class has the implementation for RSA_OAEP decoding algorithm. */ +public class KMRsaOAEPEncoding extends Cipher { + + public static final byte ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA1 = (byte) 0x1E; + public static final byte ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA256 = (byte) 0x1F; + + final short MGF1_BUF_SIZE = 256; + static byte[] mgf1Buf; + private Cipher cipher; + private byte hash; + private byte mgf1Hash; + private byte algorithm; + + public KMRsaOAEPEncoding(byte alg) { + setDigests(alg); + cipher = Cipher.getInstance(Cipher.ALG_RSA_NOPAD, false); + algorithm = alg; + if (null == mgf1Buf) { + mgf1Buf = + JCSystem.makeTransientByteArray(MGF1_BUF_SIZE, JCSystem.MEMORY_TYPE_TRANSIENT_DESELECT); + } + } + + private void setDigests(byte alg) { + switch (alg) { + case ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA1: + hash = MessageDigest.ALG_SHA_256; + mgf1Hash = MessageDigest.ALG_SHA; + break; + case ALG_RSA_PKCS1_OAEP_SHA256_MGF1_SHA256: + hash = MessageDigest.ALG_SHA_256; + mgf1Hash = MessageDigest.ALG_SHA_256; + break; + default: + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + } + } + + private short getDigestLength() { + switch (hash) { + case MessageDigest.ALG_SHA: + return MessageDigest.LENGTH_SHA; + case MessageDigest.ALG_SHA_224: + return MessageDigest.LENGTH_SHA_224; + case MessageDigest.ALG_SHA_256: + return MessageDigest.LENGTH_SHA_256; + case MessageDigest.ALG_SHA_384: + return MessageDigest.LENGTH_SHA_384; + case MessageDigest.ALG_SHA_512: + return MessageDigest.LENGTH_SHA_512; + default: + CryptoException.throwIt(CryptoException.NO_SUCH_ALGORITHM); + } + return 0; + } + + @Override + public void init(Key theKey, byte theMode) throws CryptoException { + cipher.init(theKey, theMode); + } + + @Override + public void init(Key theKey, byte theMode, byte[] bArray, short bOff, short bLen) + throws CryptoException { + cipher.init(theKey, theMode, bArray, bOff, bLen); + } + + @Override + public byte getAlgorithm() { + return algorithm; + } + + @Override + public byte getCipherAlgorithm() { + return 0; + } + + @Override + public byte getPaddingAlgorithm() { + return 0; + } + + @Override + public short doFinal( + byte[] inBuff, short inOffset, short inLength, byte[] outBuff, short outOffset) + throws CryptoException { + short len = cipher.doFinal(inBuff, inOffset, inLength, outBuff, outOffset); + + // https://tools.ietf.org/html/rfc8017#section-7.1 + // https://www.inf.pucrs.br/~calazans/graduate/TPVLSI_I/RSA-oaep_spec.pdf + // RSA OAEP Encoding and Decoding Mechanism for a 2048 bit RSA Key. + // Msg -> RSA-OAEP-ENCODE -> RSAEncryption -> RSADecryption -> + // RSA-OAEP-DECODE -> Msg + // RSA-OAEP-ENCODE generates an output length of 255, but RSAEncryption + // requires and input of length 256 so we pad 0 to the left of the input + // message and make the length equal to 256 and pass to RSAEncryption. + // RSADecryption takes input length equal to 256 and generates an + // output of length 256. After decryption the first byte of the output + // should be 0(left padding we did in encryption). + // RSA-OAEP-DECODE takes input of length 255 so remove the left padding of 1 + // byte. + if (len != 256 || outBuff[0] != 0) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + Util.arrayCopyNonAtomic( + outBuff, (short) (outOffset + 1), outBuff, (short) 0, (short) (len - 1)); + return rsaOAEPDecode(outBuff, (short) 0, (short) (len - 1)); + } + + @Override + public short update( + byte[] inBuff, short inOffset, short inLength, byte[] outBuff, short outOffset) + throws CryptoException { + return cipher.update(inBuff, inOffset, inLength, outBuff, outOffset); + } + + private void maskGenerationFunction1( + byte[] input, + short inputOffset, + short inputLen, + short expectedOutLen, + byte[] outBuf, + short outOffset) { + short counter = 0; + MessageDigest.OneShot md = null; + try { + md = MessageDigest.OneShot.open(mgf1Hash); + short digestLen = md.getLength(); + + Util.arrayCopyNonAtomic(input, inputOffset, mgf1Buf, (short) 0, inputLen); + while (counter < (short) (expectedOutLen / digestLen)) { + I2OS(counter, mgf1Buf, (short) inputLen); + md.doFinal( + mgf1Buf, + (short) 0, + (short) (4 + inputLen), + outBuf, + (short) (outOffset + (counter * digestLen))); + counter++; + } + + if ((short) (counter * digestLen) < expectedOutLen) { + I2OS(counter, mgf1Buf, (short) inputLen); + md.doFinal( + mgf1Buf, + (short) 0, + (short) (4 + inputLen), + outBuf, + (short) (outOffset + (counter * digestLen))); + } + + } finally { + if (md != null) { + md.close(); + } + Util.arrayFillNonAtomic(mgf1Buf, (short) 0, (short) MGF1_BUF_SIZE, (byte) 0); + } + } + + // Integer to Octet String conversion. + private void I2OS(short i, byte[] out, short offset) { + Util.arrayFillNonAtomic(out, (short) offset, (short) 4, (byte) 0); + out[(short) (offset + 3)] = (byte) (i >>> 0); + out[(short) (offset + 2)] = (byte) (i >>> 8); + } + + private short rsaOAEPDecode(byte[] encodedMsg, short encodedMsgOff, short encodedMsgLen) { + MessageDigest.OneShot md = null; + byte[] tmpArray = KMAndroidSEProvider.getInstance().tmpArray; + + try { + short hLen = getDigestLength(); + + if (encodedMsgLen < (short) (2 * hLen + 1)) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + // encodedMsg will be in the format of maskedSeed||maskedDB. + // maskedSeed length is hLen and maskedDB length is (encodedMsgLen - hLen) + // Now retrieve the seedMask by calling MGF(maskedDB, hLen). The length + // of the seedMask is hLen. + // seedMask = MGF(maskedDB, hLen) + maskGenerationFunction1( + encodedMsg, + (short) (encodedMsgOff + hLen), + (short) (encodedMsgLen - hLen), + hLen, + tmpArray, + (short) 0); + + // Get the seed by doing XOR of (maskedSeed ^ seedMask). + // seed = (maskedSeed ^ seedMask) + for (short i = 0; i < hLen; i++) { + // Store the seed in encodeMsg itself. + encodedMsg[(short) (encodedMsgOff + i)] ^= tmpArray[i]; + } + + // Now get the dbMask by calling MGF(seed , (emLen-hLen)). + // dbMask = MGF(seed , (emLen-hLen)). + maskGenerationFunction1( + encodedMsg, + (short) encodedMsgOff, + hLen, + (short) (encodedMsgLen - hLen), + tmpArray, + (short) 0); + + // Get the DB value. DB = (maskedDB ^ dbMask) + // DB = Hash(P)||00||01||Msg, where P is encoding parameters. (P = NULL) + for (short i = 0; i < (short) (encodedMsgLen - hLen); i++) { + // Store the DB inside encodeMsg itself. + encodedMsg[(short) (encodedMsgOff + i + hLen)] ^= tmpArray[i]; + } + + // Verify Hash. + md = MessageDigest.OneShot.open(hash); + Util.arrayFillNonAtomic(tmpArray, (short) 0, (short) 256, (byte) 0); + md.doFinal(tmpArray, (short) 0, (short) 0, tmpArray, (short) 0); + if (0 + != Util.arrayCompare( + encodedMsg, (short) (encodedMsgOff + hLen), tmpArray, (short) 0, hLen)) { + // Verification failed. + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + + // Find the Message block in DB. + // DB = Hash(P)||00||01||Msg, where P is encoding parameters. (P = NULL) + // The message will be located at the end of the Data block (DB). + // The DB block is first constructed by keeping the message at the end and + // to the message 0x01 byte is prepended. The hash of the + // encoding parameters is calculated and then copied from the + // starting of the block and a variable length of 0's are + // appended to the end of the hash till the 0x01 byte. + short start = (short) (encodedMsgOff + encodedMsgLen); + for (short i = (short) (encodedMsgOff + 2 * hLen); + i < (short) (encodedMsgOff + encodedMsgLen); + i++) { + if ((encodedMsg[i] != 0)) { + start = i; + break; + } + } + if ((start >= (short) (encodedMsgOff + encodedMsgLen)) || (encodedMsg[start] != 0x01)) { + // Bad Padding. + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + start++; // Message starting pos. + if (start < (short) (encodedMsgOff + encodedMsgLen)) { + // Copy the message + Util.arrayCopyNonAtomic( + encodedMsg, + start, + encodedMsg, + encodedMsgOff, + (short) (encodedMsgLen - (start - encodedMsgOff))); + } + return (short) (encodedMsgLen - (start - encodedMsgOff)); + + } finally { + if (md != null) { + md.close(); + } + Util.arrayFillNonAtomic(tmpArray, (short) 0, KMAndroidSEProvider.TMP_ARRAY_SIZE, (byte) 0); + } + } +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMSEProvider.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMSEProvider.java new file mode 100644 index 0000000..76f45e4 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMSEProvider.java @@ -0,0 +1,780 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import org.globalplatform.upgrade.Element; + +/** + * KMSEProvider is facade to use SE specific methods. The main intention of this interface is to + * abstract the cipher, signature and backup and restore related functions. The instance of this + * interface is created by the singleton KMSEProviderImpl class for each provider. At a time there + * can be only one provider in the applet package. + */ +public interface KMSEProvider { + + /** + * This function tells if boot signal event is supported or not. + * + * @return true if supported, false otherwise. + */ + boolean isBootSignalEventSupported(); + + /** + * This function tells if the device is booted or not. + * + * @return true if device booted, false otherwise. + */ + boolean isDeviceRebooted(); + + /** + * This function is supposed to be used to reset the device booted stated after set boot param is + * handled + * + * @param resetBootFlag is false if event has been handled + */ + void clearDeviceBooted(boolean resetBootFlag); + + /** + * Create a symmetric key instance. If the algorithm and/or keysize are not supported then it + * should throw a CryptoException. + * + * @param alg will be KMType.AES, KMType.DES or KMType.HMAC. + * @param keysize will be 128 or 256 for AES or DES. It can be 64 to 512 (multiple of 8) for HMAC. + * @param buf is the buffer in which key has to be returned + * @param startOff is the start offset. + * @return length of the data in the buf. This should match the keysize (in bytes). + */ + short createSymmetricKey(byte alg, short keysize, byte[] buf, short startOff); + + /** + * Create a asymmetric key pair. If the algorithms are not supported then it should throw a + * CryptoException. For RSA the public key exponent must always be 0x010001. The key size of RSA + * key pair must be 2048 bits and key size of EC key pair must be for p256 curve. + * + * @param alg will be KMType.RSA or KMType.EC. + * @param privKeyBuf is the buffer to return the private key exponent in case of RSA or private + * key in case of EC. + * @param privKeyStart is the start offset. + * @param privKeyMaxLength is the maximum length of this private key buffer. + * @param pubModBuf is the buffer to return the modulus in case of RSA or public key in case of + * EC. + * @param pubModStart is the start of offset. + * @param pubModMaxLength is the maximum length of this public key buffer. + * @param lengths is the actual length of the key pair - lengths[0] should be private key and + * lengths[1] should be public key. + */ + void createAsymmetricKey( + byte alg, + byte[] privKeyBuf, + short privKeyStart, + short privKeyMaxLength, + byte[] pubModBuf, + short pubModStart, + short pubModMaxLength, + short[] lengths); + + /** + * Initializes the trusted confirmation operation. + * + * @param computedHmacKey Instance of the computed Hmac key. + * @return instance of KMOperation. + */ + KMOperation initTrustedConfirmationSymmetricOperation(KMKey computedHmacKey); + + /** + * Verify that the imported key is valid. If the algorithm and/or keysize are not supported then + * it should throw a CryptoException. + * + * @param alg will be KMType.AES, KMType.DES or KMType.HMAC. + * @param keysize will be 128 or 256 for AES or DES. It can be 64 to 512 (multiple of 8) for HMAC. + * @param buf is the buffer that contains the symmetric key. + * @param startOff is the start offset. + * @param length of the data in the buf. This should match the keysize (in bytes). + * @return true if the symmetric key is supported and valid. + */ + boolean importSymmetricKey(byte alg, short keysize, byte[] buf, short startOff, short length); + + /** + * Validate that the imported asymmetric key pair is valid. For RSA the public key exponent must + * always be 0x010001. The key size of RSA key pair must be 2048 bits and key size of EC key pair + * must be for p256 curve. If the algorithms are not supported then it should throw a + * CryptoException. + * + * @param alg will be KMType.RSA or KMType.EC. + * @param privKeyBuf is the buffer that contains the private key exponent in case of RSA or + * private key in case of EC. + * @param privKeyStart is the start offset. + * @param privKeyLength is the length of this private key buffer. + * @param pubModBuf is the buffer that contains the modulus in case of RSA or public key in case + * of EC. + * @param pubModStart is the start of offset. + * @param pubModLength is the length of this public key buffer. + * @return true if the key pair is supported and valid. + */ + boolean importAsymmetricKey( + byte alg, + byte[] privKeyBuf, + short privKeyStart, + short privKeyLength, + byte[] pubModBuf, + short pubModStart, + short pubModLength); + + /** + * This is a oneshot operation that generates random number of desired length. + * + * @param num is the buffer in which random number is returned to the applet. + * @param offset is start of the buffer. + * @param length indicates the size of buffer and desired length of random number in bytes. + */ + void newRandomNumber(byte[] num, short offset, short length); + + /** + * This is a oneshot operation that adds the entropy to the entropy pool. This operation + * corresponds to addRndEntropy command. This method may ignore the added entropy value if the SE + * provider does not support it. + * + * @param num is the buffer in which entropy value is given. + * @param offset is start of the buffer. + * @param length length of the buffer. + */ + void addRngEntropy(byte[] num, short offset, short length); + + /** + * This is a oneshot operation that generates and returns back a true random number. + * + * @param num is the buffer in which entropy value is returned. + * @param offset is start of the buffer. + * @param length length of the buffer. + */ + void getTrueRandomNumber(byte[] num, short offset, short length); + + /** + * This is a oneshot operation that performs encryption operation using AES GCM algorithm. It + * throws CryptoException if algorithm is not supported or if tag length is not equal to 16 or + * nonce length is not equal to 12. + * + * @param aesKey is the buffer that contains 128 bit or 256 bit aes key used to encrypt. + * @param aesKeyStart is the start in aes key buffer. + * @param aesKeyLen is the length of aes key buffer in bytes (16 or 32 bytes). + * @param data is the buffer that contains data to encrypt. + * @param dataStart is the start of the data buffer. + * @param dataLen is the length of the data buffer. + * @param encData is the buffer of the output encrypted data. + * @param encDataStart is the start of the encrypted data buffer. + * @param nonce is the buffer of nonce. + * @param nonceStart is the start of the nonce buffer. + * @param nonceLen is the length of the nonce buffer. + * @param authData is the authentication data buffer. + * @param authDataStart is the start of the authentication buffer. + * @param authDataLen is the length of the authentication buffer. + * @param authTag is the buffer to output authentication tag. + * @param authTagStart is the start of the buffer. + * @param authTagLen is the length of the buffer. + * @return length of the encrypted data. + */ + short aesGCMEncrypt( + byte[] aesKey, + short aesKeyStart, + short aesKeyLen, + byte[] data, + short dataStart, + short dataLen, + byte[] encData, + short encDataStart, + byte[] nonce, + short nonceStart, + short nonceLen, + byte[] authData, + short authDataStart, + short authDataLen, + byte[] authTag, + short authTagStart, + short authTagLen); + + /** + * This is a oneshot operation that performs decryption operation using AES GCM algorithm. It + * throws CryptoException if algorithm is not supported. + * + * @param aesKey is the buffer that contains 128 bit or 256 bit aes key used to encrypt. + * @param aesKeyStart is the start in aes key buffer. + * @param aesKeyLen is the length of aes key buffer in bytes (16 or 32 bytes). + * @param encData is the buffer of the input encrypted data. + * @param encDataStart is the start of the encrypted data buffer. + * @param encDataLen is the length of the data buffer. + * @param data is the buffer that contains output decrypted data. + * @param dataStart is the start of the data buffer. + * @param nonce is the buffer of nonce. + * @param nonceStart is the start of the nonce buffer. + * @param nonceLen is the length of the nonce buffer. + * @param authData is the authentication data buffer. + * @param authDataStart is the start of the authentication buffer. + * @param authDataLen is the length of the authentication buffer. + * @param authTag is the buffer to output authentication tag. + * @param authTagStart is the start of the buffer. + * @param authTagLen is the length of the buffer. + * @return true if the authentication is valid. + */ + boolean aesGCMDecrypt( + byte[] aesKey, + short aesKeyStart, + short aesKeyLen, + byte[] encData, + short encDataStart, + short encDataLen, + byte[] data, + short dataStart, + byte[] nonce, + short nonceStart, + short nonceLen, + byte[] authData, + short authDataStart, + short authDataLen, + byte[] authTag, + short authTagStart, + short authTagLen); + + /** + * This is a oneshot operation that performs key derivation function using cmac kdf (CKDF) as + * defined in android keymaster hal definition. + * + * @param hmacKey of pre-shared key. + * @param label is the label to be used for ckdf. + * @param labelStart is the start of label. + * @param labelLen is the length of the label. + * @param context is the context to be used for ckdf. + * @param contextStart is the start of the context + * @param contextLength is the length of the context + * @param key is the output buffer to return the derived key + * @param keyStart is the start of the output buffer. + * @return length of the derived key buffer in bytes. + */ + short cmacKDF( + KMKey hmacKey, + byte[] label, + short labelStart, + short labelLen, + byte[] context, + short contextStart, + short contextLength, + byte[] key, + short keyStart); + + /** + * This is a oneshot operation that signs the data using hmac algorithm. + * + * @param keyBuf is the buffer with hmac key. + * @param keyStart is the start of the buffer. + * @param keyLength is the length of the buffer which will be in bytes from 8 to 64. + * @param data is the buffer containing data to be signed. + * @param dataStart is the start of the data. + * @param dataLength is the length of the data. + * @param signature is the output signature buffer + * @param signatureStart is the start of the signature + * @return length of the signature buffer in bytes. + */ + short hmacSign( + byte[] keyBuf, + short keyStart, + short keyLength, + byte[] data, + short dataStart, + short dataLength, + byte[] signature, + short signatureStart); + + /** + * This is a oneshot operation that signs the data using hmac algorithm. + * + * @param hmacKey is the KMHmacKey. + * @param data is the buffer containing data to be signed. + * @param dataStart is the start of the data. + * @param dataLength is the length of the data. + * @param signature is the output signature buffer + * @param signatureStart is the start of the signature + * @return length of the signature buffer in bytes. + */ + short hmacSign( + Object hmacKey, + byte[] data, + short dataStart, + short dataLength, + byte[] signature, + short signatureStart); + + /** + * This is a oneshot operation that signs the data using hmac algorithm. This is used to derive + * the key, which is used to encrypt the keyblob. + * + * @param masterkey of masterkey. + * @param data is the buffer containing data to be signed. + * @param dataStart is the start of the data. + * @param dataLength is the length of the data. + * @param signature is the output signature buffer + * @param signatureStart is the start of the signature + * @return length of the signature buffer in bytes. + */ + short hmacKDF( + KMKey masterkey, + byte[] data, + short dataStart, + short dataLength, + byte[] signature, + short signatureStart); + + /** + * This is a oneshot operation that verifies the signature using hmac algorithm. + * + * @param keyBuf is the buffer with hmac key. + * @param keyStart is the start of the buffer. + * @param keyLength is the length of the buffer which will be in bytes from 8 to 64. + * @param data is the buffer containing data. + * @param dataStart is the start of the data. + * @param dataLength is the length of the data. + * @param signature is the signature buffer. + * @param signatureStart is the start of the signature buffer. + * @param signatureLen is the length of the signature buffer in bytes. + * @return true if the signature matches. + */ + boolean hmacVerify( + KMKey hmacKey, + byte[] data, + short dataStart, + short dataLength, + byte[] signature, + short signatureStart, + short signatureLen); + + /** + * This is a oneshot operation that decrypts the data using RSA algorithm with oaep256 padding. + * The public exponent is always 0x010001. It throws CryptoException if OAEP encoding validation + * fails. + * + * @param privExp is the private exponent (2048 bit) buffer. + * @param privExpStart is the start of the private exponent buffer. + * @param privExpLength is the length of the private exponent buffer in bytes. + * @param modBuffer is the modulus (2048 bit) buffer. + * @param modOff is the start of the modulus buffer. + * @param modLength is the length of the modulus buffer in bytes. + * @param inputDataBuf is the buffer of the input data. + * @param inputDataStart is the start of the input data buffer. + * @param inputDataLength is the length of the input data buffer in bytes. + * @param outputDataBuf is the output buffer that contains the decrypted data. + * @param outputDataStart is the start of the output data buffer. + * @return length of the decrypted data. + */ + short rsaDecipherOAEP256( + byte[] privExp, + short privExpStart, + short privExpLength, + byte[] modBuffer, + short modOff, + short modLength, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart); + + /** + * Implementation of HKDF as per RFC5869 https://datatracker.ietf.org/doc/html/rfc5869#section-2 + * + * @param ikm is the buffer containing input key material. + * @param ikmOff is the start of the input key. + * @param ikmLen is the length of the input key. + * @param salt is the buffer containing the salt. + * @param saltOff is the start of the salt buffer. + * @param saltLen is the length of the salt buffer. + * @param info is the buffer containing the application specific information + * @param infoOff is the start of the info buffer. + * @param infoLen is the length of the info buffer. + * @param out is the output buffer. + * @param outOff is the start of the output buffer. + * @param outLen is the length of the expected out buffer. + * @return Length of the out buffer which is outLen. + */ + short hkdf( + byte[] ikm, + short ikmOff, + short ikmLen, + byte[] salt, + short saltOff, + short saltLen, + byte[] info, + short infoOff, + short infoLen, + byte[] out, + short outOff, + short outLen); + + /** + * This function performs ECDH key agreement and generates a secret. + * + * @param privKey is the buffer containing the private key from first party. + * @param privKeyOff is the offset of the private key buffer. + * @param privKeyLen is the length of the private key buffer. + * @param publicKey is the buffer containing the public key from second party. + * @param publicKeyOff is the offset of the public key buffer. + * @param publicKeyLen is the length of the public key buffer. + * @param secret is the output buffer. + * @param secretOff is the offset of the output buffer. + * @return The length of the secret. + */ + short ecdhKeyAgreement( + byte[] privKey, + short privKeyOff, + short privKeyLen, + byte[] publicKey, + short publicKeyOff, + short publicKeyLen, + byte[] secret, + short secretOff); + + /** + * This is a oneshort operation that verifies the data using EC public key + * + * @param pubKey is the public key buffer. + * @param pubKeyOffset is the start of the public key buffer. + * @param pubKeyLen is the length of the public key. + * @param inputDataBuf is the buffer of the input data. + * @param inputDataStart is the start of the input data buffer. + * @param inputDataLength is the length of the input data buffer in bytes. + * @param signatureDataBuf is the buffer the signature input data. + * @param signatureDataStart is the start of the signature input data. + * @param signatureDataLen is the length of the signature input data. + * @return true if verification is successful, otherwise false. + */ + boolean ecVerify256( + byte[] pubKey, + short pubKeyOffset, + short pubKeyLen, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] signatureDataBuf, + short signatureDataStart, + short signatureDataLen); + + /** + * This is a oneshot operation that signs the data using device unique key. + * + * @param ecPrivKey instance of KMECDeviceUniqueKey to sign the input data. + * @param inputDataBuf is the buffer of the input data. + * @param inputDataStart is the start of the input data buffer. + * @param inputDataLength is the length of the input data buffer in bytes. + * @param outputDataBuf is the output buffer that contains the signature. + * @param outputDataStart is the start of the output data buffer. + * @return length of the decrypted data. + */ + short signWithDeviceUniqueKey( + KMKey deviceUniqueKey, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart); + + short ecSign256( + byte[] secret, + short secretStart, + short secretLength, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart); + + short rsaSign256Pkcs1( + byte[] secret, + short secretStart, + short secretLength, + byte[] modBuf, + short modStart, + short modLength, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart); + + /** + * This creates a persistent operation for signing, verify, encryption and decryption using HMAC, + * AES and DES algorithms when keymaster hal's beginOperation function is executed. The + * KMOperation instance can be reclaimed by the seProvider when KMOperation is finished or + * aborted. It throws CryptoException if algorithm is not supported. + * + * @param purpose is KMType.ENCRYPT or KMType.DECRYPT for AES and DES algorithm. It will be + * KMType.SIGN and KMType.VERIFY for HMAC algorithm + * @param alg is KMType.HMAC, KMType.AES or KMType.DES. + * @param digest is KMType.SHA2_256 in case of HMAC else it will be KMType.DIGEST_NONE. + * @param padding is KMType.PADDING_NONE or KMType.PKCS7 (in case of AES and DES). + * @param blockMode is KMType.CTR, KMType.GCM. KMType.CBC or KMType.ECB for AES or DES else it is + * 0. + * @param keyBuf is aes, des or hmac key buffer. + * @param keyStart is the start of the key buffer. + * @param keyLength is the length of the key buffer. + * @param ivBuf is the iv buffer (in case on AES and DES algorithm without ECB mode) + * @param ivStart is the start of the iv buffer. + * @param ivLength is the length of the iv buffer. It will be zero in case of HMAC and AES/DES + * with ECB mode. + * @param macLength is the mac length in case of signing operation for hmac algorithm. + * @return KMOperation instance. + */ + KMOperation initSymmetricOperation( + byte purpose, + byte alg, + byte digest, + byte padding, + byte blockMode, + byte[] keyBuf, + short keyStart, + short keyLength, + byte[] ivBuf, + short ivStart, + short ivLength, + short macLength); + + /** + * This creates a persistent operation for signing, verify, encryption and decryption using HMAC, + * AES and DES algorithms when keymaster hal's beginOperation function is executed. The + * KMOperation instance can be reclaimed by the seProvider when KMOperation is finished or + * aborted. It throws CryptoException if algorithm is not supported. + * + * @param purpose is KMType.ENCRYPT or KMType.DECRYPT for AES and DES algorithm. It will be + * KMType.SIGN and KMType.VERIFY for HMAC algorithm + * @param alg is KMType.HMAC, KMType.AES or KMType.DES. + * @param digest is KMType.SHA2_256 in case of HMAC else it will be KMType.DIGEST_NONE. + * @param padding is KMType.PADDING_NONE or KMType.PKCS7 (in case of AES and DES). + * @param blockMode is KMType.CTR, KMType.GCM. KMType.CBC or KMType.ECB for AES or DES else it is + * 0. + * @param key is a key object. + * @param interfaceType defines the type of key in the key object. + * @param ivBuf is the iv buffer (in case on AES and DES algorithm without ECB mode) + * @param ivStart is the start of the iv buffer. + * @param ivLength is the length of the iv buffer. It will be zero in case of HMAC and AES/DES + * with ECB mode. + * @param macLength is the mac length in case of signing operation for hmac algorithm. + * @param oneShot if true, creates oneshot operation. + * @return KMOperation instance. + */ + KMOperation initSymmetricOperation( + byte purpose, + byte alg, + byte digest, + byte padding, + byte blockMode, + Object key, + byte interfaceType, + byte[] ivBuf, + short ivStart, + short ivLength, + short macLength, + boolean oneShot); + + /** + * This function creates an Operation instance only for RKP module. + * + * @param purpose is KMType.ENCRYPT or KMType.DECRYPT for AES and DES algorithm. It will be + * KMType.SIGN and KMType.VERIFY for HMAC algorithm + * @param alg is KMType.HMAC, KMType.AES or KMType.DES. + * @param digest is KMType.SHA2_256 in case of HMAC else it will be KMType.DIGEST_NONE. + * @param padding is KMType.PADDING_NONE or KMType.PKCS7 (in case of AES and DES). + * @param blockMode is KMType.CTR, KMType.GCM. KMType.CBC or KMType.ECB for AES or DES else it is + * 0. + * @param keyBuf is aes, des or hmac key buffer. + * @param keyStart is the start of the key buffer. + * @param keyLength is the length of the key buffer. + * @param ivBuf is the iv buffer (in case on AES and DES algorithm without ECB mode) + * @param ivStart is the start of the iv buffer. + * @param ivLength is the length of the iv buffer. It will be zero in case of HMAC and AES/DES + * with ECB mode. + * @param macLength is the mac length in case of signing operation for hmac algorithm. + * @return KMOperation instance. + */ + KMOperation getRkpOperation( + byte purpose, + byte alg, + byte digest, + byte padding, + byte blockMode, + KMKey ecPrivKey, + byte[] ivBuf, + short ivStart, + short ivLength, + short macLength); + + /** + * This creates a persistent operation for signing, verify, encryption and decryption using RSA + * and EC algorithms when keymaster hal's beginOperation function is executed. For RSA the public + * exponent is always 0x0100101. For EC the curve is always p256. The KMOperation instance can be + * reclaimed by the seProvider when KMOperation is finished or aborted. It throws CryptoException + * if algorithm is not supported. + * + * @param purpose is KMType.ENCRYPT or KMType.DECRYPT for RSA. It will be * KMType.SIGN and + * KMType.VERIFY for RSA and EC algorithms. + * @param alg is KMType.RSA or KMType.EC algorithms. + * @param padding is KMType.PADDING_NONE or KMType.RSA_OAEP, KMType.RSA_PKCS1_1_5_ENCRYPT, + * KMType.RSA_PKCS1_1_5_SIGN or KMType.RSA_PSS. + * @param digest is KMType.DIGEST_NONE or KMType.SHA2_256. + * @param mgfDigest is the MGF digest. + * @param privKeyBuf is the private key in case of EC or private key exponent is case of RSA. + * @param privKeyStart is the start of the private key. + * @param privKeyLength is the length of the private key. + * @param pubModBuf is the modulus (in case of RSA) or public key (in case of EC). + * @param pubModStart is the start of the modulus. + * @param pubModLength is the length of the modulus. + * @return KMOperation instance that can be executed. + */ + KMOperation initAsymmetricOperation( + byte purpose, + byte alg, + byte padding, + byte digest, + byte mgfDigest, + byte[] privKeyBuf, + short privKeyStart, + short privKeyLength, + byte[] pubModBuf, + short pubModStart, + short pubModLength); + + /** + * This function tells if applet is upgrading or not. + * + * @return true if upgrading, otherwise false. + */ + boolean isUpgrading(); + + /** + * This function generates an AES Key of keySizeBits, which is used as an master key. This + * generated key is maintained by the SEProvider. This function should be called only once at the + * time of installation. + * + * @param instance of the masterkey. + * @param keySizeBits key size in bits. + * @return An instance of KMMasterKey. + */ + KMKey createMasterKey(KMKey masterKey, short keySizeBits); + + /** + * This function creates an HMACKey and initializes the key with the provided input key data. + * + * @param keyData buffer containing the key data. + * @param offset start of the buffer. + * @param length length of the buffer. + * @return An instance of the KMComputedHmacKey. + */ + KMKey createComputedHmacKey(KMKey computedHmacKey, byte[] keyData, short offset, short length); + + /** Returns true if factory provisioned attestation key is supported. */ + boolean isAttestationKeyProvisioned(); + + /** + * Returns algorithm type of the attestation key. It can be KMType.EC or KMType.RSA if the + * attestation key is provisioned in the factory. + */ + short getAttestationKeyAlgorithm(); + + /** + * Creates an ECKey instance and sets the public and private keys to it. + * + * @param testMode to indicate if current execution is for test or production. + * @param pubKey buffer containing the public key. + * @param pubKeyOff public key buffer start offset. + * @param pubKeyLen public key buffer length. + * @param privKey buffer containing the private key. + * @param privKeyOff private key buffer start offset. + * @param privKeyLen private key buffer length. + * @return instance of KMDeviceUniqueKey. + */ + KMKey createRkpDeviceUniqueKeyPair( + KMKey key, + byte[] pubKey, + short pubKeyOff, + short pubKeyLen, + byte[] privKey, + short privKeyOff, + short privKeyLen); + + /** + * This is a one-shot operation the does digest of the input mesage. + * + * @param inBuff input buffer to be digested. + * @param inOffset start offset of the input buffer. + * @param inLength length of the input buffer. + * @param outBuff is the output buffer that contains the digested data. + * @param outOffset start offset of the digested output buffer. + * @return length of the digested data. + */ + short messageDigest256( + byte[] inBuff, short inOffset, short inLength, byte[] outBuff, short outOffset); + + /** + * This function generates a HMAC key from the provided key buffers. + * + * @param presharedKey instance of the presharedkey. + * @param key buffer containing the key data. + * @param offset start offset of the buffer. + * @param length is the length of the key. + * @return instance of KMPresharedKey. + */ + KMKey createPreSharedKey(KMKey presharedKey, byte[] key, short offset, short length); + + /** + * This function saves the key objects while upgrade. + * + * @param element instance of the Element class where the objects to be stored. + * @param interfaceType the type interface of the parent object. + * @param object instance of the object to be saved. + */ + void onSave(Element element, byte interfaceType, Object object); + + /** + * This function restores the the object from element instance. + * + * @param element instance of the Element class. + * @return restored object. + */ + Object onRestore(Element element); + + /** + * This function returns the count of the primitive bytes required to be stored by the + * implementation of the interface type. + * + * @param interfaceType type interface of the parent object. + * @return count of the primitive bytes. + */ + short getBackupPrimitiveByteCount(byte interfaceType); + + /** + * This function returns the object count required to be stored by the implementation of the + * interface type. + * + * @param interfaceType type interface of the parent object. + * @return count of the objects. + */ + short getBackupObjectCount(byte interfaceType); + + /** + * This function creates an HMACKey and initializes the key with the provided input key data. + * + * @param keyData buffer containing the key data. + * @param offset start of the buffer. + * @param length length of the buffer. + * @return An instance of the KMRkpMacKey. + */ + KMKey createRkpMacKey(KMKey createComputedHmacKey, byte[] keyData, short offset, short length); +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMType.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMType.java new file mode 100644 index 0000000..82cbe26 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMType.java @@ -0,0 +1,79 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.seprovider; + +/** + * This class declares all types, tag types, and tag keys. It also establishes basic structure of + * any KMType i.e. struct{byte type, short length, value} where value can any of the KMType. Also, + * KMType refers to transient memory heap in the repository. Finally KMType's subtypes are singleton + * prototype objects which just cast the structure over contiguous memory buffer. + */ +public abstract class KMType { + + public static final short INVALID_VALUE = (short) 0x8000; + + // Algorithm Enum Tag key and values + public static final short ALGORITHM = 0x0002; + public static final byte RSA = 0x01; + public static final byte DES = 0x21; + public static final byte EC = 0x03; + public static final byte AES = 0x20; + public static final byte HMAC = (byte) 0x80; + + // EcCurve Enum Tag key and values. + public static final short ECCURVE = 0x000A; + public static final byte P_224 = 0x00; + public static final byte P_256 = 0x01; + public static final byte P_384 = 0x02; + public static final byte P_521 = 0x03; + + // Purpose + public static final short PURPOSE = 0x0001; + public static final byte ENCRYPT = 0x00; + public static final byte DECRYPT = 0x01; + public static final byte SIGN = 0x02; + public static final byte VERIFY = 0x03; + public static final byte DERIVE_KEY = 0x04; + public static final byte WRAP_KEY = 0x05; + public static final byte AGREE_KEY = 0x06; + public static final byte ATTEST_KEY = (byte) 0x07; + // Block mode + public static final short BLOCK_MODE = 0x0004; + public static final byte ECB = 0x01; + public static final byte CBC = 0x02; + public static final byte CTR = 0x03; + public static final byte GCM = 0x20; + + // Digest + public static final short DIGEST = 0x0005; + public static final byte DIGEST_NONE = 0x00; + public static final byte MD5 = 0x01; + public static final byte SHA1 = 0x02; + public static final byte SHA2_224 = 0x03; + public static final byte SHA2_256 = 0x04; + public static final byte SHA2_384 = 0x05; + public static final byte SHA2_512 = 0x06; + + // Padding mode + public static final short PADDING = 0x0006; + public static final byte PADDING_NONE = 0x01; + public static final byte RSA_OAEP = 0x02; + public static final byte RSA_PSS = 0x03; + public static final byte RSA_PKCS1_1_5_ENCRYPT = 0x04; + public static final byte RSA_PKCS1_1_5_SIGN = 0x05; + public static final byte PKCS7 = 0x40; +} diff --git a/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMUpgradable.java b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMUpgradable.java new file mode 100644 index 0000000..420b2c7 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/AndroidSEProviderLib/src/com/android/javacard/seprovider/KMUpgradable.java @@ -0,0 +1,30 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.seprovider; + +import org.globalplatform.upgrade.Element; + +/** This interface helps in storing and restoring the applet data during the applet upgrades. */ +public interface KMUpgradable { + + void onSave(Element ele); + + void onRestore(Element ele, short oldVersion, short currentVersion); + + short getBackupPrimitiveByteCount(); + + short getBackupObjectCount(); +} diff --git a/ready_se/google/keymint/KM300/Applet/README.md b/ready_se/google/keymint/KM300/Applet/README.md new file mode 100644 index 0000000..17e2e49 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/README.md @@ -0,0 +1,16 @@ +# JavaCardKeymaster Applet
+
+This directory contains the implementation of the Keymint 3.0
+interface, in the form of a JavaCard 3.0.5 applet which runs in a secure
+element. It must be deployed in conjuction with the associated HAL,
+which mediates between Android Keystore and this applet.
+
+# Supported Features!
+
+ - Keymint 3.0 supported functions for required VTS compliance.
+ - SharedSecret 1.0 supported functions for required VTS compliance.
+ - RemotelyProvisionedComponent 3.0 supported functions for required VTS compliance.
+
+# Not supported features
+ - Factory provisioned attestation key will not be supported in this applet.
+ - Limited usage keys will not be supported in this applet.
diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMArray.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMArray.java new file mode 100644 index 0000000..aa54d54 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMArray.java @@ -0,0 +1,165 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMArray represents an array of KMTypes. Array is the sequence of elements of one or more sub + * types of KMType. It also acts as a vector of one subtype of KMTypes on the lines of class KMArray + * <subType>, where subType is subclass of KMType. Vector is the sequence of elements of one sub + * type e.g. KMType.BYTE_BLOB_TYPE. The KMArray instance maps to the CBOR type array. KMArray is a + * KMType and it further extends the value field in TLV_HEADER as ARRAY_HEADER struct{short subType; + * short length;} followed by sequence of short pointers to KMType instances. The subType can be 0 + * if this is an array or subType is short KMType value e.g. KMType.BYTE_BLOB_TYPE if this is a + * vector of that sub type. + */ +public class KMArray extends KMType { + + public static final short ANY_ARRAY_LENGTH = 0x1000; + private static final byte ARRAY_HEADER_SIZE = 4; + private static KMArray prototype; + + private KMArray() {} + + private static KMArray proto(short ptr) { + if (prototype == null) { + prototype = new KMArray(); + } + KMType.instanceTable[KM_ARRAY_OFFSET] = ptr; + return prototype; + } + + public static short exp() { + short ptr = instance(ARRAY_TYPE, (short) ARRAY_HEADER_SIZE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.INVALID_VALUE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), ANY_ARRAY_LENGTH); + return ptr; + } + + public static short exp(short type) { + short ptr = instance(ARRAY_TYPE, (short) ARRAY_HEADER_SIZE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), type); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), ANY_ARRAY_LENGTH); + return ptr; + } + + public static short instance(short length) { + short ptr = KMType.instance(ARRAY_TYPE, (short) (ARRAY_HEADER_SIZE + (length * 2))); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.INVALID_VALUE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), length); + return ptr; + } + + public static short instance(short length, byte type) { + short ptr = instance(length); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), type); + return ptr; + } + + public static KMArray cast(short ptr) { + if (heap[ptr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public void add(short index, short objPtr) { + short len = length(); + if (index >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + Util.setShort(heap, (short) (getStartOff() + (short) (index * 2)), objPtr); + } + + public short get(short index) { + short len = length(); + if (index >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + return Util.getShort(heap, (short) (getStartOff() + (short) (index * 2))); + } + + public void swap(short index1, short index2) { + short len = length(); + if (index1 >= len || index2 >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + short indexPtr1 = + Util.getShort( + heap, + (short) + (instanceTable[KM_ARRAY_OFFSET] + + TLV_HEADER_SIZE + + ARRAY_HEADER_SIZE + + (short) (index1 * 2))); + short indexPtr2 = + Util.getShort( + heap, + (short) + (instanceTable[KM_ARRAY_OFFSET] + + TLV_HEADER_SIZE + + ARRAY_HEADER_SIZE + + (short) (index2 * 2))); + Util.setShort( + heap, + (short) + (instanceTable[KM_ARRAY_OFFSET] + + TLV_HEADER_SIZE + + ARRAY_HEADER_SIZE + + (short) (index1 * 2)), + indexPtr2); + Util.setShort( + heap, + (short) + (instanceTable[KM_ARRAY_OFFSET] + + TLV_HEADER_SIZE + + ARRAY_HEADER_SIZE + + (short) (index2 * 2)), + indexPtr1); + } + + public short containedType() { + return Util.getShort(heap, (short) (KMType.instanceTable[KM_ARRAY_OFFSET] + TLV_HEADER_SIZE)); + } + + public short getStartOff() { + return (short) (KMType.instanceTable[KM_ARRAY_OFFSET] + TLV_HEADER_SIZE + ARRAY_HEADER_SIZE); + } + + public short length() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_ARRAY_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short setLength(short len) { + return Util.setShort( + heap, (short) (KMType.instanceTable[KM_ARRAY_OFFSET] + TLV_HEADER_SIZE + 2), len); + } + + public byte[] getBuffer() { + return heap; + } + + public void deleteLastEntry() { + short len = length(); + Util.setShort( + heap, (short) (instanceTable[KM_ARRAY_OFFSET] + TLV_HEADER_SIZE + 2), (short) (len - 1)); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMAsn1Parser.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMAsn1Parser.java new file mode 100644 index 0000000..22a16a3 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMAsn1Parser.java @@ -0,0 +1,471 @@ +package com.android.javacard.keymaster; + +import com.android.javacard.seprovider.KMException; +import javacard.framework.JCSystem; +import javacard.framework.Util; + +/** + * This is a utility class that helps in parsing the PKCS8 encoded RSA and EC keys, certificate + * subject, subjectPublicKey info and ECDSA signatures. + */ +public class KMAsn1Parser { + + // Below are the ASN.1 tag types + public static final byte ASN1_OCTET_STRING = 0x04; + public static final byte ASN1_SEQUENCE = 0x30; + public static final byte ASN1_SET = 0x31; + public static final byte ASN1_INTEGER = 0x02; + public static final byte OBJECT_IDENTIFIER = 0x06; + public static final byte ASN1_A0_TAG = (byte) 0xA0; + public static final byte ASN1_A1_TAG = (byte) 0xA1; + public static final byte ASN1_BIT_STRING = 0x03; + + public static final byte ASN1_UTF8_STRING = 0x0C; + public static final byte ASN1_TELETEX_STRING = 0x14; + public static final byte ASN1_PRINTABLE_STRING = 0x13; + public static final byte ASN1_UNIVERSAL_STRING = 0x1C; + public static final byte ASN1_BMP_STRING = 0x1E; + public static final byte IA5_STRING = 0x16; + // OID of the EC P256 curve 1.2.840.10045.3.1.7 + public static final byte[] EC_CURVE = { + 0x06, 0x08, 0x2a, (byte) 0x86, 0x48, (byte) 0xce, 0x3d, 0x03, 0x01, 0x07 + }; + // Constant for rsaEncryption pkcs#1 (1.2.840.113549.1.1.1) and NULL + public static final byte[] RSA_ALGORITHM = { + 0x06, + 0x09, + 0x2A, + (byte) 0x86, + 0x48, + (byte) 0x86, + (byte) 0xF7, + 0x0D, + 0x01, + 0x01, + 0x01, + 0x05, + 0x00 + }; + // Constant for ecPublicKey (1.2.840.10045.2.1) and prime256v1 (1.2.840.10045.3.1.7) + public static final byte[] EC_ALGORITHM = { + 0x06, + 0x07, + 0x2a, + (byte) 0x86, + 0x48, + (byte) 0xce, + 0x3d, + 0x02, + 0x01, + 0x06, + 0x08, + 0x2a, + (byte) 0x86, + 0x48, + (byte) 0xce, + 0x3d, + 0x03, + 0x01, + 0x07 + }; + // The maximum length of email id attribute. + public static final short MAX_EMAIL_ADD_LEN = 255; + // Datatable offsets. + private static final byte DATA_START_OFFSET = 0; + private static final byte DATA_LENGTH_OFFSET = 1; + private static final byte DATA_CURSOR_OFFSET = 2; + // This array contains the last byte of OID for each oid type. + // The first 4 bytes are common as shown above in COMMON_OID + private static final byte[] attributeOIds = { + 0x03, /* commonName COMMON_OID.3 */ 0x04, /* surName COMMON_OID.4*/ + 0x05, /* serialNumber COMMON_OID.5 */ 0x06, /* countryName COMMON_OID.6 */ + 0x07, /* locality COMMON_OID.7 */ 0x08, /* stateOrProviince COMMON_OID.8 */ + 0x0A, /* organizationName COMMON_OID.10 */ 0x0B, /* organizationalUnitName COMMON_OID.11 */ + 0x0C, /* title COMMON_OID.10 */ 0x29, /* name COMMON_OID.41 */ + 0x2A, /* givenName COMMON_OID.42 */ 0x2B, /* initials COMMON_OID.43 */ + 0x2C, /* generationQualifier COMMON_OID.44 */ 0x2E, /* dnQualifer COMMON_OID.46 */ + 0x41, /* pseudonym COMMON_OID.65 */ + }; + // https://datatracker.ietf.org/doc/html/rfc5280, RFC 5280, Page 124 + // TODO Specification does not mention about the DN_QUALIFIER_OID max length. + // So the max limit is set at 64. + // For name the RFC 5280 supports up to 32768, as Javacard doesn't support + // that much length, the max limit for name is set to 128. + private static final byte[] attributeValueMaxLen = { + 0x40, /* 1-64 commonName */ + 0x28, /* 1-40 surname */ + 0x40, /* 1-64 serial */ + 0x02, /* 1-2 country */ + (byte) 0x80, /* 1-128 locality */ + (byte) 0x80, /* 1-128 state */ + 0x40, /* 1-64 organization */ + 0x40, /* 1-64 organization unit*/ + 0x40, /* 1-64 title */ + 0x29, /* 1-128 name */ + 0x10, /* 1-16 givenName */ + 0x05, /* 1-5 initials */ + 0x03, /* 1-3 gen qualifier */ + 0x40, /* 1-64 dn-qualifier */ + (byte) 0x80 /* 1-128 pseudonym */ + }; + private static KMAsn1Parser inst; + // https://datatracker.ietf.org/doc/html/rfc5280, RFC 5280, Page 21 + // 2.5.4 + public byte[] COMMON_OID = new byte[] {0x06, 0x03, 0x55, 0x04}; + public byte[] EMAIL_ADDRESS_OID = + new byte[] { + 0x06, 0x09, 0x2A, (byte) 0x86, 0x48, (byte) 0x86, (byte) 0xF7, 0x0D, 0x01, 0x09, 0x01 + }; + private byte[] data; + private short[] dataInfo; + + private KMAsn1Parser() { + dataInfo = JCSystem.makeTransientShortArray((short) 3, JCSystem.CLEAR_ON_RESET); + dataInfo[DATA_START_OFFSET] = 0; + dataInfo[DATA_LENGTH_OFFSET] = 0; + dataInfo[DATA_CURSOR_OFFSET] = 0; + } + + public static KMAsn1Parser instance() { + if (inst == null) { + inst = new KMAsn1Parser(); + } + return inst; + } + + public short decodeRsa(short blob) { + init(blob); + decodeCommon((short) 0, RSA_ALGORITHM); + return decodeRsaPrivateKey((short) 0); + } + + public short decodeEc(short blob) { + init(blob); + decodeCommon((short) 0, EC_ALGORITHM); + return decodeEcPrivateKey((short) 1); + } + + /* + Name ::= CHOICE { -- only one possibility for now -- + rdnSequence RDNSequence } + RDNSequence ::= SEQUENCE OF RelativeDistinguishedName + RelativeDistinguishedName ::= + SET SIZE (1..MAX) OF AttributeTypeAndValue + AttributeTypeAndValue ::= SEQUENCE { + type AttributeType, + value AttributeValue } + AttributeType ::= OBJECT IDENTIFIER + AttributeValue ::= ANY -- DEFINED BY AttributeType + */ + public void validateDerSubject(short blob) { + init(blob); + header(ASN1_SEQUENCE); + while (dataInfo[DATA_CURSOR_OFFSET] + < ((short) (dataInfo[DATA_START_OFFSET] + dataInfo[DATA_LENGTH_OFFSET]))) { + header(ASN1_SET); + header(ASN1_SEQUENCE); + // Parse and validate OBJECT-IDENTIFIER and Value fields + // Cursor is incremented in validateAttributeTypeAndValue. + validateAttributeTypeAndValue(); + } + } + + public short decodeEcSubjectPublicKeyInfo(short blob) { + init(blob); + header(ASN1_SEQUENCE); + short len = header(ASN1_SEQUENCE); + short ecPublicInfo = KMByteBlob.instance(len); + getBytes(ecPublicInfo); + if (Util.arrayCompare( + KMByteBlob.cast(ecPublicInfo).getBuffer(), + KMByteBlob.cast(ecPublicInfo).getStartOff(), + EC_ALGORITHM, + (short) 0, + KMByteBlob.cast(ecPublicInfo).length()) + != 0) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + len = header(ASN1_BIT_STRING); + if (len < 1) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + // TODO need to handle if unused bits are not zero + byte unusedBits = getByte(); + if (unusedBits != 0) { + KMException.throwIt(KMError.UNIMPLEMENTED); + } + short pubKey = KMByteBlob.instance((short) (len - 1)); + getBytes(pubKey); + return pubKey; + } + + // Seq[Int,Int,Int,Int,<ignore rest>] + public short decodeRsaPrivateKey(short version) { + short resp = KMArray.instance((short) 3); + header(ASN1_OCTET_STRING); + header(ASN1_SEQUENCE); + short len = header(ASN1_INTEGER); + if (len != 1) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + short ver = getByte(); + if (ver != version) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + len = header(ASN1_INTEGER); + short modulus = KMByteBlob.instance(len); + getBytes(modulus); + updateRsaKeyBuffer(modulus); + len = header(ASN1_INTEGER); + short pubKey = KMByteBlob.instance(len); + getBytes(pubKey); + len = header(ASN1_INTEGER); + short privKey = KMByteBlob.instance(len); + getBytes(privKey); + updateRsaKeyBuffer(privKey); + KMArray.cast(resp).add((short) 0, modulus); + KMArray.cast(resp).add((short) 1, pubKey); + KMArray.cast(resp).add((short) 2, privKey); + return resp; + } + + private void updateRsaKeyBuffer(short blob) { + byte[] buffer = KMByteBlob.cast(blob).getBuffer(); + short startOff = KMByteBlob.cast(blob).getStartOff(); + short len = KMByteBlob.cast(blob).length(); + if (0 == buffer[startOff] && len > 256) { + KMByteBlob.cast(blob).setStartOff(++startOff); + KMByteBlob.cast(blob).setLength(--len); + } + } + + private short readEcdsa256SigIntegerHeader() { + short len = header(ASN1_INTEGER); + if (len == 33) { + if (0 != getByte()) { + KMException.throwIt(KMError.INVALID_DATA); + } + len--; + } else if (len > 33) { + KMException.throwIt(KMError.INVALID_DATA); + } + return len; + } + + // Seq [Int, Int] + public short decodeEcdsa256Signature(short blob, byte[] scratchPad, short scratchPadOff) { + init(blob); + short len = header(ASN1_SEQUENCE); + len = readEcdsa256SigIntegerHeader(); + // concatenate r and s in the buffer (r||s) + Util.arrayFillNonAtomic(scratchPad, scratchPadOff, (short) 64, (byte) 0); + // read r + getBytes(scratchPad, (short) (scratchPadOff + 32 - len), len); + len = readEcdsa256SigIntegerHeader(); + // read s + getBytes(scratchPad, (short) (scratchPadOff + 64 - len), len); + return (short) 64; + } + + // Seq [Int, Blob] + public void decodeCommon(short version, byte[] alg) { + short len = header(ASN1_SEQUENCE); + len = header(ASN1_INTEGER); + if (len != 1) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + short ver = getByte(); + if (ver != version) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + len = header(ASN1_SEQUENCE); + short blob = KMByteBlob.instance(len); + getBytes(blob); + if (Util.arrayCompare( + KMByteBlob.cast(blob).getBuffer(), + KMByteBlob.cast(blob).getStartOff(), + alg, + (short) 0, + KMByteBlob.cast(blob).length()) + != 0) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + } + + // Seq[Int,blob,blob] + public short decodeEcPrivateKey(short version) { + short resp = KMArray.instance((short) 2); + header(ASN1_OCTET_STRING); + header(ASN1_SEQUENCE); + short len = header(ASN1_INTEGER); + if (len != 1) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + short ver = getByte(); + if (ver != version) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + len = header(ASN1_OCTET_STRING); + short privKey = KMByteBlob.instance(len); + getBytes(privKey); + validateTag0IfPresent(); + header(ASN1_A1_TAG); + len = header(ASN1_BIT_STRING); + if (len < 1) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + // TODO need to handle if unused bits are not zero + byte unusedBits = getByte(); + if (unusedBits != 0) { + KMException.throwIt(KMError.UNIMPLEMENTED); + } + short pubKey = KMByteBlob.instance((short) (len - 1)); + getBytes(pubKey); + KMArray.cast(resp).add((short) 0, pubKey); + KMArray.cast(resp).add((short) 1, privKey); + return resp; + } + + private void validateTag0IfPresent() { + if (data[dataInfo[DATA_CURSOR_OFFSET]] != ASN1_A0_TAG) { + return; + } + ; + short len = header(ASN1_A0_TAG); + if (len != EC_CURVE.length) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + if (Util.arrayCompare(data, dataInfo[DATA_CURSOR_OFFSET], EC_CURVE, (short) 0, len) != 0) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + incrementCursor(len); + } + + private void validateAttributeTypeAndValue() { + // First byte should be OBJECT_IDENTIFIER, otherwise it is not well-formed DER Subject. + if (data[dataInfo[DATA_CURSOR_OFFSET]] != OBJECT_IDENTIFIER) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + // Check if the OID matches the email address + if ((Util.arrayCompare( + data, + dataInfo[DATA_CURSOR_OFFSET], + EMAIL_ADDRESS_OID, + (short) 0, + (short) EMAIL_ADDRESS_OID.length) + == 0)) { + incrementCursor((short) EMAIL_ADDRESS_OID.length); + // Validate the length of the attribute value. + if (getByte() != IA5_STRING) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + short emailLength = getLength(); + if (emailLength <= 0 && emailLength > MAX_EMAIL_ADD_LEN) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + incrementCursor(emailLength); + return; + } + // Check other OIDs. + for (short i = 0; i < (short) attributeOIds.length; i++) { + if ((Util.arrayCompare( + data, + dataInfo[DATA_CURSOR_OFFSET], + COMMON_OID, + (short) 0, + (short) COMMON_OID.length) + == 0) + && (attributeOIds[i] + == data[(short) (dataInfo[DATA_CURSOR_OFFSET] + COMMON_OID.length)])) { + incrementCursor((short) (COMMON_OID.length + 1)); + // Validate the length of the attribute value. + short tag = getByte(); + if (tag != ASN1_UTF8_STRING + && tag != ASN1_TELETEX_STRING + && tag != ASN1_PRINTABLE_STRING + && tag != ASN1_UNIVERSAL_STRING + && tag != ASN1_BMP_STRING) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + short attrValueLength = getLength(); + if (attrValueLength <= 0 && attrValueLength > attributeValueMaxLen[i]) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + incrementCursor(attrValueLength); + return; + } + } + // If no match is found above then move the cursor to next element. + getByte(); // Move Cursor by one byte (OID) + incrementCursor(getLength()); // Move cursor to AtrributeTag + getByte(); // Move cursor to AttributeValue + incrementCursor(getLength()); // Move cursor to next SET element + } + + private short header(short tag) { + short t = getByte(); + if (t != tag) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + return getLength(); + } + + private byte getByte() { + byte d = data[dataInfo[DATA_CURSOR_OFFSET]]; + incrementCursor((short) 1); + return d; + } + + private short getShort() { + short d = Util.getShort(data, dataInfo[DATA_CURSOR_OFFSET]); + incrementCursor((short) 2); + return d; + } + + private void getBytes(short blob) { + short len = KMByteBlob.cast(blob).length(); + Util.arrayCopyNonAtomic( + data, + dataInfo[DATA_CURSOR_OFFSET], + KMByteBlob.cast(blob).getBuffer(), + KMByteBlob.cast(blob).getStartOff(), + len); + incrementCursor(len); + } + + private void getBytes(byte[] buffer, short offset, short len) { + Util.arrayCopyNonAtomic(data, dataInfo[DATA_CURSOR_OFFSET], buffer, offset, len); + incrementCursor(len); + } + + private short getLength() { + byte len = getByte(); + if (len >= 0) { + return len; + } + len = (byte) (len & 0x7F); + if (len == 1) { + return (short) (getByte() & 0xFF); + } else if (len == 2) { + return getShort(); + } else { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + return KMType.INVALID_VALUE; // should not come here + } + + public void init(short blob) { + data = KMByteBlob.cast(blob).getBuffer(); + dataInfo[DATA_START_OFFSET] = KMByteBlob.cast(blob).getStartOff(); + dataInfo[DATA_LENGTH_OFFSET] = KMByteBlob.cast(blob).length(); + dataInfo[DATA_CURSOR_OFFSET] = dataInfo[DATA_START_OFFSET]; + } + + public void incrementCursor(short n) { + dataInfo[DATA_CURSOR_OFFSET] += n; + if (dataInfo[DATA_CURSOR_OFFSET] + > ((short) (dataInfo[DATA_START_OFFSET] + dataInfo[DATA_LENGTH_OFFSET]))) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMBignumTag.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMBignumTag.java new file mode 100644 index 0000000..5eb7eae --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMBignumTag.java @@ -0,0 +1,110 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMBignumTag represents BIGNUM Tag Type from android keymaster hal specifications. The tag value + * of this tag is the KMByteBlob pointer i.e. offset of KMByteBlob in memory heap. struct{byte + * TAG_TYPE; short length; struct{short BIGNUM_TAG; short tagKey; short blobPtr}} + */ +public class KMBignumTag extends KMTag { + + private static KMBignumTag prototype; + + private KMBignumTag() {} + + private static KMBignumTag proto(short ptr) { + if (prototype == null) { + prototype = new KMBignumTag(); + } + KMType.instanceTable[KM_BIGNUM_TAG_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short blobPtr = KMByteBlob.exp(); + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), BIGNUM_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), INVALID_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), blobPtr); + return ptr; + } + + public static short instance(short key, short byteBlob) { + if (!validateKey(key, byteBlob)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (heap[byteBlob] != BYTE_BLOB_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), BIGNUM_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), key); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), byteBlob); + return ptr; + } + + public static KMBignumTag cast(short ptr) { + if (heap[ptr] != TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)) != BIGNUM_TAG) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + private static boolean validateKey(short key, short byteBlob) { + short valueLen = KMByteBlob.cast(byteBlob).length(); + switch (key) { + case CERTIFICATE_SERIAL_NUM: + if (valueLen > MAX_CERTIFICATE_SERIAL_SIZE) { + return false; + } + break; + default: + return false; + } + return true; + } + + public short getKey() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_BIGNUM_TAG_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short getTagType() { + return KMType.BIGNUM_TAG; + } + + public short getValue() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_BIGNUM_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + } + + public short length() { + short blobPtr = + Util.getShort( + heap, (short) (KMType.instanceTable[KM_BIGNUM_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + return KMByteBlob.cast(blobPtr).length(); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMBoolTag.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMBoolTag.java new file mode 100644 index 0000000..27730a5 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMBoolTag.java @@ -0,0 +1,115 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import com.android.javacard.seprovider.KMException; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMBoolTag represents BOOL TAG type from the android keymaster hal specifications. If it is + * present in the key parameter list then its value is always true. A KMTag always requires a value + * because it is a key value pair. The bool tag always has 0x01 as its value. struct{byte TAG_TYPE; + * short length; struct{short BOOL_TAG; short tagKey; byte value 1}} + */ +public class KMBoolTag extends KMTag { + + // The allowed tag keys of type bool tag. + private static final short[] tags = { + CALLER_NONCE, + INCLUDE_UNIQUE_ID, + BOOTLOADER_ONLY, + ROLLBACK_RESISTANCE, + NO_AUTH_REQUIRED, + ALLOW_WHILE_ON_BODY, + TRUSTED_USER_PRESENCE_REQUIRED, + TRUSTED_CONFIRMATION_REQUIRED, + UNLOCKED_DEVICE_REQUIRED, + RESET_SINCE_ID_ROTATION, + EARLY_BOOT_ONLY, + DEVICE_UNIQUE_ATTESTATION + }; + private static KMBoolTag prototype; + + private KMBoolTag() {} + + private static KMBoolTag proto(short ptr) { + if (prototype == null) { + prototype = new KMBoolTag(); + } + KMType.instanceTable[KM_BOOL_TAG_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short ptr = instance(TAG_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), BOOL_TAG); + return ptr; + } + + public static short instance(short key) { + if (!validateKey(key)) { + KMException.throwIt(KMError.INVALID_TAG); + } + short ptr = KMType.instance(TAG_TYPE, (short) 5); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), BOOL_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), key); + // Value is always 1. + heap[(short) (ptr + TLV_HEADER_SIZE + 4)] = 0x01; + return ptr; + } + + public static KMBoolTag cast(short ptr) { + if (heap[ptr] != TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)) != BOOL_TAG) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + // validate the tag key. + private static boolean validateKey(short key) { + short index = (short) tags.length; + while (--index >= 0) { + if (tags[index] == key) { + return true; + } + } + return false; + } + + public static short[] getTags() { + return tags; + } + + public short getKey() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_BOOL_TAG_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short getTagType() { + return KMType.BOOL_TAG; + } + + public byte getVal() { + return heap[(short) (KMType.instanceTable[KM_BOOL_TAG_OFFSET] + TLV_HEADER_SIZE + 4)]; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMByteBlob.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMByteBlob.java new file mode 100644 index 0000000..98d81fc --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMByteBlob.java @@ -0,0 +1,142 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMByteBlob represents contiguous block of bytes. It corresponds to CBOR type of Byte String. It + * extends KMType by specifying value field as zero or more sequence of bytes. struct{byte + * BYTE_BLOB_TYPE; short length; sequence of bytes} + */ +public class KMByteBlob extends KMType { + + private static byte OFFSET_SIZE = 2; + private static KMByteBlob prototype; + + protected KMByteBlob() {} + + private static KMByteBlob proto(short ptr) { + if (prototype == null) { + prototype = new KMByteBlob(); + } + KMType.instanceTable[KM_BYTE_BLOB_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + return KMType.exp(BYTE_BLOB_TYPE); + } + + // return an empty byte blob instance + public static short instance(short length) { + short ptr = KMType.instance(BYTE_BLOB_TYPE, (short) (length + 2)); + Util.setShort( + heap, (short) (ptr + TLV_HEADER_SIZE), (short) (ptr + TLV_HEADER_SIZE + OFFSET_SIZE)); + Util.setShort(heap, (short) (ptr + 1), length); + return ptr; + } + + // byte blob from existing buf + public static short instance(byte[] buf, short startOff, short length) { + short ptr = instance(length); + Util.arrayCopyNonAtomic( + buf, startOff, heap, (short) (ptr + TLV_HEADER_SIZE + OFFSET_SIZE), length); + return ptr; + } + + // cast the ptr to KMByteBlob + public static KMByteBlob cast(short ptr) { + if (heap[ptr] != BYTE_BLOB_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + 1)) == INVALID_VALUE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + // Add the byte + public void add(short index, byte val) { + short len = length(); + if (index >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + heap[(short) (getStartOff() + index)] = val; + } + + // Get the byte + public byte get(short index) { + short len = length(); + if (index >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + return heap[(short) (getStartOff() + index)]; + } + + // Get the start of blob + public short getStartOff() { + return Util.getShort(heap, (short) (getBaseOffset() + TLV_HEADER_SIZE)); + } + + public void setStartOff(short offset) { + Util.setShort(heap, (short) (getBaseOffset() + TLV_HEADER_SIZE), offset); + } + + // Get the length of the blob + public short length() { + return Util.getShort(heap, (short) (getBaseOffset() + 1)); + } + + // Get the buffer pointer in which blob is contained. + public byte[] getBuffer() { + return heap; + } + + public void getValue(byte[] destBuf, short destStart, short destLength) { + Util.arrayCopyNonAtomic(heap, getStartOff(), destBuf, destStart, destLength); + } + + public short getValues(byte[] destBuf, short destStart) { + short destLength = length(); + Util.arrayCopyNonAtomic(heap, getStartOff(), destBuf, destStart, destLength); + return destLength; + } + + public void setValue(byte[] srcBuf, short srcStart, short srcLength) { + if (length() < srcLength) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + Util.arrayCopyNonAtomic(srcBuf, srcStart, heap, getStartOff(), srcLength); + setLength(srcLength); + } + + public boolean isValid() { + return (length() != 0); + } + + protected short getBaseOffset() { + return instanceTable[KM_BYTE_BLOB_OFFSET]; + } + + public void setLength(short len) { + Util.setShort(heap, (short) (getBaseOffset() + 1), len); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMByteTag.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMByteTag.java new file mode 100644 index 0000000..5f7231f --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMByteTag.java @@ -0,0 +1,146 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMByteTag represents BYTES Tag Type from android keymaster hal specifications. The tag value of + * this tag is the KMByteBlob pointer i.e. offset of KMByteBlob in memory heap. struct{byte + * TAG_TYPE; short length; struct{short BYTES_TAG; short tagKey; short blobPtr}} + */ +public class KMByteTag extends KMTag { + + private static KMByteTag prototype; + + private KMByteTag() {} + + private static KMByteTag proto(short ptr) { + if (prototype == null) { + prototype = new KMByteTag(); + } + KMType.instanceTable[KM_BYTE_TAG_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short blobPtr = KMByteBlob.exp(); + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), BYTES_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), INVALID_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), blobPtr); + return ptr; + } + + public static short instance(short key, short byteBlob) { + if (!validateKey(key, byteBlob)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (heap[byteBlob] != BYTE_BLOB_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), BYTES_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), key); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), byteBlob); + return ptr; + } + + public static KMByteTag cast(short ptr) { + if (heap[ptr] != TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)) != BYTES_TAG) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + private static boolean validateKey(short key, short byteBlob) { + short valueLen = KMByteBlob.cast(byteBlob).length(); + switch (key) { + case ATTESTATION_APPLICATION_ID: + if (valueLen > MAX_ATTESTATION_APP_ID_SIZE) { + return false; + } + break; + case CERTIFICATE_SUBJECT_NAME: + { + if (valueLen > KMConfigurations.MAX_SUBJECT_DER_LEN) { + return false; + } + KMAsn1Parser asn1Decoder = KMAsn1Parser.instance(); + asn1Decoder.validateDerSubject(byteBlob); + } + break; + case APPLICATION_ID: + case APPLICATION_DATA: + if (valueLen > MAX_APP_ID_APP_DATA_SIZE) { + return false; + } + break; + case ATTESTATION_CHALLENGE: + if (valueLen > MAX_ATTESTATION_CHALLENGE_SIZE) { + return false; + } + break; + case ATTESTATION_ID_BRAND: + case ATTESTATION_ID_DEVICE: + case ATTESTATION_ID_PRODUCT: + case ATTESTATION_ID_SERIAL: + case ATTESTATION_ID_IMEI: + case ATTESTATION_ID_SECOND_IMEI: + case ATTESTATION_ID_MEID: + case ATTESTATION_ID_MANUFACTURER: + case ATTESTATION_ID_MODEL: + if (valueLen > KMConfigurations.MAX_ATTESTATION_IDS_SIZE) { + return false; + } + break; + case ROOT_OF_TRUST: + case NONCE: + break; + default: + return false; + } + return true; + } + + public short getKey() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_BYTE_TAG_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short getTagType() { + return KMType.BYTES_TAG; + } + + public short getValue() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_BYTE_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + } + + public short length() { + short blobPtr = + Util.getShort( + heap, (short) (KMType.instanceTable[KM_BYTE_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + return KMByteBlob.cast(blobPtr).length(); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCose.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCose.java new file mode 100644 index 0000000..bcfb5f8 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCose.java @@ -0,0 +1,513 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; + +/** + * This class constructs the Cose messages like CoseKey, CoseMac0, MacStructure, CoseSign1, + * SignStructure, CoseEncrypt, EncryptStructure and ReceipientStructures. + */ +public class KMCose { + + // COSE SIGN1 + public static final byte COSE_SIGN1_ENTRY_COUNT = 4; + public static final byte COSE_SIGN1_PROTECTED_PARAMS_OFFSET = 0; + public static final byte COSE_SIGN1_PAYLOAD_OFFSET = 2; + public static final byte COSE_SIGN1_SIGNATURE_OFFSET = 3; + // COSE MAC0 + public static final byte COSE_MAC0_ENTRY_COUNT = 4; + public static final byte COSE_MAC0_PROTECTED_PARAMS_OFFSET = 0; + public static final byte COSE_MAC0_PAYLOAD_OFFSET = 2; + public static final byte COSE_MAC0_TAG_OFFSET = 3; + // COSE ENCRYPT + public static final byte COSE_ENCRYPT_ENTRY_COUNT = 4; + public static final byte COSE_ENCRYPT_STRUCTURE_ENTRY_COUNT = 3; + public static final byte COSE_ENCRYPT_RECIPIENT_ENTRY_COUNT = 3; + + // COSE Labels + public static final byte COSE_LABEL_ALGORITHM = 1; + public static final byte COSE_LABEL_KEYID = 4; + public static final byte COSE_LABEL_IV = 5; + public static final byte COSE_LABEL_COSE_KEY = (byte) 0xFF; // -1 + + // COSE Algorithms + public static final byte COSE_ALG_AES_GCM_256 = 3; // AES-GCM mode w/ 256-bit key, 128-bit tag. + public static final byte COSE_ALG_HMAC_256 = 5; // HMAC w/ SHA-256 + public static final byte COSE_ALG_ES256 = (byte) 0xF9; // ECDSA w/ SHA-256; -7 + public static final byte COSE_ALG_ECDH_ES_HKDF_256 = (byte) 0xE7; // ECDH-EC+HKDF-256; -25 + + // COSE P256 EC Curve + public static final byte COSE_ECCURVE_256 = 1; + + // COSE key types + public static final byte COSE_KEY_TYPE_EC2 = 2; + public static final byte COSE_KEY_TYPE_SYMMETRIC_KEY = 4; + + // COSE Key Operations + public static final byte COSE_KEY_OP_SIGN = 1; + public static final byte COSE_KEY_OP_VERIFY = 2; + public static final byte COSE_KEY_OP_ENCRYPT = 3; + public static final byte COSE_KEY_OP_DECRYPT = 4; + + // AES GCM + public static final short AES_GCM_KEY_SIZE_BITS = 256; + // Cose key parameters. + public static final byte COSE_KEY_KEY_TYPE = 1; + public static final byte COSE_KEY_KEY_ID = 2; + public static final byte COSE_KEY_ALGORITHM = 3; + public static final byte COSE_KEY_CURVE = -1; + public static final byte COSE_KEY_PUBKEY_X = -2; + public static final byte COSE_KEY_PUBKEY_Y = -3; + public static final byte COSE_KEY_PRIV_KEY = -4; + public static final byte[] COSE_TEST_KEY = { + (byte) 0xFF, (byte) 0xFE, (byte) 0xEE, (byte) 0x90 + }; // -70000 + public static final byte COSE_KEY_MAX_SIZE = 4; + + // kdfcontext strings + public static final byte[] client = {0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74}; + public static final byte[] server = {0x73, 0x65, 0x72, 0x76, 0x65, 0x72}; + // Context strings + public static final byte[] MAC_CONTEXT = {0x4d, 0x41, 0x43, 0x30}; // MAC0 + public static final byte[] SIGNATURE1_CONTEXT = { + 0x53, 0x69, 0x67, 0x6E, 0x61, 0x74, 0x75, 0x72, 0x65, 0x31 + }; // Signature1 + // Certificate payload supported keys + public static final byte ISSUER = (byte) 0x01; + public static final byte SUBJECT = (byte) 0x02; + public static final byte[] SUBJECT_PUBLIC_KEY = { + (byte) 0xFF, (byte) 0xB8, (byte) 0xBB, (byte) 0xA8 + }; + public static final byte[] KEY_USAGE = {(byte) 0xFF, (byte) 0xB8, (byte) 0xBB, (byte) 0xA7}; + // text strings + public static final byte[] TEST_ISSUER_NAME = { + (byte) 0x49, 0x73, 0x73, 0x75, 0x65, 0x72 + }; // "Issuer" + public static final byte[] TEST_SUBJECT_NAME = { + 0x53, 0x75, 0x62, 0x6A, 0x65, 0x63, 0x74 + }; // "Subject" + public static final byte[] KEY_USAGE_SIGN = {0x20}; // Key usage sign + + public static final short[] COSE_KEY_LABELS = { + KMCose.COSE_KEY_KEY_TYPE, + KMCose.COSE_KEY_KEY_ID, + KMCose.COSE_KEY_ALGORITHM, + KMCose.COSE_KEY_CURVE, + KMCose.COSE_KEY_PUBKEY_X, + KMCose.COSE_KEY_PUBKEY_Y, + KMCose.COSE_KEY_PRIV_KEY + }; + public static final short[] COSE_HEADER_LABELS = { + KMCose.COSE_LABEL_ALGORITHM, + KMCose.COSE_LABEL_KEYID, + KMCose.COSE_LABEL_IV, + KMCose.COSE_LABEL_COSE_KEY + }; + + /** + * Constructs the Cose MAC structure. + * + * @param protectedHeader Bstr pointer which holds the protected header. + * @param extAad Bstr pointer which holds the external Aad. + * @param payload Bstr pointer which holds the payload of the MAC structure. + * @return KMArray instance of MAC structure. + */ + public static short constructCoseMacStructure( + short protectedHeader, short extAad, short payload) { + // Create MAC Structure and compute HMAC as per https://tools.ietf.org/html/rfc8152#section-6.3 + // MAC_structure = [ + // context : "MAC" / "MAC0", + // protected : empty_or_serialized_map, + // external_aad : bstr, + // payload : bstr + // ] + short arrPtr = KMArray.instance(KMCose.COSE_MAC0_ENTRY_COUNT); + // 1 - Context + KMArray.cast(arrPtr) + .add( + (short) 0, + KMTextString.instance( + KMCose.MAC_CONTEXT, (short) 0, (short) KMCose.MAC_CONTEXT.length)); + // 2 - Protected headers. + KMArray.cast(arrPtr).add((short) 1, protectedHeader); + // 3 - external aad + KMArray.cast(arrPtr).add((short) 2, extAad); + // 4 - payload. + KMArray.cast(arrPtr).add((short) 3, payload); + return arrPtr; + } + + /** + * Constructs the COSE_MAC0 object. + * + * @param protectedHeader Bstr pointer which holds the protected header. + * @param unprotectedHeader Bstr pointer which holds the unprotected header. + * @param payload Bstr pointer which holds the payload of the MAC structure. + * @param tag Bstr pointer which holds the tag value. + * @return KMArray instance of COSE_MAC0 object. + */ + public static short constructCoseMac0( + short protectedHeader, short unprotectedHeader, short payload, short tag) { + // Construct Cose_MAC0 + // COSE_Mac0 = [ + // protectedHeader, + // unprotectedHeader, + // payload : bstr / nil, + // tag : bstr, + // ] + short arrPtr = KMArray.instance(KMCose.COSE_MAC0_ENTRY_COUNT); + // 1 - protected headers + KMArray.cast(arrPtr).add((short) 0, protectedHeader); + // 2 - unprotected headers + KMArray.cast(arrPtr).add((short) 1, unprotectedHeader); + // 2 - payload + KMArray.cast(arrPtr).add((short) 2, payload); + // 3 - tag + KMArray.cast(arrPtr).add((short) 3, tag); + // Do encode. + return arrPtr; + } + + /** + * Constructs the COSE_Signature structure. + * + * @param protectedHeader Bstr pointer which holds the protected header. + * @param extAad Bstr pointer which holds the aad. + * @param payload Bstr pointer which holds the payload. + * @return KMArray instance of COSE_Signature object. + */ + public static short constructCoseSignStructure( + short protectedHeader, short extAad, short payload) { + // Sig_structure = [ + // context : "Signature" / "Signature1" / "CounterSignature", + // body_protected : empty_or_serialized_map, + // ? sign_protected : empty_or_serialized_map, + // external_aad : bstr, + // payload : bstr + // ] + short arrPtr = KMArray.instance(KMCose.COSE_SIGN1_ENTRY_COUNT); + // 1 - Context + KMArray.cast(arrPtr) + .add( + (short) 0, + KMTextString.instance( + KMCose.SIGNATURE1_CONTEXT, (short) 0, (short) KMCose.SIGNATURE1_CONTEXT.length)); + // 2 - Protected headers. + KMArray.cast(arrPtr).add((short) 1, protectedHeader); + // 3 - external aad + KMArray.cast(arrPtr).add((short) 2, extAad); + // 4 - payload. + KMArray.cast(arrPtr).add((short) 3, payload); + return arrPtr; + } + + /** + * Constructs the COSE_Sign1 object. + * + * @param protectedHeader Bstr pointer which holds the protected header. + * @param unProtectedHeader Bstr pointer which holds the unprotected header. + * @param payload Bstr pointer which holds the payload. + * @param signature Bstr pointer which holds the signature. + * @return KMArray instance of COSE_Sign1 object. + */ + public static short constructCoseSign1( + short protectedHeader, short unProtectedHeader, short payload, short signature) { + // COSE_Sign = [ + // protectedHeader, + // unprotectedHeader, + // payload : bstr / nil, + // signatures : [+ COSE_Signature] + // ] + short arrPtr = KMArray.instance(KMCose.COSE_SIGN1_ENTRY_COUNT); + // 1 - protected headers + KMArray.cast(arrPtr).add((short) 0, protectedHeader); + // 2 - unprotected headers + KMArray.cast(arrPtr).add((short) 1, unProtectedHeader); + // 2 - payload + KMArray.cast(arrPtr).add((short) 2, payload); + // 3 - tag + KMArray.cast(arrPtr).add((short) 3, signature); + return arrPtr; + } + + /** + * Constructs array based on the tag values provided. + * + * @param tag array of tag values to be constructed. + * @param includeTestMode flag which indicates if TEST_COSE_KEY should be included or not. + * @return instance of KMArray. + */ + private static short handleCosePairTags( + short[] tag, short[] keyValues, short valueIndex, boolean includeTestMode) { + short index = 0; + // var is used to calculate the length of the array. + short var = 0; + short tagLen = (short) tag.length; + // var is used to calculate the length of the array. + while (index < tagLen) { + if (keyValues[index] != KMType.INVALID_VALUE) { + keyValues[(short) (index + valueIndex)] = + buildCosePairTag((byte) tag[index], keyValues[index]); + var++; + } + index++; + } + var += includeTestMode ? 1 : 0; + short arrPtr = KMArray.instance(var); + index = 0; + // var is used to index the array. + var = 0; + while (index < tagLen) { + if (keyValues[(short) (index + valueIndex)] != KMType.INVALID_VALUE) { + KMArray.cast(arrPtr).add(var++, keyValues[(short) (index + valueIndex)]); + } + index++; + } + return arrPtr; + } + + /** + * Constructs the COSE_sign1 payload for certificate. + * + * @param issuer instance of KMCosePairTextStringTag which contains issuer value. + * @param subject instance of KMCosePairTextStringTag which contains subject value. + * @param subPublicKey instance of KMCosePairByteBlobTag which contains encoded KMCoseKey. + * @param keyUsage instance of KMCosePairByteBlobTag which contains key usage value. + * @return instance of KMArray. + */ + public static short constructCoseCertPayload( + short issuer, short subject, short subPublicKey, short keyUsage) { + short certPayload = KMArray.instance((short) 4); + KMArray.cast(certPayload).add((short) 0, issuer); + KMArray.cast(certPayload).add((short) 1, subject); + KMArray.cast(certPayload).add((short) 2, subPublicKey); + KMArray.cast(certPayload).add((short) 3, keyUsage); + certPayload = KMCoseCertPayload.instance(certPayload); + KMCoseCertPayload.cast(certPayload).canonicalize(); + return certPayload; + } + + /** + * Construct headers structure. Headers can be part of COSE_Sign1, COSE_Encrypt, COSE_Mac0 and + * COSE_Key. + * + * @param alg instance of either KMNInteger or KMInteger, based on the sign of algorithm value. + * @param keyId instance of KMByteBlob which contains the key identifier. + * @param iv instance of KMByteblob which contains the iv buffer. + * @param ephemeralKey instance of KMCoseKey. + * @return instance of KMCoseHeaders. + */ + public static short constructHeaders( + short[] buff, short alg, short keyId, short iv, short ephemeralKey) { + buff[0] = alg; + buff[1] = keyId; + buff[2] = iv; + buff[3] = ephemeralKey; + for (short i = 4; i < 8; i++) { + buff[i] = KMType.INVALID_VALUE; + } + short ptr = handleCosePairTags(COSE_HEADER_LABELS, buff, (short) 4, false); + ptr = KMCoseHeaders.instance(ptr); + KMCoseHeaders.cast(ptr).canonicalize(); + return ptr; + } + + /** + * Constructs the instance of KMCosePair*Tag. + * + * @param key value of the key. + * @param valuePtr instance of one of KMType. + * @return instance of KMCosePair*Value object. + */ + public static short buildCosePairTag(byte key, short valuePtr) { + short type = KMType.getType(valuePtr); + short keyPtr; + if (key < 0) { + keyPtr = KMNInteger.uint_8(key); + } else { + keyPtr = KMInteger.uint_8(key); + } + switch (type) { + case KMType.INTEGER_TYPE: + return KMCosePairIntegerTag.instance(keyPtr, valuePtr); + case KMType.NEG_INTEGER_TYPE: + return KMCosePairNegIntegerTag.instance(keyPtr, valuePtr); + case KMType.BYTE_BLOB_TYPE: + return KMCosePairByteBlobTag.instance(keyPtr, valuePtr); + case KMType.TEXT_STRING_TYPE: + return KMCosePairTextStringTag.instance(keyPtr, valuePtr); + case KMType.COSE_KEY_TYPE: + return KMCosePairCoseKeyTag.instance(keyPtr, valuePtr); + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + return 0; + } + } + + /** + * Constructs a CoseKey with the provided input parameters. Note that construction of the key_ops + * label is not needed to be supported. In the KeyMint3.0 specifications: The CoseKey inside + * MacedPublicKeys and DiceCertChain does not have key_ops label. + * + * @param keyType Instance of the identification of the key type. + * @param keyId Instance of key identification value. + * @param keyAlg Instance of the algorithm that is used with this key. + * @param curve Instance of the EC curve that is used with this key. + * @param pubKey Buffer containing the public key. + * @param pubKeyOff Start offset of the buffer. + * @param pubKeyLen Length of the public key. + * @param privKeyPtr Instance of the private key. + * @param testMode Represents if key is used in test mode or production mode. + * @return Instance of the CoseKey structure. + */ + public static short constructCoseKey( + short[] buff, + short keyType, + short keyId, + short keyAlg, + short curve, + byte[] pubKey, + short pubKeyOff, + short pubKeyLen, + short privKeyPtr, + boolean testMode) { + if (pubKey[pubKeyOff] == 0x04) { // uncompressed format + pubKeyOff += 1; + pubKeyLen -= 1; + } + pubKeyLen = (short) (pubKeyLen / 2); + short xPtr = KMByteBlob.instance(pubKey, pubKeyOff, pubKeyLen); + short yPtr = KMByteBlob.instance(pubKey, (short) (pubKeyOff + pubKeyLen), pubKeyLen); + short coseKey = + constructCoseKey(buff, keyType, keyId, keyAlg, curve, xPtr, yPtr, privKeyPtr, testMode); + KMCoseKey.cast(coseKey).canonicalize(); + return coseKey; + } + + /** + * Constructs the cose key based on input parameters supplied. All the parameters must be + * instantiated from either KMInteger or KMNInteger or KMByteblob types. + * + * @param keyType instance of KMInteger/KMNInteger which holds valid COSE key types. + * @param keyId instance of KMByteBlob which holds key identifier value. + * @param keyAlg instance of KMInteger/KMNInteger which holds valid COSE key algorithm. + * @param curve instance of KMInteger/KMNInteger which holds valid COSE EC curve. + * @param pubX instance of KMByteBlob which holds EC public key's x value. + * @param pubY instance of KMByteBlob which holds EC public key's y value. + * @param priv instance of KMByteBlob which holds EC private value. + * @param includeTestKey flag which identifies whether to construct test key or production key. + * @return instance of the KMCoseKey object. + */ + public static short constructCoseKey( + short[] buff, + short keyType, + short keyId, + short keyAlg, + short curve, + short pubX, + short pubY, + short priv, + boolean includeTestKey) { + short valueIndex = 7; + buff[0] = keyType; + buff[1] = keyId; + buff[2] = keyAlg; + buff[3] = curve; + buff[4] = pubX; + buff[5] = pubY; + buff[6] = priv; + for (short i = valueIndex; i < 16; i++) { + buff[i] = KMType.INVALID_VALUE; + } + short arrPtr = handleCosePairTags(COSE_KEY_LABELS, buff, valueIndex, includeTestKey); + if (includeTestKey) { + short testKey = + KMCosePairSimpleValueTag.instance( + KMNInteger.uint_32(KMCose.COSE_TEST_KEY, (short) 0), + KMSimpleValue.instance(KMSimpleValue.NULL)); + KMArray.cast(arrPtr).add((short) (KMArray.cast(arrPtr).length() - 1), testKey); + } + arrPtr = KMCoseKey.instance(arrPtr); + KMCoseKey.cast(arrPtr).canonicalize(); + return arrPtr; + } + + /** + * Constructs key derivation context which is required to compute HKDF. + * + * @param publicKeyA public key buffer from the first party. + * @param publicKeyAOff start position of the public key buffer from first party. + * @param publicKeyALen length of the public key buffer from first party. + * @param publicKeyB public key buffer from the second party. + * @param publicKeyBOff start position of the public key buffer from second party. + * @param publicKeyBLen length of the public key buffer from second party. + * @param senderIsA true if caller is first party, false if caller is second party. + * @return instance of KMArray. + */ + public static short constructKdfContext( + byte[] publicKeyA, + short publicKeyAOff, + short publicKeyALen, + byte[] publicKeyB, + short publicKeyBOff, + short publicKeyBLen, + boolean senderIsA) { + short index = 0; + // Prepare sender info + short senderInfo = KMArray.instance((short) 3); + KMArray.cast(senderInfo) + .add(index++, KMByteBlob.instance(client, (short) 0, (short) client.length)); + KMArray.cast(senderInfo).add(index++, KMByteBlob.instance((short) 0)); + KMArray.cast(senderInfo) + .add( + index, + senderIsA + ? KMByteBlob.instance(publicKeyA, publicKeyAOff, publicKeyALen) + : KMByteBlob.instance(publicKeyB, publicKeyBOff, publicKeyBLen)); + + // Prepare recipient info + index = 0; + short recipientInfo = KMArray.instance((short) 3); + KMArray.cast(recipientInfo) + .add(index++, KMByteBlob.instance(server, (short) 0, (short) server.length)); + KMArray.cast(recipientInfo).add(index++, KMByteBlob.instance((short) 0)); + KMArray.cast(recipientInfo) + .add( + index, + senderIsA + ? KMByteBlob.instance(publicKeyB, publicKeyBOff, publicKeyBLen) + : KMByteBlob.instance(publicKeyA, publicKeyAOff, publicKeyALen)); + + // supply public info + index = 0; + short publicInfo = KMArray.instance((short) 2); + KMArray.cast(publicInfo).add(index++, KMInteger.uint_16(AES_GCM_KEY_SIZE_BITS)); + KMArray.cast(publicInfo).add(index, KMByteBlob.instance((short) 0)); + + // construct kdf context + index = 0; + short arrPtr = KMArray.instance((short) 4); + KMArray.cast(arrPtr).add(index++, KMInteger.uint_8(COSE_ALG_AES_GCM_256)); + KMArray.cast(arrPtr).add(index++, senderInfo); + KMArray.cast(arrPtr).add(index++, recipientInfo); + KMArray.cast(arrPtr).add(index, publicInfo); + + return arrPtr; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCoseCertPayload.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCoseCertPayload.java new file mode 100644 index 0000000..fff9cf8 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCoseCertPayload.java @@ -0,0 +1,136 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCoseCertPayload represents the COSE_Sign1 payload for each certificate in BCC. The supported + * key types are KMInteger, KMNInteger and the supported value types are KMByteBlob and + * KMTextString. It corresponds to a CBOR Map type. struct{byte TAG_TYPE; short length; short + * arrayPtr } where arrayPtr is a pointer to array with any KMCosePairTagType subtype instances. + */ +public class KMCoseCertPayload extends KMCoseMap { + + private static KMCoseCertPayload prototype; + + private KMCoseCertPayload() {} + + private static KMCoseCertPayload proto(short ptr) { + if (prototype == null) { + prototype = new KMCoseCertPayload(); + } + instanceTable[KM_COSE_CERT_PAYLOAD_OFFSET] = ptr; + return prototype; + } + + public static short exp() { + short arrPtr = KMArray.instance((short) 2); + KMArray arr = KMArray.cast(arrPtr); + arr.add((short) 0, KMCosePairTextStringTag.exp()); + arr.add((short) 1, KMCosePairByteBlobTag.exp()); + return KMCoseCertPayload.instance(arrPtr); + } + + public static short instance(short vals) { + short ptr = KMType.instance(COSE_CERT_PAYLOAD_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMCoseCertPayload cast(short ptr) { + if (heap[ptr] != COSE_CERT_PAYLOAD_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (heap[arrPtr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + @Override + public short getVals() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_CERT_PAYLOAD_OFFSET] + TLV_HEADER_SIZE)); + } + + @Override + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); + } + + @Override + public void canonicalize() { + KMCoseMap.canonicalize(getVals()); + } + + private short getValueType(short key, short significantKey) { + short arr = getVals(); + short length = length(); + short keyPtr; + short valPtr = 0; + short index = 0; + short tagType; + boolean found = false; + while (index < length) { + tagType = KMCosePairTagType.getTagValueType(KMArray.cast(arr).get(index)); + switch (tagType) { + case KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE: + keyPtr = KMCosePairByteBlobTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == KMCosePairTagType.getKeyValueShort(keyPtr) + && significantKey == KMCosePairTagType.getKeyValueSignificantShort(keyPtr)) { + valPtr = KMCosePairByteBlobTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + case KMType.COSE_PAIR_TEXT_STR_TAG_TYPE: + keyPtr = KMCosePairTextStringTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairTextStringTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + default: + break; + } + if (found) { + break; + } + index++; + } + return valPtr; + } + + public short getSubjectPublicKey() { + return getValueType( + Util.getShort(KMCose.SUBJECT_PUBLIC_KEY, (short) 2), // LSB + Util.getShort(KMCose.SUBJECT_PUBLIC_KEY, (short) 0) // MSB (Significant) + ); + } + + public short getSubject() { + return getValueType(KMCose.SUBJECT, KMType.INVALID_VALUE); + } + + public short getIssuer() { + return getValueType(KMCose.ISSUER, KMType.INVALID_VALUE); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCoseHeaders.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCoseHeaders.java new file mode 100644 index 0000000..0e722d2 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCoseHeaders.java @@ -0,0 +1,203 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCoseHeaders represents headers section from the Cose standard + * https://datatracker.ietf.org/doc/html/rfc8152#section-3. The supported key types are KMInteger, + * KMNInteger and the supported value types are KMInteger, KMNInteger, KMByteBlob, KMCoseKey. It + * corresponds to a CBOR Map type. struct{byte TAG_TYPE; short length; short arrayPtr } where + * arrayPtr is a pointer to array with any KMTag subtype instances. + */ +public class KMCoseHeaders extends KMCoseMap { + + private static KMCoseHeaders prototype; + + private KMCoseHeaders() {} + + private static KMCoseHeaders proto(short ptr) { + if (prototype == null) { + prototype = new KMCoseHeaders(); + } + instanceTable[KM_COSE_HEADERS_OFFSET] = ptr; + return prototype; + } + + public static short exp() { + short arrPtr = KMArray.instance((short) 4); + // CoseKey is internally an Array so evaluate it separately. + short coseKeyValueExp = KMCosePairCoseKeyTag.exp(); + KMArray arr = KMArray.cast(arrPtr); + arr.add((short) 0, KMCosePairIntegerTag.exp()); + arr.add((short) 1, KMCosePairNegIntegerTag.exp()); + arr.add((short) 2, KMCosePairByteBlobTag.exp()); + arr.add((short) 3, coseKeyValueExp); + return KMCoseHeaders.instance(arrPtr); + } + + public static short instance(short vals) { + short ptr = KMType.instance(COSE_HEADERS_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMCoseHeaders cast(short ptr) { + if (heap[ptr] != COSE_HEADERS_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (heap[arrPtr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + @Override + public short getVals() { + return Util.getShort(heap, (short) (instanceTable[KM_COSE_HEADERS_OFFSET] + TLV_HEADER_SIZE)); + } + + @Override + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); + } + + @Override + public void canonicalize() { + KMCoseMap.canonicalize(getVals()); + } + + private short getValueType(short key) { + short index = 0; + short len = length(); + short arr = getVals(); + short tagType; + short valPtr = 0; + short keyPtr; + boolean found = false; + while (index < len) { + tagType = KMCosePairTagType.getTagValueType(KMArray.cast(arr).get(index)); + switch (tagType) { + case KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE: + keyPtr = KMCosePairByteBlobTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairByteBlobTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + case KMType.COSE_PAIR_COSE_KEY_TAG_TYPE: + keyPtr = KMCosePairCoseKeyTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairCoseKeyTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + case KMType.COSE_PAIR_INT_TAG_TYPE: + keyPtr = KMCosePairIntegerTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairIntegerTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + case KMType.COSE_PAIR_NEG_INT_TAG_TYPE: + keyPtr = KMCosePairNegIntegerTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairNegIntegerTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + default: + break; + } + if (found) { + break; + } + index++; + } + return valPtr; + } + + public short getKeyIdentifier() { + return getValueType(KMCose.COSE_LABEL_KEYID); + } + + public short getCoseKey() { + return getValueType(KMCose.COSE_LABEL_COSE_KEY); + } + + public short getIV() { + return getValueType(KMCose.COSE_LABEL_IV); + } + + public short getAlgorithm() { + return getValueType(KMCose.COSE_LABEL_ALGORITHM); + } + + public boolean isDataValid(short[] buff, short alg, short keyIdPtr) { + short bufLen = 4; + buff[0] = KMCose.COSE_LABEL_ALGORITHM; + buff[1] = alg; + buff[2] = KMCose.COSE_LABEL_KEYID; + buff[3] = keyIdPtr; + boolean valid = false; + short value; + short ptr; + short tagIndex = 0; + while (tagIndex < bufLen) { + value = buff[(short) (tagIndex + 1)]; + if (value != KMType.INVALID_VALUE) { + valid = false; + ptr = getValueType(buff[tagIndex]); + switch (KMType.getType(ptr)) { + case KMType.BYTE_BLOB_TYPE: + if ((KMByteBlob.cast(value).length() == KMByteBlob.cast(ptr).length()) + && (0 + == Util.arrayCompare( + KMByteBlob.cast(value).getBuffer(), + KMByteBlob.cast(value).getStartOff(), + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()))) { + valid = true; + } + break; + case KMType.INTEGER_TYPE: + if (value == KMInteger.cast(ptr).getShort()) { + valid = true; + } + break; + case KMType.NEG_INTEGER_TYPE: + if ((byte) value == (byte) KMNInteger.cast(ptr).getShort()) { + valid = true; + } + break; + default: + break; + } + if (!valid) { + break; + } + } + tagIndex += 2; + } + return valid; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCoseKey.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCoseKey.java new file mode 100644 index 0000000..d1bfec1 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCoseKey.java @@ -0,0 +1,255 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCoseKey represents COSE_Key section from the Cose standard + * https://datatracker.ietf.org/doc/html/rfc8152#section-7 The supported key types are KMNInteger, + * KMInteger and the supported value types are KMInteger, KMNInteger, KMByteBlob, KMSimpleValue. It + * corresponds to a CBOR Map type. struct{byte TAG_TYPE; short length; short arrayPtr } where + * arrayPtr is a pointer to array with any KMTag subtype instances. Note that construction of the + * key_ops label is not needed to be supported. In the KeyMint3.0 specifications: The CoseKey inside + * MacedPublicKeys and DiceCertChain does not have key_ops label. + */ +public class KMCoseKey extends KMCoseMap { + + private static KMCoseKey prototype; + + private KMCoseKey() {} + + private static KMCoseKey proto(short ptr) { + if (prototype == null) { + prototype = new KMCoseKey(); + } + instanceTable[KM_COSE_KEY_OFFSET] = ptr; + return prototype; + } + + public static short exp() { + short arrPtr = KMArray.instance((short) 4); + KMArray arr = KMArray.cast(arrPtr); + arr.add((short) 0, KMCosePairIntegerTag.exp()); + arr.add((short) 1, KMCosePairNegIntegerTag.exp()); + arr.add((short) 2, KMCosePairByteBlobTag.exp()); + arr.add((short) 3, KMCosePairSimpleValueTag.exp()); + return KMCoseKey.instance(arrPtr); + } + + public static short instance(short vals) { + short ptr = KMType.instance(COSE_KEY_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMCoseKey cast(short ptr) { + if (heap[ptr] != COSE_KEY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (heap[arrPtr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + @Override + public short getVals() { + return Util.getShort(heap, (short) (instanceTable[KM_COSE_KEY_OFFSET] + TLV_HEADER_SIZE)); + } + + @Override + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); + } + + private short getValueType(short key, short significantKey) { + short arr = getVals(); + short length = length(); + short keyPtr; + short valPtr = 0; + short index = 0; + short tagType; + boolean found = false; + while (index < length) { + tagType = KMCosePairTagType.getTagValueType(KMArray.cast(arr).get(index)); + switch (tagType) { + case KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE: + keyPtr = KMCosePairByteBlobTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairByteBlobTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + case KMType.COSE_PAIR_INT_TAG_TYPE: + keyPtr = KMCosePairIntegerTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairIntegerTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + case KMType.COSE_PAIR_NEG_INT_TAG_TYPE: + keyPtr = KMCosePairNegIntegerTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairNegIntegerTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + case KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE: + keyPtr = KMCosePairSimpleValueTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == KMCosePairTagType.getKeyValueShort(keyPtr) + && significantKey == KMCosePairTagType.getKeyValueSignificantShort(keyPtr)) { + valPtr = KMCosePairSimpleValueTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + default: + break; + } + if (found) { + break; + } + index++; + } + return valPtr; + } + + public short getKeyIdentifier() { + return getValueType(KMCose.COSE_KEY_KEY_ID, KMType.INVALID_VALUE); + } + + public short getEcdsa256PublicKey(byte[] pubKey, short pubKeyOff) { + short baseOffset = pubKeyOff; + pubKey[pubKeyOff] = (byte) 0x04; // uncompressed. + pubKeyOff++; + short ptr = getValueType(KMCose.COSE_KEY_PUBKEY_X, KMType.INVALID_VALUE); + Util.arrayCopy( + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + pubKey, + pubKeyOff, + KMByteBlob.cast(ptr).length()); + pubKeyOff += KMByteBlob.cast(ptr).length(); + ptr = getValueType(KMCose.COSE_KEY_PUBKEY_Y, KMType.INVALID_VALUE); + Util.arrayCopy( + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + pubKey, + pubKeyOff, + KMByteBlob.cast(ptr).length()); + pubKeyOff += KMByteBlob.cast(ptr).length(); + return (short) (pubKeyOff - baseOffset); + } + + public short getPrivateKey(byte[] priv, short privOff) { + short ptr = getValueType(KMCose.COSE_KEY_PRIV_KEY, KMType.INVALID_VALUE); + Util.arrayCopy( + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + priv, + privOff, + KMByteBlob.cast(ptr).length()); + return KMByteBlob.cast(ptr).length(); + } + + public boolean isTestKey() { + short ptr = + getValueType( + Util.getShort(KMCose.COSE_TEST_KEY, (short) 2), // LSB + Util.getShort(KMCose.COSE_TEST_KEY, (short) 0) // MSB (Significant) + ); + boolean isTestKey = false; + if (ptr != 0) { + isTestKey = (KMSimpleValue.cast(ptr).getValue() == KMSimpleValue.NULL); + } + return isTestKey; + } + + /** + * Verifies the KMCoseKey values against the input values. Note that construction of the key_ops + * label is not needed to be supported. In the KeyMint3.0 specifications: The CoseKey inside + * MacedPublicKeys and DiceCertChain does not have key_ops label. + * + * @param keyType value of the key type + * @param keyIdPtr instance of KMByteBlob containing the key id. + * @param keyAlg value of the algorithm. + * @param keyOps value of the key operations. + * @param curve value of the curve. + * @return true if valid, otherwise false. + */ + public boolean isDataValid( + short[] buff, short keyType, short keyIdPtr, short keyAlg, short curve) { + short buffLen = 8; + buff[0] = KMCose.COSE_KEY_KEY_TYPE; + buff[1] = keyType; + buff[2] = KMCose.COSE_KEY_KEY_ID; + buff[3] = keyIdPtr; + buff[4] = KMCose.COSE_KEY_ALGORITHM; + buff[5] = keyAlg; + buff[6] = KMCose.COSE_KEY_CURVE; + buff[7] = curve; + boolean valid = false; + short ptr; + short tagIndex = 0; + short value; + while (tagIndex < buffLen) { + value = buff[(short) (tagIndex + 1)]; + if (value != KMType.INVALID_VALUE) { + valid = false; + ptr = getValueType(buff[tagIndex], KMType.INVALID_VALUE); + switch (KMType.getType(ptr)) { + case KMType.BYTE_BLOB_TYPE: + if ((KMByteBlob.cast(value).length() == KMByteBlob.cast(ptr).length()) + && (0 + == Util.arrayCompare( + KMByteBlob.cast(value).getBuffer(), + KMByteBlob.cast(value).getStartOff(), + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()))) { + valid = true; + } + break; + case KMType.INTEGER_TYPE: + if (value == KMInteger.cast(ptr).getShort()) { + valid = true; + } + break; + case KMType.NEG_INTEGER_TYPE: + if ((byte) value == (byte) KMNInteger.cast(ptr).getShort()) { + valid = true; + } + break; + } + if (!valid) { + break; + } + } + tagIndex += 2; + } + return valid; + } + + @Override + public void canonicalize() { + KMCoseMap.canonicalize(getVals()); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCoseMap.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCoseMap.java new file mode 100644 index 0000000..5d373a7 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCoseMap.java @@ -0,0 +1,171 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; + +/** + * This class represents either a Cose_key or Cose headers as defined in + * https://datatracker.ietf.org/doc/html/rfc8152 This is basically a map containing key value pairs. + * The label for the key can be (uint / int / tstr) and the value can be of any type. But this class + * is confined to support only key and value types which are required for remote key provisioning. + * So keys of type (int / uint) and values of type (int / uint / simple / bstr) only are supported. + * KMCoseHeaders and KMCoseKey implements this class. + */ +public abstract class KMCoseMap extends KMType { + + public static byte[] scratchpad; + + /** + * This function creates an instance of either KMCoseHeaders or KMCoseKey based on the type + * information provided. + * + * @param typePtr type information of the underlying KMType. + * @param arrPtr instance of KMArray. + * @return instance type of either KMCoseHeaders or KMCoseKey. + */ + public static short createInstanceFromType(short typePtr, short arrPtr) { + short mapType = KMType.getType(typePtr); + switch (mapType) { + case KMType.COSE_HEADERS_TYPE: + return KMCoseHeaders.instance(arrPtr); + case KMType.COSE_KEY_TYPE: + return KMCoseKey.instance(arrPtr); + case KMType.COSE_CERT_PAYLOAD_TYPE: + return KMCoseCertPayload.instance(arrPtr); + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + return 0; + } + } + + public static short getVals(short ptr) { + short mapType = KMType.getType(ptr); + switch (mapType) { + case KMType.COSE_HEADERS_TYPE: + return KMCoseHeaders.cast(ptr).getVals(); + case KMType.COSE_KEY_TYPE: + return KMCoseKey.cast(ptr).getVals(); + case KMType.COSE_CERT_PAYLOAD_TYPE: + return KMCoseCertPayload.cast(ptr).getVals(); + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + return 0; + } + } + + private static short getKey(short tagPtr) { + short tagType = KMCosePairTagType.getTagValueType(tagPtr); + switch (tagType) { + case KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE: + return KMCosePairByteBlobTag.cast(tagPtr).getKeyPtr(); + case KMType.COSE_PAIR_INT_TAG_TYPE: + return KMCosePairIntegerTag.cast(tagPtr).getKeyPtr(); + case KMType.COSE_PAIR_NEG_INT_TAG_TYPE: + return KMCosePairNegIntegerTag.cast(tagPtr).getKeyPtr(); + case KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE: + return KMCosePairSimpleValueTag.cast(tagPtr).getKeyPtr(); + case KMType.COSE_PAIR_COSE_KEY_TAG_TYPE: + return KMCosePairCoseKeyTag.cast(tagPtr).getKeyPtr(); + case KMType.COSE_PAIR_TEXT_STR_TAG_TYPE: + return KMCosePairTextStringTag.cast(tagPtr).getKeyPtr(); + default: + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return 0; + } + + private static void createScratchBuffer() { + if (scratchpad == null) { + scratchpad = JCSystem.makeTransientByteArray((short) 120, JCSystem.CLEAR_ON_RESET); + } + } + + protected static void canonicalize(short arr) { + canonicalize(arr, KMArray.cast(arr).length()); + } + + private static void swap(short ptr, short firstIndex, short secondIndex) { + if (KMType.getType(ptr) == KMType.ARRAY_TYPE) { + KMArray.cast(ptr).swap(firstIndex, secondIndex); + } else { + KMMap.cast(ptr).swap(firstIndex, secondIndex); + } + } + + private static boolean compareAndSwap(short ptr, short index) { + short firstKey; + short secondKey; + short firstKeyLen; + short secondKeyLen; + if (KMType.getType(ptr) == KMType.ARRAY_TYPE) { + firstKey = getKey(KMArray.cast(ptr).get(index)); + secondKey = getKey(KMArray.cast(ptr).get((short) (index + 1))); + } else { // Map + firstKey = KMMap.cast(ptr).getKey(index); + secondKey = KMMap.cast(ptr).getKey((short) (index + 1)); + } + firstKeyLen = + KMKeymasterApplet.encoder.encode( + firstKey, scratchpad, (short) 0, (short) scratchpad.length); + secondKeyLen = + KMKeymasterApplet.encoder.encode( + secondKey, scratchpad, firstKeyLen, (short) scratchpad.length); + if ((firstKeyLen > secondKeyLen) + || ((firstKeyLen == secondKeyLen) + && (0 + < Util.arrayCompare( + scratchpad, (short) 0, scratchpad, firstKeyLen, firstKeyLen)))) { + swap(ptr, index, (short) (index + 1)); + return true; + } + return false; + } + + /** + * Canonicalizes using bubble sort. + * + * @param ptr instance pointer of either array or map. + * @param length length of the array or map instance. + */ + public static void canonicalize(short ptr, short length) { + short index = 0; + short innerIndex = 0; + createScratchBuffer(); + boolean swapped; + while (index < length) { + swapped = false; + innerIndex = 0; + while (innerIndex < (short) (length - index - 1)) { + swapped |= compareAndSwap(ptr, innerIndex); + innerIndex++; + } + if (!swapped) { + break; + } + index++; + } + } + + public abstract short getVals(); + + public abstract short length(); + + public abstract void canonicalize(); +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairByteBlobTag.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairByteBlobTag.java new file mode 100644 index 0000000..04c3abe --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairByteBlobTag.java @@ -0,0 +1,137 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCosePairByteBlobTag represents a key-value type, where key can be KMInteger or KMNInteger and + * value is KMByteBlob type. struct{byte TAG_TYPE; short length; struct{short BYTE_BLOB_TYPE; short + * key; short value}}. + */ +public class KMCosePairByteBlobTag extends KMCosePairTagType { + + public static Object[] keys; + private static KMCosePairByteBlobTag prototype; + + private KMCosePairByteBlobTag() {} + + private static KMCosePairByteBlobTag proto(short ptr) { + if (prototype == null) { + prototype = new KMCosePairByteBlobTag(); + } + instanceTable[KM_COSE_KEY_BYTE_BLOB_VAL_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short ptr = instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), KMType.INVALID_VALUE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), KMByteBlob.exp()); + return ptr; + } + + public static short instance(short keyPtr, short valuePtr) { + if (!isKeyValueValid(keyPtr)) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (KMType.getType(valuePtr) != BYTE_BLOB_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short ptr = KMType.instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), keyPtr); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), valuePtr); + return ptr; + } + + public static KMCosePairByteBlobTag cast(short ptr) { + byte[] heap = repository.getHeap(); + if (heap[ptr] != COSE_PAIR_TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + // Validate the value pointer. + short valuePtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4)); + if (KMType.getType(valuePtr) != BYTE_BLOB_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + private static void createKeys() { + if (keys == null) { + keys = + new Object[] { + (Object) new byte[] {(byte) 0, (byte) 0, (byte) 0, KMCose.COSE_KEY_PUBKEY_X}, + (Object) new byte[] {(byte) 0, (byte) 0, (byte) 0, KMCose.COSE_KEY_PUBKEY_Y}, + (Object) new byte[] {(byte) 0, (byte) 0, (byte) 0, KMCose.COSE_KEY_PRIV_KEY}, + (Object) new byte[] {(byte) 0, (byte) 0, (byte) 0, KMCose.COSE_LABEL_IV}, + (Object) new byte[] {(byte) 0, (byte) 0, (byte) 0, KMCose.COSE_LABEL_KEYID}, + (Object) new byte[] {(byte) 0, (byte) 0, (byte) 0, KMCose.COSE_KEY_KEY_ID}, + (Object) KMCose.SUBJECT_PUBLIC_KEY, + (Object) KMCose.KEY_USAGE + }; + } + } + + public static boolean isKeyValueValid(short keyPtr) { + createKeys(); + short type = KMType.getType(keyPtr); + short offset = 0; + if (type == INTEGER_TYPE) { + offset = KMInteger.cast(keyPtr).getStartOff(); + } else if (type == NEG_INTEGER_TYPE) { + offset = KMNInteger.cast(keyPtr).getStartOff(); + } else { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short index = 0; + while (index < (short) keys.length) { + if (0 + == Util.arrayCompare( + (byte[]) keys[index], + (short) 0, + heap, + offset, + (short) ((byte[]) keys[index]).length)) { + return true; + } + index++; + } + return false; + } + + public short getValueType() { + return BYTE_BLOB_TYPE; + } + + @Override + public short getKeyPtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_BYTE_BLOB_VAL_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + @Override + public short getValuePtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_BYTE_BLOB_VAL_OFFSET] + TLV_HEADER_SIZE + 4)); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairCoseKeyTag.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairCoseKeyTag.java new file mode 100644 index 0000000..5290da2 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairCoseKeyTag.java @@ -0,0 +1,89 @@ +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCosePairCoseKeyTag represents a key-value type, where key can be KMInteger or KMNInteger and + * value is KMCoseKey type. struct{byte TAG_TYPE; short length; struct{short COSE_KEY_VALUE_TYPE; + * short key; short value}}. + */ +public class KMCosePairCoseKeyTag extends KMCosePairTagType { + + public static final byte[] keys = {KMCose.COSE_LABEL_COSE_KEY}; + private static KMCosePairCoseKeyTag prototype; + + private KMCosePairCoseKeyTag() {} + + private static KMCosePairCoseKeyTag proto(short ptr) { + if (prototype == null) { + prototype = new KMCosePairCoseKeyTag(); + } + instanceTable[KM_COSE_KEY_COSE_KEY_VAL_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short ptr = instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_COSE_KEY_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), KMType.INVALID_VALUE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), KMCoseKey.exp()); + return ptr; + } + + public static short instance(short keyPtr, short valuePtr) { + if (!isKeyValueValid(KMCosePairTagType.getKeyValueShort(keyPtr))) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (KMType.getType(valuePtr) != COSE_KEY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short ptr = KMType.instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_COSE_KEY_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), keyPtr); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), valuePtr); + return ptr; + } + + public static KMCosePairCoseKeyTag cast(short ptr) { + byte[] heap = repository.getHeap(); + if (heap[ptr] != COSE_PAIR_TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + // Validate the value pointer. + short valuePtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4)); + if (KMType.getType(valuePtr) != COSE_KEY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static boolean isKeyValueValid(short keyVal) { + short index = 0; + while (index < (short) keys.length) { + if ((byte) (keyVal & 0xFF) == keys[index]) { + return true; + } + index++; + } + return false; + } + + public short getValueType() { + return COSE_KEY_TYPE; + } + + @Override + public short getKeyPtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_COSE_KEY_VAL_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + @Override + public short getValuePtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_COSE_KEY_VAL_OFFSET] + TLV_HEADER_SIZE + 4)); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairIntegerTag.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairIntegerTag.java new file mode 100644 index 0000000..ea052a6 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairIntegerTag.java @@ -0,0 +1,92 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCosePairIntegerTag represents a key-value type, where key can be KMInteger or KMNInteger and + * value is KMInteger type. struct{byte TAG_TYPE; short length; struct{short INT_VALUE_TYPE; short + * key; short value}}. + */ +public class KMCosePairIntegerTag extends KMCosePairTagType { + + private static KMCosePairIntegerTag prototype; + + private KMCosePairIntegerTag() {} + + private static KMCosePairIntegerTag proto(short ptr) { + if (prototype == null) { + prototype = new KMCosePairIntegerTag(); + } + instanceTable[KM_COSE_KEY_INT_VAL_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short ptr = instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_INT_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), KMType.INVALID_VALUE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), KMInteger.exp()); + return ptr; + } + + public static short instance(short keyPtr, short valuePtr) { + short offset = KMCosePairTagType.getKeyStartOffset(keyPtr); + if (!KMCosePairTagType.isKeyPairValid( + heap, offset, KMCose.COSE_KEY_MAX_SIZE, KMInteger.cast(valuePtr).getShort())) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short ptr = KMType.instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_INT_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), keyPtr); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), valuePtr); + return ptr; + } + + public static KMCosePairIntegerTag cast(short ptr) { + byte[] heap = repository.getHeap(); + if (heap[ptr] != COSE_PAIR_TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + // Validate the value ptr. + short valuePtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4)); + if (INTEGER_TYPE != getType(valuePtr)) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public short getValueType() { + return INTEGER_TYPE; + } + + @Override + public short getKeyPtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_INT_VAL_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + @Override + public short getValuePtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_INT_VAL_OFFSET] + TLV_HEADER_SIZE + 4)); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairNegIntegerTag.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairNegIntegerTag.java new file mode 100644 index 0000000..7f01202 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairNegIntegerTag.java @@ -0,0 +1,92 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCosePairNegIntegerTag represents a key-value type, where key can be KMInteger or KMNInteger and + * value is KMNInteger type. struct{byte TAG_TYPE; short length; struct{short NINT_VALUE_TYPE; short + * key; short value}}. + */ +public class KMCosePairNegIntegerTag extends KMCosePairTagType { + + private static KMCosePairNegIntegerTag prototype; + + private KMCosePairNegIntegerTag() {} + + private static KMCosePairNegIntegerTag proto(short ptr) { + if (prototype == null) { + prototype = new KMCosePairNegIntegerTag(); + } + instanceTable[KM_COSE_KEY_NINT_VAL_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short ptr = instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_NEG_INT_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), KMType.INVALID_VALUE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), KMNInteger.exp()); + return ptr; + } + + public static KMCosePairNegIntegerTag cast(short ptr) { + byte[] heap = repository.getHeap(); + if (heap[ptr] != COSE_PAIR_TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + // Validate the value ptr. + short valuePtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4)); + if (NEG_INTEGER_TYPE != getType(valuePtr)) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static short instance(short keyPtr, short valuePtr) { + short offset = KMCosePairTagType.getKeyStartOffset(keyPtr); + if (!KMCosePairTagType.isKeyPairValid( + heap, offset, KMCose.COSE_KEY_MAX_SIZE, KMNInteger.cast(valuePtr).getShort())) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short ptr = KMType.instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_NEG_INT_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), keyPtr); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), valuePtr); + return ptr; + } + + public short getValueType() { + return NEG_INTEGER_TYPE; + } + + @Override + public short getKeyPtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_NINT_VAL_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + @Override + public short getValuePtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_NINT_VAL_OFFSET] + TLV_HEADER_SIZE + 4)); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairSimpleValueTag.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairSimpleValueTag.java new file mode 100644 index 0000000..a0d7da8 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairSimpleValueTag.java @@ -0,0 +1,76 @@ +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCosePairSimpleValueTag represents a key-value type, where key can be KMInteger or KMNInteger + * and value is KMSimpleValue type. struct{byte TAG_TYPE; short length; struct{short + * SIMPLE_VALUE_TYPE; short key; short value}}. + */ +public class KMCosePairSimpleValueTag extends KMCosePairTagType { + + private static KMCosePairSimpleValueTag prototype; + + private KMCosePairSimpleValueTag() {} + + private static KMCosePairSimpleValueTag proto(short ptr) { + if (prototype == null) { + prototype = new KMCosePairSimpleValueTag(); + } + instanceTable[KM_COSE_KEY_SIMPLE_VAL_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short ptr = instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), KMType.INVALID_VALUE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), KMSimpleValue.exp()); + return ptr; + } + + public static short instance(short keyPtr, short valuePtr) { + short offset = KMCosePairTagType.getKeyStartOffset(keyPtr); + if (!KMCosePairTagType.isKeyPairValid( + heap, offset, KMCose.COSE_KEY_MAX_SIZE, KMSimpleValue.cast(valuePtr).getValue())) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short ptr = KMType.instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), keyPtr); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), valuePtr); + return ptr; + } + + public static KMCosePairSimpleValueTag cast(short ptr) { + byte[] heap = repository.getHeap(); + if (heap[ptr] != COSE_PAIR_TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + // Validate the value pointer. + short valuePtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4)); + if (KMType.getType(valuePtr) != SIMPLE_VALUE_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public short getValueType() { + return SIMPLE_VALUE_TYPE; + } + + @Override + public short getKeyPtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_SIMPLE_VAL_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + @Override + public short getValuePtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_SIMPLE_VAL_OFFSET] + TLV_HEADER_SIZE + 4)); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairTagType.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairTagType.java new file mode 100644 index 0000000..baa0855 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairTagType.java @@ -0,0 +1,248 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * This class represents the a key-value types. This is basically a map containing key value pairs. + * The label for the key can be (uint / int / tstr) and the value can be of any type. But this class + * is confined to support only key and value types which are required for remote key provisioning. + * So keys of type (int / uint) and values of type (int / uint / simple / bstr) only are supported. + * The structure representing all the sub classes of KMCosePairTagType is as follows: + * KM_COSE_PAIR_TAG_TYPE(1byte), Length(2 bytes), COSE_PAIR_*_TAG_TYPE(2 bytes), Key(2 bytes), + * Value(2 bytes). Key can be either KMInteger or KMNInteger and Value can be either KMIntger or + * KMNinteger or KMSimpleValue or KMByteBlob or KMTextString or KMCoseKey. Each subclass of + * KMCosePairTagType is named after their corresponding value type of the Cose pair. + */ +public abstract class KMCosePairTagType extends KMType { + + /** + * Below table represents the allowed values for a key. The maximum length of the key can be 4 + * bytes so each key is represented as 4 bytes. The allowed values are placed next to their + * corresponding key. + */ + public static Object[] allowedKeyPairs; + + private static void createAllowedKeyPairs() { + if (allowedKeyPairs == null) { + allowedKeyPairs = + new Object[] { + // Key type + (Object) new byte[] {0, 0, 0, KMCose.COSE_KEY_KEY_TYPE}, + (Object) new byte[] {KMCose.COSE_KEY_TYPE_EC2, KMCose.COSE_KEY_TYPE_SYMMETRIC_KEY}, + // Key Algorithm + (Object) new byte[] {0, 0, 0, KMCose.COSE_KEY_ALGORITHM}, + (Object) + new byte[] { + KMCose.COSE_ALG_AES_GCM_256, + KMCose.COSE_ALG_HMAC_256, + KMCose.COSE_ALG_ECDH_ES_HKDF_256, + KMCose.COSE_ALG_ES256 + }, + // Key Curve + (Object) new byte[] {0, 0, 0, KMCose.COSE_KEY_CURVE}, + (Object) new byte[] {KMCose.COSE_ECCURVE_256}, + // Header Label Algorithm + (Object) new byte[] {0, 0, 0, KMCose.COSE_LABEL_ALGORITHM}, + (Object) + new byte[] { + KMCose.COSE_ALG_AES_GCM_256, + KMCose.COSE_ALG_HMAC_256, + KMCose.COSE_ALG_ES256, + KMCose.COSE_ALG_ECDH_ES_HKDF_256 + }, + // Test Key + KMCose.COSE_TEST_KEY, + (Object) new byte[] {KMSimpleValue.NULL}, + }; + } + } + + /** + * Validates the key and the values corresponding to key. + * + * @param key Buffer containing the key. + * @param keyOff Offset in the buffer from where key starts. + * @param keyLen Length of the key buffer. + * @param value Value corresponding to the key. + * @return true if key pair is valid, otherwise false. + */ + public static boolean isKeyPairValid(byte[] key, short keyOff, short keyLen, short value) { + short index = 0; + short valueIdx; + byte[] values; + boolean valid = false; + createAllowedKeyPairs(); + while (index < allowedKeyPairs.length) { + valueIdx = 0; + if (isEqual( + (byte[]) allowedKeyPairs[index], + (short) 0, + (short) ((byte[]) allowedKeyPairs[index]).length, + key, + keyOff, + keyLen)) { + values = (byte[]) allowedKeyPairs[(short) (index + 1)]; + while (valueIdx < values.length) { + if (values[valueIdx] == (byte) value) { + valid = true; + break; + } + valueIdx++; + } + if (valid) { + break; + } + } + index += (short) 2; + } + return valid; + } + + /** + * Compares two key buffers. + * + * @param key1 First buffer containing the key. + * @param offset1 Offset of the first buffer. + * @param length1 Length of the first buffer. + * @param key2 Second buffer containing the key. + * @param offset2 Offset of the second buffer. + * @param length2 Length of the second buffer. + * @return true if both keys are equal, otherwise false. + */ + private static boolean isEqual( + byte[] key1, short offset1, short length1, byte[] key2, short offset2, short length2) { + if (length1 != length2) { + return false; + } + return (0 == KMInteger.unsignedByteArrayCompare(key1, offset1, key2, offset2, length1)); + } + + /** + * Returns the short value of the key. + * + * @param keyPtr Pointer to either KMInteger or KMNInteger + * @return value of the key as short. + */ + public static short getKeyValueShort(short keyPtr) { + short type = KMType.getType(keyPtr); + short value = 0; + if (type == INTEGER_TYPE) { + value = KMInteger.cast(keyPtr).getShort(); + } else if (type == NEG_INTEGER_TYPE) { + value = KMNInteger.cast(keyPtr).getShort(); + } else { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return value; + } + + /** + * Returns the significant short value of the key. + * + * @param keyPtr Pointer to either KMInteger or KMNInteger + * @return value of the key as short. + */ + public static short getKeyValueSignificantShort(short keyPtr) { + short type = KMType.getType(keyPtr); + short value = 0; + if (type == INTEGER_TYPE) { + value = KMInteger.cast(keyPtr).getSignificantShort(); + } else if (type == NEG_INTEGER_TYPE) { + value = KMNInteger.cast(keyPtr).getSignificantShort(); + } else { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return value; + } + + public static void getKeyValue(short keyPtr, byte[] dest, short offset, short len) { + short type = KMType.getType(keyPtr); + if (type == INTEGER_TYPE) { + KMInteger.cast(keyPtr).getValue(dest, offset, len); + } else if (type == NEG_INTEGER_TYPE) { + KMNInteger.cast(keyPtr).getValue(dest, offset, len); + } else { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + } + + /** + * Returns the key offset from the key pointer. + * + * @param keyPtr Pointer to either KMInteger or KMNInteger + * @return offset from where the key starts. + */ + public static short getKeyStartOffset(short keyPtr) { + short type = KMType.getType(keyPtr); + short offset = 0; + if (type == INTEGER_TYPE) { + offset = KMInteger.cast(keyPtr).getStartOff(); + } else if (type == NEG_INTEGER_TYPE) { + offset = KMNInteger.cast(keyPtr).getStartOff(); + } else { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return offset; + } + + /** + * Returns the key length. + * + * @param keyPtr pointer to either KMInteger/KMInteger. + * @return length of the key. + */ + public static short getKeyLength(short keyPtr) { + short type = KMType.getType(keyPtr); + short len = 0; + if (type == INTEGER_TYPE) { + len = KMInteger.cast(keyPtr).length(); + } else if (type == NEG_INTEGER_TYPE) { + len = KMNInteger.cast(keyPtr).length(); + } else { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return len; + } + + /** + * This function returns one of COSE_KEY_TAG_*_VALUE_TYPE tag information. + * + * @param ptr Pointer to one of the KMCoseKey*Value class. + * @return Tag value type. + */ + public static short getTagValueType(short ptr) { + return Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + } + + /** + * This function returns the key pointer. + * + * @return key pointer. + */ + public abstract short getKeyPtr(); + + /** + * This function returns the value pointer. + * + * @return value pointer. + */ + public abstract short getValuePtr(); +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairTextStringTag.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairTextStringTag.java new file mode 100644 index 0000000..99506b6 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMCosePairTextStringTag.java @@ -0,0 +1,91 @@ +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCosePairTextStringTag represents a key-value type, where key can be KMInteger or KMNInteger and + * value is KMTextString type. struct{byte TAG_TYPE; short length; struct{short TXT_STR_VALUE_TYPE; + * short key; short value}}. + */ +public class KMCosePairTextStringTag extends KMCosePairTagType { + + public static final byte[] keys = { + KMCose.ISSUER, KMCose.SUBJECT, + }; + private static KMCosePairTextStringTag prototype; + + private KMCosePairTextStringTag() {} + + private static KMCosePairTextStringTag proto(short ptr) { + if (prototype == null) { + prototype = new KMCosePairTextStringTag(); + } + instanceTable[KM_COSE_KEY_TXT_STR_VAL_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short ptr = instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_TEXT_STR_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), KMType.INVALID_VALUE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), KMTextString.exp()); + return ptr; + } + + public static short instance(short keyPtr, short valuePtr) { + if (!isKeyValueValid(KMCosePairTagType.getKeyValueShort(keyPtr))) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (KMType.getType(valuePtr) != TEXT_STRING_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short ptr = KMType.instance(COSE_PAIR_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), KMType.COSE_PAIR_TEXT_STR_TAG_TYPE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), keyPtr); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), valuePtr); + return ptr; + } + + public static KMCosePairTextStringTag cast(short ptr) { + byte[] heap = repository.getHeap(); + if (heap[ptr] != COSE_PAIR_TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + // Validate the value pointer. + short valuePtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4)); + if (KMType.getType(valuePtr) != TEXT_STRING_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static boolean isKeyValueValid(short keyVal) { + short index = 0; + while (index < (short) keys.length) { + if ((byte) (keyVal & 0xFF) == keys[index]) { + return true; + } + index++; + } + return false; + } + + public short getValueType() { + return TEXT_STRING_TYPE; + } + + @Override + public short getKeyPtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_TXT_STR_VAL_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + @Override + public short getValuePtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_COSE_KEY_TXT_STR_VAL_OFFSET] + TLV_HEADER_SIZE + 4)); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMDecoder.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMDecoder.java new file mode 100644 index 0000000..dc0101a --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMDecoder.java @@ -0,0 +1,781 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import com.android.javacard.seprovider.KMException; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; + +/** + * This class decodes the CBOR format data into a KMType structure. It interprets the input CBOR + * format using the input expression provided. Validation of KeyMint tags and tag types happens in + * the process of decoding, while constructing the subtype of a KMType structure. + */ +public class KMDecoder { + + // major types + private static final short UINT_TYPE = 0x00; + private static final short NEG_INT_TYPE = 0x20; + private static final short BYTES_TYPE = 0x40; + private static final short TSTR_TYPE = 0x60; + private static final short ARRAY_TYPE = 0x80; + private static final short MAP_TYPE = 0xA0; + private static final short SIMPLE_VALUE_TYPE = 0xE0; + private static final short SEMANTIC_TAG_TYPE = 0xC0; + + // masks + private static final short ADDITIONAL_MASK = 0x1F; + private static final short MAJOR_TYPE_MASK = 0xE0; + + // value length + private static final short UINT8_LENGTH = 0x18; + private static final short UINT16_LENGTH = 0x19; + private static final short UINT32_LENGTH = 0x1A; + private static final short UINT64_LENGTH = 0x1B; + + private static final byte SCRATCH_BUF_SIZE = 6; + private static final byte START_OFFSET = 0; + private static final byte LEN_OFFSET = 2; + private static final byte TAG_KEY_OFFSET = 4; + private Object[] bufferRef; + private short[] scratchBuf; + + public KMDecoder() { + bufferRef = JCSystem.makeTransientObjectArray((short) 1, JCSystem.CLEAR_ON_RESET); + scratchBuf = JCSystem.makeTransientShortArray(SCRATCH_BUF_SIZE, JCSystem.CLEAR_ON_RESET); + bufferRef[0] = null; + scratchBuf[START_OFFSET] = (short) 0; + scratchBuf[LEN_OFFSET] = (short) 0; + scratchBuf[TAG_KEY_OFFSET] = (short) 0; + } + + public short decode(short expression, byte[] buffer, short startOff, short length) { + bufferRef[0] = buffer; + scratchBuf[START_OFFSET] = startOff; + scratchBuf[LEN_OFFSET] = (short) (startOff + length); + return decode(expression); + } + + public short decodeArray(short exp, byte[] buffer, short startOff, short length) { + bufferRef[0] = buffer; + scratchBuf[START_OFFSET] = startOff; + scratchBuf[LEN_OFFSET] = (short) (startOff + length); + short payloadLength = readMajorTypeWithPayloadLength(ARRAY_TYPE); + short expLength = KMArray.cast(exp).length(); + if (payloadLength > expLength) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + short index = 0; + short obj; + short type; + short arrPtr = KMArray.instance(payloadLength); + while (index < payloadLength) { + type = KMArray.cast(exp).get(index); + obj = decode(type); + KMArray.cast(arrPtr).add(index, obj); + index++; + } + return arrPtr; + } + + private short decode(short exp) { + byte type = KMType.getType(exp); + switch (type) { + case KMType.BYTE_BLOB_TYPE: + return decodeByteBlob(exp); + case KMType.TEXT_STRING_TYPE: + return decodeTstr(exp); + case KMType.INTEGER_TYPE: + return decodeInteger(exp); + case KMType.SIMPLE_VALUE_TYPE: + return decodeSimpleValue(exp); + case KMType.SEMANTIC_TAG_TYPE: + return decodeSemanticTagValue(exp); + case KMType.NEG_INTEGER_TYPE: + return decodeNegInteger(exp); + case KMType.ARRAY_TYPE: + return decodeArray(exp); + case KMType.MAP_TYPE: + return decodeMap(exp); + case KMType.ENUM_TYPE: + return decodeEnum(exp); + case KMType.KEY_PARAM_TYPE: + return decodeKeyParam(exp); + case KMType.KEY_CHAR_TYPE: + return decodeKeyChar(exp); + case KMType.VERIFICATION_TOKEN_TYPE: + return decodeVerificationToken(exp); + case KMType.HMAC_SHARING_PARAM_TYPE: + return decodeHmacSharingParam(exp); + case KMType.HW_AUTH_TOKEN_TYPE: + return decodeHwAuthToken(exp); + case KMType.COSE_KEY_TYPE: + case KMType.COSE_HEADERS_TYPE: + case KMType.COSE_CERT_PAYLOAD_TYPE: + return decodeCoseMap(exp); + case KMType.COSE_PAIR_TAG_TYPE: + short tagValueType = KMCosePairTagType.getTagValueType(exp); + return decodeCosePairTag(tagValueType, exp); + case KMType.TAG_TYPE: + short tagType = KMTag.getTagType(exp); + return decodeTag(tagType, exp); + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + return 0; + } + } + + private short decodeTag(short tagType, short exp) { + switch (tagType) { + case KMType.BIGNUM_TAG: + return decodeBignumTag(exp); + case KMType.BYTES_TAG: + return decodeBytesTag(exp); + case KMType.BOOL_TAG: + return decodeBoolTag(exp); + case KMType.UINT_TAG: + case KMType.ULONG_TAG: + case KMType.DATE_TAG: + return decodeIntegerTag(exp); + case KMType.ULONG_ARRAY_TAG: + case KMType.UINT_ARRAY_TAG: + return decodeIntegerArrayTag(exp); + case KMType.ENUM_TAG: + return decodeEnumTag(exp); + case KMType.ENUM_ARRAY_TAG: + return decodeEnumArrayTag(exp); + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + return 0; + } + } + + private short decodeVerificationToken(short exp) { + short vals = decode(KMVerificationToken.cast(exp).getVals()); + return KMVerificationToken.instance(vals); + } + + private short decodeHwAuthToken(short exp) { + short vals = decode(KMHardwareAuthToken.cast(exp).getVals()); + return KMHardwareAuthToken.instance(vals); + } + + private short decodeHmacSharingParam(short exp) { + short vals = decode(KMHmacSharingParameters.cast(exp).getVals()); + return KMHmacSharingParameters.instance(vals); + } + + private short decodeKeyChar(short exp) { + short vals = decode(KMKeyCharacteristics.cast(exp).getVals()); + return KMKeyCharacteristics.instance(vals); + } + + private short decodeCosePairKey(short exp) { + byte[] buffer = (byte[]) bufferRef[0]; + short startOff = scratchBuf[START_OFFSET]; + short keyPtr = (short) 0; + // Cose Key should be always either UINT or Negative int + if ((buffer[startOff] & MAJOR_TYPE_MASK) == UINT_TYPE) { + keyPtr = decodeInteger(exp); + } else if ((buffer[startOff] & MAJOR_TYPE_MASK) == NEG_INT_TYPE) { + keyPtr = decodeNegInteger(exp); + } else { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + return keyPtr; + } + + private short decodeCosePairSimpleValueTag(short exp) { + short keyPtr = decodeCosePairKey((KMCosePairSimpleValueTag.cast(exp).getKeyPtr())); + short valuePtr = decode(KMCosePairSimpleValueTag.cast(exp).getValuePtr()); + return KMCosePairSimpleValueTag.instance(keyPtr, valuePtr); + } + + private short decodeCosePairIntegerValueTag(short exp) { + short keyPtr = decodeCosePairKey((KMCosePairIntegerTag.cast(exp).getKeyPtr())); + short valuePtr = decode(KMCosePairIntegerTag.cast(exp).getValuePtr()); + return KMCosePairIntegerTag.instance(keyPtr, valuePtr); + } + + private short decodeCosePairNegIntegerTag(short exp) { + short keyPtr = decodeCosePairKey((KMCosePairNegIntegerTag.cast(exp).getKeyPtr())); + short valuePtr = decode(KMCosePairNegIntegerTag.cast(exp).getValuePtr()); + return KMCosePairNegIntegerTag.instance(keyPtr, valuePtr); + } + + private short decodeCosePairTxtStringTag(short exp) { + short keyPtr = decodeCosePairKey((KMCosePairTextStringTag.cast(exp).getKeyPtr())); + short valuePtr = decode(KMCosePairTextStringTag.cast(exp).getValuePtr()); + return KMCosePairTextStringTag.instance(keyPtr, valuePtr); + } + + private short decodeCosePairCoseKeyTag(short exp) { + short keyPtr = decodeCosePairKey((KMCosePairCoseKeyTag.cast(exp).getKeyPtr())); + short valuePtr = decode(KMCosePairCoseKeyTag.cast(exp).getValuePtr()); + return KMCosePairCoseKeyTag.instance(keyPtr, valuePtr); + } + + private short decodeCosePairByteBlobTag(short exp) { + short keyPtr = decodeCosePairKey((KMCosePairByteBlobTag.cast(exp).getKeyPtr())); + short valuePtr = decode(KMCosePairByteBlobTag.cast(exp).getValuePtr()); + return KMCosePairByteBlobTag.instance(keyPtr, valuePtr); + } + + private short peekCosePairTagType() { + byte[] buffer = (byte[]) bufferRef[0]; + short startOff = scratchBuf[START_OFFSET]; + // This decoder is confined to support only key and value types which are required for remote + // key provisioning. So keys of type (int / uint) and values of type (int / uint / simple / bstr / + // tstr / Cosekey) only are supported. + if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE + && (buffer[startOff] & MAJOR_TYPE_MASK) != NEG_INT_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + + short additionalMask = (short) (buffer[startOff] & ADDITIONAL_MASK); + short increment = 0; + if (additionalMask < UINT8_LENGTH) { + increment++; + } else if (additionalMask == UINT8_LENGTH) { + increment += 2; + } else if (additionalMask == UINT16_LENGTH) { + increment += 3; + } else if (additionalMask == UINT32_LENGTH) { + increment += 5; + } else { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short majorType = (short) (buffer[(short) (startOff + increment)] & MAJOR_TYPE_MASK); + short tagValueType = 0; + if (majorType == BYTES_TYPE) { + tagValueType = KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE; + } else if (majorType == UINT_TYPE) { + tagValueType = KMType.COSE_PAIR_INT_TAG_TYPE; + } else if (majorType == NEG_INT_TYPE) { + tagValueType = KMType.COSE_PAIR_NEG_INT_TAG_TYPE; + } else if (majorType == MAP_TYPE) { + tagValueType = KMType.COSE_PAIR_COSE_KEY_TAG_TYPE; + } else if (majorType == SIMPLE_VALUE_TYPE) { + tagValueType = KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE; + } else if (majorType == TSTR_TYPE) { + tagValueType = KMType.COSE_PAIR_TEXT_STR_TAG_TYPE; + } else { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + return tagValueType; + } + + private short decodeCosePairTag(short tagValueType, short exp) { + switch (tagValueType) { + case KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE: + return decodeCosePairByteBlobTag(exp); + case KMType.COSE_PAIR_NEG_INT_TAG_TYPE: + return decodeCosePairNegIntegerTag(exp); + case KMType.COSE_PAIR_INT_TAG_TYPE: + return decodeCosePairIntegerValueTag(exp); + case KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE: + return decodeCosePairSimpleValueTag(exp); + case KMType.COSE_PAIR_COSE_KEY_TAG_TYPE: + return decodeCosePairCoseKeyTag(exp); + case KMType.COSE_PAIR_TEXT_STR_TAG_TYPE: + return decodeCosePairTxtStringTag(exp); + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + return 0; + } + } + + private short decodeCoseMap(short exp) { + short payloadLength = readMajorTypeWithPayloadLength(MAP_TYPE); + // get allowed key pairs + short allowedKeyPairs = KMCoseMap.getVals(exp); + short vals = KMArray.instance(payloadLength); + short length = KMArray.cast(allowedKeyPairs).length(); + short index = 0; + boolean tagFound; + short tagInd; + short cosePairTagType; + short tagClass; + short allowedType; + short obj; + + // For each tag in payload ... + while (index < payloadLength) { + tagFound = false; + tagInd = 0; + cosePairTagType = peekCosePairTagType(); + // Check against the allowed tags ... + while (tagInd < length) { + tagClass = KMArray.cast(allowedKeyPairs).get(tagInd); + allowedType = KMCosePairTagType.getTagValueType(tagClass); + if (allowedType == cosePairTagType) { + obj = decode(tagClass); + KMArray.cast(vals).add(index, obj); + tagFound = true; + break; + } + tagInd++; + } + if (!tagFound) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } else { + index++; + } + } + return KMCoseMap.createInstanceFromType(exp, vals); + } + + private short decodeKeyParam(short exp) { + short payloadLength = readMajorTypeWithPayloadLength(MAP_TYPE); + // allowed tags + short allowedTags = KMKeyParameters.cast(exp).getVals(); + short tagRule = KMArray.cast(allowedTags).get((short) 0); + boolean ignoreInvalidTags = KMEnum.cast(tagRule).getVal() == KMType.IGNORE_INVALID_TAGS; + short vals = KMArray.instance(payloadLength); + short length = KMArray.cast(allowedTags).length(); + short index = 0; + boolean tagFound; + short tagInd; + short tagType; + short tagClass; + short allowedType; + short obj; + short arrPos = 0; + // For each tag in payload ... + while (index < payloadLength) { + tagFound = false; + tagInd = 1; + tagType = peekTagType(); + // Check against the allowed tags ... + while (tagInd < length) { + tagClass = KMArray.cast(allowedTags).get(tagInd); + allowedType = KMTag.getTagType(tagClass); + // If it is part of allowed tags ... + if (tagType == allowedType) { + // then decodeByteBlob and add that to the array. + try { + tagFound = true; + obj = decode(tagClass); + KMArray.cast(vals).add(arrPos++, obj); + break; + } catch (KMException e) { + if (KMException.reason() == KMError.INVALID_TAG) { + if (!ignoreInvalidTags) { + KMException.throwIt(KMError.INVALID_TAG); + } + } else { + KMException.throwIt(KMException.reason()); + } + break; + } + } + tagInd++; + } + if (!tagFound) { + KMException.throwIt(KMError.INVALID_TAG); + } else { + index++; + } + } + KMArray.cast(vals).setLength(arrPos); + return KMKeyParameters.instance(vals); + } + + private short decodeEnumArrayTag(short exp) { + readTagKey(KMEnumArrayTag.cast(exp).getTagType()); + return KMEnumArrayTag.instance( + scratchBuf[TAG_KEY_OFFSET], decode(KMEnumArrayTag.cast(exp).getValues())); + } + + private short decodeIntegerArrayTag(short exp) { + readTagKey(KMIntegerArrayTag.cast(exp).getTagType()); + // the values are array of integers. + return KMIntegerArrayTag.instance( + KMIntegerArrayTag.cast(exp).getTagType(), + scratchBuf[TAG_KEY_OFFSET], + decode(KMIntegerArrayTag.cast(exp).getValues())); + } + + private short decodeIntegerTag(short exp) { + readTagKey(KMIntegerTag.cast(exp).getTagType()); + // the value is an integer + return KMIntegerTag.instance( + KMIntegerTag.cast(exp).getTagType(), + scratchBuf[TAG_KEY_OFFSET], + decode(KMIntegerTag.cast(exp).getValue())); + } + + private short decodeBytesTag(short exp) { + readTagKey(KMByteTag.cast(exp).getTagType()); + // The value must be byte blob + return KMByteTag.instance(scratchBuf[TAG_KEY_OFFSET], decode(KMByteTag.cast(exp).getValue())); + } + + private short decodeBignumTag(short exp) { + readTagKey(KMBignumTag.cast(exp).getTagType()); + // The value must be byte blob + return KMBignumTag.instance( + scratchBuf[TAG_KEY_OFFSET], decode(KMBignumTag.cast(exp).getValue())); + } + + private short decodeMap(short exp) { + short payloadLength = readMajorTypeWithPayloadLength(MAP_TYPE); + short mapPtr = KMMap.instance(payloadLength); + short index = 0; + short type; + short keyobj; + short valueobj; + while (index < payloadLength) { + type = KMMap.cast(exp).getKey(index); + keyobj = decode(type); + type = KMMap.cast(exp).getKeyValue(index); + valueobj = decode(type); + KMMap.cast(mapPtr).add(index, keyobj, valueobj); + index++; + } + return mapPtr; + } + + private short decodeArray(short exp) { + short payloadLength = readMajorTypeWithPayloadLength(ARRAY_TYPE); + short arrPtr = KMArray.instance(payloadLength); + short index = 0; + short type; + short obj; + // check whether array contains one type of objects or multiple types + if (KMArray.cast(exp).containedType() + == KMType.INVALID_VALUE) { // multiple types specified by expression. + if (KMArray.cast(exp).length() != KMArray.ANY_ARRAY_LENGTH) { + if (KMArray.cast(exp).length() != payloadLength) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + } + while (index < payloadLength) { + type = KMArray.cast(exp).get(index); + obj = decode(type); + KMArray.cast(arrPtr).add(index, obj); + index++; + } + } else { // Array is a Vector containing objects of one type + type = KMArray.cast(exp).containedType(); + while (index < payloadLength) { + obj = decode(type); + KMArray.cast(arrPtr).add(index, obj); + index++; + } + } + return arrPtr; + } + + private short decodeEnumTag(short exp) { + readTagKey(KMEnumTag.cast(exp).getTagType()); + byte[] buffer = (byte[]) bufferRef[0]; + short startOff = scratchBuf[START_OFFSET]; + // Enum Tag value will always be integer with max 1 byte length. + if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short len = (short) (buffer[startOff] & ADDITIONAL_MASK); + byte enumVal = 0; + if (len > UINT8_LENGTH) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + if (len < UINT8_LENGTH) { + enumVal = (byte) (len & ADDITIONAL_MASK); + incrementStartOff((short) 1); + } else if (len == UINT8_LENGTH) { + incrementStartOff((short) 1); + // startOff is incremented so update the startOff + // with latest value before using it. + startOff = scratchBuf[START_OFFSET]; + enumVal = buffer[startOff]; + incrementStartOff((short) 1); + } + return KMEnumTag.instance(scratchBuf[TAG_KEY_OFFSET], enumVal); + } + + private short decodeBoolTag(short exp) { + readTagKey(KMBoolTag.cast(exp).getTagType()); + byte[] buffer = (byte[]) bufferRef[0]; + short startOff = scratchBuf[START_OFFSET]; + // BOOL Tag is a leaf node and it must always have tiny encoded uint value = 1. + if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if ((byte) (buffer[startOff] & ADDITIONAL_MASK) != 0x01) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + incrementStartOff((short) 1); + return KMBoolTag.instance(scratchBuf[TAG_KEY_OFFSET]); + } + + private short decodeEnum(short exp) { + byte[] buffer = (byte[]) bufferRef[0]; + short startOff = scratchBuf[START_OFFSET]; + // Enum value will always be integer with max 1 byte length. + if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short len = (short) (buffer[startOff] & ADDITIONAL_MASK); + byte enumVal; + if (len > UINT8_LENGTH) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + if (len < UINT8_LENGTH) { + enumVal = (byte) (len & ADDITIONAL_MASK); + incrementStartOff((short) 1); + } else { + incrementStartOff((short) 1); + // startOff is incremented so update the startOff + // with latest value before using it. + startOff = scratchBuf[START_OFFSET]; + enumVal = buffer[startOff]; + incrementStartOff((short) 1); + } + return KMEnum.instance(KMEnum.cast(exp).getEnumType(), enumVal); + } + + private short decodeSimpleValue(short exp) { + short startOff = scratchBuf[START_OFFSET]; + byte[] buffer = (byte[]) bufferRef[0]; + if ((buffer[startOff] & MAJOR_TYPE_MASK) != SIMPLE_VALUE_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + byte addInfo = (byte) (buffer[startOff] & ADDITIONAL_MASK); + incrementStartOff((short) 1); + return KMSimpleValue.instance(addInfo); + } + + private short decodeSemanticTagValue(short exp) { + // Decode tag. + short tag = readMajorTypeWithInteger(exp, SEMANTIC_TAG_TYPE, UINT32_LENGTH); + // Decode value pointer. + short valuePtr = decode(KMSemanticTag.cast(exp).getValuePtr()); + return KMSemanticTag.instance(tag, valuePtr); + } + + private short readMajorTypeWithInteger(short exp, short majorType, short maxLimit) { + short inst; + short startOff = scratchBuf[START_OFFSET]; + byte[] buffer = (byte[]) bufferRef[0]; + if ((buffer[startOff] & MAJOR_TYPE_MASK) != majorType) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short len = (short) (buffer[startOff] & ADDITIONAL_MASK); + if (len > maxLimit) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + incrementStartOff((short) 1); + // startOff is incremented so update the startOff + // with latest value before using it. + startOff = scratchBuf[START_OFFSET]; + if (len < UINT8_LENGTH) { + inst = KMInteger.uint_8((byte) (len & ADDITIONAL_MASK)); + } else if (len == UINT8_LENGTH) { + inst = KMInteger.instance(buffer, startOff, (short) 1); + incrementStartOff((short) 1); + } else if (len == UINT16_LENGTH) { + inst = KMInteger.instance(buffer, startOff, (short) 2); + incrementStartOff((short) 2); + } else if (len == UINT32_LENGTH) { + inst = KMInteger.instance(buffer, startOff, (short) 4); + incrementStartOff((short) 4); + } else { + inst = KMInteger.instance(buffer, startOff, (short) 8); + incrementStartOff((short) 8); + } + return inst; + } + + private short decodeInteger(short exp) { + return readMajorTypeWithInteger(exp, UINT_TYPE, UINT64_LENGTH); + } + + private short decodeNegIntegerValue(byte addInfo, byte[] buf, short startOffset) { + short inst; + short len = 0; + short scratchpad; + if (addInfo < UINT8_LENGTH) { + addInfo = (byte) (-1 - addInfo); + inst = KMNInteger.uint_8(addInfo); + } else { + switch (addInfo) { + case UINT8_LENGTH: + len = 1; + break; + case UINT16_LENGTH: + len = 2; + break; + case UINT32_LENGTH: + len = 4; + break; + case UINT64_LENGTH: + len = 8; + break; + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + // Do (-1 - N), as per cbor negative integer decoding rule. + // N is the integer value. + scratchpad = KMByteBlob.instance((short) (len * 3)); + byte[] input = KMByteBlob.cast(scratchpad).getBuffer(); + short offset = KMByteBlob.cast(scratchpad).getStartOff(); + Util.arrayFillNonAtomic(input, offset, len, (byte) -1); + Util.arrayCopyNonAtomic(buf, startOffset, input, (short) (offset + len), len); + KMUtils.subtract( + input, offset, (short) (offset + len), (short) (offset + 2 * len), (byte) len); + inst = KMNInteger.instance(input, (short) (offset + 2 * len), len); + incrementStartOff(len); + } + return inst; + } + + private short decodeNegInteger(short exp) { + short startOff = scratchBuf[START_OFFSET]; + byte[] buffer = (byte[]) bufferRef[0]; + if ((buffer[startOff] & MAJOR_TYPE_MASK) != NEG_INT_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short len = (short) (buffer[startOff] & ADDITIONAL_MASK); + if (len > UINT64_LENGTH) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + incrementStartOff((short) 1); + // startOff is incremented so update the startOff + // with latest value before using it. + startOff = scratchBuf[START_OFFSET]; + return decodeNegIntegerValue((byte) len, buffer, startOff); + } + + private short decodeTstr(short exp) { + short payloadLength = readMajorTypeWithPayloadLength(TSTR_TYPE); + short inst = + KMTextString.instance((byte[]) bufferRef[0], scratchBuf[START_OFFSET], payloadLength); + incrementStartOff(payloadLength); + return inst; + } + + private short decodeByteBlob(short exp) { + short payloadLength = readMajorTypeWithPayloadLength(BYTES_TYPE); + short inst = + KMByteBlob.instance((byte[]) bufferRef[0], scratchBuf[START_OFFSET], payloadLength); + incrementStartOff(payloadLength); + return inst; + } + + private short peekTagType() { + byte[] buffer = (byte[]) bufferRef[0]; + short startOff = scratchBuf[START_OFFSET]; + if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + + if ((short) (buffer[startOff] & ADDITIONAL_MASK) != UINT32_LENGTH) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + return (short) + ((Util.makeShort(buffer[(short) (startOff + 1)], buffer[(short) (startOff + 2)])) + & KMType.TAG_TYPE_MASK); + } + + private void readTagKey(short expectedTagType) { + byte[] buffer = (byte[]) bufferRef[0]; + short startOff = scratchBuf[START_OFFSET]; + if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if ((byte) (buffer[startOff] & ADDITIONAL_MASK) != UINT32_LENGTH) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + incrementStartOff((short) 1); + short tagType = readShort(); + scratchBuf[TAG_KEY_OFFSET] = readShort(); + if (tagType != expectedTagType) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + } + + // payload length cannot be more then 16 bits. + private short readMajorTypeWithPayloadLength(short majorType) { + short payloadLength; + byte val = readByte(); + if ((short) (val & MAJOR_TYPE_MASK) != majorType) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short lenType = (short) (val & ADDITIONAL_MASK); + if (lenType > UINT16_LENGTH) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + if (lenType < UINT8_LENGTH) { + payloadLength = lenType; + } else if (lenType == UINT8_LENGTH) { + payloadLength = (short) (readByte() & 0xFF); + } else { + payloadLength = readShort(); + } + return payloadLength; + } + + private short readShort() { + byte[] buffer = (byte[]) bufferRef[0]; + short startOff = scratchBuf[START_OFFSET]; + short val = Util.makeShort(buffer[startOff], buffer[(short) (startOff + 1)]); + incrementStartOff((short) 2); + return val; + } + + private byte readByte() { + short startOff = scratchBuf[START_OFFSET]; + byte val = ((byte[]) bufferRef[0])[startOff]; + incrementStartOff((short) 1); + return val; + } + + private void incrementStartOff(short inc) { + scratchBuf[START_OFFSET] += inc; + if (scratchBuf[START_OFFSET] > scratchBuf[LEN_OFFSET]) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + } + + public short readKeyblobVersion(byte[] buf, short bufOffset, short bufLen) { + bufferRef[0] = buf; + scratchBuf[START_OFFSET] = bufOffset; + scratchBuf[LEN_OFFSET] = (short) (bufOffset + bufLen); + short arrayLen = readMajorTypeWithPayloadLength(ARRAY_TYPE); + if (arrayLen == 0) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short version = KMType.INVALID_VALUE; + try { + version = decodeInteger(KMInteger.exp()); + } catch (Exception e) { + // Fail to decode Integer. It can happen if it is an old KeyBlob. + } + return version; + } + + public short readCertificateChainHeaderLen(byte[] buf, short bufOffset, short bufLen) { + bufferRef[0] = buf; + scratchBuf[START_OFFSET] = bufOffset; + scratchBuf[LEN_OFFSET] = (short) (bufOffset + bufLen); + readMajorTypeWithPayloadLength(BYTES_TYPE); + return (short) (scratchBuf[START_OFFSET] - bufOffset); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMEncoder.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMEncoder.java new file mode 100644 index 0000000..941a0ac --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMEncoder.java @@ -0,0 +1,785 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import com.android.javacard.seprovider.KMException; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; + +/** + * This class encodes KMType structures to a cbor format data recursively. Encoded bytes are written + * on the buffer provided by the caller. An exception will be thrown if the encoded data length is + * greater than the buffer length provided. + */ +public class KMEncoder { + + // major types + private static final byte UINT_TYPE = 0x00; + private static final byte NEG_INT_TYPE = 0x20; + private static final byte BYTES_TYPE = 0x40; + private static final byte TSTR_TYPE = 0x60; + private static final byte ARRAY_TYPE = (byte) 0x80; + private static final byte MAP_TYPE = (byte) 0xA0; + private static final byte SIMPLE_VALUE_TYPE = (byte) 0xE0; + private static final byte SEMANTIC_TAG_TYPE = (byte) 0xC0; + + // masks + private static final byte ADDITIONAL_MASK = 0x1F; + + // value length + private static final byte UINT8_LENGTH = (byte) 0x18; + private static final byte UINT16_LENGTH = (byte) 0x19; + private static final byte UINT32_LENGTH = (byte) 0x1A; + private static final byte UINT64_LENGTH = (byte) 0x1B; + private static final short TINY_PAYLOAD = 0x17; + private static final short SHORT_PAYLOAD = 0x100; + private static final byte STACK_SIZE = 50; + private static final byte SCRATCH_BUF_SIZE = 6; + private static final byte START_OFFSET = 0; + private static final byte LEN_OFFSET = 2; + private static final byte STACK_PTR_OFFSET = 4; + + private Object[] bufferRef; + private short[] scratchBuf; + private short[] stack; + + public KMEncoder() { + bufferRef = JCSystem.makeTransientObjectArray((short) 1, JCSystem.CLEAR_ON_RESET); + scratchBuf = JCSystem.makeTransientShortArray(SCRATCH_BUF_SIZE, JCSystem.CLEAR_ON_RESET); + stack = JCSystem.makeTransientShortArray(STACK_SIZE, JCSystem.CLEAR_ON_RESET); + bufferRef[0] = null; + scratchBuf[START_OFFSET] = (short) 0; + scratchBuf[LEN_OFFSET] = (short) 0; + scratchBuf[STACK_PTR_OFFSET] = (short) 0; + } + + private void push(short objPtr) { + stack[scratchBuf[STACK_PTR_OFFSET]] = objPtr; + scratchBuf[STACK_PTR_OFFSET]++; + } + + private short pop() { + scratchBuf[STACK_PTR_OFFSET]--; + return stack[scratchBuf[STACK_PTR_OFFSET]]; + } + + private void encode(short obj) { + push(obj); + } + + /** + * This functions encodes the given object into the provider buffer space in cbor format. + * + * @param object Object to be encoded into cbor data. + * @param buffer Output where cbor data is copied. + * @param startOff is the start offset of the buffer. + * @param bufLen length of the buffer + * @param encoderOutLimitLen excepted encoded output length. + * @return length of the encoded buffer. + */ + public short encode( + short object, byte[] buffer, short startOff, short bufLen, short encoderOutLimitLen) { + scratchBuf[STACK_PTR_OFFSET] = 0; + bufferRef[0] = buffer; + scratchBuf[START_OFFSET] = startOff; + if ((short) (startOff + encoderOutLimitLen) > bufLen) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + scratchBuf[LEN_OFFSET] = (short) (startOff + encoderOutLimitLen); + push(object); + encode(); + return (short) (scratchBuf[START_OFFSET] - startOff); + } + + public short encode(short object, byte[] buffer, short startOff, short bufLen) { + return encode(object, buffer, startOff, bufLen, (short) (bufLen - startOff)); + } + + // array{KMError.OK,Array{KMByteBlobs}} + public short encodeCert(byte[] certBuffer, short bufferStart, short certStart, short certLength) { + if (bufferStart > certStart) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + bufferRef[0] = certBuffer; + scratchBuf[START_OFFSET] = certStart; + scratchBuf[LEN_OFFSET] = (short) (certStart + 1); + // Byte Header + cert length + scratchBuf[START_OFFSET] -= getEncodedBytesLength(certLength); + // Array header - 1 elements i.e. 1 byte + scratchBuf[START_OFFSET]--; + if (scratchBuf[START_OFFSET] < bufferStart) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + bufferStart = scratchBuf[START_OFFSET]; + writeMajorTypeWithLength(ARRAY_TYPE, (short) 1); // Array of 1 elements + writeMajorTypeWithLength(BYTES_TYPE, certLength); // Cert Byte Blob of length + return bufferStart; + } + + private void encode() { + while (scratchBuf[STACK_PTR_OFFSET] > 0) { + short exp = pop(); + byte type = KMType.getType(exp); + switch (type) { + case KMType.BYTE_BLOB_TYPE: + encodeByteBlob(exp); + break; + case KMType.TEXT_STRING_TYPE: + encodeTextString(exp); + break; + case KMType.INTEGER_TYPE: + encodeUnsignedInteger(exp); + break; + case KMType.SIMPLE_VALUE_TYPE: + encodeSimpleValue(exp); + break; + case KMType.NEG_INTEGER_TYPE: + encodeNegInteger(exp); + break; + case KMType.ARRAY_TYPE: + encodeArray(exp); + break; + case KMType.MAP_TYPE: + encodeMap(exp); + break; + case KMType.ENUM_TYPE: + encodeEnum(exp); + break; + case KMType.KEY_PARAM_TYPE: + encodeKeyParam(exp); + break; + case KMType.SEMANTIC_TAG_TYPE: + encodeSemanticTag(exp); + break; + case KMType.COSE_KEY_TYPE: + case KMType.COSE_HEADERS_TYPE: + case KMType.COSE_CERT_PAYLOAD_TYPE: + encodeCoseMap(exp); + break; + case KMType.KEY_CHAR_TYPE: + encodeKeyChar(exp); + break; + case KMType.VERIFICATION_TOKEN_TYPE: + encodeVeriToken(exp); + break; + case KMType.HMAC_SHARING_PARAM_TYPE: + encodeHmacSharingParam(exp); + break; + case KMType.HW_AUTH_TOKEN_TYPE: + encodeHwAuthToken(exp); + break; + case KMType.TAG_TYPE: + short tagType = KMTag.getTagType(exp); + encodeTag(tagType, exp); + break; + case KMType.COSE_PAIR_TAG_TYPE: + short cosePairTagType = KMCosePairTagType.getTagValueType(exp); + encodeCosePairTag(cosePairTagType, exp); + break; + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + } + } + + private void encodeCosePairIntegerTag(short exp) { + KMCosePairIntegerTag cosePairIntTag = KMCosePairIntegerTag.cast(exp); + // push key and value ptr in stack to get encoded. + encode(cosePairIntTag.getValuePtr()); + encode(cosePairIntTag.getKeyPtr()); + } + + private void encodeCosePairByteBlobTag(short exp) { + KMCosePairByteBlobTag cosePairByteBlobTag = KMCosePairByteBlobTag.cast(exp); + // push key and value ptr in stack to get encoded. + encode(cosePairByteBlobTag.getValuePtr()); + encode(cosePairByteBlobTag.getKeyPtr()); + } + + private void encodeCosePairCoseKeyTag(short exp) { + KMCosePairCoseKeyTag cosePairCoseKeyTag = KMCosePairCoseKeyTag.cast(exp); + // push key and value ptr in stack to get encoded. + encode(cosePairCoseKeyTag.getValuePtr()); + encode(cosePairCoseKeyTag.getKeyPtr()); + } + + private void encodeCosePairTextStringTag(short exp) { + KMCosePairTextStringTag cosePairTextStringTag = KMCosePairTextStringTag.cast(exp); + // push key and value ptr in stack to get encoded. + encode(cosePairTextStringTag.getValuePtr()); + encode(cosePairTextStringTag.getKeyPtr()); + } + + private void encodeCosePairSimpleValueTag(short exp) { + KMCosePairSimpleValueTag cosePairSimpleValueTag = KMCosePairSimpleValueTag.cast(exp); + // push key and value ptr in stack to get encoded. + encode(cosePairSimpleValueTag.getValuePtr()); + encode(cosePairSimpleValueTag.getKeyPtr()); + } + + private void encodeCosePairNegIntegerTag(short exp) { + KMCosePairNegIntegerTag cosePairNegIntegerTag = KMCosePairNegIntegerTag.cast(exp); + // push key and value ptr in stack to get encoded. + encode(cosePairNegIntegerTag.getValuePtr()); + encode(cosePairNegIntegerTag.getKeyPtr()); + } + + private void encodeCosePairTag(short tagType, short exp) { + switch (tagType) { + case KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE: + encodeCosePairByteBlobTag(exp); + return; + case KMType.COSE_PAIR_INT_TAG_TYPE: + encodeCosePairIntegerTag(exp); + return; + case KMType.COSE_PAIR_NEG_INT_TAG_TYPE: + encodeCosePairNegIntegerTag(exp); + return; + case KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE: + encodeCosePairSimpleValueTag(exp); + return; + case KMType.COSE_PAIR_TEXT_STR_TAG_TYPE: + encodeCosePairTextStringTag(exp); + return; + case KMType.COSE_PAIR_COSE_KEY_TAG_TYPE: + encodeCosePairCoseKeyTag(exp); + return; + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + } + + private void encodeTag(short tagType, short exp) { + switch (tagType) { + case KMType.BIGNUM_TAG: + encodeBignumTag(exp); + return; + case KMType.BYTES_TAG: + encodeBytesTag(exp); + return; + case KMType.BOOL_TAG: + encodeBoolTag(exp); + return; + case KMType.UINT_TAG: + case KMType.ULONG_TAG: + case KMType.DATE_TAG: + encodeIntegerTag(exp); + return; + case KMType.ULONG_ARRAY_TAG: + case KMType.UINT_ARRAY_TAG: + encodeIntegerArrayTag(exp); + return; + case KMType.ENUM_TAG: + encodeEnumTag(exp); + return; + case KMType.ENUM_ARRAY_TAG: + encodeEnumArrayTag(exp); + return; + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + } + + private void encodeCoseMap(short obj) { + encodeAsMap(KMCoseMap.getVals(obj)); + } + + private void encodeKeyParam(short obj) { + encodeAsMap(KMKeyParameters.cast(obj).getVals()); + } + + private void encodeKeyChar(short obj) { + encode(KMKeyCharacteristics.cast(obj).getVals()); + } + + private void encodeVeriToken(short obj) { + encode(KMVerificationToken.cast(obj).getVals()); + } + + private void encodeHwAuthToken(short obj) { + encode(KMHardwareAuthToken.cast(obj).getVals()); + } + + private void encodeHmacSharingParam(short obj) { + encode(KMHmacSharingParameters.cast(obj).getVals()); + } + + private void encodeArray(short obj) { + writeMajorTypeWithLength(ARRAY_TYPE, KMArray.cast(obj).length()); + short len = KMArray.cast(obj).length(); + short index = (short) (len - 1); + short subObj; + while (index >= 0) { + subObj = KMArray.cast(obj).get(index); + if (subObj != KMType.INVALID_VALUE) { + encode(subObj); + } + index--; + } + } + + public void encodeArrayOnlyLength(short arrLength, byte[] buffer, short offset, short length) { + bufferRef[0] = buffer; + scratchBuf[START_OFFSET] = offset; + scratchBuf[LEN_OFFSET] = (short) (offset + length + 1); + writeMajorTypeWithLength(ARRAY_TYPE, length); + } + + private void encodeMap(short obj) { + writeMajorTypeWithLength(MAP_TYPE, KMMap.cast(obj).length()); + short len = KMMap.cast(obj).length(); + short index = (short) (len - 1); + while (index >= 0) { + encode(KMMap.cast(obj).getKeyValue(index)); + encode(KMMap.cast(obj).getKey(index)); + index--; + } + } + + private void encodeAsMap(short obj) { + writeMajorTypeWithLength(MAP_TYPE, KMArray.cast(obj).length()); + short len = KMArray.cast(obj).length(); + short index = (short) (len - 1); + short inst; + while (index >= 0) { + inst = KMArray.cast(obj).get(index); + encode(inst); + index--; + } + } + + private void encodeIntegerArrayTag(short obj) { + writeTag(KMIntegerArrayTag.cast(obj).getTagType(), KMIntegerArrayTag.cast(obj).getKey()); + encode(KMIntegerArrayTag.cast(obj).getValues()); + } + + private void encodeEnumArrayTag(short obj) { + writeTag(KMEnumArrayTag.cast(obj).getTagType(), KMEnumArrayTag.cast(obj).getKey()); + encode(KMEnumArrayTag.cast(obj).getValues()); + } + + private void encodeIntegerTag(short obj) { + writeTag(KMIntegerTag.cast(obj).getTagType(), KMIntegerTag.cast(obj).getKey()); + encode(KMIntegerTag.cast(obj).getValue()); + } + + private void encodeBignumTag(short obj) { + writeTag(KMBignumTag.getTagType(obj), KMBignumTag.getKey(obj)); + encode(KMBignumTag.cast(obj).getValue()); + } + + private void encodeBytesTag(short obj) { + writeTag(KMByteTag.cast(obj).getTagType(), KMByteTag.cast(obj).getKey()); + encode(KMByteTag.cast(obj).getValue()); + } + + private void encodeBoolTag(short obj) { + writeTag(KMBoolTag.cast(obj).getTagType(), KMBoolTag.cast(obj).getKey()); + writeByteValue(KMBoolTag.cast(obj).getVal()); + } + + private void encodeEnumTag(short obj) { + writeTag(KMEnumTag.cast(obj).getTagType(), KMEnumTag.cast(obj).getKey()); + writeByteValue(KMEnumTag.cast(obj).getValue()); + } + + private void encodeEnum(short obj) { + writeByteValue(KMEnum.cast(obj).getVal()); + } + + private void encodeInteger(byte[] val, short len, short startOff, short majorType) { + // find out the most significant byte + short msbIndex = findMsb(val, startOff, len); + // find the difference between most significant byte and len + short diff = (short) (len - msbIndex); + if (diff == 0) { + writeByte((byte) (majorType | 0)); + } else if ((diff == 1) + && (val[(short) (startOff + msbIndex)] < UINT8_LENGTH) + && (val[(short) (startOff + msbIndex)] >= 0)) { + writeByte((byte) (majorType | val[(short) (startOff + msbIndex)])); + } else if (diff == 1) { + writeByte((byte) (majorType | UINT8_LENGTH)); + writeByte(val[(short) (startOff + msbIndex)]); + } else if (diff == 2) { + writeByte((byte) (majorType | UINT16_LENGTH)); + writeBytes(val, (short) (startOff + msbIndex), (short) 2); + } else if (diff <= 4) { + writeByte((byte) (majorType | UINT32_LENGTH)); + writeBytes(val, (short) (startOff + len - 4), (short) 4); + } else { + writeByte((byte) (majorType | UINT64_LENGTH)); + writeBytes(val, startOff, (short) 8); + } + } + + // find out the most significant byte + public short findMsb(byte[] buf, short offset, short len) { + byte index = 0; + // find out the most significant byte + while (index < len) { + if (buf[(short) (offset + index)] > 0) { + break; + } else if (buf[(short) (offset + index)] < 0) { + break; + } + index++; // index will be equal to len if value is 0. + } + return index; + } + + public void computeOnesCompliment(short msbIndex, byte[] buf, short offset, short len) { + // find the difference between most significant byte and len + short diff = (short) (len - msbIndex); + short correctedOffset = offset; + short correctedLen = len; + // The offset and length of the buffer for Short and Byte types should be + // corrected before computing the 1s compliment. The reason for doing this + // is to avoid computation of 1s compliment on the MSB bytes. + if (diff == 0) { + // Fail + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } else if (diff == 1) { + correctedOffset = (short) (offset + 3); + correctedLen = 1; + } else if (diff == 2) { + correctedOffset = (short) (offset + 2); + correctedLen = 2; + } + // For int and long values the len and offset values are always proper. + // int - 4 bytes + // long - 8 bytes. + KMUtils.computeOnesCompliment(buf, correctedOffset, correctedLen); + } + + // Encoding rule for negative Integers is taken from + // https://datatracker.ietf.org/doc/html/rfc7049#section-2.1, Major type 1. + public short handleNegIntegerEncodingRule(byte[] buf, short offset, short len) { + short msbIndex = findMsb(buf, offset, len); + // Do -1-N, where N is the negative integer + // The value of -1-N is equal to the 1s compliment of N. + computeOnesCompliment(msbIndex, buf, offset, len); + return msbIndex; + } + + // Note: This function modifies the buffer's actual value. So after encoding, restore the original + // value by calling removeNegIntegerEncodingRule(). + public short applyNegIntegerEncodingRule(byte[] buf, short offset, short len) { + return handleNegIntegerEncodingRule(buf, offset, len); + } + + public void removeNegIntegerEncodingRule( + byte[] buf, short offset, short len, short origMsbIndex) { + // Do -1-N, where N is the negative integer + // The value of -1-N is equal to the 1s compliment of N. + computeOnesCompliment(origMsbIndex, buf, offset, len); + } + + private void encodeNegInteger(short obj) { + byte[] val = KMNInteger.cast(obj).getBuffer(); + short len = KMNInteger.cast(obj).length(); + short startOff = KMNInteger.cast(obj).getStartOff(); + short msbIndex = applyNegIntegerEncodingRule(val, startOff, len); + encodeInteger(val, len, startOff, NEG_INT_TYPE); + removeNegIntegerEncodingRule(val, startOff, len, msbIndex); + } + + private void encodeSemanticTag(short obj) { + short tag = KMSemanticTag.cast(obj).getKeyPtr(); + encode(KMSemanticTag.cast(obj).getValuePtr()); + encodeInteger( + KMInteger.cast(tag).getBuffer(), + KMInteger.cast(tag).length(), + KMInteger.cast(tag).getStartOff(), + SEMANTIC_TAG_TYPE); + } + + private void encodeUnsignedInteger(short obj) { + byte[] val = KMInteger.cast(obj).getBuffer(); + short len = KMInteger.cast(obj).length(); + short startOff = KMInteger.cast(obj).getStartOff(); + encodeInteger(val, len, startOff, UINT_TYPE); + } + + private void encodeSimpleValue(short obj) { + byte value = KMSimpleValue.cast(obj).getValue(); + writeByte((byte) (SIMPLE_VALUE_TYPE | value)); + } + + private void encodeTextString(short obj) { + writeMajorTypeWithLength(TSTR_TYPE, KMTextString.cast(obj).length()); + writeBytes( + KMTextString.cast(obj).getBuffer(), + KMTextString.cast(obj).getStartOff(), + KMTextString.cast(obj).length()); + } + + public short encodeByteBlobHeader(short bufLen, byte[] buffer, short startOff, short length) { + bufferRef[0] = buffer; + scratchBuf[START_OFFSET] = startOff; + scratchBuf[LEN_OFFSET] = (short) (startOff + length + 1); + writeMajorTypeWithLength(BYTES_TYPE, bufLen); + return (short) (scratchBuf[START_OFFSET] - startOff); + } + + private void encodeByteBlob(short obj) { + writeMajorTypeWithLength(BYTES_TYPE, KMByteBlob.cast(obj).length()); + writeBytes( + KMByteBlob.cast(obj).getBuffer(), + KMByteBlob.cast(obj).getStartOff(), + KMByteBlob.cast(obj).length()); + } + + public short getEncodedLength(short ptr) { + short len = 0; + short type = KMType.getType(ptr); + switch (type) { + case KMType.BYTE_BLOB_TYPE: + len += getEncodedByteBlobLength(ptr); + break; + case KMType.TEXT_STRING_TYPE: + len += getEncodedTextStringLength(ptr); + break; + case KMType.INTEGER_TYPE: + len += getEncodedIntegerLength(ptr); + break; + case KMType.NEG_INTEGER_TYPE: + len += getEncodedNegIntegerLength(ptr); + break; + case KMType.ARRAY_TYPE: + len += getEncodedArrayLen(ptr); + break; + case KMType.MAP_TYPE: + len += getEncodedMapLen(ptr); + break; + case KMType.COSE_PAIR_TAG_TYPE: + short cosePairTagType = KMCosePairTagType.getTagValueType(ptr); + len += getEncodedCosePairTagLen(cosePairTagType, ptr); + break; + case KMType.COSE_KEY_TYPE: + case KMType.COSE_HEADERS_TYPE: + case KMType.COSE_CERT_PAYLOAD_TYPE: + len += getEncodedArrayLen(KMCoseMap.getVals(ptr)); + break; + default: + KMException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return len; + } + + private short getEncodedCosePairTagLen(short tagType, short exp) { + short length = 0; + switch (tagType) { + case KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE: + KMCosePairByteBlobTag cosePairByteBlobTag = KMCosePairByteBlobTag.cast(exp); + length = getEncodedLength(cosePairByteBlobTag.getKeyPtr()); + length += getEncodedLength(cosePairByteBlobTag.getValuePtr()); + break; + case KMType.COSE_PAIR_INT_TAG_TYPE: + KMCosePairIntegerTag cosePairIntTag = KMCosePairIntegerTag.cast(exp); + length = getEncodedLength(cosePairIntTag.getValuePtr()); + length += getEncodedLength(cosePairIntTag.getKeyPtr()); + break; + case KMType.COSE_PAIR_NEG_INT_TAG_TYPE: + KMCosePairNegIntegerTag cosePairNegIntegerTag = KMCosePairNegIntegerTag.cast(exp); + length = getEncodedLength(cosePairNegIntegerTag.getValuePtr()); + length += getEncodedLength(cosePairNegIntegerTag.getKeyPtr()); + break; + case KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE: + KMCosePairSimpleValueTag cosePairSimpleValueTag = KMCosePairSimpleValueTag.cast(exp); + length = getEncodedLength(cosePairSimpleValueTag.getValuePtr()); + length += getEncodedLength(cosePairSimpleValueTag.getKeyPtr()); + break; + case KMType.COSE_PAIR_TEXT_STR_TAG_TYPE: + KMCosePairTextStringTag cosePairTextStringTag = KMCosePairTextStringTag.cast(exp); + length = getEncodedLength(cosePairTextStringTag.getValuePtr()); + length += getEncodedLength(cosePairTextStringTag.getKeyPtr()); + break; + case KMType.COSE_PAIR_COSE_KEY_TAG_TYPE: + KMCosePairCoseKeyTag cosePairCoseKeyTag = KMCosePairCoseKeyTag.cast(exp); + length = getEncodedLength(cosePairCoseKeyTag.getValuePtr()); + length += getEncodedLength(cosePairCoseKeyTag.getKeyPtr()); + break; + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + return length; + } + + private short getEncodedMapLen(short obj) { + short mapLen = KMMap.cast(obj).length(); + short len = getEncodedBytesLength(mapLen); + short index = 0; + while (index < mapLen) { + len += getEncodedLength(KMMap.cast(obj).getKey(index)); + len += getEncodedLength(KMMap.cast(obj).getKeyValue(index)); + index++; + } + return len; + } + + private short getEncodedArrayLen(short obj) { + short arrLen = KMArray.cast(obj).length(); + short len = getEncodedBytesLength(arrLen); + short index = 0; + short subObj; + while (index < arrLen) { + subObj = KMArray.cast(obj).get(index); + if (subObj != KMType.INVALID_VALUE) { + len += getEncodedLength(subObj); + } + index++; + } + return len; + } + + public short getEncodedBytesLength(short len) { + short ret = 0; + if (len < KMEncoder.UINT8_LENGTH && len >= 0) { + ret = 1; + } else if (len >= KMEncoder.UINT8_LENGTH && len <= (short) 0x00FF) { + ret = 2; + } else if (len > (short) 0x00FF && len <= (short) 0x7FFF) { + ret = 3; + } else { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + return ret; + } + + private short getEncodedByteBlobLength(short obj) { + short len = KMByteBlob.cast(obj).length(); + len += getEncodedBytesLength(len); + return len; + } + + private short getEncodedTextStringLength(short obj) { + short len = KMTextString.cast(obj).length(); + len += getEncodedBytesLength(len); + return len; + } + + private short getEncodedNegIntegerLength(short obj) { + byte[] buf = KMNInteger.cast(obj).getBuffer(); + short len = KMNInteger.cast(obj).length(); + short offset = KMNInteger.cast(obj).getStartOff(); + short msbIndex = applyNegIntegerEncodingRule(buf, offset, len); + short ret = getEncodedIntegerLength(buf, offset, len); + removeNegIntegerEncodingRule(buf, offset, len, msbIndex); + return ret; + } + + private short getEncodedIntegerLength(byte[] val, short startOff, short len) { + short msbIndex = findMsb(val, startOff, len); + // find the difference between most significant byte and len + short diff = (short) (len - msbIndex); + switch (diff) { + case 0: + case 1: // Byte + if ((val[(short) (startOff + msbIndex)] < KMEncoder.UINT8_LENGTH) + && (val[(short) (startOff + msbIndex)] >= 0)) { + return (short) 1; + } else { + return (short) 2; + } + case 2: // Short + return (short) 3; + case 3: + case 4: // UInt32 + return (short) 5; + case 5: + case 6: + case 7: + case 8: // UInt64 + return (short) 9; + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + return 0; + } + + private short getEncodedIntegerLength(short obj) { + byte[] val = KMInteger.cast(obj).getBuffer(); + short len = KMInteger.cast(obj).length(); + short startOff = KMInteger.cast(obj).getStartOff(); + return getEncodedIntegerLength(val, startOff, len); + } + + private void writeByteValue(byte val) { + if ((val < UINT8_LENGTH) && (val >= 0)) { + writeByte((byte) (UINT_TYPE | val)); + } else { + writeByte((byte) (UINT_TYPE | UINT8_LENGTH)); + writeByte(val); + } + } + + private void writeTag(short tagType, short tagKey) { + writeByte((byte) (UINT_TYPE | UINT32_LENGTH)); + writeShort(tagType); + writeShort(tagKey); + } + + private void writeMajorTypeWithLength(byte majorType, short len) { + if (len <= TINY_PAYLOAD) { + writeByte((byte) (majorType | (byte) (len & ADDITIONAL_MASK))); + } else if (len < SHORT_PAYLOAD) { + writeByte((byte) (majorType | UINT8_LENGTH)); + writeByte((byte) (len & 0xFF)); + } else { + writeByte((byte) (majorType | UINT16_LENGTH)); + writeShort(len); + } + } + + private void writeBytes(byte[] buf, short start, short len) { + byte[] buffer = (byte[]) bufferRef[0]; + Util.arrayCopyNonAtomic(buf, start, buffer, scratchBuf[START_OFFSET], len); + incrementStartOff(len); + } + + private void writeShort(short val) { + byte[] buffer = (byte[]) bufferRef[0]; + buffer[scratchBuf[START_OFFSET]] = (byte) ((val >> 8) & 0xFF); + incrementStartOff((short) 1); + buffer[scratchBuf[START_OFFSET]] = (byte) ((val & 0xFF)); + incrementStartOff((short) 1); + } + + private void writeByte(byte val) { + byte[] buffer = (byte[]) bufferRef[0]; + buffer[scratchBuf[START_OFFSET]] = val; + incrementStartOff((short) 1); + } + + private void incrementStartOff(short inc) { + scratchBuf[START_OFFSET] += inc; + if (scratchBuf[START_OFFSET] >= scratchBuf[LEN_OFFSET]) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + } + + public short encodeArrayHeader(short bufLen, byte[] buffer, short startOff, short length) { + bufferRef[0] = buffer; + scratchBuf[START_OFFSET] = startOff; + scratchBuf[LEN_OFFSET] = (short) (startOff + length + 1); + writeMajorTypeWithLength(ARRAY_TYPE, bufLen); + return (short) (scratchBuf[START_OFFSET] - startOff); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMEnum.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMEnum.java new file mode 100644 index 0000000..44bf477 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMEnum.java @@ -0,0 +1,166 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMEnum represents an enumeration specified in android keymaster hal specifications. It + * corresponds to uint CBOR type and it is a byte value. struct{byte ENUM_TYPE; short length; + * struct{short enumType; byte val}} + */ +public class KMEnum extends KMType { + + private static KMEnum prototype; + + // The allowed enum types. + private static short[] types = { + HARDWARE_TYPE, + KEY_FORMAT, + KEY_DERIVATION_FUNCTION, + VERIFIED_BOOT_STATE, + DEVICE_LOCKED, + USER_AUTH_TYPE, + PURPOSE, + ECCURVE, + RULE + }; + + private static Object[] enums = null; + + private KMEnum() {} + + private static KMEnum proto(short ptr) { + if (prototype == null) { + prototype = new KMEnum(); + } + KMType.instanceTable[KM_ENUM_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + return KMType.exp(ENUM_TYPE); + } + + public static KMEnum cast(short ptr) { + if (heap[ptr] != ENUM_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + 1)) == INVALID_VALUE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static short instance(short enumType) { + if (!validateEnum(enumType, NO_VALUE)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = KMType.instance(ENUM_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), enumType); + return ptr; + } + + public static short instance(short enumType, byte val) { + if (!validateEnum(enumType, val)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = KMType.instance(ENUM_TYPE, (short) 3); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), enumType); + heap[(short) (ptr + TLV_HEADER_SIZE + 2)] = val; + return ptr; + } + + private static void create() { + // The allowed enum values to corresponding enum types in the types array. + if (enums == null) { + enums = + new Object[] { + new byte[] {SOFTWARE, TRUSTED_ENVIRONMENT, STRONGBOX}, + new byte[] {X509, PKCS8, RAW}, + new byte[] { + DERIVATION_NONE, + RFC5869_SHA256, + ISO18033_2_KDF1_SHA1, + ISO18033_2_KDF1_SHA256, + ISO18033_2_KDF2_SHA1, + ISO18033_2_KDF2_SHA256 + }, + new byte[] {SELF_SIGNED_BOOT, VERIFIED_BOOT, UNVERIFIED_BOOT, FAILED_BOOT}, + new byte[] {DEVICE_LOCKED_TRUE, DEVICE_LOCKED_FALSE}, + new byte[] {USER_AUTH_NONE, PASSWORD, FINGERPRINT, BOTH}, + new byte[] {ENCRYPT, DECRYPT, SIGN, VERIFY, WRAP_KEY, ATTEST_KEY, AGREE_KEY}, + new byte[] {P_224, P_256, P_384, P_521}, + new byte[] {IGNORE_INVALID_TAGS, FAIL_ON_INVALID_TAGS} + }; + } + } + + // isValidTag enumeration keys and values. + private static boolean validateEnum(short key, byte value) { + create(); + byte[] vals; + short enumInd; + // check if key exists + short index = (short) types.length; + while (--index >= 0) { + if (types[index] == key) { + // check if value given + if (value != NO_VALUE) { + // check if the value exist + vals = (byte[]) enums[index]; + enumInd = (short) vals.length; + while (--enumInd >= 0) { + if (vals[enumInd] == value) { + // return true if value exist + return true; + } + } + // return false if value does not exist + return false; + } + // return true if key exist and value not given + return true; + } + } + // return false if key does not exist + return false; + } + + public short length() { + return Util.getShort(heap, (short) (KMType.instanceTable[KM_ENUM_OFFSET] + 1)); + } + + public byte getVal() { + return heap[(short) (KMType.instanceTable[KM_ENUM_OFFSET] + TLV_HEADER_SIZE + 2)]; + } + + public void setVal(byte val) { + heap[(short) (KMType.instanceTable[KM_ENUM_OFFSET] + TLV_HEADER_SIZE + 2)] = val; + } + + public short getEnumType() { + return Util.getShort(heap, (short) (KMType.instanceTable[KM_ENUM_OFFSET] + TLV_HEADER_SIZE)); + } + + public void setEnumType(short type) { + Util.setShort(heap, (short) (KMType.instanceTable[KM_ENUM_OFFSET] + TLV_HEADER_SIZE), type); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMEnumArrayTag.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMEnumArrayTag.java new file mode 100644 index 0000000..ea73c40 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMEnumArrayTag.java @@ -0,0 +1,305 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMEnumArrayTag represents ENUM_REP tag type. It has following structure, struct{byte TAG_TYPE; + * short length; struct{short ENUM_ARRAY_TAG; short tagKey; sequence of byte values}} + */ +public class KMEnumArrayTag extends KMTag { + + private static KMEnumArrayTag prototype; + + // The allowed tag keys of enum array type. + private static short[] tags = {PURPOSE, BLOCK_MODE, DIGEST, PADDING, RSA_OAEP_MGF_DIGEST}; + + // Tag Values. + private static Object[] enums = null; + + private KMEnumArrayTag() {} + + private static KMEnumArrayTag proto(short ptr) { + if (prototype == null) { + prototype = new KMEnumArrayTag(); + } + KMType.instanceTable[KM_ENUM_ARRAY_TAG_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short blobPtr = KMByteBlob.exp(); + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), ENUM_ARRAY_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), INVALID_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), blobPtr); + return ptr; + } + + public static short instance(short key) { + byte[] vals = getAllowedEnumValues(key); + if (vals == null) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short blobPtr = KMByteBlob.exp(); + return instance(key, blobPtr); + } + + public static short instance(short key, short byteBlob) { + byte[] allowedVals = getAllowedEnumValues(key); + if (allowedVals == null) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + KMByteBlob blob = KMByteBlob.cast(byteBlob); + short byteIndex = 0; + short enumIndex; + boolean validValue; + while (byteIndex < blob.length()) { + enumIndex = 0; + validValue = false; + while (enumIndex < allowedVals.length) { + if (blob.get(byteIndex) == allowedVals[enumIndex]) { + validValue = true; + break; + } + enumIndex++; + } + if (!validValue) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + byteIndex++; + } + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), ENUM_ARRAY_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), key); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), byteBlob); + return ptr; + } + + public static KMEnumArrayTag cast(short ptr) { + if (heap[ptr] != TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)) != ENUM_ARRAY_TAG) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static void create() { + if (enums == null) { + // allowed tag values. + enums = + new Object[] { + new byte[] {ENCRYPT, DECRYPT, SIGN, VERIFY, WRAP_KEY, ATTEST_KEY, AGREE_KEY}, + new byte[] {ECB, CBC, CTR, GCM}, + new byte[] {DIGEST_NONE, MD5, SHA1, SHA2_224, SHA2_256, SHA2_384, SHA2_512}, + new byte[] { + PADDING_NONE, RSA_OAEP, RSA_PSS, RSA_PKCS1_1_5_ENCRYPT, RSA_PKCS1_1_5_SIGN, PKCS7 + }, + new byte[] {DIGEST_NONE, MD5, SHA1, SHA2_224, SHA2_256, SHA2_384, SHA2_512}, + }; + } + } + + private static byte[] getAllowedEnumValues(short key) { + create(); + short index = (short) tags.length; + while (--index >= 0) { + if (tags[index] == key) { + return (byte[]) enums[index]; + } + } + return null; + } + + public static short getValues(short tagId, short params, byte[] buf, short start) { + short tag = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, tagId, params); + if (tag == KMType.INVALID_VALUE) { + return KMType.INVALID_VALUE; + } + tag = KMEnumArrayTag.cast(tag).getValues(); + return KMByteBlob.cast(tag).getValues(buf, start); + } + + public static boolean contains(short tagId, short tagValue, short params) { + short tag = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, tagId, params); + if (tag != KMType.INVALID_VALUE) { + short index = 0; + while (index < KMEnumArrayTag.cast(tag).length()) { + if (tagValue == KMEnumArrayTag.cast(tag).get(index)) { + return true; + } + index++; + } + } + return false; + } + + public static short length(short tagId, short params) { + short tag = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, tagId, params); + if (tag != KMType.INVALID_VALUE) { + return KMEnumArrayTag.cast(tag).length(); + } + return KMType.INVALID_VALUE; + } + + public short getKey() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_ENUM_ARRAY_TAG_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short getTagType() { + return KMType.ENUM_ARRAY_TAG; + } + + public short getValues() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_ENUM_ARRAY_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + } + + public short length() { + short blobPtr = + Util.getShort( + heap, (short) (KMType.instanceTable[KM_ENUM_ARRAY_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + return KMByteBlob.cast(blobPtr).length(); + } + + public short get(short index) { + return KMByteBlob.cast(getValues()).get(index); + } + + public boolean contains(short tagValue) { + short index = 0; + while (index < length()) { + if (get(index) == (byte) tagValue) { + return true; + } + index++; + } + return false; + } + + public boolean isValidDigests(byte alg) { + short index = 0; + short digest; + while (index < length()) { + digest = get(index); + switch (alg) { + case KMType.EC: + case KMType.RSA: + if (digest != KMType.DIGEST_NONE && digest != KMType.SHA2_256 && digest != KMType.SHA1) { + return false; + } + break; + case KMType.HMAC: + if (digest != KMType.SHA2_256) { + return false; + } + break; + case KMType.AES: + case KMType.DES: + if (digest != KMType.DIGEST_NONE) { + return false; + } + break; + default: + return false; + } + index++; + } + return true; + } + + public boolean isValidPaddingModes(byte alg) { + short index = 0; + short padding; + while (index < length()) { + padding = get(index); + switch (alg) { + case KMType.RSA: + if (padding != KMType.RSA_OAEP + && padding != KMType.PADDING_NONE + && padding != KMType.RSA_PKCS1_1_5_SIGN + && padding != KMType.RSA_PKCS1_1_5_ENCRYPT + && padding != KMType.RSA_PSS) { + return false; + } + break; + case KMType.AES: + case KMType.DES: + if (padding != KMType.PKCS7 && padding != KMType.PADDING_NONE) { + return false; + } + break; + case KMType.EC: + case KMType.HMAC: + if (padding != PADDING_NONE) { + return false; + } + break; + default: + return false; + } + index++; + } + return true; + } + + public boolean isValidPurpose(byte alg) { + short index = 0; + short purpose; + while (index < length()) { + purpose = get(index); + switch (purpose) { + case KMType.DECRYPT: + case KMType.ENCRYPT: + if (alg != KMType.RSA && alg != KMType.AES && alg != KMType.DES) { + return false; + } + break; + case KMType.SIGN: + case KMType.VERIFY: + if (alg != KMType.HMAC && alg != KMType.RSA && alg != KMType.EC) { + return false; + } + break; + case KMType.WRAP_KEY: + if (alg != KMType.RSA) { + return false; + } + break; + default: + return false; + } + index++; + } + return true; + } + + public boolean isValidBlockMode(byte alg) { + if (alg == KMType.AES || alg == KMType.DES) { + return true; + } else { + return false; + } + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMEnumTag.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMEnumTag.java new file mode 100644 index 0000000..a7bcbe6 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMEnumTag.java @@ -0,0 +1,152 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMEnumTag represents ENUM Tag type specified in android keymaster hal specifications. struct{byte + * TAG_TYPE; short length; struct{short ENUM_TAG; short tagKey; byte value}} + */ +public class KMEnumTag extends KMTag { + + private static KMEnumTag prototype; + + // The allowed tag keys of type enum tag. + private static short[] tags = { + ALGORITHM, ECCURVE, BLOB_USAGE_REQ, USER_AUTH_TYPE, ORIGIN, HARDWARE_TYPE + }; + + private static Object[] enums = null; + + private KMEnumTag() {} + + private static KMEnumTag proto(short ptr) { + if (prototype == null) { + prototype = new KMEnumTag(); + } + KMType.instanceTable[KM_ENUM_TAG_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + short ptr = instance(TAG_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), ENUM_TAG); + return ptr; + } + + public static short instance(short key) { + if (!validateEnum(key, NO_VALUE)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = KMType.instance(TAG_TYPE, (short) 4); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), ENUM_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), key); + return ptr; + } + + public static short instance(short key, byte val) { + if (!validateEnum(key, val)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(TAG_TYPE, (short) 5); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), ENUM_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), key); + heap[(short) (ptr + TLV_HEADER_SIZE + 4)] = val; + return ptr; + } + + public static KMEnumTag cast(short ptr) { + if (heap[ptr] != TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)) != ENUM_TAG) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static void create() { + if (enums == null) { + // enum tag values. + enums = + new Object[] { + new byte[] {RSA, DES, EC, AES, HMAC}, + new byte[] {P_224, P_256, P_384, P_521, CURVE_25519}, + new byte[] {STANDALONE, REQUIRES_FILE_SYSTEM}, + new byte[] {USER_AUTH_NONE, PASSWORD, FINGERPRINT, BOTH, ANY}, + new byte[] {GENERATED, DERIVED, IMPORTED, UNKNOWN, SECURELY_IMPORTED}, + new byte[] {SOFTWARE, TRUSTED_ENVIRONMENT, STRONGBOX} + }; + } + } + + // isValidTag enumeration keys and values. + private static boolean validateEnum(short key, byte value) { + create(); + byte[] vals; + short enumInd; + // check if key exists + short index = (short) tags.length; + while (--index >= 0) { + if (tags[index] == key) { + // check if value given + if (value != NO_VALUE) { + // check if the value exist + vals = (byte[]) enums[index]; + enumInd = (short) vals.length; + while (--enumInd >= 0) { + if (vals[enumInd] == value) { + // return true if value exist + return true; + } + } + // return false if value does not exist + return false; + } + // return true if key exist and value not given + return true; + } + } + // return false if key does not exist + return false; + } + + public static short getValue(short tagKey, short keyParameters) { + short tagPtr = KMKeyParameters.findTag(KMType.ENUM_TAG, tagKey, keyParameters); + if (tagPtr != KMType.INVALID_VALUE) { + return heap[(short) (tagPtr + TLV_HEADER_SIZE + 4)]; + } + return KMType.INVALID_VALUE; + } + + public short getKey() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_ENUM_TAG_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short getTagType() { + return KMType.ENUM_TAG; + } + + public byte getValue() { + return heap[(short) (KMType.instanceTable[KM_ENUM_TAG_OFFSET] + TLV_HEADER_SIZE + 4)]; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMError.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMError.java new file mode 100644 index 0000000..bbae870 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMError.java @@ -0,0 +1,134 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.keymaster; + +/** + * KMError includes all the error codes from android keymaster hal specifications. The values are + * positive unlike negative values in keymaster hal. + */ +public class KMError { + + public static final short OK = 0; + public static final short UNSUPPORTED_PURPOSE = 2; + public static final short INCOMPATIBLE_PURPOSE = 3; + public static final short UNSUPPORTED_ALGORITHM = 4; + public static final short INCOMPATIBLE_ALGORITHM = 5; + public static final short UNSUPPORTED_KEY_SIZE = 6; + public static final short UNSUPPORTED_BLOCK_MODE = 7; + public static final short INCOMPATIBLE_BLOCK_MODE = 8; + public static final short UNSUPPORTED_MAC_LENGTH = 9; + public static final short UNSUPPORTED_PADDING_MODE = 10; + public static final short INCOMPATIBLE_PADDING_MODE = 11; + public static final short UNSUPPORTED_DIGEST = 12; + public static final short INCOMPATIBLE_DIGEST = 13; + + public static final short UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM = 19; + + /** For PKCS8 & PKCS12 */ + public static final short INVALID_INPUT_LENGTH = 21; + + public static final short KEY_USER_NOT_AUTHENTICATED = 26; + public static final short INVALID_OPERATION_HANDLE = 28; + public static final short INSUFFICIENT_BUFFER_SPACE = 29; + public static final short VERIFICATION_FAILED = 30; + public static final short TOO_MANY_OPERATIONS = 31; + public static final short INVALID_KEY_BLOB = 33; + + public static final short INVALID_ARGUMENT = 38; + public static final short UNSUPPORTED_TAG = 39; + public static final short INVALID_TAG = 40; + public static final short IMPORT_PARAMETER_MISMATCH = 44; + public static final short OPERATION_CANCELLED = 46; + + public static final short MISSING_NONCE = 51; + public static final short INVALID_NONCE = 52; + public static final short MISSING_MAC_LENGTH = 53; + public static final short CALLER_NONCE_PROHIBITED = 55; + public static final short KEY_MAX_OPS_EXCEEDED = 56; + public static final short INVALID_MAC_LENGTH = 57; + public static final short MISSING_MIN_MAC_LENGTH = 58; + public static final short UNSUPPORTED_MIN_MAC_LENGTH = 59; + public static final short UNSUPPORTED_EC_CURVE = 61; + public static final short KEY_REQUIRES_UPGRADE = 62; + + public static final short ATTESTATION_CHALLENGE_MISSING = 63; + public static final short ATTESTATION_APPLICATION_ID_MISSING = 65; + public static final short CANNOT_ATTEST_IDS = 66; + public static final short ROLLBACK_RESISTANCE_UNAVAILABLE = 67; + + public static final short NO_USER_CONFIRMATION = 71; + public static final short DEVICE_LOCKED = 72; + public static final short EARLY_BOOT_ENDED = 73; + public static final short ATTESTATION_KEYS_NOT_PROVISIONED = 74; + public static final short INCOMPATIBLE_MGF_DIGEST = 78; + public static final short UNSUPPORTED_MGF_DIGEST = 79; + public static final short MISSING_NOT_BEFORE = 80; + public static final short MISSING_NOT_AFTER = 81; + public static final short MISSING_ISSUER_SUBJECT_NAME = 82; + public static final short INVALID_ISSUER_SUBJECT_NAME = 83; + + public static final short UNIMPLEMENTED = 100; + public static final short UNKNOWN_ERROR = 1000; + + // Extended errors + public static final short SW_CONDITIONS_NOT_SATISFIED = 10001; + public static final short UNSUPPORTED_CLA = 10002; + public static final short INVALID_P1P2 = 10003; + public static final short UNSUPPORTED_INSTRUCTION = 10004; + public static final short CMD_NOT_ALLOWED = 10005; + public static final short SW_WRONG_LENGTH = 10006; + public static final short INVALID_DATA = 10007; + + // Crypto errors + public static final short CRYPTO_ILLEGAL_USE = 10008; + public static final short CRYPTO_ILLEGAL_VALUE = 10009; + public static final short CRYPTO_INVALID_INIT = 10010; + public static final short CRYPTO_NO_SUCH_ALGORITHM = 10011; + public static final short CRYPTO_UNINITIALIZED_KEY = 10012; + // Generic Unknown error. + public static final short GENERIC_UNKNOWN_ERROR = 10013; + + // Remote key provisioning error codes. + public static final short STATUS_FAILED = 32000; + public static final short STATUS_INVALID_MAC = 32001; + public static final short STATUS_PRODUCTION_KEY_IN_TEST_REQUEST = 32002; + public static final short STATUS_TEST_KEY_IN_PRODUCTION_REQUEST = 32003; + public static final short STATUS_INVALID_EEK = 32004; + public static final short INVALID_STATE = 32005; + + public static short translate(short err) { + switch (err) { + case SW_CONDITIONS_NOT_SATISFIED: + case UNSUPPORTED_CLA: + case INVALID_P1P2: + case INVALID_DATA: + case CRYPTO_ILLEGAL_USE: + case CRYPTO_ILLEGAL_VALUE: + case CRYPTO_INVALID_INIT: + case CRYPTO_UNINITIALIZED_KEY: + case GENERIC_UNKNOWN_ERROR: + case CMD_NOT_ALLOWED: + case UNKNOWN_ERROR: + return UNKNOWN_ERROR; + case CRYPTO_NO_SUCH_ALGORITHM: + return UNSUPPORTED_ALGORITHM; + case UNSUPPORTED_INSTRUCTION: + case SW_WRONG_LENGTH: + return UNIMPLEMENTED; + } + return err; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMHardwareAuthToken.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMHardwareAuthToken.java new file mode 100644 index 0000000..e6b1d37 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMHardwareAuthToken.java @@ -0,0 +1,171 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMHardwareAuthToken represents HardwareAuthToken structure from android keymaster hal + * specifications. It corresponds to CBOR array type. struct{byte HW_AUTH_TOKEN_TYPE; short + * length=2; short arrayPtr} where arrayPtr is a pointer to ordered array with following elements: + * {KMInteger Challenge; KMInteger UserId; KMInteger AuthenticatorId; UserAuthType + * HwAuthenticatorId; KMInteger TimeStamp; KMByteBlob Mac} + */ +public class KMHardwareAuthToken extends KMType { + + public static final byte CHALLENGE = 0x00; + public static final byte USER_ID = 0x01; + public static final byte AUTHENTICATOR_ID = 0x02; + public static final byte HW_AUTHENTICATOR_TYPE = 0x03; + public static final byte TIMESTAMP = 0x04; + public static final byte MAC = 0x05; + + private static KMHardwareAuthToken prototype; + + private KMHardwareAuthToken() {} + + public static short exp() { + short arrPtr = KMArray.instance((short) 6); + KMArray arr = KMArray.cast(arrPtr); + arr.add(CHALLENGE, KMInteger.exp()); + arr.add(USER_ID, KMInteger.exp()); + arr.add(AUTHENTICATOR_ID, KMInteger.exp()); + arr.add(HW_AUTHENTICATOR_TYPE, KMEnum.instance(KMType.USER_AUTH_TYPE)); + arr.add(TIMESTAMP, KMInteger.exp()); + arr.add(MAC, KMByteBlob.exp()); + return instance(arrPtr); + } + + private static KMHardwareAuthToken proto(short ptr) { + if (prototype == null) { + prototype = new KMHardwareAuthToken(); + } + KMType.instanceTable[KM_HARDWARE_AUTH_TOKEN_OFFSET] = ptr; + return prototype; + } + + public static short instance() { + short arrPtr = KMArray.instance((short) 6); + KMArray arr = KMArray.cast(arrPtr); + arr.add(CHALLENGE, KMInteger.uint_16((short) 0)); + arr.add(USER_ID, KMInteger.uint_16((short) 0)); + arr.add(AUTHENTICATOR_ID, KMInteger.uint_16((short) 0)); + arr.add(HW_AUTHENTICATOR_TYPE, KMEnum.instance(KMType.USER_AUTH_TYPE, KMType.USER_AUTH_NONE)); + arr.add(TIMESTAMP, KMInteger.uint_16((short) 0)); + arr.add(MAC, KMByteBlob.instance((short) 0)); + return instance(arrPtr); + } + + public static short instance(short vals) { + KMArray arr = KMArray.cast(vals); + if (arr.length() != 6) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + short ptr = KMType.instance(HW_AUTH_TOKEN_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMHardwareAuthToken cast(short ptr) { + if (heap[ptr] != HW_AUTH_TOKEN_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (heap[arrPtr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public short getVals() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_HARDWARE_AUTH_TOKEN_OFFSET] + TLV_HEADER_SIZE)); + } + + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); + } + + public short getChallenge() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(CHALLENGE); + } + + public void setChallenge(short vals) { + KMInteger.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(CHALLENGE, vals); + } + + public short getUserId() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(USER_ID); + } + + public void setUserId(short vals) { + KMInteger.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(USER_ID, vals); + } + + public short getAuthenticatorId() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(AUTHENTICATOR_ID); + } + + public void setAuthenticatorId(short vals) { + KMInteger.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(AUTHENTICATOR_ID, vals); + } + + public short getHwAuthenticatorType() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(HW_AUTHENTICATOR_TYPE); + } + + public void setHwAuthenticatorType(short vals) { + KMEnum.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(HW_AUTHENTICATOR_TYPE, vals); + } + + public short getTimestamp() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(TIMESTAMP); + } + + public void setTimestamp(short vals) { + KMInteger.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(TIMESTAMP, vals); + } + + public short getMac() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(MAC); + } + + public void setMac(short vals) { + KMByteBlob.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(MAC, vals); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMHmacSharingParameters.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMHmacSharingParameters.java new file mode 100644 index 0000000..8642803 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMHmacSharingParameters.java @@ -0,0 +1,110 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMHmacSharingParameters represents HmacSharingParameters structure from android keymaster hal + * specifications. It corresponds to CBOR array type. struct{byte HMAC_SHARING_PARAM_TYPE; short + * length=2; short arrayPtr} where arrayPtr is a pointer to ordered array with following elements: + * {KMByteBlob Seed; KMByteBlob Nonce} + */ +public class KMHmacSharingParameters extends KMType { + + public static final byte SEED = 0x00; + public static final byte NONCE = 0x01; + + private static KMHmacSharingParameters prototype; + + private KMHmacSharingParameters() {} + + public static short exp() { + short arrPtr = KMArray.instance((short) 2); + KMArray arr = KMArray.cast(arrPtr); + arr.add(SEED, KMByteBlob.exp()); + arr.add(NONCE, KMByteBlob.exp()); + return instance(arrPtr); + } + + private static KMHmacSharingParameters proto(short ptr) { + if (prototype == null) { + prototype = new KMHmacSharingParameters(); + } + KMType.instanceTable[KM_HMAC_SHARING_PARAMETERS_OFFSET] = ptr; + return prototype; + } + + public static short instance() { + short arrPtr = KMArray.instance((short) 2); + return instance(arrPtr); + } + + public static short instance(short vals) { + short ptr = KMType.instance(HMAC_SHARING_PARAM_TYPE, (short) 2); + if (KMArray.cast(vals).length() != 2) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMHmacSharingParameters cast(short ptr) { + if (heap[ptr] != HMAC_SHARING_PARAM_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (heap[arrPtr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public short getVals() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_HMAC_SHARING_PARAMETERS_OFFSET] + TLV_HEADER_SIZE)); + } + + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); + } + + public short getNonce() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(NONCE); + } + + public void setNonce(short vals) { + KMByteBlob.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(NONCE, vals); + } + + public short getSeed() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(SEED); + } + + public void setSeed(short vals) { + KMByteBlob.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(SEED, vals); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMInteger.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMInteger.java new file mode 100644 index 0000000..b09de0f --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMInteger.java @@ -0,0 +1,215 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import com.android.javacard.seprovider.KMException; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * Represents 8 bit, 16 bit, 32 bit and 64 bit unsigned integer. It corresponds to CBOR uint type. + * struct{byte INTEGER_TYPE; short length; 4 or 8 bytes of value} + */ +public class KMInteger extends KMType { + + public static final byte UINT_32 = 4; + public static final byte UINT_64 = 8; + private static KMInteger prototype; + + protected KMInteger() {} + + private static KMInteger proto(short ptr) { + if (prototype == null) { + prototype = new KMInteger(); + } + KMType.instanceTable[KM_INTEGER_OFFSET] = ptr; + return prototype; + } + + // | TYPE(1) | LEN(2) | DATA(4 / 8) | + public static short exp() { + return KMType.exp(INTEGER_TYPE); + } + + // return an empty integer instance + public static short instance(short length) { + if ((length <= 0) || (length > 8)) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + if (length > 4) { + length = UINT_64; + } else { + length = UINT_32; + } + return KMType.instance(INTEGER_TYPE, length); + } + + public static short instance(byte[] num, short srcOff, short length) { + if (length > 8) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + if (length == 1) { + return uint_8(num[srcOff]); + } else if (length == 2) { + return uint_16(Util.getShort(num, srcOff)); + } else if (length == 4) { + return uint_32(num, srcOff); + } else { + return uint_64(num, srcOff); + } + } + + public static KMInteger cast(short ptr) { + byte[] heap = repository.getHeap(); + if (heap[ptr] != INTEGER_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + 1)) == INVALID_VALUE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + // create integer and copy byte value + public static short uint_8(byte num) { + short ptr = instance(UINT_32); + heap[(short) (ptr + TLV_HEADER_SIZE + 3)] = num; + return ptr; + } + + // create integer and copy short value + public static short uint_16(short num) { + short ptr = instance(UINT_32); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), num); + return ptr; + } + + // create integer and copy integer value + public static short uint_32(byte[] num, short offset) { + short ptr = instance(UINT_32); + Util.arrayCopy(num, offset, heap, (short) (ptr + TLV_HEADER_SIZE), UINT_32); + return ptr; + } + + // create integer and copy integer value + public static short uint_64(byte[] num, short offset) { + short ptr = instance(UINT_64); + Util.arrayCopy(num, offset, heap, (short) (ptr + TLV_HEADER_SIZE), UINT_64); + return ptr; + } + + public static short compare(short num1, short num2) { + short num1Buf = repository.alloc((short) 8); + short num2Buf = repository.alloc((short) 8); + Util.arrayFillNonAtomic(repository.getHeap(), num1Buf, (short) 8, (byte) 0); + Util.arrayFillNonAtomic(repository.getHeap(), num2Buf, (short) 8, (byte) 0); + short len = KMInteger.cast(num1).length(); + KMInteger.cast(num1).getValue(repository.getHeap(), (short) (num1Buf + (short) (8 - len)), len); + len = KMInteger.cast(num2).length(); + KMInteger.cast(num2).getValue(repository.getHeap(), (short) (num2Buf + (short) (8 - len)), len); + return KMInteger.unsignedByteArrayCompare( + repository.getHeap(), num1Buf, repository.getHeap(), num2Buf, (short) 8); + } + + public static byte unsignedByteArrayCompare( + byte[] a1, short offset1, byte[] a2, short offset2, short length) { + byte count = (byte) 0; + short val1 = (short) 0; + short val2 = (short) 0; + + for (; count < length; count++) { + val1 = (short) (a1[(short) (count + offset1)] & 0x00FF); + val2 = (short) (a2[(short) (count + offset2)] & 0x00FF); + + if (val1 < val2) { + return -1; + } + if (val1 > val2) { + return 1; + } + } + return 0; + } + + // Get the length of the integer + public short length() { + return Util.getShort(heap, (short) (getBaseOffset() + 1)); + } + + // Get the buffer pointer in which blob is contained. + public byte[] getBuffer() { + return heap; + } + + // Get the start of value + public short getStartOff() { + return (short) (getBaseOffset() + TLV_HEADER_SIZE); + } + + public void getValue(byte[] dest, short destOff, short length) { + if (length < length()) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + if (length > length()) { + length = length(); + destOff += length; + } + Util.arrayCopyNonAtomic(heap, getStartOff(), dest, destOff, length); + } + + public void setValue(byte[] src, short srcOff) { + Util.arrayCopyNonAtomic(src, srcOff, heap, getStartOff(), length()); + } + + public short value(byte[] dest, short destOff) { + Util.arrayCopyNonAtomic(heap, getStartOff(), dest, destOff, length()); + return length(); + } + + public short toLittleEndian(byte[] dest, short destOff) { + short index = (short) (length() - 1); + while (index >= 0) { + dest[destOff++] = heap[(short) (instanceTable[KM_INTEGER_OFFSET] + TLV_HEADER_SIZE + index)]; + index--; + } + return length(); + } + + public short getShort() { + return Util.getShort(heap, (short) (getStartOff() + 2)); + } + + public short getSignificantShort() { + return Util.getShort(heap, getStartOff()); + } + + public byte getByte() { + return heap[(short) (getStartOff() + 3)]; + } + + public boolean isZero() { + if (getShort() == 0 && getSignificantShort() == 0) { + return true; + } + return false; + } + + protected short getBaseOffset() { + return instanceTable[KM_INTEGER_OFFSET]; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMIntegerArrayTag.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMIntegerArrayTag.java new file mode 100644 index 0000000..bf45981 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMIntegerArrayTag.java @@ -0,0 +1,163 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMIntegerArrayTag represents UINT_REP and ULONG_REP tags specified in keymaster hal specs. + * struct{byte TAG_TYPE; short length; struct{short UINT_TAG/ULONG_TAG; short tagKey; short arrPtr}, + * where arrPtr is the pointer to KMArray of KMInteger instances. + */ +public class KMIntegerArrayTag extends KMTag { + + private static final short[] tags = {USER_SECURE_ID}; + private static KMIntegerArrayTag prototype; + + private KMIntegerArrayTag() {} + + private static KMIntegerArrayTag proto(short ptr) { + if (prototype == null) { + prototype = new KMIntegerArrayTag(); + } + KMType.instanceTable[KM_INTEGER_ARRAY_TAG_OFFSET] = ptr; + return prototype; + } + + public static short exp(short tagType) { + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short arrPtr = KMArray.exp(KMInteger.exp()); + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), tagType); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), INVALID_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), arrPtr); + return ptr; + } + + public static short instance(short tagType, short key) { + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (!validateKey(key)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short arrPtr = KMArray.exp(); + return instance(tagType, key, arrPtr); + } + + public static short instance(short tagType, short key, short arrObj) { + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (!validateKey(key)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (heap[arrObj] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), tagType); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), key); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), arrObj); + return ptr; + } + + public static KMIntegerArrayTag cast(short ptr) { + if (heap[ptr] != TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short tagType = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + private static boolean validateKey(short key) { + short index = (short) tags.length; + while (--index >= 0) { + if (tags[index] == key) { + return true; + } + } + return false; + } + + private static boolean validateTagType(short tagType) { + return (tagType == ULONG_ARRAY_TAG) || (tagType == UINT_ARRAY_TAG); + } + + public static boolean contains(short tagId, short tagValue, short params) { + short tag = KMKeyParameters.findTag(KMType.UINT_ARRAY_TAG, tagId, params); + if (tag != KMType.INVALID_VALUE) { + short index = 0; + tag = KMIntegerArrayTag.cast(tag).getValues(); + while (index < KMArray.cast(tag).length()) { + if (KMInteger.compare(tagValue, KMArray.cast(tag).get(index)) == 0) { + return true; + } + index++; + } + } + return false; + } + + public short getTagType() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_INTEGER_ARRAY_TAG_OFFSET] + TLV_HEADER_SIZE)); + } + + public short getKey() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_INTEGER_ARRAY_TAG_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short getValues() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_INTEGER_ARRAY_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + } + + public short length() { + short ptr = getValues(); + return KMArray.cast(ptr).length(); + } + + public void add(short index, short val) { + KMArray arr = KMArray.cast(getValues()); + arr.add(index, val); + } + + public short get(short index) { + KMArray arr = KMArray.cast(getValues()); + return arr.get(index); + } + + public boolean contains(short tagValue) { + short index = 0; + while (index < length()) { + if (KMInteger.compare(tagValue, get(index)) == 0) { + return true; + } + index++; + } + return false; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMIntegerTag.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMIntegerTag.java new file mode 100644 index 0000000..d4c4458 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMIntegerTag.java @@ -0,0 +1,218 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMIntegerTag represents UINT, ULONG and DATE tags specified in keymaster hal specs. struct{byte + * TAG_TYPE; short length; struct{short UINT_TAG/ULONG_TAG/DATE_TAG; short tagKey; 4 or 8 byte + * value}} + */ +public class KMIntegerTag extends KMTag { + + // Allowed tag keys. + private static final short[] tags = { + // UINT + KEYSIZE, + MIN_MAC_LENGTH, + MIN_SEC_BETWEEN_OPS, + MAX_USES_PER_BOOT, + USERID, + AUTH_TIMEOUT, + OS_VERSION, + OS_PATCH_LEVEL, + VENDOR_PATCH_LEVEL, + BOOT_PATCH_LEVEL, + MAC_LENGTH, + // ULONG + RSA_PUBLIC_EXPONENT, + // DATE + ACTIVE_DATETIME, + ORIGINATION_EXPIRE_DATETIME, + USAGE_EXPIRE_DATETIME, + CREATION_DATETIME, + CERTIFICATE_NOT_BEFORE, + CERTIFICATE_NOT_AFTER, + USAGE_COUNT_LIMIT, + // custom tag + AUTH_TIMEOUT_MILLIS, + }; + private static KMIntegerTag prototype; + + private KMIntegerTag() {} + + private static KMIntegerTag proto(short ptr) { + if (prototype == null) { + prototype = new KMIntegerTag(); + } + KMType.instanceTable[KM_INTEGER_TAG_OFFSET] = ptr; + return prototype; + } + + public static short exp(short tagType) { + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short intPtr = KMInteger.exp(); + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), tagType); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), INVALID_TAG); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), intPtr); + return ptr; + } + + public static short instance(short tagType, short key) { + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (!validateKey(key)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short intPtr = KMInteger.exp(); + return instance(tagType, key, intPtr); + } + + public static short instance(short tagType, short key, short intObj) { + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (!validateKey(key)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (heap[intObj] != INTEGER_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), tagType); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), key); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), intObj); + return ptr; + } + + public static KMIntegerTag cast(short ptr) { + if (heap[ptr] != TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short tagType = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (!validateTagType(tagType)) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + private static boolean validateKey(short key) { + short index = (short) tags.length; + while (--index >= 0) { + if (tags[index] == key) { + return true; + } + } + return false; + } + + private static boolean validateTagType(short tagType) { + return (tagType == DATE_TAG) || (tagType == UINT_TAG) || (tagType == ULONG_TAG); + } + + public static short getShortValue(short tagType, short tagKey, short keyParameters) { + short ptr; + if (tagType == UINT_TAG) { + ptr = KMKeyParameters.findTag(KMType.UINT_TAG, tagKey, keyParameters); + if (ptr != KMType.INVALID_VALUE) { + ptr = KMIntegerTag.cast(ptr).getValue(); + if (KMInteger.cast(ptr).getSignificantShort() == 0) { + return KMInteger.cast(ptr).getShort(); + } + } + } + return KMType.INVALID_VALUE; + } + + public static short getValue( + byte[] buf, short offset, short tagType, short tagKey, short keyParameters) { + short ptr; + if ((tagType == UINT_TAG) || (tagType == ULONG_TAG) || (tagType == DATE_TAG)) { + ptr = KMKeyParameters.findTag(tagType, tagKey, keyParameters); + if (ptr != KMType.INVALID_VALUE) { + ptr = KMIntegerTag.cast(ptr).getValue(); + return KMInteger.cast(ptr).value(buf, offset); + } + } + return KMType.INVALID_VALUE; + } + + public short getTagType() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_INTEGER_TAG_OFFSET] + TLV_HEADER_SIZE)); + } + + public short getKey() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_INTEGER_TAG_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short getValue() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_INTEGER_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + } + + public short length() { + KMInteger obj = KMInteger.cast(getValue()); + return obj.length(); + } + + public boolean isValidKeySize(byte alg) { + short val = KMIntegerTag.cast(KMType.instanceTable[KM_INTEGER_TAG_OFFSET]).getValue(); + if (KMInteger.cast(val).getSignificantShort() != 0) { + return false; + } + val = KMInteger.cast(val).getShort(); + switch (alg) { + case KMType.RSA: + if (val == 2048) { + return true; + } + break; + case KMType.AES: + if (val == 128 || val == 256) { + return true; + } + break; + case KMType.DES: + if (val == 168) { + return true; + } + break; + case KMType.EC: + if (val == 256) { + return true; + } + break; + case KMType.HMAC: + if (val % 8 == 0 && val >= 64 && val <= 512) { + return true; + } + break; + default: + break; + } + return false; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMKeyCharacteristics.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMKeyCharacteristics.java new file mode 100644 index 0000000..37b8b7f --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMKeyCharacteristics.java @@ -0,0 +1,124 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMKeyCharacteristics represents KeyCharacteristics structure from android keymaster hal + * specifications. It corresponds to CBOR array type. struct{byte KEY_CHAR_TYPE; short length=3; + * short arrayPtr} where arrayPtr is a pointer to ordered array with 1 or 3 following elements: + * {KMKeyParameters sb; KMKeyParameters tee; KMKeyParameters keystore} + */ +public class KMKeyCharacteristics extends KMType { + + public static final byte STRONGBOX_ENFORCED = 0x00; + public static final byte TEE_ENFORCED = 0x01; + public static final byte KEYSTORE_ENFORCED = 0x02; + private static KMKeyCharacteristics prototype; + + private KMKeyCharacteristics() {} + + public static short exp() { + short keyParamExp = KMKeyParameters.exp(); + short arrPtr = KMArray.instance((short) 3); + + KMArray arr = KMArray.cast(arrPtr); + arr.add(STRONGBOX_ENFORCED, keyParamExp); + arr.add(TEE_ENFORCED, keyParamExp); + arr.add(KEYSTORE_ENFORCED, keyParamExp); + return instance(arrPtr); + } + + private static KMKeyCharacteristics proto(short ptr) { + if (prototype == null) { + prototype = new KMKeyCharacteristics(); + } + KMType.instanceTable[KM_KEY_CHARACTERISTICS_OFFSET] = ptr; + return prototype; + } + + public static short instance() { + short arrPtr = KMArray.instance((short) 3); + return instance(arrPtr); + } + + public static short instance(short vals) { + short ptr = KMType.instance(KEY_CHAR_TYPE, (short) 3); + if (KMArray.cast(vals).length() != 3) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMKeyCharacteristics cast(short ptr) { + if (heap[ptr] != KEY_CHAR_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (heap[arrPtr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public short getVals() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_KEY_CHARACTERISTICS_OFFSET] + TLV_HEADER_SIZE)); + } + + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); + } + + public short getKeystoreEnforced() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(KEYSTORE_ENFORCED); + } + + public void setKeystoreEnforced(short ptr) { + KMKeyParameters.cast(ptr); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(KEYSTORE_ENFORCED, ptr); + } + + public short getTeeEnforced() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(TEE_ENFORCED); + } + + public void setTeeEnforced(short ptr) { + KMKeyParameters.cast(ptr); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(TEE_ENFORCED, ptr); + } + + public short getStrongboxEnforced() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(STRONGBOX_ENFORCED); + } + + public void setStrongboxEnforced(short ptr) { + KMKeyParameters.cast(ptr); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(STRONGBOX_ENFORCED, ptr); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMKeyParameters.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMKeyParameters.java new file mode 100644 index 0000000..54ab6ee --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMKeyParameters.java @@ -0,0 +1,472 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import com.android.javacard.seprovider.KMException; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMKeyParameters represents KeyParameters structure from android keymaster hal specifications. It + * corresponds to CBOR map type. struct{byte KEY_PARAM_TYPE; short length=2; short arrayPtr} where + * arrayPtr is a pointer to array with any KMTag subtype instances. + */ +public class KMKeyParameters extends KMType { + + private static final short[] customTags = { + KMType.ULONG_TAG, KMType.AUTH_TIMEOUT_MILLIS, + }; + private static final short[] tagArr = { + // Unsupported tags. + KMType.BOOL_TAG, KMType.TRUSTED_USER_PRESENCE_REQUIRED, + KMType.UINT_TAG, KMType.MIN_SEC_BETWEEN_OPS + }; + private static final short[] hwEnforcedTagArr = { + // HW Enforced + KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, + KMType.ENUM_TAG, KMType.ALGORITHM, + KMType.UINT_TAG, KMType.KEYSIZE, + KMType.ULONG_TAG, KMType.RSA_PUBLIC_EXPONENT, + KMType.ENUM_TAG, KMType.BLOB_USAGE_REQ, + KMType.ENUM_ARRAY_TAG, KMType.DIGEST, + KMType.ENUM_ARRAY_TAG, KMType.PADDING, + KMType.ENUM_ARRAY_TAG, KMType.BLOCK_MODE, + KMType.ENUM_ARRAY_TAG, KMType.RSA_OAEP_MGF_DIGEST, + KMType.BOOL_TAG, KMType.NO_AUTH_REQUIRED, + KMType.BOOL_TAG, KMType.CALLER_NONCE, + KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, + KMType.ENUM_TAG, KMType.ECCURVE, + KMType.BOOL_TAG, KMType.INCLUDE_UNIQUE_ID, + KMType.BOOL_TAG, KMType.ROLLBACK_RESISTANCE, + KMType.BOOL_TAG, KMType.EARLY_BOOT_ONLY, + KMType.BOOL_TAG, KMType.BOOTLOADER_ONLY, + KMType.UINT_TAG, KMType.MAX_USES_PER_BOOT, + }; + private static final short[] swEnforcedTagsArr = { + KMType.DATE_TAG, KMType.ACTIVE_DATETIME, + KMType.DATE_TAG, KMType.ORIGINATION_EXPIRE_DATETIME, + KMType.DATE_TAG, KMType.USAGE_EXPIRE_DATETIME, + KMType.UINT_TAG, KMType.USERID, + KMType.DATE_TAG, KMType.CREATION_DATETIME, + KMType.UINT_TAG, KMType.USAGE_COUNT_LIMIT, + KMType.BOOL_TAG, KMType.ALLOW_WHILE_ON_BODY, + KMType.UINT_TAG, KMType.MAX_BOOT_LEVEL, + }; + private static final short[] teeEnforcedTagsArr = { + KMType.ULONG_ARRAY_TAG, KMType.USER_SECURE_ID, + KMType.UINT_TAG, KMType.AUTH_TIMEOUT, + KMType.ENUM_TAG, KMType.USER_AUTH_TYPE, + KMType.BOOL_TAG, KMType.UNLOCKED_DEVICE_REQUIRED, + KMType.BOOL_TAG, KMType.TRUSTED_CONFIRMATION_REQUIRED, + }; + private static final short[] invalidTagsArr = { + KMType.BYTES_TAG, KMType.NONCE, + KMType.BYTES_TAG, KMType.ASSOCIATED_DATA, + KMType.BYTES_TAG, KMType.UNIQUE_ID, + KMType.UINT_TAG, KMType.MAC_LENGTH, + }; + private static KMKeyParameters prototype; + + private KMKeyParameters() {} + + private static KMKeyParameters proto(short ptr) { + if (prototype == null) { + prototype = new KMKeyParameters(); + } + KMType.instanceTable[KM_KEY_PARAMETERS_OFFSET] = ptr; + return prototype; + } + + public static short exp() { + short arrPtr = KMArray.instance((short) 11); + KMArray arr = KMArray.cast(arrPtr); + arr.add((short) 0, KMEnum.instance(KMType.RULE, KMType.FAIL_ON_INVALID_TAGS)); + arr.add((short) 1, KMIntegerTag.exp(UINT_TAG)); + arr.add((short) 2, KMIntegerArrayTag.exp(UINT_ARRAY_TAG)); + arr.add((short) 3, KMIntegerTag.exp(ULONG_TAG)); + arr.add((short) 4, KMIntegerTag.exp(DATE_TAG)); + arr.add((short) 5, KMIntegerArrayTag.exp(ULONG_ARRAY_TAG)); + arr.add((short) 6, KMEnumTag.exp()); + arr.add((short) 7, KMEnumArrayTag.exp()); + arr.add((short) 8, KMByteTag.exp()); + arr.add((short) 9, KMBoolTag.exp()); + arr.add((short) 10, KMBignumTag.exp()); + return instance(arrPtr); + } + + public static short expAny() { + short arrPtr = KMArray.instance((short) 11); + KMArray arr = KMArray.cast(arrPtr); + arr.add((short) 0, KMEnum.instance(KMType.RULE, KMType.IGNORE_INVALID_TAGS)); + arr.add((short) 1, KMIntegerTag.exp(UINT_TAG)); + arr.add((short) 2, KMIntegerArrayTag.exp(UINT_ARRAY_TAG)); + arr.add((short) 3, KMIntegerTag.exp(ULONG_TAG)); + arr.add((short) 4, KMIntegerTag.exp(DATE_TAG)); + arr.add((short) 5, KMIntegerArrayTag.exp(ULONG_ARRAY_TAG)); + arr.add((short) 6, KMEnumTag.exp()); + arr.add((short) 7, KMEnumArrayTag.exp()); + arr.add((short) 8, KMByteTag.exp()); + arr.add((short) 9, KMBoolTag.exp()); + arr.add((short) 10, KMBignumTag.exp()); + return instance(arrPtr); + } + + public static short instance(short vals) { + short ptr = KMType.instance(KEY_PARAM_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMKeyParameters cast(short ptr) { + if (heap[ptr] != KEY_PARAM_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (heap[arrPtr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static short findTag(short tagType, short tagKey, short keyParam) { + KMKeyParameters instParam = KMKeyParameters.cast(keyParam); + return instParam.findTag(tagType, tagKey); + } + + public static boolean hasUnsupportedTags(short keyParamsPtr) { + byte index = 0; + short tagInd; + short tagPtr; + short tagKey; + short tagType; + short arrPtr = KMKeyParameters.cast(keyParamsPtr).getVals(); + short len = KMArray.cast(arrPtr).length(); + while (index < len) { + tagInd = 0; + tagPtr = KMArray.cast(arrPtr).get(index); + tagKey = KMTag.getKey(tagPtr); + tagType = KMTag.getTagType(tagPtr); + while (tagInd < (short) tagArr.length) { + if ((tagArr[tagInd] == tagType) && (tagArr[(short) (tagInd + 1)] == tagKey)) { + return true; + } + tagInd += 2; + } + index++; + } + return false; + } + + // KDF, ECIES_SINGLE_HASH_MODE missing from types.hal + public static short makeSbEnforced( + short keyParamsPtr, + byte origin, + short osVersionObjPtr, + short osPatchObjPtr, + short vendorPatchObjPtr, + short bootPatchObjPtr, + byte[] scratchPad) { + byte index = 0; + short tagInd; + short arrInd = 0; + short tagPtr; + short tagKey; + short tagType; + short arrPtr = KMKeyParameters.cast(keyParamsPtr).getVals(); + short len = KMArray.cast(arrPtr).length(); + while (index < len) { + tagInd = 0; + tagPtr = KMArray.cast(arrPtr).get(index); + tagKey = KMTag.getKey(tagPtr); + tagType = KMTag.getTagType(tagPtr); + if (!isValidTag(tagType, tagKey)) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + while (tagInd < (short) hwEnforcedTagArr.length) { + if ((hwEnforcedTagArr[tagInd] == tagType) + && (hwEnforcedTagArr[(short) (tagInd + 1)] == tagKey)) { + Util.setShort(scratchPad, arrInd, tagPtr); + arrInd += 2; + break; + } + tagInd += 2; + } + index++; + } + short originTag = KMEnumTag.instance(KMType.ORIGIN, origin); + Util.setShort(scratchPad, arrInd, originTag); + arrInd += 2; + short osVersionTag = KMIntegerTag.instance(KMType.UINT_TAG, KMType.OS_VERSION, osVersionObjPtr); + Util.setShort(scratchPad, arrInd, osVersionTag); + arrInd += 2; + short osPatchTag = KMIntegerTag.instance(KMType.UINT_TAG, KMType.OS_PATCH_LEVEL, osPatchObjPtr); + Util.setShort(scratchPad, arrInd, osPatchTag); + arrInd += 2; + short vendorPatchTag = + KMIntegerTag.instance(KMType.UINT_TAG, KMType.VENDOR_PATCH_LEVEL, vendorPatchObjPtr); + Util.setShort(scratchPad, arrInd, vendorPatchTag); + arrInd += 2; + short bootPatchTag = + KMIntegerTag.instance(KMType.UINT_TAG, KMType.BOOT_PATCH_LEVEL, bootPatchObjPtr); + Util.setShort(scratchPad, arrInd, bootPatchTag); + arrInd += 2; + return createKeyParameters(scratchPad, (short) (arrInd / 2)); + } + + public static short makeHwEnforced(short sb, short tee) { + short len = KMKeyParameters.cast(sb).length(); + len += KMKeyParameters.cast(tee).length(); + short hwEnf = KMArray.instance(len); + sb = KMKeyParameters.cast(sb).getVals(); + tee = KMKeyParameters.cast(tee).getVals(); + len = KMArray.cast(sb).length(); + short src = 0; + short dest = 0; + short val = 0; + while (src < len) { + val = KMArray.cast(sb).get(src); + KMArray.cast(hwEnf).add(dest, val); + src++; + dest++; + } + src = 0; + len = KMArray.cast(tee).length(); + while (src < len) { + val = KMArray.cast(tee).get(src); + KMArray.cast(hwEnf).add(dest, val); + src++; + dest++; + } + return KMKeyParameters.instance(hwEnf); + } + + // ALL_USERS, EXPORTABLE missing from types.hal + public static short makeKeystoreEnforced(short keyParamsPtr, byte[] scratchPad) { + byte index = 0; + short tagInd; + short arrInd = 0; + short tagPtr; + short tagKey; + short tagType; + short arrPtr = KMKeyParameters.cast(keyParamsPtr).getVals(); + short len = KMArray.cast(arrPtr).length(); + while (index < len) { + tagInd = 0; + tagPtr = KMArray.cast(arrPtr).get(index); + tagKey = KMTag.getKey(tagPtr); + tagType = KMTag.getTagType(tagPtr); + if (!isValidTag(tagType, tagKey)) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + while (tagInd < (short) swEnforcedTagsArr.length) { + if ((swEnforcedTagsArr[tagInd] == tagType) + && (swEnforcedTagsArr[(short) (tagInd + 1)] == tagKey)) { + Util.setShort(scratchPad, arrInd, tagPtr); + arrInd += 2; + break; + } + tagInd += 2; + } + index++; + } + return createKeyParameters(scratchPad, (short) (arrInd / 2)); + } + + public static short makeTeeEnforced(short keyParamsPtr, byte[] scratchPad) { + byte index = 0; + short tagInd; + short arrInd = 0; + short tagPtr; + short tagKey; + short tagType; + short arrPtr = KMKeyParameters.cast(keyParamsPtr).getVals(); + short len = KMArray.cast(arrPtr).length(); + while (index < len) { + tagInd = 0; + tagPtr = KMArray.cast(arrPtr).get(index); + tagKey = KMTag.getKey(tagPtr); + tagType = KMTag.getTagType(tagPtr); + if (!isValidTag(tagType, tagKey)) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + while (tagInd < (short) teeEnforcedTagsArr.length) { + if ((teeEnforcedTagsArr[tagInd] == tagType) + && (teeEnforcedTagsArr[(short) (tagInd + 1)] == tagKey)) { + Util.setShort(scratchPad, arrInd, tagPtr); + arrInd += 2; + break; + } + tagInd += 2; + } + index++; + } + return createKeyParameters(scratchPad, (short) (arrInd / 2)); + } + + public static short makeHidden(short keyParamsPtr, short rootOfTrustBlob, byte[] scratchPad) { + short appId = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_ID, keyParamsPtr); + if (appId != KMTag.INVALID_VALUE) { + appId = KMByteTag.cast(appId).getValue(); + if (KMByteBlob.cast(appId).length() == 0) { + appId = KMTag.INVALID_VALUE; + } + } + short appData = + KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_DATA, keyParamsPtr); + if (appData != KMTag.INVALID_VALUE) { + appData = KMByteTag.cast(appData).getValue(); + if (KMByteBlob.cast(appData).length() == 0) { + appData = KMTag.INVALID_VALUE; + } + } + return makeHidden(appId, appData, rootOfTrustBlob, scratchPad); + } + + public static short makeHidden( + short appIdBlob, short appDataBlob, short rootOfTrustBlob, byte[] scratchPad) { + // Order in which the hidden array is created should not change. + short index = 0; + KMByteBlob.cast(rootOfTrustBlob); + Util.setShort(scratchPad, index, rootOfTrustBlob); + index += 2; + if (appIdBlob != KMTag.INVALID_VALUE) { + KMByteBlob.cast(appIdBlob); + Util.setShort(scratchPad, index, appIdBlob); + index += 2; + } + if (appDataBlob != KMTag.INVALID_VALUE) { + KMByteBlob.cast(appDataBlob); + Util.setShort(scratchPad, index, appDataBlob); + index += 2; + } + return createKeyParameters(scratchPad, (short) (index / 2)); + } + + public static boolean isValidTag(short tagType, short tagKey) { + short index = 0; + if (tagKey == KMType.INVALID_TAG) { + return false; + } + while (index < invalidTagsArr.length) { + if ((tagType == invalidTagsArr[index]) && (tagKey == invalidTagsArr[(short) (index + 1)])) { + return false; + } + index += 2; + } + return true; + } + + public static short createKeyParameters(byte[] ptrArr, short len) { + short arrPtr = KMArray.instance(len); + short index = 0; + short ptr = 0; + while (index < len) { + KMArray.cast(arrPtr).add(index, Util.getShort(ptrArr, ptr)); + index++; + ptr += 2; + } + return KMKeyParameters.instance(arrPtr); + } + + public static short makeCustomTags(short keyParams, byte[] scratchPad) { + short index = 0; + short tagPtr; + short offset = 0; + short len = (short) customTags.length; + short tagType; + while (index < len) { + tagType = customTags[(short) (index + 1)]; + switch (tagType) { + case KMType.AUTH_TIMEOUT_MILLIS: + short authTimeOutTag = + KMKeyParameters.cast(keyParams).findTag(KMType.UINT_TAG, KMType.AUTH_TIMEOUT); + if (authTimeOutTag != KMType.INVALID_VALUE) { + tagPtr = createAuthTimeOutMillisTag(authTimeOutTag, scratchPad, offset); + Util.setShort(scratchPad, offset, tagPtr); + offset += 2; + } + break; + default: + break; + } + index += 2; + } + return createKeyParameters(scratchPad, (short) (offset / 2)); + } + + public static short createAuthTimeOutMillisTag( + short authTimeOutTag, byte[] scratchPad, short offset) { + short authTime = KMIntegerTag.cast(authTimeOutTag).getValue(); + Util.arrayFillNonAtomic(scratchPad, offset, (short) 40, (byte) 0); + Util.arrayCopyNonAtomic( + KMInteger.cast(authTime).getBuffer(), + KMInteger.cast(authTime).getStartOff(), + scratchPad, + (short) (offset + 8 - KMInteger.cast(authTime).length()), + KMInteger.cast(authTime).length()); + KMUtils.convertToMilliseconds(scratchPad, offset, (short) (offset + 8), (short) (offset + 16)); + return KMIntegerTag.instance( + KMType.ULONG_TAG, + KMType.AUTH_TIMEOUT_MILLIS, + KMInteger.uint_64(scratchPad, (short) (offset + 8))); + } + + public short getVals() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_KEY_PARAMETERS_OFFSET] + TLV_HEADER_SIZE)); + } + + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); + } + + public short findTag(short tagType, short tagKey) { + KMArray vals = KMArray.cast(getVals()); + short index = 0; + short length = vals.length(); + short key; + short type; + short ret = KMType.INVALID_VALUE; + short obj; + while (index < length) { + obj = vals.get(index); + key = KMTag.getKey(obj); + type = KMTag.getTagType(obj); + if ((tagKey == key) && (tagType == type)) { + ret = obj; + break; + } + index++; + } + return ret; + } + + public void deleteCustomTags() { + short arrPtr = getVals(); + short index = (short) (customTags.length - 1); + short obj; + while (index >= 0) { + obj = findTag(customTags[(short) (index - 1)], customTags[index]); + if (obj != KMType.INVALID_VALUE) { + KMArray.cast(arrPtr).deleteLastEntry(); + } + index -= 2; + } + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java new file mode 100644 index 0000000..d8bc21d --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java @@ -0,0 +1,5172 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import com.android.javacard.seprovider.KMAttestationCert; +import com.android.javacard.seprovider.KMDataStoreConstants; +import com.android.javacard.seprovider.KMException; +import com.android.javacard.seprovider.KMKey; +import com.android.javacard.seprovider.KMOperation; +import com.android.javacard.seprovider.KMSEProvider; +import javacard.framework.APDU; +import javacard.framework.Applet; +import javacard.framework.AppletEvent; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; +import javacard.security.CryptoException; +import javacardx.apdu.ExtendedLength; + +/** + * KMKeymasterApplet implements the javacard applet. It creates an instance of the KMRepository and + * other install time objects. It also implements the keymaster state machine and handles javacard + * applet life cycle events. + */ +public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLength { + + // Constants. + // Represents RSA_PUBLIC_EXPONENT value 65537. + public static final byte[] F4 = {0x01, 0x00, 0x01}; + // Block size of AES algorithm. + public static final byte AES_BLOCK_SIZE = 16; + // Block size of DES algorithm. + public static final byte DES_BLOCK_SIZE = 8; + // The Key size in bits for the master key. + public static final short MASTER_KEY_SIZE = 128; + // The Key size of the transport key used in importWrappedKey. + public static final byte WRAPPING_KEY_SIZE = 32; + // The maximum allowed simultaneous operations. + public static final byte MAX_OPERATIONS_COUNT = 4; + // The size of the verified boot key in ROT. + public static final byte VERIFIED_BOOT_KEY_SIZE = 32; + // The size of the verified boot hash in ROT. + public static final byte VERIFIED_BOOT_HASH_SIZE = 32; + // The security level of TEE. + public static final byte TRUSTED_ENVIRONMENT = 1; + // "Keymaster HMAC Verification" - used for HMAC key verification. + public static final byte[] sharingCheck = { + 0x4B, 0x65, 0x79, 0x6D, 0x61, 0x73, 0x74, 0x65, 0x72, 0x20, 0x48, 0x4D, 0x41, 0x43, 0x20, 0x56, + 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, 0x6E + }; + // The ckdfLabel "KeymasterSharedMac" in hex. + public static final byte[] ckdfLabel = { + 0x4B, 0x65, 0x79, 0x6D, 0x61, 0x73, 0x74, 0x65, 0x72, 0x53, 0x68, 0x61, 0x72, 0x65, 0x64, 0x4D, + 0x61, 0x63 + }; + // The "Auth Verification" string in hex. + public static final byte[] authVerification = { + 0x41, 0x75, 0x74, 0x68, 0x20, 0x56, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6F, + 0x6E + }; + // The "confirmation token" string in hex. + public static final byte[] confirmationToken = { + 0x63, 0x6F, 0x6E, 0x66, 0x69, 0x72, 0x6D, 0x61, 0x74, 0x69, 0x6F, 0x6E, 0x20, 0x74, 0x6F, 0x6B, + 0x65, 0x6E + }; + // The maximum buffer size for the encoded COSE structures. + public static final short MAX_COSE_BUF_SIZE = (short) 512; + // Maximum allowed buffer size for to encode the key parameters + // which is used while creating mac for key parameters. + public static final short MAX_KEY_PARAMS_BUF_SIZE = (short) 3072; // 3K + // Temporary variables array size to store intermediary results. + public static final byte TMP_VARIABLE_ARRAY_SIZE = 5; + // Data Dictionary items + // Maximum Dictionary size. + public static final byte DATA_ARRAY_SIZE = 39; + // Below are the offsets of the data dictionary items. + public static final byte KEY_PARAMETERS = 0; + public static final byte KEY_CHARACTERISTICS = 1; + public static final byte HIDDEN_PARAMETERS = 2; + public static final byte HW_PARAMETERS = 3; + public static final byte SW_PARAMETERS = 4; + public static final byte AUTH_DATA = 5; + public static final byte AUTH_TAG = 6; + public static final byte NONCE = 7; + public static final byte KEY_BLOB = 8; + public static final byte AUTH_DATA_LENGTH = 9; + public static final byte SECRET = 10; + public static final byte ROT = 11; + public static final byte DERIVED_KEY = 12; + public static final byte RSA_PUB_EXPONENT = 13; + public static final byte APP_ID = 14; + public static final byte APP_DATA = 15; + public static final byte PUB_KEY = 16; + public static final byte IMPORTED_KEY_BLOB = 17; + public static final byte ORIGIN = 18; + public static final byte NOT_USED = 19; + public static final byte MASKING_KEY = 20; + public static final byte HMAC_SHARING_PARAMS = 21; + public static final byte OP_HANDLE = 22; + public static final byte IV = 23; + public static final byte INPUT_DATA = 24; + public static final byte OUTPUT_DATA = 25; + public static final byte HW_TOKEN = 26; + public static final byte VERIFICATION_TOKEN = 27; + public static final byte SIGNATURE = 28; + public static final byte ATTEST_KEY_BLOB = 29; + public static final byte ATTEST_KEY_PARAMS = 30; + public static final byte ATTEST_KEY_ISSUER = 31; + public static final byte CERTIFICATE = 32; + public static final byte PLAIN_SECRET = 33; + public static final byte TEE_PARAMETERS = 34; + public static final byte SB_PARAMETERS = 35; + public static final byte CONFIRMATION_TOKEN = 36; + public static final byte KEY_BLOB_VERSION_DATA_OFFSET = 37; + public static final byte CUSTOM_TAGS = 38; + // Below are the Keyblob offsets. + public static final byte KEY_BLOB_VERSION_OFFSET = 0; + public static final byte KEY_BLOB_SECRET = 1; + public static final byte KEY_BLOB_NONCE = 2; + public static final byte KEY_BLOB_AUTH_TAG = 3; + public static final byte KEY_BLOB_PARAMS = 4; + public static final byte KEY_BLOB_CUSTOM_TAGS = 5; + public static final byte KEY_BLOB_PUB_KEY = 6; + // AES GCM Auth tag length to be used while encrypting or decrypting the KeyBlob. + public static final byte AES_GCM_AUTH_TAG_LENGTH = 16; + // AES GCM nonce length to be used while encrypting or decrypting the KeyBlob. + public static final byte AES_GCM_NONCE_LENGTH = 12; + // KEYBLOB_CURRENT_VERSION goes into KeyBlob and will affect all + // the KeyBlobs if it is changed. please increment this + // version number whenever you change anything related to + // KeyBlob (structure, encryption algorithm etc). + public static final byte KEYBLOB_CURRENT_VERSION = 3; + // KeyBlob Verion 1 constant. + public static final byte KEYBLOB_VERSION_1 = 1; + // Array sizes of KeyBlob under different versions. + // The array size of a Symmetric key's KeyBlob for Version2 and Version3 + public static final byte SYM_KEY_BLOB_SIZE_V2_V3 = 6; + // The array size of a Asymmetric key's KeyBlob for Version2 and Version3 + public static final byte ASYM_KEY_BLOB_SIZE_V2_V3 = 7; + // The array size of a Symmetric key's KeyBlob for Version1 + public static final byte SYM_KEY_BLOB_SIZE_V1 = 5; + // The array size of a Asymmetric key's KeyBlob for Version1 + public static final byte ASYM_KEY_BLOB_SIZE_V1 = 6; + // The array size of a Symmetric key's KeyBlob for Version0 + public static final byte SYM_KEY_BLOB_SIZE_V0 = 4; + // The array size of a Asymmetric key's KeyBlob for Version0 + public static final byte ASYM_KEY_BLOB_SIZE_V0 = 5; + // Key type constants + // Represents the type of the Symmetric key. + public static final byte SYM_KEY_TYPE = 0; + // Represents the type of the Asymmetric key. + public static final byte ASYM_KEY_TYPE = 1; + // SHA-256 Digest length in bits + public static final short SHA256_DIGEST_LEN_BITS = 256; + // Minimum HMAC length in bits + public static final byte MIN_HMAC_LENGTH_BITS = 64; + // Below are the constants for provision reporting status + public static final short NOT_PROVISIONED = 0x0000; + public static final short PROVISION_STATUS_ATTESTATION_KEY = 0x0001; + public static final short PROVISION_STATUS_ATTESTATION_CERT_CHAIN = 0x0002; + public static final short PROVISION_STATUS_ATTESTATION_CERT_PARAMS = 0x0004; + public static final short PROVISION_STATUS_ATTEST_IDS = 0x0008; + public static final short PROVISION_STATUS_PRESHARED_SECRET = 0x0010; + public static final short PROVISION_STATUS_PROVISIONING_LOCKED = 0x0020; + public static final short PROVISION_STATUS_DEVICE_UNIQUE_KEYPAIR = 0x0040; + public static final short PROVISION_STATUS_UDS_CERT_CHAIN = 0x0080; + public static final short PROVISION_STATUS_SE_LOCKED = 0x0100; + public static final short PROVISION_STATUS_OEM_PUBLIC_KEY = 0x0200; + public static final short PROVISION_STATUS_SECURE_BOOT_MODE = 0x0400; + // This is the P1P2 constant of the APDU command header. + protected static final short KM_HAL_VERSION = (short) 0x6000; + // OEM lock / unlock verification constants. + // This is the verification label to authenticate the OEM to lock the provisioning for the + // OEM provision commands. + protected static final byte[] OEM_LOCK_PROVISION_VERIFICATION_LABEL = { // "OEM Provisioning Lock" + 0x4f, 0x45, 0x4d, 0x20, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x69, 0x6e, 0x67, + 0x20, 0x4c, 0x6f, 0x63, 0x6b + }; + // This is the verification label to authenticate the OEM to unlock the provisioning for the + // OEM provision commands. + protected static final byte[] OEM_UNLOCK_PROVISION_VERIFICATION_LABEL = { // "Enable RMA" + 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x52, 0x4d, 0x41 + }; + // The maximum size of the seed allowed for the RNG entropy + protected static final short MAX_SEED_SIZE = 2048; + // The maximum size of the certificate returned by the generate key command. + protected static final short MAX_CERT_SIZE = 3000; + // The maximum size of the encoded key characteristics in CBOR. + protected static final short MAX_KEY_CHARS_SIZE = 512; + // The maximum size of the serialized KeyBlob. + protected static final short MAX_KEYBLOB_SIZE = 1024; + // The maximum size of the Auth data which is used while encrypting/decrypting the KeyBlob. + private static final short MAX_AUTH_DATA_SIZE = (short) 512; + // The minimum bits in length for AES-GCM tag. + private static final byte MIN_GCM_TAG_LENGTH_BITS = (short) 96; + // The maximum bits in length for AES-GCM tag. + private static final short MAX_GCM_TAG_LENGTH_BITS = (short) 128; + // Subject is a fixed field with only CN= Android Keystore Key - same for all the keys + private static final byte[] defaultSubject = { + 0x30, 0x1F, 0x31, 0x1D, 0x30, 0x1B, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x14, 0x41, 0x6e, 0x64, + 0x72, 0x6f, 0x69, 0x64, 0x20, 0x4B, 0x65, 0x79, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x20, 0x4B, 0x65, + 0x79 + }; + // Constant for Dec 31, 9999 in milliseconds in hex. + private static final byte[] dec319999Ms = { + (byte) 0, (byte) 0, (byte) 0xE6, (byte) 0x77, (byte) 0xD2, (byte) 0x1F, (byte) 0xD8, (byte) 0x18 + }; + // Dec 31, 9999 represented in Generalized time format YYYYMMDDhhmmssZ. + // "99991231235959Z" in hex. Refer RFC 5280 section 4.1.2.5.2 + private static final byte[] dec319999 = { + 0x39, 0x39, 0x39, 0x39, 0x31, 0x32, 0x33, 0x31, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, + }; + // Jan 01, 1970 represented in UTC time format YYMMDDhhmmssZ. + // "700101000000Z" in hex. Refer RFC 5280 section 4.1.2.5.1 + private static final byte[] jan01970 = { + 0x37, 0x30, 0x30, 0x31, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, + }; + // The KeyMint name "JavacardKeymintDevice" returned from getHwInfo. + private static final byte[] JavacardKeymintDevice = { + 0x4a, 0x61, 0x76, 0x61, 0x63, 0x61, 0x72, 0x64, 0x4b, 0x65, 0x79, 0x6d, 0x69, 0x6e, 0x74, 0x44, + 0x65, 0x76, 0x69, 0x63, 0x65, + }; + // The KeyMint author name "Google" returned from getHwInfo. + public static final byte[] Google = {0x47, 0x6F, 0x6F, 0x67, 0x6C, 0x65}; + // Attestation ID tags to be included in attestation record. + private static final short[] attTags = { + KMType.ATTESTATION_ID_BRAND, + KMType.ATTESTATION_ID_DEVICE, + KMType.ATTESTATION_ID_IMEI, + KMType.ATTESTATION_ID_MANUFACTURER, + KMType.ATTESTATION_ID_MEID, + KMType.ATTESTATION_ID_MODEL, + KMType.ATTESTATION_ID_PRODUCT, + KMType.ATTESTATION_ID_SERIAL + }; + // Below are the constants of instructions in APDU command header. + // Top 32 commands are reserved for provisioning. + private static final byte KEYMINT_CMD_APDU_START = 0x20; + // RKP + public static final byte INS_GET_RKP_HARDWARE_INFO = KEYMINT_CMD_APDU_START + 27; // 0x3B + public static final byte INS_GENERATE_RKP_KEY_CMD = KEYMINT_CMD_APDU_START + 28; // 0x3C + public static final byte INS_BEGIN_SEND_DATA_CMD = KEYMINT_CMD_APDU_START + 29; // 0x3D + public static final byte INS_UPDATE_KEY_CMD = KEYMINT_CMD_APDU_START + 30; // 0x3E + // Constant + public static final byte INS_UPDATE_EEK_CHAIN_CMD = KEYMINT_CMD_APDU_START + 31; // 0x3F + public static final byte INS_UPDATE_CHALLENGE_CMD = KEYMINT_CMD_APDU_START + 32; // 0x40 + public static final byte INS_FINISH_SEND_DATA_CMD = KEYMINT_CMD_APDU_START + 33; // 0x41 + public static final byte INS_GET_RESPONSE_CMD = KEYMINT_CMD_APDU_START + 34; // 0x42 + public static final byte INS_GET_UDS_CERTS_CMD = KEYMINT_CMD_APDU_START + 35; // 0x43 + public static final byte INS_GET_DICE_CERT_CHAIN_CMD = KEYMINT_CMD_APDU_START + 36; // 0x44 + private static final byte INS_GENERATE_KEY_CMD = KEYMINT_CMD_APDU_START + 1; // 0x21 + private static final byte INS_IMPORT_KEY_CMD = KEYMINT_CMD_APDU_START + 2; // 0x22 + private static final byte INS_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 3; // 0x23 + private static final byte INS_EXPORT_KEY_CMD = KEYMINT_CMD_APDU_START + 4; // 0x24 + private static final byte INS_ATTEST_KEY_CMD = KEYMINT_CMD_APDU_START + 5; // 0x25 + private static final byte INS_UPGRADE_KEY_CMD = KEYMINT_CMD_APDU_START + 6; // 0x26 + private static final byte INS_DELETE_KEY_CMD = KEYMINT_CMD_APDU_START + 7; // 0x27 + private static final byte INS_DELETE_ALL_KEYS_CMD = KEYMINT_CMD_APDU_START + 8; // 0x28 + private static final byte INS_ADD_RNG_ENTROPY_CMD = KEYMINT_CMD_APDU_START + 9; // 0x29 + private static final byte INS_COMPUTE_SHARED_HMAC_CMD = KEYMINT_CMD_APDU_START + 10; // 0x2A + private static final byte INS_DESTROY_ATT_IDS_CMD = KEYMINT_CMD_APDU_START + 11; // 0x2B + private static final byte INS_VERIFY_AUTHORIZATION_CMD = KEYMINT_CMD_APDU_START + 12; // 0x2C + private static final byte INS_GET_HMAC_SHARING_PARAM_CMD = KEYMINT_CMD_APDU_START + 13; // 0x2D + private static final byte INS_GET_KEY_CHARACTERISTICS_CMD = KEYMINT_CMD_APDU_START + 14; // 0x2E + private static final byte INS_GET_HW_INFO_CMD = KEYMINT_CMD_APDU_START + 15; // 0x2F + private static final byte INS_BEGIN_OPERATION_CMD = KEYMINT_CMD_APDU_START + 16; // 0x30 + private static final byte INS_UPDATE_OPERATION_CMD = KEYMINT_CMD_APDU_START + 17; // 0x31 + private static final byte INS_FINISH_OPERATION_CMD = KEYMINT_CMD_APDU_START + 18; // 0x32 + private static final byte INS_ABORT_OPERATION_CMD = KEYMINT_CMD_APDU_START + 19; // 0x33 + private static final byte INS_DEVICE_LOCKED_CMD = KEYMINT_CMD_APDU_START + 20; // 0x34 + private static final byte INS_EARLY_BOOT_ENDED_CMD = KEYMINT_CMD_APDU_START + 21; // 0x35 + private static final byte INS_GET_CERT_CHAIN_CMD = KEYMINT_CMD_APDU_START + 22; // 0x36 + private static final byte INS_UPDATE_AAD_OPERATION_CMD = KEYMINT_CMD_APDU_START + 23; // 0x37 + private static final byte INS_BEGIN_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 24; // 0x38 + private static final byte INS_FINISH_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 25; // 0x39 + private static final byte INS_INIT_STRONGBOX_CMD = KEYMINT_CMD_APDU_START + 26; // 0x3A + // The instructions from 0x43 to 0x4C will be reserved for KeyMint 1.0 for any future use. + // KeyMint 2.0 Instructions + private static final byte INS_GET_ROT_CHALLENGE_CMD = KEYMINT_CMD_APDU_START + 45; // 0x4D + private static final byte INS_GET_ROT_DATA_CMD = KEYMINT_CMD_APDU_START + 46; // 0x4E + private static final byte INS_SEND_ROT_DATA_CMD = KEYMINT_CMD_APDU_START + 47; // 0x4F + private static final byte KEYMINT_CMD_APDU_END = KEYMINT_CMD_APDU_START + 48; // 0x50 + private static final byte INS_END_KM_CMD = 0x7F; + // Instruction values from 0xCD to 0xFF are completely reserved for Vendors to use and + // will never be used by the base line code in future. + private static final byte INS_KM_VENDOR_START_CMD = (byte) 0xCD; + private static final byte INS_KM_VENDOR_END_CMD = (byte) 0xFF; + // Index in apduFlagsStatus[] to check if instruction command is case 4 type in the Apdu + protected static final byte APDU_CASE4_COMMAND_STATUS_INDEX = 0; + // Index in apduFlagsStatus[] to check if Apdu setIncomingAndReceive function is called + protected static final byte APDU_INCOMING_AND_RECEIVE_STATUS_INDEX = 1; + // The maximum buffer size of combined seed and nonce. + private static final byte HMAC_SHARED_PARAM_MAX_SIZE = 64; + // Instance of RemotelyProvisionedComponentDevice, used to redirect the rkp commands. + protected static KMRemotelyProvisionedComponentDevice rkp; + // Instance of Cbor encoder. + protected static KMEncoder encoder; + // Instance of Cbor decoder. + protected static KMDecoder decoder; + // Instance of KMRepository class for memory management. + protected static KMRepository repository; + // Instance of KMSEProvider for doing crypto operations. + protected static KMSEProvider seProvider; + // Holds the instance of KMOperationStates. A maximum of 4 instances of KMOperatioState is + // allowed. + protected static KMOperationState[] opTable; + // Instance of KMKeymintDataStore which helps to store and retrieve the data. + protected static KMKeymintDataStore kmDataStore; + + // Short array used to store the temporary results. + protected static short[] tmpVariables; + // Short array used to hold the dictionary items. + protected static short[] data; + // Buffer to store the transportKey which is used in the import wrapped key. Import wrapped + // key is divided into two stages 1. BEGIN_IMPORT_WRAPPED_KEY 2. FINISH_IMPORT_WRAPPED_KEY. + // The transportKey is retrieved and stored in this buffer at stage 1) and is later used in + // stage 2). + protected static byte[] wrappingKey; + // Transient byte array used to store the flags if APDU command type is of case 4 and if + // APDU setIncomingAndReceive() function is called or not. + protected static byte[] apduStatusFlags; + + /** Registers this applet. */ + protected KMKeymasterApplet(KMSEProvider seImpl) { + seProvider = seImpl; + boolean isUpgrading = seProvider.isUpgrading(); + repository = new KMRepository(isUpgrading); + encoder = new KMEncoder(); + decoder = new KMDecoder(); + kmDataStore = new KMKeymintDataStore(seProvider, repository); + data = JCSystem.makeTransientShortArray(DATA_ARRAY_SIZE, JCSystem.CLEAR_ON_DESELECT); + tmpVariables = + JCSystem.makeTransientShortArray(TMP_VARIABLE_ARRAY_SIZE, JCSystem.CLEAR_ON_DESELECT); + wrappingKey = + JCSystem.makeTransientByteArray((short) (WRAPPING_KEY_SIZE + 1), JCSystem.CLEAR_ON_RESET); + resetWrappingKey(); + apduStatusFlags = JCSystem.makeTransientByteArray((short) 2, JCSystem.CLEAR_ON_RESET); + opTable = new KMOperationState[MAX_OPERATIONS_COUNT]; + short index = 0; + while (index < MAX_OPERATIONS_COUNT) { + opTable[index] = new KMOperationState(); + index++; + } + KMType.initialize(); + if (!isUpgrading) { + // For keyMint 3.0 and above installation, set ignore second Imei flag to false. + kmDataStore.ignoreSecondImei = false; + kmDataStore.createMasterKey(MASTER_KEY_SIZE); + } + // initialize default values + initHmacNonceAndSeed(); + rkp = + new KMRemotelyProvisionedComponentDevice( + encoder, decoder, repository, seProvider, kmDataStore); + } + + /** Sends a response, may be extended response, as requested by the command. */ + public static void sendOutgoing(APDU apdu, short resp) { + // TODO handle the extended buffer stuff. We can reuse this. + short bufferStartOffset = repository.allocAvailableMemory(); + byte[] buffer = repository.getHeap(); + // TODO we can change the following to incremental send. + short bufferLength = + encoder.encode(resp, buffer, bufferStartOffset, repository.getHeapReclaimIndex()); + if (((short) (bufferLength + bufferStartOffset)) > ((short) repository.getHeap().length)) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + + /* In T=0 protocol, On a case 4 command, setIncomingAndReceive() must + * be invoked prior to calling setOutgoing(). Otherwise, erroneous + * behavior may result + * */ + if (apduStatusFlags[APDU_CASE4_COMMAND_STATUS_INDEX] == 1 + && apduStatusFlags[APDU_INCOMING_AND_RECEIVE_STATUS_INDEX] == 0 + && APDU.getProtocol() == APDU.PROTOCOL_T0) { + apdu.setIncomingAndReceive(); + } + // Send data + apdu.setOutgoing(); + apdu.setOutgoingLength(bufferLength); + apdu.sendBytesLong(buffer, bufferStartOffset, bufferLength); + } + + /** Receives data, which can be extended data, as requested by the command instance. */ + public static short receiveIncoming(APDU apdu, short reqExp) { + byte[] srcBuffer = apdu.getBuffer(); + short recvLen = apdu.setIncomingAndReceive(); + short srcOffset = apdu.getOffsetCdata(); + apduStatusFlags[APDU_INCOMING_AND_RECEIVE_STATUS_INDEX] = 1; + // TODO add logic to handle the extended length buffer. In this case the memory can be reused + // from extended buffer. + short bufferLength = apdu.getIncomingLength(); + short bufferStartOffset = repository.allocReclaimableMemory(bufferLength); + short index = bufferStartOffset; + byte[] buffer = repository.getHeap(); + while (recvLen > 0 && ((short) (index - bufferStartOffset) < bufferLength)) { + Util.arrayCopyNonAtomic(srcBuffer, srcOffset, buffer, index, recvLen); + index += recvLen; + recvLen = apdu.receiveBytes(srcOffset); + } + short req = decoder.decode(reqExp, buffer, bufferStartOffset, bufferLength); + repository.reclaimMemory(bufferLength); + return req; + } + + private static short createKeyBlobInstance(byte keyType) { + short arrayLen = 0; + switch (keyType) { + case ASYM_KEY_TYPE: + arrayLen = ASYM_KEY_BLOB_SIZE_V2_V3; + break; + case SYM_KEY_TYPE: + arrayLen = SYM_KEY_BLOB_SIZE_V2_V3; + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + return KMArray.instance(arrayLen); + } + + private static void addTags(short params, boolean hwEnforced, KMAttestationCert cert) { + short index = 0; + short arr = KMKeyParameters.cast(params).getVals(); + short len = KMArray.cast(arr).length(); + short tag; + while (index < len) { + tag = KMArray.cast(arr).get(index); + cert.extensionTag(tag, hwEnforced); + index++; + } + } + + private static void setUniqueId(KMAttestationCert cert, short attAppId, byte[] scratchPad) { + if (!KMTag.isPresent(data[KEY_PARAMETERS], KMType.BOOL_TAG, KMType.INCLUDE_UNIQUE_ID)) { + return; + } + // temporal count T + short time = + KMKeyParameters.findTag(KMType.DATE_TAG, KMType.CREATION_DATETIME, data[KEY_PARAMETERS]); + if (time == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + time = KMIntegerTag.cast(time).getValue(); + + // Reset After Rotation R - it will be part of HW Enforced key + // characteristics + byte resetAfterRotation = 0; + if (KMTag.isPresent(data[KEY_PARAMETERS], KMType.BOOL_TAG, KMType.RESET_SINCE_ID_ROTATION)) { + resetAfterRotation = 0x01; + } + + cert.makeUniqueId( + scratchPad, + (short) 0, + KMInteger.cast(time).getBuffer(), + KMInteger.cast(time).getStartOff(), + KMInteger.cast(time).length(), + KMByteBlob.cast(attAppId).getBuffer(), + KMByteBlob.cast(attAppId).getStartOff(), + KMByteBlob.cast(attAppId).length(), + resetAfterRotation, + kmDataStore.getMasterKey()); + } + + private static void validateRSAKey(byte[] scratchPad) { + // Read key size + if (!KMTag.isValidKeySize(data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + if (!KMTag.isValidPublicExponent(data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + } + + // Generate key handlers + private static void generateRSAKey(byte[] scratchPad) { + // Validate RSA Key + validateRSAKey(scratchPad); + // Now generate 2048 bit RSA keypair for the given exponent + short[] lengths = tmpVariables; + data[PUB_KEY] = KMByteBlob.instance((short) 256); + data[SECRET] = KMByteBlob.instance((short) 256); + seProvider.createAsymmetricKey( + KMType.RSA, + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + KMByteBlob.cast(data[PUB_KEY]).getBuffer(), + KMByteBlob.cast(data[PUB_KEY]).getStartOff(), + KMByteBlob.cast(data[PUB_KEY]).length(), + lengths); + + data[KEY_BLOB] = createKeyBlobInstance(ASYM_KEY_TYPE); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PUB_KEY, data[PUB_KEY]); + } + + private static void validateAESKey() { + // Read key size + if (!KMTag.isValidKeySize(data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + // Read Block mode - array of byte values + if (KMTag.isPresent(data[KEY_PARAMETERS], KMType.ENUM_ARRAY_TAG, KMType.BLOCK_MODE)) { + short blockModes = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.BLOCK_MODE, data[KEY_PARAMETERS]); + // If it is a GCM mode + if (KMEnumArrayTag.cast(blockModes).contains(KMType.GCM)) { + // Min mac length must be present + KMTag.assertPresence( + data[KEY_PARAMETERS], + KMType.UINT_TAG, + KMType.MIN_MAC_LENGTH, + KMError.MISSING_MIN_MAC_LENGTH); + short macLength = + KMKeyParameters.findTag(KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, data[KEY_PARAMETERS]); + macLength = KMIntegerTag.cast(macLength).getValue(); + // Validate the MIN_MAC_LENGTH for AES - should be multiple of 8, less then 128 bits + // and greater the 96 bits + if (KMInteger.cast(macLength).getSignificantShort() != 0 + || KMInteger.cast(macLength).getShort() > 128 + || KMInteger.cast(macLength).getShort() < 96 + || (KMInteger.cast(macLength).getShort() % 8) != 0) { + KMException.throwIt(KMError.UNSUPPORTED_MIN_MAC_LENGTH); + } + } + } + } + + private static void generateAESKey(byte[] scratchPad) { + validateAESKey(); + short keysize = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + short len = seProvider.createSymmetricKey(KMType.AES, keysize, scratchPad, (short) 0); + data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, len); + data[KEY_BLOB] = createKeyBlobInstance(SYM_KEY_TYPE); + } + + private static void validateECKeys() { + // Read key size + short ecCurve = KMEnumTag.getValue(KMType.ECCURVE, data[KEY_PARAMETERS]); + /* In KeyMint 2.0, If EC_CURVE not provided, generateKey + * must return ErrorCode::UNSUPPORTED_KEY_SIZE or ErrorCode::UNSUPPORTED_EC_CURVE. + */ + if (ecCurve != KMType.P_256) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + short ecKeySize = KMEnumTag.getValue(KMType.KEYSIZE, data[KEY_PARAMETERS]); + if ((ecKeySize != KMType.INVALID_VALUE) && !KMTag.isValidKeySize(data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + } + + private static void generateECKeys(byte[] scratchPad) { + validateECKeys(); + short[] lengths = tmpVariables; + seProvider.createAsymmetricKey( + KMType.EC, + scratchPad, + (short) 0, + (short) 128, + scratchPad, + (short) 128, + (short) 128, + lengths); + data[PUB_KEY] = KMByteBlob.instance(scratchPad, (short) 128, lengths[1]); + data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, lengths[0]); + data[KEY_BLOB] = createKeyBlobInstance(ASYM_KEY_TYPE); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PUB_KEY, data[PUB_KEY]); + } + + private static void validateTDESKey() { + if (!KMTag.isValidKeySize(data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + // Read Minimum Mac length - it must not be present + KMTag.assertAbsence( + data[KEY_PARAMETERS], KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, KMError.INVALID_TAG); + } + + private static void generateTDESKey(byte[] scratchPad) { + validateTDESKey(); + short len = seProvider.createSymmetricKey(KMType.DES, (short) 168, scratchPad, (short) 0); + data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, len); + data[KEY_BLOB] = createKeyBlobInstance(SYM_KEY_TYPE); + } + + private static void validateHmacKey() { + // If params does not contain any digest throw unsupported digest error. + KMTag.assertPresence( + data[KEY_PARAMETERS], KMType.ENUM_ARRAY_TAG, KMType.DIGEST, KMError.UNSUPPORTED_DIGEST); + + // check whether digest sizes are greater then or equal to min mac length. + // Only SHA256 digest must be supported. + if (!KMEnumArrayTag.contains(KMType.DIGEST, KMType.SHA2_256, data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.UNSUPPORTED_DIGEST); + } + // Read Minimum Mac length + KMTag.assertPresence( + data[KEY_PARAMETERS], + KMType.UINT_TAG, + KMType.MIN_MAC_LENGTH, + KMError.MISSING_MIN_MAC_LENGTH); + short minMacLength = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, data[KEY_PARAMETERS]); + + if (((short) (minMacLength % 8) != 0) + || minMacLength < MIN_HMAC_LENGTH_BITS + || minMacLength > SHA256_DIGEST_LEN_BITS) { + KMException.throwIt(KMError.UNSUPPORTED_MIN_MAC_LENGTH); + } + // Read Keysize + if (!KMTag.isValidKeySize(data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + } + + private static void generateHmacKey(byte[] scratchPad) { + validateHmacKey(); + short keysize = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + // generate HMAC Key + short len = seProvider.createSymmetricKey(KMType.HMAC, keysize, scratchPad, (short) 0); + data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, len); + data[KEY_BLOB] = createKeyBlobInstance(SYM_KEY_TYPE); + } + + // This function is only called from processUpgradeKey command. + // 1. Update the latest values of OSVersion, OSPatch, VendorPatch and BootPatch in the + // KeyBlob's KeyCharacteristics. + // 2. Re-create KeyBlob's KeyCharacteristics from HW_PARAMS to make sure we don't miss + // anything which happens in these functions makeSbEnforced and makeTeeEnforced in + // the future. Like validations. + // 3. No need to create Keystore Enforced list here as it is not required to be included in + // the KeyBlob's KeyCharacteristics. + // 4. No need to create KeyCharacteristics as upgradeKey does not require to return any + // KeyCharacteristics back. + private static void upgradeKeyBlobKeyCharacteristics(short hwParams, byte[] scratchPad) { + short osVersion = kmDataStore.getOsVersion(); + short osPatch = kmDataStore.getOsPatch(); + short vendorPatch = kmDataStore.getVendorPatchLevel(); + short bootPatch = kmDataStore.getBootPatchLevel(); + data[SB_PARAMETERS] = + KMKeyParameters.makeSbEnforced( + hwParams, (byte) data[ORIGIN], osVersion, osPatch, vendorPatch, bootPatch, scratchPad); + data[TEE_PARAMETERS] = KMKeyParameters.makeTeeEnforced(hwParams, scratchPad); + data[HW_PARAMETERS] = KMKeyParameters.makeHwEnforced(data[SB_PARAMETERS], data[TEE_PARAMETERS]); + } + + private static void makeKeyCharacteristics(byte[] scratchPad) { + short osVersion = kmDataStore.getOsVersion(); + short osPatch = kmDataStore.getOsPatch(); + short vendorPatch = kmDataStore.getVendorPatchLevel(); + short bootPatch = kmDataStore.getBootPatchLevel(); + data[SB_PARAMETERS] = + KMKeyParameters.makeSbEnforced( + data[KEY_PARAMETERS], + (byte) data[ORIGIN], + osVersion, + osPatch, + vendorPatch, + bootPatch, + scratchPad); + data[TEE_PARAMETERS] = KMKeyParameters.makeTeeEnforced(data[KEY_PARAMETERS], scratchPad); + data[SW_PARAMETERS] = KMKeyParameters.makeKeystoreEnforced(data[KEY_PARAMETERS], scratchPad); + data[HW_PARAMETERS] = KMKeyParameters.makeHwEnforced(data[SB_PARAMETERS], data[TEE_PARAMETERS]); + data[KEY_CHARACTERISTICS] = KMKeyCharacteristics.instance(); + KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).setStrongboxEnforced(data[SB_PARAMETERS]); + KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).setKeystoreEnforced(data[SW_PARAMETERS]); + KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).setTeeEnforced(data[TEE_PARAMETERS]); + } + + private static void createEncryptedKeyBlob(byte[] scratchPad) { + // make root of trust blob + data[ROT] = readROT(scratchPad, KEYBLOB_CURRENT_VERSION); + if (data[ROT] == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + // make hidden key params list + data[HIDDEN_PARAMETERS] = + KMKeyParameters.makeHidden(data[KEY_PARAMETERS], data[ROT], scratchPad); + data[KEY_BLOB_VERSION_DATA_OFFSET] = KMInteger.uint_16(KEYBLOB_CURRENT_VERSION); + // create custom tags + data[CUSTOM_TAGS] = KMKeyParameters.makeCustomTags(data[HW_PARAMETERS], scratchPad); + // encrypt the secret and cryptographically attach that to authorization data + encryptSecret(scratchPad); + // create key blob array + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_SECRET, data[SECRET]); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_AUTH_TAG, data[AUTH_TAG]); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_NONCE, data[NONCE]); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_VERSION_OFFSET, data[KEY_BLOB_VERSION_DATA_OFFSET]); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_CUSTOM_TAGS, data[CUSTOM_TAGS]); + + short tempChar = KMKeyCharacteristics.instance(); + short emptyParam = KMArray.instance((short) 0); + emptyParam = KMKeyParameters.instance(emptyParam); + KMKeyCharacteristics.cast(tempChar).setStrongboxEnforced(data[SB_PARAMETERS]); + KMKeyCharacteristics.cast(tempChar).setKeystoreEnforced(emptyParam); + KMKeyCharacteristics.cast(tempChar).setTeeEnforced(data[TEE_PARAMETERS]); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PARAMS, tempChar); + } + + // Read RoT + public static short readROT(byte[] scratchPad, short version) { + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + short len = kmDataStore.getBootKey(scratchPad, (short) 0); + // As per IKeyMintDevice.aidl specification The root of trust + // consists of verifyBootKey, boot state and device locked. + if (version <= KEYBLOB_VERSION_1) { + // To parse old keyblobs verified boot hash is included in + // the root of trust. + len += kmDataStore.getVerifiedBootHash(scratchPad, (short) len); + } + short bootState = kmDataStore.getBootState(); + len = Util.setShort(scratchPad, len, bootState); + if (kmDataStore.isDeviceBootLocked()) { + scratchPad[len] = (byte) 1; + } else { + scratchPad[len] = (byte) 0; + } + len++; + return KMByteBlob.instance(scratchPad, (short) 0, len); + } + + private static void encryptSecret(byte[] scratchPad) { + // make nonce + data[NONCE] = KMByteBlob.instance(AES_GCM_NONCE_LENGTH); + data[AUTH_TAG] = KMByteBlob.instance(AES_GCM_AUTH_TAG_LENGTH); + seProvider.newRandomNumber( + KMByteBlob.cast(data[NONCE]).getBuffer(), + KMByteBlob.cast(data[NONCE]).getStartOff(), + KMByteBlob.cast(data[NONCE]).length()); + // derive master key - stored in derivedKey + short len = deriveKey(scratchPad); + len = + seProvider.aesGCMEncrypt( + KMByteBlob.cast(data[DERIVED_KEY]).getBuffer(), + KMByteBlob.cast(data[DERIVED_KEY]).getStartOff(), + KMByteBlob.cast(data[DERIVED_KEY]).length(), + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + scratchPad, + (short) 0, + KMByteBlob.cast(data[NONCE]).getBuffer(), + KMByteBlob.cast(data[NONCE]).getStartOff(), + KMByteBlob.cast(data[NONCE]).length(), + null, + (short) 0, + (short) 0, + KMByteBlob.cast(data[AUTH_TAG]).getBuffer(), + KMByteBlob.cast(data[AUTH_TAG]).getStartOff(), + KMByteBlob.cast(data[AUTH_TAG]).length()); + + if (len > 0 && len != KMByteBlob.cast(data[SECRET]).length()) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + data[SECRET] = KMByteBlob.instance(scratchPad, (short) 0, len); + } + + private static byte getKeyType(short hardwareParams) { + short alg = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, hardwareParams); + if (KMEnumTag.cast(alg).getValue() == KMType.RSA + || KMEnumTag.cast(alg).getValue() == KMType.EC) { + return ASYM_KEY_TYPE; + } + return SYM_KEY_TYPE; + } + + private static void makeAuthData(short version, byte[] scratchPad) { + // For KeyBlob V2: Auth Data includes HW_PARAMETERS, HIDDEN_PARAMETERS, CUSTOM_TAGS, VERSION and + // PUB_KEY. + // For KeyBlob V1: Auth Data includes HW_PARAMETERS, HIDDEN_PARAMETERS, VERSION and PUB_KEY. + // For KeyBlob V0: Auth Data includes HW_PARAMETERS, HIDDEN_PARAMETERS and PUB_KEY. + // VERSION is included only for KeyBlobs having version >= 1. + // PUB_KEY is included for only ASYMMETRIC KeyBlobs. + short index = 0; + short numParams = 0; + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 10, (byte) 0); + byte keyType = getKeyType(data[HW_PARAMETERS]); + // Copy the relevant parameters in the scratchPad in the order + // 1. HW_PARAMETERS + // 2. HIDDEN_PARAMETERS + // 3. VERSION ( Only Version >= 1) + // 4. PUB_KEY ( Only for Asymmetric Keys) + switch (version) { + case (short) 0: + numParams = 2; + Util.setShort(scratchPad, (short) 0, KMKeyParameters.cast(data[HW_PARAMETERS]).getVals()); + Util.setShort( + scratchPad, (short) 2, KMKeyParameters.cast(data[HIDDEN_PARAMETERS]).getVals()); + // For Asymmetric Keys include the PUB_KEY. + if (keyType == ASYM_KEY_TYPE) { + numParams = 3; + Util.setShort(scratchPad, (short) 4, data[PUB_KEY]); + } + break; + case (short) 1: + numParams = 3; + Util.setShort(scratchPad, (short) 0, KMKeyParameters.cast(data[HW_PARAMETERS]).getVals()); + Util.setShort( + scratchPad, (short) 2, KMKeyParameters.cast(data[HIDDEN_PARAMETERS]).getVals()); + Util.setShort(scratchPad, (short) 4, data[KEY_BLOB_VERSION_DATA_OFFSET]); + // For Asymmetric Keys include the PUB_KEY. + if (keyType == ASYM_KEY_TYPE) { + numParams = 4; + Util.setShort(scratchPad, (short) 6, data[PUB_KEY]); + } + break; + case (short) 2: + numParams = 4; + Util.setShort(scratchPad, (short) 0, KMKeyParameters.cast(data[HW_PARAMETERS]).getVals()); + Util.setShort( + scratchPad, (short) 2, KMKeyParameters.cast(data[HIDDEN_PARAMETERS]).getVals()); + Util.setShort(scratchPad, (short) 4, KMKeyParameters.cast(data[CUSTOM_TAGS]).getVals()); + Util.setShort(scratchPad, (short) 6, data[KEY_BLOB_VERSION_DATA_OFFSET]); + // For Asymmetric Keys include the PUB_KEY. + if (keyType == ASYM_KEY_TYPE) { + numParams = 5; + Util.setShort(scratchPad, (short) 8, data[PUB_KEY]); + } + break; + default: + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + short prevReclaimIndex = repository.getHeapReclaimIndex(); + short authIndex = repository.allocReclaimableMemory(MAX_AUTH_DATA_SIZE); + index = 0; + short len = 0; + Util.arrayFillNonAtomic(repository.getHeap(), authIndex, MAX_AUTH_DATA_SIZE, (byte) 0); + while (index < numParams) { + short tag = Util.getShort(scratchPad, (short) (index * 2)); + len = encoder.encode(tag, repository.getHeap(), (short) (authIndex + 32), prevReclaimIndex); + Util.arrayCopyNonAtomic( + repository.getHeap(), + authIndex, + repository.getHeap(), + (short) (authIndex + len + 32), + (short) 32); + len = + seProvider.messageDigest256( + repository.getHeap(), + (short) (authIndex + 32), + (short) (len + 32), + repository.getHeap(), + authIndex); + if (len != 32) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + index++; + } + short authDataIndex = repository.alloc(len); + Util.arrayCopyNonAtomic( + repository.getHeap(), authIndex, repository.getHeap(), authDataIndex, len); + repository.reclaimMemory(MAX_AUTH_DATA_SIZE); + data[AUTH_DATA] = authDataIndex; + data[AUTH_DATA_LENGTH] = len; + } + + private static short deriveKeyForOldKeyBlobs(byte[] scratchPad) { + // KeyDerivation: + // 1. Do HMAC Sign, Auth data. + // 2. HMAC Sign generates an output of 32 bytes length. + // Consume only first 16 bytes as derived key. + // Hmac sign. + short len = + seProvider.hmacKDF( + kmDataStore.getMasterKey(), + repository.getHeap(), + data[AUTH_DATA], + data[AUTH_DATA_LENGTH], + scratchPad, + (short) 0); + if (len < 16) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + len = 16; + data[DERIVED_KEY] = KMByteBlob.instance(scratchPad, (short) 0, len); + return len; + } + + private static short deriveKey(byte[] scratchPad) { + // For KeyBlob V3: Auth Data includes HW_PARAMETERS, HIDDEN_PARAMETERS, CUSTOM_TAGS, VERSION and + // PUB_KEY. + short index = 0; + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 10, (byte) 0); + byte keyType = getKeyType(data[HW_PARAMETERS]); + // Copy the relevant parameters in the scratchPad in the order + // 1. HW_PARAMETERS + // 2. HIDDEN_PARAMETERS + // 3. CUSTOM_TAGS + // 3. VERSION ( Only Version >= 1) + // 4. PUB_KEY ( Only for Asymmetric Keys) + short numParams = 4; + Util.setShort(scratchPad, (short) 0, KMKeyParameters.cast(data[HW_PARAMETERS]).getVals()); + Util.setShort(scratchPad, (short) 2, KMKeyParameters.cast(data[HIDDEN_PARAMETERS]).getVals()); + Util.setShort(scratchPad, (short) 4, KMKeyParameters.cast(data[CUSTOM_TAGS]).getVals()); + Util.setShort(scratchPad, (short) 6, data[KEY_BLOB_VERSION_DATA_OFFSET]); + // For Asymmetric Keys include the PUB_KEY. + if (keyType == ASYM_KEY_TYPE) { + numParams = 5; + Util.setShort(scratchPad, (short) 8, data[PUB_KEY]); + } + short prevReclaimIndex = repository.getHeapReclaimIndex(); + short authIndex = repository.allocReclaimableMemory(MAX_AUTH_DATA_SIZE); + Util.arrayFillNonAtomic(repository.getHeap(), authIndex, MAX_AUTH_DATA_SIZE, (byte) 0); + short len = 0; + KMOperation operation = null; + try { + operation = + seProvider.initSymmetricOperation( + KMType.SIGN, + KMType.HMAC, + KMType.SHA2_256, + KMType.PADDING_NONE, + (byte) KMType.INVALID_VALUE, + (Object) kmDataStore.getMasterKey(), + KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY, + (byte[]) null, + (short) 0, + (short) 0, + (short) 0, + false); + + byte arrayHeader = (byte) 0x80; + arrayHeader |= (byte) numParams; + ((byte[]) repository.getHeap())[authIndex] = arrayHeader; + operation.update(repository.getHeap(), authIndex, (short) 1); + + while (index < numParams) { + short tag = Util.getShort(scratchPad, (short) (index * 2)); + len = encoder.encode(tag, repository.getHeap(), (short) authIndex, prevReclaimIndex); + operation.update(repository.getHeap(), authIndex, len); + index++; + } + repository.reclaimMemory(MAX_AUTH_DATA_SIZE); + // KeyDerivation: + // 1. Do HMAC Sign, Auth data. + // 2. HMAC Sign generates an output of 32 bytes length. + // Consume only first 16 bytes as derived key. + // Hmac sign. + len = operation.sign(scratchPad, (short) 0, (short) 0, scratchPad, (short) 0); + } finally { + if (operation != null) { + operation.abort(); + } + } + if (len < 16) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + len = 16; + data[DERIVED_KEY] = KMByteBlob.instance(scratchPad, (short) 0, len); + return len; + } + + public static void sendResponse(APDU apdu, short err) { + short resp = KMArray.instance((short) 1); + err = KMError.translate(err); + short error = KMInteger.uint_16(err); + KMArray.cast(resp).add((short) 0, error); + sendOutgoing(apdu, resp); + } + + public static void generateRkpKey(byte[] scratchPad, short keyParams) { + data[KEY_PARAMETERS] = keyParams; + generateECKeys(scratchPad); + // create key blob + data[ORIGIN] = KMType.GENERATED; + makeKeyCharacteristics(scratchPad); + createEncryptedKeyBlob(scratchPad); + short prevReclaimIndex = repository.getHeapReclaimIndex(); + short offset = repository.allocReclaimableMemory(MAX_KEYBLOB_SIZE); + data[KEY_BLOB] = + encoder.encode( + data[KEY_BLOB], repository.getHeap(), offset, prevReclaimIndex, MAX_KEYBLOB_SIZE); + data[KEY_BLOB] = KMByteBlob.instance(repository.getHeap(), offset, data[KEY_BLOB]); + repository.reclaimMemory(MAX_KEYBLOB_SIZE); + } + + public static short getPubKey() { + return data[PUB_KEY]; + } + + public static short getPivateKey() { + return data[KEY_BLOB]; + } + + /** + * Encodes the object to the provided apdu buffer. + * + * @param object Object to be encoded. + * @param apduBuf Buffer on which the encoded data is copied. + * @param apduOff Start offset of the buffer. + * @param maxLen Max value of the expected out length. + * @return length of the encoded buffer. + */ + public static short encodeToApduBuffer( + short object, byte[] apduBuf, short apduOff, short maxLen) { + short prevReclaimIndex = repository.getHeapReclaimIndex(); + short offset = repository.allocReclaimableMemory(maxLen); + short len = encoder.encode(object, repository.getHeap(), offset, prevReclaimIndex, maxLen); + Util.arrayCopyNonAtomic(repository.getHeap(), offset, apduBuf, apduOff, len); + // release memory + repository.reclaimMemory(maxLen); + return len; + } + + public static short validateCertChain( + boolean validateEekRoot, + byte expCertAlg, + byte expLeafCertAlg, + short certChainArr, + byte[] scratchPad, + Object[] authorizedEekRoots) { + short len = KMArray.cast(certChainArr).length(); + short coseHeadersExp = KMCoseHeaders.exp(); + // prepare exp for coseky + short coseKeyExp = KMCoseKey.exp(); + short ptr1; + short ptr2; + short signStructure; + short encodedLen; + short prevCoseKey = 0; + short keySize; + short alg = expCertAlg; + short index; + for (index = 0; index < len; index++) { + ptr1 = KMArray.cast(certChainArr).get(index); + + // validate protected Headers + ptr2 = KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_PROTECTED_PARAMS_OFFSET); + ptr2 = + decoder.decode( + coseHeadersExp, + KMByteBlob.cast(ptr2).getBuffer(), + KMByteBlob.cast(ptr2).getStartOff(), + KMByteBlob.cast(ptr2).length()); + if (!KMCoseHeaders.cast(ptr2).isDataValid(rkp.rkpTmpVariables, alg, KMType.INVALID_VALUE)) { + KMException.throwIt(KMError.STATUS_FAILED); + } + + // parse and get the public key from payload. + ptr2 = KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_PAYLOAD_OFFSET); + ptr2 = + decoder.decode( + coseKeyExp, + KMByteBlob.cast(ptr2).getBuffer(), + KMByteBlob.cast(ptr2).getStartOff(), + KMByteBlob.cast(ptr2).length()); + if ((index == (short) (len - 1)) && len > 1) { + alg = expLeafCertAlg; + } + if (!KMCoseKey.cast(ptr2) + .isDataValid( + rkp.rkpTmpVariables, + KMCose.COSE_KEY_TYPE_EC2, + KMType.INVALID_VALUE, + alg, + KMCose.COSE_ECCURVE_256)) { + KMException.throwIt(KMError.STATUS_FAILED); + } + if (prevCoseKey == 0) { + prevCoseKey = ptr2; + } + // Get the public key. + keySize = KMCoseKey.cast(prevCoseKey).getEcdsa256PublicKey(scratchPad, (short) 0); + if (keySize != 65) { + KMException.throwIt(KMError.STATUS_FAILED); + } + if (validateEekRoot && (index == 0)) { + boolean found = false; + // In prod mode the first pubkey should match a well-known Google public key. + for (short i = 0; i < (short) authorizedEekRoots.length; i++) { + if (0 + == Util.arrayCompare( + scratchPad, + (short) 0, + (byte[]) authorizedEekRoots[i], + (short) 0, + (short) ((byte[]) authorizedEekRoots[i]).length)) { + found = true; + break; + } + } + if (!found) { + KMException.throwIt(KMError.STATUS_FAILED); + } + } + // Validate signature. + signStructure = + KMCose.constructCoseSignStructure( + KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_PROTECTED_PARAMS_OFFSET), + KMByteBlob.instance((short) 0), + KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_PAYLOAD_OFFSET)); + encodedLen = + KMKeymasterApplet.encodeToApduBuffer( + signStructure, scratchPad, keySize, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + + short signatureLen = + rkp.encodeES256CoseSignSignature( + KMByteBlob.cast(KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_SIGNATURE_OFFSET)) + .getBuffer(), + KMByteBlob.cast(KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_SIGNATURE_OFFSET)) + .getStartOff(), + KMByteBlob.length(KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_SIGNATURE_OFFSET)), + scratchPad, + (short) (keySize + encodedLen)); + + if (!seProvider.ecVerify256( + scratchPad, + (short) 0, + keySize, + scratchPad, + keySize, + encodedLen, + scratchPad, + (short) (keySize + encodedLen), + signatureLen)) { + KMException.throwIt(KMError.STATUS_FAILED); + } + prevCoseKey = ptr2; + } + return prevCoseKey; + } + + public static short generateDiceCertChain(byte[] scratchPad) { + if (kmDataStore.isProvisionLocked()) { + KMException.throwIt(KMError.STATUS_FAILED); + } + KMKey deviceUniqueKey = kmDataStore.getRkpDeviceUniqueKeyPair(); + short temp = deviceUniqueKey.getPublicKey(scratchPad, (short) 0); + short coseKey = + KMCose.constructCoseKey( + rkp.rkpTmpVariables, + KMInteger.uint_8(KMCose.COSE_KEY_TYPE_EC2), + KMType.INVALID_VALUE, + KMNInteger.uint_8(KMCose.COSE_ALG_ES256), + KMInteger.uint_8(KMCose.COSE_ECCURVE_256), + scratchPad, + (short) 0, + temp, + KMType.INVALID_VALUE, + false); + temp = + KMKeymasterApplet.encodeToApduBuffer( + coseKey, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + // Construct payload. + short payload = + KMCose.constructCoseCertPayload( + KMCosePairTextStringTag.instance( + KMInteger.uint_8(KMCose.ISSUER), + KMTextString.instance( + KMCose.TEST_ISSUER_NAME, (short) 0, (short) KMCose.TEST_ISSUER_NAME.length)), + KMCosePairTextStringTag.instance( + KMInteger.uint_8(KMCose.SUBJECT), + KMTextString.instance( + KMCose.TEST_SUBJECT_NAME, (short) 0, (short) KMCose.TEST_SUBJECT_NAME.length)), + KMCosePairByteBlobTag.instance( + KMNInteger.uint_32(KMCose.SUBJECT_PUBLIC_KEY, (short) 0), + KMByteBlob.instance(scratchPad, (short) 0, temp)), + KMCosePairByteBlobTag.instance( + KMNInteger.uint_32(KMCose.KEY_USAGE, (short) 0), + KMByteBlob.instance( + KMCose.KEY_USAGE_SIGN, (short) 0, (short) KMCose.KEY_USAGE_SIGN.length))); + // temp temporarily holds the length of encoded cert payload. + temp = + KMKeymasterApplet.encodeToApduBuffer( + payload, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + payload = KMByteBlob.instance(scratchPad, (short) 0, temp); + + // protected header + short protectedHeader = + KMCose.constructHeaders( + rkp.rkpTmpVariables, + KMNInteger.uint_8(KMCose.COSE_ALG_ES256), + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE); + // temp temporarily holds the length of encoded headers. + temp = + KMKeymasterApplet.encodeToApduBuffer( + protectedHeader, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + protectedHeader = KMByteBlob.instance(scratchPad, (short) 0, temp); + + // unprotected headers. + short arr = KMArray.instance((short) 0); + short unprotectedHeader = KMCoseHeaders.instance(arr); + + // construct cose sign structure. + short coseSignStructure = + KMCose.constructCoseSignStructure(protectedHeader, KMByteBlob.instance((short) 0), payload); + // temp temporarily holds the length of encoded sign structure. + // Encode cose Sign_Structure. + temp = + KMKeymasterApplet.encodeToApduBuffer( + coseSignStructure, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + // do sign + short len = + seProvider.signWithDeviceUniqueKey( + deviceUniqueKey, scratchPad, (short) 0, temp, scratchPad, temp); + len = + KMAsn1Parser.instance() + .decodeEcdsa256Signature(KMByteBlob.instance(scratchPad, temp, len), scratchPad, temp); + coseSignStructure = KMByteBlob.instance(scratchPad, temp, len); + + // construct cose_sign1 + short coseSign1 = + KMCose.constructCoseSign1(protectedHeader, unprotectedHeader, payload, coseSignStructure); + + // [Cose_Key, Cose_Sign1] + short dcc = KMArray.instance((short) 2); + KMArray.cast(dcc).add((short) 0, coseKey); + KMArray.cast(dcc).add((short) 1, coseSign1); + return dcc; + } + + protected void initHmacNonceAndSeed() { + short nonce = repository.alloc((short) 32); + seProvider.newRandomNumber( + repository.getHeap(), nonce, KMKeymintDataStore.HMAC_SEED_NONCE_SIZE); + kmDataStore.initHmacNonce(repository.getHeap(), nonce, KMKeymintDataStore.HMAC_SEED_NONCE_SIZE); + } + + private void releaseAllOperations() { + short index = 0; + while (index < MAX_OPERATIONS_COUNT) { + opTable[index].reset(); + index++; + } + } + + private KMOperationState reserveOperation(short algorithm, short opHandle) { + short index = 0; + while (index < MAX_OPERATIONS_COUNT) { + if (opTable[index].getAlgorithm() == KMType.INVALID_VALUE) { + opTable[index].reset(); + opTable[index].setAlgorithm(algorithm); + opTable[index].setHandle( + KMInteger.cast(opHandle).getBuffer(), + KMInteger.cast(opHandle).getStartOff(), + KMInteger.cast(opHandle).length()); + return opTable[index]; + } + index++; + } + return null; + } + + private KMOperationState findOperation(short handle) { + return findOperation( + KMInteger.cast(handle).getBuffer(), + KMInteger.cast(handle).getStartOff(), + KMInteger.cast(handle).length()); + } + + private KMOperationState findOperation(byte[] opHandle, short start, short len) { + short index = 0; + while (index < MAX_OPERATIONS_COUNT) { + if (opTable[index].compare(opHandle, start, len) == 0) { + if (opTable[index].getAlgorithm() != KMType.INVALID_VALUE) { + return opTable[index]; + } + } + index++; + } + return null; + } + + private void releaseOperation(KMOperationState op) { + op.reset(); + } + + /** + * Selects this applet. + * + * @return Returns true if the keymaster is in correct state + */ + @Override + public boolean select() { + repository.onSelect(); + return true; + } + + /** De-selects this applet. */ + @Override + public void deselect() { + repository.onDeselect(); + } + + /** Uninstalls the applet after cleaning the repository. */ + @Override + public void uninstall() { + repository.onUninstall(); + } + + protected short mapISOErrorToKMError(short reason) { + switch (reason) { + case ISO7816.SW_CLA_NOT_SUPPORTED: + return KMError.UNSUPPORTED_CLA; + case ISO7816.SW_CONDITIONS_NOT_SATISFIED: + return KMError.SW_CONDITIONS_NOT_SATISFIED; + case ISO7816.SW_COMMAND_NOT_ALLOWED: + return KMError.CMD_NOT_ALLOWED; + case ISO7816.SW_DATA_INVALID: + return KMError.INVALID_DATA; + case ISO7816.SW_INCORRECT_P1P2: + return KMError.INVALID_P1P2; + case ISO7816.SW_INS_NOT_SUPPORTED: + return KMError.UNSUPPORTED_INSTRUCTION; + case ISO7816.SW_WRONG_LENGTH: + return KMError.SW_WRONG_LENGTH; + case ISO7816.SW_UNKNOWN: + default: + return KMError.UNKNOWN_ERROR; + } + } + + protected short mapCryptoErrorToKMError(short reason) { + switch (reason) { + case CryptoException.ILLEGAL_USE: + return KMError.CRYPTO_ILLEGAL_USE; + case CryptoException.ILLEGAL_VALUE: + return KMError.CRYPTO_ILLEGAL_VALUE; + case CryptoException.INVALID_INIT: + return KMError.CRYPTO_INVALID_INIT; + case CryptoException.NO_SUCH_ALGORITHM: + return KMError.CRYPTO_NO_SUCH_ALGORITHM; + case CryptoException.UNINITIALIZED_KEY: + return KMError.CRYPTO_UNINITIALIZED_KEY; + default: + return KMError.UNKNOWN_ERROR; + } + } + + public void updateApduStatusFlags(short apduIns) { + switch (apduIns) { + case INS_EXPORT_KEY_CMD: + case INS_DELETE_ALL_KEYS_CMD: + case INS_DESTROY_ATT_IDS_CMD: + case INS_VERIFY_AUTHORIZATION_CMD: + case INS_GET_HMAC_SHARING_PARAM_CMD: + case INS_GET_HW_INFO_CMD: + case INS_EARLY_BOOT_ENDED_CMD: + case INS_GET_ROT_CHALLENGE_CMD: + case INS_GET_ROT_DATA_CMD: + case INS_GET_RKP_HARDWARE_INFO: + case INS_FINISH_SEND_DATA_CMD: + case INS_GET_UDS_CERTS_CMD: + case INS_GET_DICE_CERT_CHAIN_CMD: + apduStatusFlags[APDU_CASE4_COMMAND_STATUS_INDEX] = 0; + break; + default: + // By default the instruction is set to case 4 command instruction. + break; + } + } + + /** + * Processes an incoming APDU and handles it using command objects. + * + * @param apdu the incoming APDU + */ + @Override + public void process(APDU apdu) { + try { + resetTransientBuffers(); + repository.onProcess(); + // If this is select applet apdu which is selecting this applet then return + if (apdu.isISOInterindustryCLA()) { + if (selectingApplet()) { + return; + } + } + byte[] apduBuffer = apdu.getBuffer(); + byte apduIns = apduBuffer[ISO7816.OFFSET_INS]; + if (!isKeyMintReady(apduIns)) { + ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED); + } + switch (apduIns) { + case INS_INIT_STRONGBOX_CMD: + processInitStrongBoxCmd(apdu); + sendResponse(apdu, KMError.OK); + return; + case INS_GENERATE_KEY_CMD: + processGenerateKey(apdu); + break; + case INS_IMPORT_KEY_CMD: + processImportKeyCmd(apdu); + break; + case INS_BEGIN_IMPORT_WRAPPED_KEY_CMD: + processBeginImportWrappedKeyCmd(apdu); + break; + case INS_FINISH_IMPORT_WRAPPED_KEY_CMD: + processFinishImportWrappedKeyCmd(apdu); + break; + case INS_EXPORT_KEY_CMD: + processExportKeyCmd(apdu); + break; + case INS_UPGRADE_KEY_CMD: + processUpgradeKeyCmd(apdu); + break; + case INS_DELETE_KEY_CMD: + processDeleteKeyCmd(apdu); + break; + case INS_DELETE_ALL_KEYS_CMD: + processDeleteAllKeysCmd(apdu); + break; + case INS_ADD_RNG_ENTROPY_CMD: + processAddRngEntropyCmd(apdu); + break; + case INS_COMPUTE_SHARED_HMAC_CMD: + processComputeSharedHmacCmd(apdu); + break; + case INS_DESTROY_ATT_IDS_CMD: + processDestroyAttIdsCmd(apdu); + break; + case INS_VERIFY_AUTHORIZATION_CMD: + processVerifyAuthorizationCmd(apdu); + break; + case INS_GET_HMAC_SHARING_PARAM_CMD: + processGetHmacSharingParamCmd(apdu); + break; + case INS_GET_KEY_CHARACTERISTICS_CMD: + processGetKeyCharacteristicsCmd(apdu); + break; + case INS_GET_HW_INFO_CMD: + processGetHwInfoCmd(apdu); + break; + case INS_BEGIN_OPERATION_CMD: + processBeginOperationCmd(apdu); + break; + case INS_UPDATE_OPERATION_CMD: + processUpdateOperationCmd(apdu); + break; + case INS_FINISH_OPERATION_CMD: + processFinishOperationCmd(apdu); + break; + case INS_ABORT_OPERATION_CMD: + processAbortOperationCmd(apdu); + break; + case INS_DEVICE_LOCKED_CMD: + processDeviceLockedCmd(apdu); + break; + case INS_EARLY_BOOT_ENDED_CMD: + processEarlyBootEndedCmd(apdu); + break; + case INS_UPDATE_AAD_OPERATION_CMD: + processUpdateAadOperationCmd(apdu); + break; + case INS_GENERATE_RKP_KEY_CMD: + case INS_BEGIN_SEND_DATA_CMD: + case INS_UPDATE_KEY_CMD: + case INS_FINISH_SEND_DATA_CMD: + case INS_GET_RKP_HARDWARE_INFO: + case INS_GET_UDS_CERTS_CMD: + case INS_GET_DICE_CERT_CHAIN_CMD: + rkp.process(apduIns, apdu); + break; + // KeyMint 2.0 + case INS_GET_ROT_CHALLENGE_CMD: + processGetRootOfTrustChallenge(apdu); + break; + case INS_GET_ROT_DATA_CMD: + sendResponse(apdu, KMError.UNIMPLEMENTED); + break; + case INS_SEND_ROT_DATA_CMD: + processSendRootOfTrust(apdu); + break; + default: + ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); + } + } catch (KMException exception) { + freeOperations(); + resetWrappingKey(); + sendResponse(apdu, KMException.reason()); + } catch (ISOException exp) { + freeOperations(); + resetWrappingKey(); + sendResponse(apdu, mapISOErrorToKMError(exp.getReason())); + } catch (CryptoException e) { + freeOperations(); + resetWrappingKey(); + sendResponse(apdu, mapCryptoErrorToKMError(e.getReason())); + } catch (Exception e) { + freeOperations(); + resetWrappingKey(); + sendResponse(apdu, KMError.GENERIC_UNKNOWN_ERROR); + } finally { + repository.clean(); + } + } + + private void processGetRootOfTrustChallenge(APDU apdu) { + byte[] scratchpad = apdu.getBuffer(); + // Generate 16-byte random challenge nonce, used to prove freshness when exchanging root of + // trust data. + seProvider.newRandomNumber(scratchpad, (short) 0, (short) 16); + kmDataStore.setChallenge(scratchpad, (short) 0, (short) 16); + short challenge = KMByteBlob.instance(scratchpad, (short) 0, (short) 16); + short arr = KMArray.instance((short) 2); + KMArray.cast(arr).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(arr).add((short) 1, challenge); + sendOutgoing(apdu, arr); + } + + private short sendRootOfTrustCmd(APDU apdu) { + short arrInst = KMArray.instance((short) 4); + short headers = KMCoseHeaders.exp(); + KMArray.cast(arrInst).add((short) 0, KMByteBlob.exp()); + KMArray.cast(arrInst).add((short) 1, headers); + KMArray.cast(arrInst).add((short) 2, KMByteBlob.exp()); + KMArray.cast(arrInst).add((short) 3, KMByteBlob.exp()); + short semanticTag = KMSemanticTag.exp(arrInst); + short arr = KMArray.exp(semanticTag); + return receiveIncoming(apdu, arr); + } + + private void processSendRootOfTrust(APDU apdu) { + byte[] scratchPad = apdu.getBuffer(); + short cmd = KMType.INVALID_VALUE; + // As per VTS if the input data is empty or not well-formed + // CoseMac return VERIFICATION_FAILED error. + try { + cmd = sendRootOfTrustCmd(apdu); + } catch (Exception e) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + + short semanticTag = KMArray.cast(cmd).get((short) 0); + short coseMacPtr = KMSemanticTag.cast(semanticTag).getValuePtr(); + // Exp for KMCoseHeaders + short coseHeadersExp = KMCoseHeaders.exp(); + // validate protected Headers + short ptr = KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_PROTECTED_PARAMS_OFFSET); + ptr = + decoder.decode( + coseHeadersExp, + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()); + + if (!KMCoseHeaders.cast(ptr) + .isDataValid(tmpVariables, KMCose.COSE_ALG_HMAC_256, KMType.INVALID_VALUE)) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + + // Validate the Mac + short len = kmDataStore.getChallenge(scratchPad, (short) 0); + short extAad = KMByteBlob.instance(scratchPad, (short) 0, len); + // Compute CoseMac Structure and compare the macs. + short rotPayload = KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_PAYLOAD_OFFSET); + short macStructure = + KMCose.constructCoseMacStructure( + KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_PROTECTED_PARAMS_OFFSET), + extAad, + rotPayload); + short encodedLen = + KMKeymasterApplet.encodeToApduBuffer( + macStructure, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + + if (!seProvider.hmacVerify( + kmDataStore.getComputedHmacKey(), + scratchPad, + (short) 0, + encodedLen, + KMByteBlob.cast(KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_TAG_OFFSET)).getBuffer(), + KMByteBlob.cast(KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_TAG_OFFSET)).getStartOff(), + KMByteBlob.cast(KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_TAG_OFFSET)).length())) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + // Store the data only once after reboot. + // Allow set boot params only when the host device reboots and the applet is in + // active state. If host does not support boot signal event, then allow this + // instruction any time. + kmDataStore.getDeviceBootStatus(scratchPad, (short) 0); + if (((scratchPad[0] & KMKeymintDataStore.SET_BOOT_PARAMS_SUCCESS) == 0)) { + // store the data. + storeRootOfTrust(rotPayload, scratchPad); + kmDataStore.setDeviceBootStatus(KMKeymintDataStore.SET_BOOT_PARAMS_SUCCESS); + } + // Invalidate the challenge + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 16, (byte) 0); + kmDataStore.setChallenge(scratchPad, (short) 0, (short) 16); + sendResponse(apdu, KMError.OK); + } + + private void storeRootOfTrust(short rotPayload, byte[] scratchPad) { + short byteBlobExp = KMByteBlob.exp(); + short intExp = KMInteger.exp(); + short boolExp = KMSimpleValue.exp(); + short arr = KMArray.instance((short) 5); + KMArray.cast(arr).add((short) 0, byteBlobExp); // Verfied boot key. + KMArray.cast(arr).add((short) 1, boolExp); // deviceLocked. + KMArray.cast(arr).add((short) 2, intExp); // Verified Boot State. + KMArray.cast(arr).add((short) 3, byteBlobExp); // Verfied boot hash. + KMArray.cast(arr).add((short) 4, intExp); // Boot patch level + short semanticExp = KMSemanticTag.exp(arr); + + short semanticPtr = + decoder.decode( + semanticExp, + KMByteBlob.cast(rotPayload).getBuffer(), + KMByteBlob.cast(rotPayload).getStartOff(), + KMByteBlob.cast(rotPayload).length()); + short rotArr = KMSemanticTag.cast(semanticPtr).getValuePtr(); + // Store verified boot key + short ptr = KMArray.cast(rotArr).get((short) 0); + kmDataStore.setBootKey( + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()); + // Store Boot device locked. + ptr = KMArray.cast(rotArr).get((short) 1); + kmDataStore.setDeviceLocked( + (KMSimpleValue.cast(ptr).getValue() == KMSimpleValue.TRUE) ? true : false); + // Store verified boot state + ptr = KMArray.cast(rotArr).get((short) 2); + kmDataStore.setBootState(KMInteger.cast(ptr).getShort()); + // Store Verified boot hash + ptr = KMArray.cast(rotArr).get((short) 3); + kmDataStore.setVerifiedBootHash( + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()); + // Store boot patch level + ptr = KMArray.cast(rotArr).get((short) 4); + kmDataStore.setBootPatchLevel( + KMInteger.cast(ptr).getBuffer(), + KMInteger.cast(ptr).getStartOff(), + KMInteger.cast(ptr).length()); + } + + // After every device boot, the Keymaster becomes ready to execute all the commands only after + // 1. boot parameters are set, + // 2. system properties are set and + // 3. computed the shared secret successfully. + private boolean isKeyMintReady(byte apduIns) { + if (kmDataStore.isDeviceReady()) { + return true; + } + // Below commands are allowed even if the Keymaster is not ready. + switch (apduIns) { + case INS_GET_HW_INFO_CMD: + case INS_GET_RKP_HARDWARE_INFO: + case INS_ADD_RNG_ENTROPY_CMD: + case INS_GET_HMAC_SHARING_PARAM_CMD: + case INS_COMPUTE_SHARED_HMAC_CMD: + case INS_EARLY_BOOT_ENDED_CMD: + case INS_INIT_STRONGBOX_CMD: + case INS_GET_ROT_CHALLENGE_CMD: + case INS_SEND_ROT_DATA_CMD: + return true; + default: + break; + } + return false; + } + + private void generateUniqueOperationHandle(byte[] buf, short offset, short len) { + do { + seProvider.newRandomNumber(buf, offset, len); + } while (null != findOperation(buf, offset, len)); + } + + private void freeOperations() { + if (data[OP_HANDLE] != KMType.INVALID_VALUE) { + KMOperationState op = findOperation(data[OP_HANDLE]); + if (op != null) { + releaseOperation(op); + } + } + } + + private void processEarlyBootEndedCmd(APDU apdu) { + kmDataStore.setEarlyBootEndedStatus(true); + sendResponse(apdu, KMError.OK); + } + + private short deviceLockedCmd(APDU apdu) { + short cmd = KMArray.instance((short) 2); + short ptr = KMVerificationToken.exp(); + // passwordOnly + KMArray.cast(cmd).add((short) 0, KMInteger.exp()); + // verification token + KMArray.cast(cmd).add((short) 1, ptr); + return receiveIncoming(apdu, cmd); + } + + private void processDeviceLockedCmd(APDU apdu) { + short cmd = deviceLockedCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + short passwordOnly = KMArray.cast(cmd).get((short) 0); + short verToken = KMArray.cast(cmd).get((short) 1); + passwordOnly = KMInteger.cast(passwordOnly).getByte(); + validateVerificationToken(verToken, scratchPad); + short verTime = KMVerificationToken.cast(verToken).getTimestamp(); + short lastDeviceLockedTime; + try { + lastDeviceLockedTime = kmDataStore.getDeviceTimeStamp(); + } catch (KMException e) { + lastDeviceLockedTime = KMInteger.uint_8((byte) 0); + } + if (KMInteger.compare(verTime, lastDeviceLockedTime) > 0) { + Util.arrayFillNonAtomic(scratchPad, (short) 0, KMInteger.UINT_64, (byte) 0); + KMInteger.cast(verTime).getValue(scratchPad, (short) 0, KMInteger.UINT_64); + kmDataStore.setDeviceLock(true); + kmDataStore.setDeviceLockPasswordOnly(passwordOnly == 0x01); + kmDataStore.setDeviceLockTimestamp(scratchPad, (short) 0, KMInteger.UINT_64); + } + sendResponse(apdu, KMError.OK); + } + + private void resetWrappingKey() { + if (!isValidWrappingKey()) { + return; + } + Util.arrayFillNonAtomic(wrappingKey, (short) 1, WRAPPING_KEY_SIZE, (byte) 0); + wrappingKey[0] = -1; + } + + private boolean isValidWrappingKey() { + return wrappingKey[0] != -1; + } + + private short getWrappingKey() { + return KMByteBlob.instance(wrappingKey, (short) 1, WRAPPING_KEY_SIZE); + } + + private void setWrappingKey(short key) { + if (KMByteBlob.cast(key).length() != WRAPPING_KEY_SIZE) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + wrappingKey[0] = 0; + Util.arrayCopyNonAtomic( + KMByteBlob.cast(key).getBuffer(), + KMByteBlob.cast(key).getStartOff(), + wrappingKey, + (short) 1, + WRAPPING_KEY_SIZE); + } + + protected void resetTransientBuffers() { + short index = 0; + while (index < data.length) { + data[index] = KMType.INVALID_VALUE; + index++; + } + index = 0; + while (index < tmpVariables.length) { + tmpVariables[index] = KMType.INVALID_VALUE; + index++; + } + } + + public void sendOutgoing( + APDU apdu, KMAttestationCert cert, short certStart, short keyblob, short keyChars) { + // This is the special case where the output is encoded manually without using + // the encoder algorithm. Encoder creates a duplicate copy for each KMType Object. + // The output of the generateKey, importKey and importWrappedKey commands are huge so + // by manually encoding we can avoid duplicate copies. + // The output data is directly written to the end of heap in the below order + // output = [ + // errorCode : uint // ErrorCode + // keyBlob : bstr // KeyBlob. + // keyChars + // certifcate + // ] + // certificate = [ + // x509_cert : bstr // X509 certificate + // ] + // keyChars = { // Map + // } + byte[] buffer = repository.getHeap(); + + if (cert == null) { + // This happens for Symmetric keys. + short bufferStart = repository.allocReclaimableMemory((short) 1); + buffer[bufferStart] = (byte) 0x80; // Array of 0 length. + } else { + // Encode the certificate into cbor data at the end of the heap + // certData = [ + // x509_cert : bstr // X509 certificate + // ] + short bufferStart = + encoder.encodeCert( + repository.getHeap(), certStart, cert.getCertStart(), cert.getCertLength()); + // reclaim the unused memory in the certificate. + repository.reclaimMemory((short) (bufferStart - certStart)); + } + + // Encode KeyCharacteristics at the end of heap just before data[CERTIFICATE] + encodeKeyCharacteristics(keyChars); + // and encode it to the end of the buffer before KEY_CHARACTERISTICS + encodeKeyBlob(keyblob); + // Write Array header and ErrorCode before data[KEY_BLOB] + short bufferStartOffset = repository.allocReclaimableMemory((short) 2); + Util.setShort(buffer, bufferStartOffset, (short) 0x8400); + + short bufferLength = (short) (KMRepository.HEAP_SIZE - bufferStartOffset); + /* In T=0 protocol, On a case 4 command, setIncomingAndReceive() must + * be invoked prior to calling setOutgoing(). Otherwise, erroneous + * behavior may result + * */ + if (apduStatusFlags[APDU_CASE4_COMMAND_STATUS_INDEX] == 1 + && apduStatusFlags[APDU_INCOMING_AND_RECEIVE_STATUS_INDEX] == 0 + && APDU.getProtocol() == APDU.PROTOCOL_T0) { + apdu.setIncomingAndReceive(); + } + // Send data + apdu.setOutgoing(); + apdu.setOutgoingLength(bufferLength); + apdu.sendBytesLong(buffer, bufferStartOffset, bufferLength); + } + + private void processGetHwInfoCmd(APDU apdu) { + // No arguments expected + final byte version = 3; + // Make the response + short respPtr = KMArray.instance((short) 6); + KMArray resp = KMArray.cast(respPtr); + resp.add((short) 0, KMInteger.uint_16(KMError.OK)); + resp.add((short) 1, KMInteger.uint_8(version)); + resp.add((short) 2, KMEnum.instance(KMType.HARDWARE_TYPE, KMType.STRONGBOX)); + resp.add( + (short) 3, + KMByteBlob.instance( + JavacardKeymintDevice, (short) 0, (short) JavacardKeymintDevice.length)); + resp.add((short) 4, KMByteBlob.instance(Google, (short) 0, (short) Google.length)); + resp.add((short) 5, KMInteger.uint_8((byte) 1)); + // send buffer to host + sendOutgoing(apdu, respPtr); + } + + private short addRngEntropyCmd(APDU apdu) { + short cmd = KMArray.instance((short) 1); + // Rng entropy + KMArray.cast(cmd).add((short) 0, KMByteBlob.exp()); + return receiveIncoming(apdu, cmd); + } + + private void processAddRngEntropyCmd(APDU apdu) { + // Receive the incoming request fully from the host. + short cmd = addRngEntropyCmd(apdu); + // Process + KMByteBlob blob = KMByteBlob.cast(KMArray.cast(cmd).get((short) 0)); + // Maximum 2KiB of seed is allowed. + if (blob.length() > MAX_SEED_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + seProvider.addRngEntropy(blob.getBuffer(), blob.getStartOff(), blob.length()); + sendResponse(apdu, KMError.OK); + } + + private short getKeyCharacteristicsCmd(APDU apdu) { + short cmd = KMArray.instance((short) 3); + KMArray.cast(cmd).add((short) 0, KMByteBlob.exp()); + KMArray.cast(cmd).add((short) 1, KMByteBlob.exp()); + KMArray.cast(cmd).add((short) 2, KMByteBlob.exp()); + return receiveIncoming(apdu, cmd); + } + + private void processGetKeyCharacteristicsCmd(APDU apdu) { + // Receive the incoming request fully from the host. + short cmd = getKeyCharacteristicsCmd(apdu); + // Re-purpose the apdu buffer as scratch pad. + byte[] scratchPad = apdu.getBuffer(); + data[KEY_BLOB] = KMArray.cast(cmd).get((short) 0); + data[APP_ID] = KMArray.cast(cmd).get((short) 1); + data[APP_DATA] = KMArray.cast(cmd).get((short) 2); + if (KMByteBlob.cast(data[APP_ID]).length() > KMByteTag.MAX_APP_ID_APP_DATA_SIZE + || KMByteBlob.cast(data[APP_DATA]).length() > KMByteTag.MAX_APP_ID_APP_DATA_SIZE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + if (!KMByteBlob.cast(data[APP_ID]).isValid()) { + data[APP_ID] = KMType.INVALID_VALUE; + } + if (!KMByteBlob.cast(data[APP_DATA]).isValid()) { + data[APP_DATA] = KMType.INVALID_VALUE; + } + // Check if key requires upgrade. The KeyBlob is parsed inside isKeyUpgradeRequired + // function itself. + if (isKeyUpgradeRequired(data[KEY_BLOB], data[APP_ID], data[APP_DATA], scratchPad)) { + KMException.throwIt(KMError.KEY_REQUIRES_UPGRADE); + } + // make response. + short resp = KMArray.instance((short) 2); + KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(resp).add((short) 1, data[KEY_CHARACTERISTICS]); + sendOutgoing(apdu, resp); + } + + private void processGetHmacSharingParamCmd(APDU apdu) { + // No Arguments + // Create HMAC Sharing Parameters + short params = KMHmacSharingParameters.instance(); + short nonce = kmDataStore.getHmacNonce(); + short seed = KMByteBlob.instance((short) 0); + KMHmacSharingParameters.cast(params).setNonce(nonce); + KMHmacSharingParameters.cast(params).setSeed(seed); + // prepare the response + short resp = KMArray.instance((short) 2); + KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(resp).add((short) 1, params); + sendOutgoing(apdu, resp); + } + + private void processDeleteAllKeysCmd(APDU apdu) { + // No arguments + // This function is triggered when a factory reset event occurs. + // Regenerate the master key to render all keys unusable. + kmDataStore.regenerateMasterKey(); + // Send ok + sendResponse(apdu, KMError.OK); + } + + private short createKeyBlobExp(short version) { + short keyBlob = KMType.INVALID_VALUE; + short byteBlobExp = KMByteBlob.exp(); + short keyChar = KMKeyCharacteristics.exp(); + short keyParam = KMKeyParameters.exp(); + switch (version) { + case (short) 0: + // Old KeyBlob has a maximum of 5 elements. + keyBlob = KMArray.instance(ASYM_KEY_BLOB_SIZE_V0); + KMArray.cast(keyBlob).add((short) 0, byteBlobExp); // Secret + KMArray.cast(keyBlob).add((short) 1, byteBlobExp); // Nonce + KMArray.cast(keyBlob).add((short) 2, byteBlobExp); // AuthTag + KMArray.cast(keyBlob).add((short) 3, keyChar); // KeyChars + KMArray.cast(keyBlob).add((short) 4, byteBlobExp); // PubKey + break; + case (short) 1: + keyBlob = KMArray.instance(ASYM_KEY_BLOB_SIZE_V1); + KMArray.cast(keyBlob).add((short) 0, KMInteger.exp()); // Version + KMArray.cast(keyBlob).add((short) 1, byteBlobExp); // Secret + KMArray.cast(keyBlob).add((short) 2, byteBlobExp); // Nonce + KMArray.cast(keyBlob).add((short) 3, byteBlobExp); // AuthTag + KMArray.cast(keyBlob).add((short) 4, keyChar); // KeyChars + KMArray.cast(keyBlob).add((short) 5, byteBlobExp); // PubKey + break; + case (short) 2: + case (short) 3: + keyBlob = KMArray.instance(ASYM_KEY_BLOB_SIZE_V2_V3); + KMArray.cast(keyBlob).add(KMKeymasterApplet.KEY_BLOB_VERSION_OFFSET, KMInteger.exp()); + KMArray.cast(keyBlob).add(KMKeymasterApplet.KEY_BLOB_SECRET, byteBlobExp); + KMArray.cast(keyBlob).add(KMKeymasterApplet.KEY_BLOB_AUTH_TAG, byteBlobExp); + KMArray.cast(keyBlob).add(KMKeymasterApplet.KEY_BLOB_NONCE, byteBlobExp); + KMArray.cast(keyBlob).add(KMKeymasterApplet.KEY_BLOB_PARAMS, keyChar); + KMArray.cast(keyBlob).add(KMKeymasterApplet.KEY_BLOB_CUSTOM_TAGS, keyParam); + KMArray.cast(keyBlob).add(KMKeymasterApplet.KEY_BLOB_PUB_KEY, byteBlobExp); + break; + default: + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + return keyBlob; + } + + private void processDeleteKeyCmd(APDU apdu) { + // Send ok + sendResponse(apdu, KMError.OK); + } + + private short computeSharedHmacCmd(APDU apdu) { + short params = KMHmacSharingParameters.exp(); + short paramsVec = KMArray.exp(params); + short cmd = KMArray.instance((short) 1); + KMArray.cast(cmd).add((short) 0, paramsVec); + return receiveIncoming(apdu, cmd); + } + + private void processComputeSharedHmacCmd(APDU apdu) { + // Receive the incoming request fully from the host into buffer. + short cmd = computeSharedHmacCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + data[HMAC_SHARING_PARAMS] = KMArray.cast(cmd).get((short) 0); + // Concatenate HMAC Params + // tmpVariables[0] + short paramsLen = KMArray.cast(data[HMAC_SHARING_PARAMS]).length(); // total number of params + // tmpVariables[1] + short concateBuffer = repository.alloc((short) (paramsLen * HMAC_SHARED_PARAM_MAX_SIZE)); + // tmpVariables[2] + short paramIndex = 0; // index for params + // tmpVariables[3] + short bufferIndex = 0; // index for concatenation buffer + // To check if nonce created by Strongbox is found. This value becomes 1 if both + // seed and nonce created here are found in hmac sharing parameters received. + // tmpVariables[7] = 0; + short found = 0; + // tmpVariables[9] + short nonce = kmDataStore.getHmacNonce(); + + while (paramIndex < paramsLen) { + // read HmacSharingParam + // tmpVariables[4] + short param = KMArray.cast(data[HMAC_SHARING_PARAMS]).get(paramIndex); + // get seed - 32 bytes max + // tmpVariables[5] + short seed = KMHmacSharingParameters.cast(param).getSeed(); + // tmpVariables[6] + short seedLength = KMByteBlob.cast(seed).length(); + // if seed is present + if (seedLength != 0) { + // then copy that to concatenation buffer + Util.arrayCopyNonAtomic( + KMByteBlob.cast(seed).getBuffer(), + KMByteBlob.cast(seed).getStartOff(), + repository.getHeap(), + (short) (concateBuffer + bufferIndex), // concat index + seedLength); + bufferIndex += seedLength; // increment the concat index + } else if (found == 0) { + found = 1; // Applet does not have any seed. Potentially + } + // if nonce is present get nonce - 32 bytes + // tmpVariables[5] + short paramNonce = KMHmacSharingParameters.cast(param).getNonce(); + short nonceLen = KMByteBlob.cast(paramNonce).length(); + // if nonce is less then 32 - it is an error + if (nonceLen < 32) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // copy nonce to concatenation buffer + Util.arrayCopyNonAtomic( + KMByteBlob.cast(paramNonce).getBuffer(), + KMByteBlob.cast(paramNonce).getStartOff(), + repository.getHeap(), + (short) (concateBuffer + bufferIndex), // index + nonceLen); + + // Check if the nonce generated here is present in the hmacSharingParameters array. + // Otherwise throw INVALID_ARGUMENT error. + if (found == 1) { + if (0 + == Util.arrayCompare( + repository.getHeap(), + (short) (concateBuffer + bufferIndex), + KMByteBlob.cast(nonce).getBuffer(), + KMByteBlob.cast(nonce).getStartOff(), + nonceLen)) { + found = 2; // hmac nonce for this keymaster found. + } else { + found = 0; + } + } + bufferIndex += nonceLen; // increment by nonce length + paramIndex++; // go to next hmac param in the vector + } + if (found != 2) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // generate the key and store it in scratch pad - 32 bytes + // tmpVariables[6] + short keyLen = + seProvider.cmacKDF( + kmDataStore.getPresharedKey(), + ckdfLabel, + (short) 0, + (short) ckdfLabel.length, + repository.getHeap(), + concateBuffer, + bufferIndex, + scratchPad, + (short) 0); + + // persist the computed hmac key. + kmDataStore.createComputedHmacKey(scratchPad, (short) 0, keyLen); + // Generate sharingKey verification signature and store that in scratch pad. + // tmpVariables[5] + short signLen = + seProvider.hmacSign( + scratchPad, + (short) 0, + keyLen, + sharingCheck, + (short) 0, + (short) sharingCheck.length, + scratchPad, + keyLen); + kmDataStore.setDeviceBootStatus(KMKeymintDataStore.NEGOTIATED_SHARED_SECRET_SUCCESS); + // verification signature blob - 32 bytes + // tmpVariables[1] + short signature = KMByteBlob.instance(scratchPad, keyLen, signLen); + // prepare the response + short resp = KMArray.instance((short) 2); + KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(resp).add((short) 1, signature); + sendOutgoing(apdu, resp); + } + + private short upgradeKeyCmd(APDU apdu) { + short cmd = KMArray.instance((short) 2); + short keyParams = KMKeyParameters.exp(); + KMArray.cast(cmd).add((short) 0, KMByteBlob.exp()); // Key Blob + KMArray.cast(cmd).add((short) 1, keyParams); // Key Params + return receiveIncoming(apdu, cmd); + } + + private boolean isKeyUpgradeRequired( + short keyBlob, short appId, short appData, byte[] scratchPad) { + // Check if the KeyBlob is compatible. If there is any change in the KeyBlob, the version + // Parameter in the KeyBlob should be updated to the next version. + short version = readKeyBlobVersion(keyBlob); + parseEncryptedKeyBlob(keyBlob, appId, appData, scratchPad, version); + if (version < KEYBLOB_CURRENT_VERSION) { + return true; + } + short bootPatchLevel = kmDataStore.getBootPatchLevel(); + // Fill the key-value properties in the scratchpad + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 16, (byte) 0); + Util.setShort(scratchPad, (short) 0, KMType.OS_VERSION); + Util.setShort(scratchPad, (short) 2, kmDataStore.getOsVersion()); + Util.setShort(scratchPad, (short) 4, KMType.OS_PATCH_LEVEL); + Util.setShort(scratchPad, (short) 6, kmDataStore.getOsPatch()); + Util.setShort(scratchPad, (short) 8, KMType.VENDOR_PATCH_LEVEL); + Util.setShort(scratchPad, (short) 10, kmDataStore.getVendorPatchLevel()); + Util.setShort(scratchPad, (short) 12, KMType.BOOT_PATCH_LEVEL); + Util.setShort(scratchPad, (short) 14, bootPatchLevel); + short index = 0; + short tag; + short systemParam; + boolean isKeyUpgradeRequired = false; + while (index < 16) { + tag = Util.getShort(scratchPad, index); + systemParam = Util.getShort(scratchPad, (short) (index + 2)); + // validate the tag and check if key needs upgrade. + short tagValue = KMKeyParameters.findTag(KMType.UINT_TAG, tag, data[HW_PARAMETERS]); + tagValue = KMIntegerTag.cast(tagValue).getValue(); + short zero = KMInteger.uint_8((byte) 0); + if (tagValue != KMType.INVALID_VALUE) { + // OS version in key characteristics must be less the OS version stored in Javacard or the + // stored version must be zero. Then only upgrade is allowed else it is invalid argument. + if ((tag == KMType.OS_VERSION + && KMInteger.compare(tagValue, systemParam) == 1 + && KMInteger.compare(systemParam, zero) == 0)) { + // Key needs upgrade. + isKeyUpgradeRequired = true; + } else if ((KMInteger.compare(tagValue, systemParam) == -1)) { + // Each os version or patch level associated with the key must be less than it's + // corresponding value stored in Javacard, then only upgrade is allowed otherwise it + // is invalid argument. + isKeyUpgradeRequired = true; + } else if (KMInteger.compare(tagValue, systemParam) == 1) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + } else { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + index += 4; + } + return isKeyUpgradeRequired; + } + + private void processUpgradeKeyCmd(APDU apdu) { + // Receive the incoming request fully from the host into buffer. + short cmd = upgradeKeyCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + + short keyBlob = KMArray.cast(cmd).get((short) 0); + data[KEY_PARAMETERS] = KMArray.cast(cmd).get((short) 1); + short appId = getApplicationId(data[KEY_PARAMETERS]); + short appData = getApplicationData(data[KEY_PARAMETERS]); + + data[KEY_BLOB] = KMType.INVALID_VALUE; + // Check if the KeyBlob requires upgrade. The KeyBlob is parsed inside isKeyUpgradeRequired + // function itself, but if there is a difference in the KeyBlob version isKeyUpgradeRequired() + // does not parse the KeyBlob. + boolean isKeyUpgradeRequired = isKeyUpgradeRequired(keyBlob, appId, appData, scratchPad); + if (isKeyUpgradeRequired) { + // copy origin + data[ORIGIN] = KMEnumTag.getValue(KMType.ORIGIN, data[HW_PARAMETERS]); + byte keyType = getKeyType(data[HW_PARAMETERS]); + switch (keyType) { + case ASYM_KEY_TYPE: + data[KEY_BLOB] = KMArray.instance(ASYM_KEY_BLOB_SIZE_V2_V3); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PUB_KEY, data[PUB_KEY]); + break; + case SYM_KEY_TYPE: + data[KEY_BLOB] = KMArray.instance(SYM_KEY_BLOB_SIZE_V2_V3); + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + // Update the system properties to the latest values and also re-create the KeyBlob's + // KeyCharacteristics to make sure all the values are up-to-date with the latest applet + // changes. + upgradeKeyBlobKeyCharacteristics(data[HW_PARAMETERS], scratchPad); + // create new key blob with current os version etc. + createEncryptedKeyBlob(scratchPad); + short prevReclaimIndex = repository.getHeapReclaimIndex(); + short offset = repository.allocReclaimableMemory(MAX_KEYBLOB_SIZE); + data[KEY_BLOB] = + encoder.encode( + data[KEY_BLOB], repository.getHeap(), offset, prevReclaimIndex, MAX_KEYBLOB_SIZE); + data[KEY_BLOB] = KMByteBlob.instance(repository.getHeap(), offset, data[KEY_BLOB]); + repository.reclaimMemory(MAX_KEYBLOB_SIZE); + } else { + data[KEY_BLOB] = KMByteBlob.instance((short) 0); + } + // prepare the response + short resp = KMArray.instance((short) 2); + KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(resp).add((short) 1, data[KEY_BLOB]); + sendOutgoing(apdu, resp); + } + + private void processExportKeyCmd(APDU apdu) { + sendResponse(apdu, KMError.UNIMPLEMENTED); + } + + private void processWrappingKeyBlob(short keyBlob, short wrapParams, byte[] scratchPad) { + // Read App Id and App Data if any from un wrapping key params + data[APP_ID] = getApplicationId(wrapParams); + data[APP_DATA] = getApplicationData(wrapParams); + data[KEY_PARAMETERS] = wrapParams; + data[KEY_BLOB] = keyBlob; + // Check if key requires upgrade. The KeyBlob is parsed inside isKeyUpgradeRequired + // function itself. + if (isKeyUpgradeRequired(data[KEY_BLOB], data[APP_ID], data[APP_DATA], scratchPad)) { + KMException.throwIt(KMError.KEY_REQUIRES_UPGRADE); + } + validateWrappingKeyBlob(); + } + + private void validateWrappingKeyBlob() { + // check whether the wrapping key is RSA with purpose KEY_WRAP, padding RSA_OAEP and Digest + // SHA2_256. + KMTag.assertPresence( + data[SB_PARAMETERS], + KMType.ENUM_TAG, + KMType.ALGORITHM, + KMError.UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM); + if (KMEnumTag.getValue(KMType.ALGORITHM, data[HW_PARAMETERS]) != KMType.RSA) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_ENCRYPTION_ALGORITHM); + } + if (!KMEnumArrayTag.contains(KMType.DIGEST, KMType.SHA2_256, data[HW_PARAMETERS])) { + KMException.throwIt(KMError.INCOMPATIBLE_DIGEST); + } + if (!KMEnumArrayTag.contains(KMType.PADDING, KMType.RSA_OAEP, data[HW_PARAMETERS])) { + KMException.throwIt(KMError.INCOMPATIBLE_PADDING_MODE); + } + if (!KMEnumArrayTag.contains(KMType.PURPOSE, KMType.WRAP_KEY, data[HW_PARAMETERS])) { + KMException.throwIt((KMError.INCOMPATIBLE_PURPOSE)); + } + + // Check that the digest and padding mode specified in unwrapping parameters are SHA2_256 + // and RSA_OAEP respectively. + if (!KMEnumArrayTag.contains(KMType.DIGEST, KMType.SHA2_256, data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.INCOMPATIBLE_DIGEST); + } + if (!KMEnumArrayTag.contains(KMType.PADDING, KMType.RSA_OAEP, data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.INCOMPATIBLE_PADDING_MODE); + } + } + + private short decryptTransportKey( + short privExp, short modulus, short transportKey, byte[] scratchPad) { + short length = + seProvider.rsaDecipherOAEP256( + KMByteBlob.cast(privExp).getBuffer(), + KMByteBlob.cast(privExp).getStartOff(), + KMByteBlob.cast(privExp).length(), + KMByteBlob.cast(modulus).getBuffer(), + KMByteBlob.cast(modulus).getStartOff(), + KMByteBlob.cast(modulus).length(), + KMByteBlob.cast(transportKey).getBuffer(), + KMByteBlob.cast(transportKey).getStartOff(), + KMByteBlob.cast(transportKey).length(), + scratchPad, + (short) 0); + return KMByteBlob.instance(scratchPad, (short) 0, length); + } + + private void unmask(short data, short maskingKey) { + short dataLength = KMByteBlob.cast(data).length(); + short maskLength = KMByteBlob.cast(maskingKey).length(); + // Length of masking key and transport key must be same. + if (maskLength != dataLength) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + short index = 0; // index + // Xor every byte of masking and key and store the result in data[SECRET] + while (index < maskLength) { + short var1 = (short) (((short) KMByteBlob.cast(maskingKey).get(index)) & 0x00FF); + short var2 = (short) (((short) KMByteBlob.cast(data).get(index)) & 0x00FF); + KMByteBlob.cast(data).add(index, (byte) (var1 ^ var2)); + index++; + } + } + + private short beginImportWrappedKeyCmd(APDU apdu) { + short cmd = KMArray.instance((short) 4); + short params = KMKeyParameters.expAny(); + KMArray.cast(cmd).add((short) 0, KMByteBlob.exp()); // Encrypted Transport Key + KMArray.cast(cmd).add((short) 1, KMByteBlob.exp()); // Wrapping Key KeyBlob + KMArray.cast(cmd).add((short) 2, KMByteBlob.exp()); // Masking Key + params = KMKeyParameters.exp(); + KMArray.cast(cmd).add((short) 3, params); // Wrapping key blob Params + return receiveIncoming(apdu, cmd); + } + + private void processBeginImportWrappedKeyCmd(APDU apdu) { + // Receive the incoming request fully from the host into buffer. + short cmd = beginImportWrappedKeyCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + // Step -1 parse the wrapping key blob + // read wrapping key blob + short keyBlob = KMArray.cast(cmd).get((short) 1); + // read un wrapping key params + short wrappingKeyParameters = KMArray.cast(cmd).get((short) 3); + processWrappingKeyBlob(keyBlob, wrappingKeyParameters, scratchPad); + // Step 2 - decrypt the encrypted transport key - 32 bytes AES-GCM key + short transportKey = + decryptTransportKey( + data[SECRET], data[PUB_KEY], KMArray.cast(cmd).get((short) 0), scratchPad); + // Step 3 - XOR the decrypted AES-GCM key with with masking key + unmask(transportKey, KMArray.cast(cmd).get((short) 2)); + if (isValidWrappingKey()) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + setWrappingKey(transportKey); + sendResponse(apdu, KMError.OK); + } + + private short aesGCMEncrypt( + short aesSecret, short input, short nonce, short authData, short authTag, byte[] scratchPad) { + Util.arrayFillNonAtomic(scratchPad, (short) 0, KMByteBlob.cast(input).length(), (byte) 0); + short len = + seProvider.aesGCMEncrypt( + KMByteBlob.cast(aesSecret).getBuffer(), + KMByteBlob.cast(aesSecret).getStartOff(), + KMByteBlob.cast(aesSecret).length(), + KMByteBlob.cast(input).getBuffer(), + KMByteBlob.cast(input).getStartOff(), + KMByteBlob.cast(input).length(), + scratchPad, + (short) 0, + KMByteBlob.cast(nonce).getBuffer(), + KMByteBlob.cast(nonce).getStartOff(), + KMByteBlob.cast(nonce).length(), + KMByteBlob.cast(authData).getBuffer(), + KMByteBlob.cast(authData).getStartOff(), + KMByteBlob.cast(authData).length(), + KMByteBlob.cast(authTag).getBuffer(), + KMByteBlob.cast(authTag).getStartOff(), + KMByteBlob.cast(authTag).length()); + return KMByteBlob.instance(scratchPad, (short) 0, len); + } + + private short aesGCMDecrypt( + short aesSecret, short input, short nonce, short authData, short authTag, byte[] scratchPad) { + Util.arrayFillNonAtomic(scratchPad, (short) 0, KMByteBlob.cast(input).length(), (byte) 0); + if (!seProvider.aesGCMDecrypt( + KMByteBlob.cast(aesSecret).getBuffer(), + KMByteBlob.cast(aesSecret).getStartOff(), + KMByteBlob.cast(aesSecret).length(), + KMByteBlob.cast(input).getBuffer(), + KMByteBlob.cast(input).getStartOff(), + KMByteBlob.cast(input).length(), + scratchPad, + (short) 0, + KMByteBlob.cast(nonce).getBuffer(), + KMByteBlob.cast(nonce).getStartOff(), + KMByteBlob.cast(nonce).length(), + KMByteBlob.cast(authData).getBuffer(), + KMByteBlob.cast(authData).getStartOff(), + KMByteBlob.cast(authData).length(), + KMByteBlob.cast(authTag).getBuffer(), + KMByteBlob.cast(authTag).getStartOff(), + KMByteBlob.cast(authTag).length())) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + return KMByteBlob.instance(scratchPad, (short) 0, KMByteBlob.cast(input).length()); + } + + private short finishImportWrappedKeyCmd(APDU apdu) { + short cmd = KMArray.instance((short) 8); + short params = KMKeyParameters.expAny(); + KMArray.cast(cmd).add((short) 0, params); // Key Params of wrapped key + KMArray.cast(cmd).add((short) 1, KMEnum.instance(KMType.KEY_FORMAT)); // Key Format + KMArray.cast(cmd).add((short) 2, KMByteBlob.exp()); // Wrapped Import Key Blob + KMArray.cast(cmd).add((short) 3, KMByteBlob.exp()); // Auth Tag + KMArray.cast(cmd).add((short) 4, KMByteBlob.exp()); // IV - Nonce + KMArray.cast(cmd).add((short) 5, KMByteBlob.exp()); // Wrapped Key ASSOCIATED AUTH DATA + KMArray.cast(cmd).add((short) 6, KMInteger.exp()); // Password Sid + KMArray.cast(cmd).add((short) 7, KMInteger.exp()); // Biometric Sid + return receiveIncoming(apdu, cmd); + } + + // TODO remove cmd later on + private void processFinishImportWrappedKeyCmd(APDU apdu) { + short cmd = finishImportWrappedKeyCmd(apdu); + short keyParameters = KMArray.cast(cmd).get((short) 0); + short keyFmt = KMArray.cast(cmd).get((short) 1); + keyFmt = KMEnum.cast(keyFmt).getVal(); + validateImportKey(keyParameters, keyFmt); + byte[] scratchPad = apdu.getBuffer(); + // Step 4 - AES-GCM decrypt the wrapped key + data[INPUT_DATA] = KMArray.cast(cmd).get((short) 2); + data[AUTH_TAG] = KMArray.cast(cmd).get((short) 3); + data[NONCE] = KMArray.cast(cmd).get((short) 4); + data[AUTH_DATA] = KMArray.cast(cmd).get((short) 5); + + if (!isValidWrappingKey()) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + data[IMPORTED_KEY_BLOB] = + aesGCMDecrypt( + getWrappingKey(), + data[INPUT_DATA], + data[NONCE], + data[AUTH_DATA], + data[AUTH_TAG], + scratchPad); + resetWrappingKey(); + // Step 5 - Import decrypted key + data[ORIGIN] = KMType.SECURELY_IMPORTED; + data[KEY_PARAMETERS] = keyParameters; + // create key blob array + importKey(apdu, keyFmt, scratchPad); + } + + private KMAttestationCert makeCommonCert(byte[] scratchPad) { + short alg = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, data[KEY_PARAMETERS]); + boolean rsaCert = KMEnumTag.cast(alg).getValue() == KMType.RSA; + KMAttestationCert cert = KMAttestationCertImpl.instance(rsaCert, seProvider); + + short subject = + KMKeyParameters.findTag( + KMType.BYTES_TAG, KMType.CERTIFICATE_SUBJECT_NAME, data[KEY_PARAMETERS]); + + // If no subject name is specified then use the default subject name. + if (subject == KMType.INVALID_VALUE || KMByteTag.cast(subject).length() == 0) { + subject = KMByteBlob.instance(defaultSubject, (short) 0, (short) defaultSubject.length); + } else { + subject = KMByteTag.cast(subject).getValue(); + } + cert.subjectName(subject); + // Validity period must be specified + short notBefore = + KMKeyParameters.findTag( + KMType.DATE_TAG, KMType.CERTIFICATE_NOT_BEFORE, data[KEY_PARAMETERS]); + if (notBefore == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.MISSING_NOT_BEFORE); + } + notBefore = KMIntegerTag.cast(notBefore).getValue(); + short notAfter = + KMKeyParameters.findTag( + KMType.DATE_TAG, KMType.CERTIFICATE_NOT_AFTER, data[KEY_PARAMETERS]); + if (notAfter == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.MISSING_NOT_AFTER); + } + notAfter = KMIntegerTag.cast(notAfter).getValue(); + // VTS sends notBefore == Epoch. + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 8, (byte) 0); + short epoch = KMInteger.instance(scratchPad, (short) 0, (short) 8); + short end = KMInteger.instance(dec319999Ms, (short) 0, (short) dec319999Ms.length); + if (KMInteger.compare(notBefore, epoch) == 0) { + cert.notBefore( + KMByteBlob.instance(jan01970, (short) 0, (short) jan01970.length), true, scratchPad); + } else { + cert.notBefore(notBefore, false, scratchPad); + } + // VTS sends notAfter == Dec 31st 9999 + if (KMInteger.compare(notAfter, end) == 0) { + cert.notAfter( + KMByteBlob.instance(dec319999, (short) 0, (short) dec319999.length), true, scratchPad); + } else { + cert.notAfter(notAfter, false, scratchPad); + } + // Serial number + short serialNum = + KMKeyParameters.findTag( + KMType.BIGNUM_TAG, KMType.CERTIFICATE_SERIAL_NUM, data[KEY_PARAMETERS]); + if (serialNum != KMType.INVALID_VALUE) { + serialNum = KMBignumTag.cast(serialNum).getValue(); + } else { + serialNum = KMByteBlob.instance((short) 1); + KMByteBlob.cast(serialNum).add((short) 0, (byte) 1); + } + cert.serialNumber(serialNum); + return cert; + } + + private KMAttestationCert makeAttestationCert( + short attKeyBlob, short attKeyParam, short attChallenge, short issuer, byte[] scratchPad) { + KMAttestationCert cert = makeCommonCert(scratchPad); + + // Read App Id and App Data. + short appId = getApplicationId(attKeyParam); + short appData = getApplicationData(attKeyParam); + // Take backup of the required global variables KEY_BLOB, PUB_KEY, SECRET, KEY_CHAR + // and HW_PARAMS before they get overridden by isKeyUpgradeRequired() function. + short origBlob = data[KEY_BLOB]; + short pubKey = data[PUB_KEY]; + short privKey = data[SECRET]; + short hwParams = data[HW_PARAMETERS]; + short keyChars = data[KEY_CHARACTERISTICS]; + short customTags = data[CUSTOM_TAGS]; + // Check if key requires upgrade for attestKeyBlob. The KeyBlob is parsed inside + // isKeyUpgradeRequired function itself. + if (isKeyUpgradeRequired(attKeyBlob, appId, appData, scratchPad)) { + KMException.throwIt(KMError.KEY_REQUIRES_UPGRADE); + } + // Get the private key of the attest key. + short attestationKeySecret = KMArray.cast(data[KEY_BLOB]).get(KEY_BLOB_SECRET); + // Get the KeyCharacteristics and SB param of the attest key + short attestKeyCharacteristics = KMArray.cast(data[KEY_BLOB]).get(KEY_BLOB_PARAMS); + short attestKeySbParams = + KMKeyCharacteristics.cast(attestKeyCharacteristics).getStrongboxEnforced(); + // If the attest key's purpose is not "attest key" then error. + short attKeyPurpose = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, attestKeySbParams); + if (!KMEnumArrayTag.cast(attKeyPurpose).contains(KMType.ATTEST_KEY)) { + KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); + } + KMAsn1Parser asn1Decoder = KMAsn1Parser.instance(); + try { + asn1Decoder.validateDerSubject(issuer); + } catch (KMException e) { + KMException.throwIt(KMError.INVALID_ISSUER_SUBJECT_NAME); + } + if (KMByteBlob.cast(issuer).length() > KMConfigurations.MAX_SUBJECT_DER_LEN) { + KMException.throwIt(KMError.INVALID_ISSUER_SUBJECT_NAME); + } + // If issuer is not present then it is an error + if (KMByteBlob.cast(issuer).length() <= 0) { + KMException.throwIt(KMError.MISSING_ISSUER_SUBJECT_NAME); + } + short alg = KMEnumTag.getValue(KMType.ALGORITHM, attestKeySbParams); + if (alg == KMType.RSA) { + short attestationKeyPublic = KMArray.cast(data[KEY_BLOB]).get(KEY_BLOB_PUB_KEY); + cert.rsaAttestKey(attestationKeySecret, attestationKeyPublic, KMType.ATTESTATION_CERT); + } else if (alg == KMType.EC) { + cert.ecAttestKey(attestationKeySecret, KMType.ATTESTATION_CERT); + } else { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + cert.attestationChallenge(attChallenge); + cert.issuer(issuer); + + // Restore back the global variables. + data[PUB_KEY] = pubKey; + data[SECRET] = privKey; + data[KEY_BLOB] = origBlob; + data[HW_PARAMETERS] = hwParams; + data[KEY_CHARACTERISTICS] = keyChars; + data[CUSTOM_TAGS] = customTags; + data[SW_PARAMETERS] = + KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getKeystoreEnforced(); + data[TEE_PARAMETERS] = KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getTeeEnforced(); + data[SB_PARAMETERS] = + KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getStrongboxEnforced(); + cert.publicKey(data[PUB_KEY]); + + // Save attestation application id - must be present. + short attAppId = + KMKeyParameters.findTag( + KMType.BYTES_TAG, KMType.ATTESTATION_APPLICATION_ID, data[KEY_PARAMETERS]); + if (attAppId == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.ATTESTATION_APPLICATION_ID_MISSING); + } + cert.extensionTag(attAppId, false); + // unique id byte blob - uses application id and temporal month count of + // creation time. + attAppId = KMByteTag.cast(attAppId).getValue(); + setUniqueId(cert, attAppId, scratchPad); + // Add Attestation Ids if present + addAttestationIds(cert, scratchPad); + + // Add Tags + addTags(data[HW_PARAMETERS], true, cert); + addTags(data[SW_PARAMETERS], false, cert); + // Add Device Boot locked status + cert.deviceLocked(kmDataStore.isDeviceBootLocked()); + // VB data + cert.verifiedBootHash(getVerifiedBootHash(scratchPad)); + cert.verifiedBootKey(getBootKey(scratchPad)); + cert.verifiedBootState((byte) kmDataStore.getBootState()); + return cert; + } + + private KMAttestationCert makeSelfSignedCert( + short attPrivKey, short attPubKey, short mode, byte[] scratchPad) { + KMAttestationCert cert = makeCommonCert(scratchPad); + short alg = KMEnumTag.getValue(KMType.ALGORITHM, data[KEY_PARAMETERS]); + short subject = + KMKeyParameters.findTag( + KMType.BYTES_TAG, KMType.CERTIFICATE_SUBJECT_NAME, data[KEY_PARAMETERS]); + // If no subject name is specified then use the default subject name. + if (subject == KMType.INVALID_VALUE || KMByteTag.cast(subject).length() == 0) { + subject = KMByteBlob.instance(defaultSubject, (short) 0, (short) defaultSubject.length); + } else { + subject = KMByteTag.cast(subject).getValue(); + } + + if (alg == KMType.RSA) { + cert.rsaAttestKey(attPrivKey, attPubKey, (byte) mode); + } else { + cert.ecAttestKey(attPrivKey, (byte) mode); + } + cert.issuer(subject); + cert.subjectName(subject); + cert.publicKey(attPubKey); + return cert; + } + + protected short getBootKey(byte[] scratchPad) { + Util.arrayFillNonAtomic(scratchPad, (short) 0, VERIFIED_BOOT_KEY_SIZE, (byte) 0); + short len = kmDataStore.getBootKey(scratchPad, (short) 0); + if (len != VERIFIED_BOOT_KEY_SIZE) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + return KMByteBlob.instance(scratchPad, (short) 0, VERIFIED_BOOT_KEY_SIZE); + } + + protected short getVerifiedBootHash(byte[] scratchPad) { + Util.arrayFillNonAtomic(scratchPad, (short) 0, VERIFIED_BOOT_HASH_SIZE, (byte) 0); + short len = kmDataStore.getVerifiedBootHash(scratchPad, (short) 0); + if (len != VERIFIED_BOOT_HASH_SIZE) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + return KMByteBlob.instance(scratchPad, (short) 0, VERIFIED_BOOT_HASH_SIZE); + } + + // -------------------------------- + // Only add the Attestation ids which are requested in the attestation parameters. + // If the requested attestation ids are not provisioned or deleted then + // throw CANNOT_ATTEST_IDS error. If there is mismatch in the attestation + // id values of both the requested parameters and the provisioned parameters + // then throw INVALID_TAG error. + private void addAttestationIds(KMAttestationCert cert, byte[] scratchPad) { + byte index = 0; + short attIdTag; + short attIdTagValue; + short storedAttIdLen; + while (index < (short) attTags.length) { + attIdTag = KMKeyParameters.findTag(KMType.BYTES_TAG, attTags[index], data[KEY_PARAMETERS]); + if (attIdTag != KMType.INVALID_VALUE) { + attIdTagValue = KMByteTag.cast(attIdTag).getValue(); + storedAttIdLen = kmDataStore.getAttestationId(attTags[index], scratchPad, (short) 0); + // Return CANNOT_ATTEST_IDS if Attestation IDs are not provisioned or + // Attestation IDs are deleted. + if (storedAttIdLen == 0) { + // Ignore the SECOND_IMEI tag if the previous Applet's KeyMint version is less than + // 3.0 and no SECOND_IMEI is provisioned. + if (!(kmDataStore.ignoreSecondImei + && attTags[index] == KMType.ATTESTATION_ID_SECOND_IMEI)) { + KMException.throwIt(KMError.CANNOT_ATTEST_IDS); + } + } else { + // Return INVALID_TAG if Attestation IDs does not match. + if ((storedAttIdLen != KMByteBlob.cast(attIdTagValue).length()) + || (0 + != Util.arrayCompare( + scratchPad, + (short) 0, + KMByteBlob.cast(attIdTagValue).getBuffer(), + KMByteBlob.cast(attIdTagValue).getStartOff(), + storedAttIdLen))) { + KMException.throwIt(KMError.CANNOT_ATTEST_IDS); + } + short blob = KMByteBlob.instance(scratchPad, (short) 0, storedAttIdLen); + cert.extensionTag(KMByteTag.instance(attTags[index], blob), true); + } + } + index++; + } + } + + private void processDestroyAttIdsCmd(APDU apdu) { + kmDataStore.deleteAttestationIds(); + sendResponse(apdu, KMError.OK); + } + + private void processVerifyAuthorizationCmd(APDU apdu) { + sendResponse(apdu, KMError.UNIMPLEMENTED); + } + + private short abortOperationCmd(APDU apdu) { + short cmd = KMArray.instance((short) 1); + KMArray.cast(cmd).add((short) 0, KMInteger.exp()); + return receiveIncoming(apdu, cmd); + } + + private void processAbortOperationCmd(APDU apdu) { + short cmd = abortOperationCmd(apdu); + data[OP_HANDLE] = KMArray.cast(cmd).get((short) 0); + KMOperationState op = findOperation(data[OP_HANDLE]); + if (op == null) { + sendResponse(apdu, KMError.INVALID_OPERATION_HANDLE); + } else { + releaseOperation(op); + sendResponse(apdu, KMError.OK); + } + } + + private short finishOperationCmd(APDU apdu) { + short cmd = KMArray.instance((short) 6); + KMArray.cast(cmd).add((short) 0, KMInteger.exp()); // op handle + KMArray.cast(cmd).add((short) 1, KMByteBlob.exp()); // input data + KMArray.cast(cmd).add((short) 2, KMByteBlob.exp()); // signature + short authToken = KMHardwareAuthToken.exp(); + KMArray.cast(cmd).add((short) 3, authToken); // auth token + short verToken = KMVerificationToken.exp(); + KMArray.cast(cmd).add((short) 4, verToken); // time stamp token + KMArray.cast(cmd).add((short) 5, KMByteBlob.exp()); // confirmation token + return receiveIncoming(apdu, cmd); + } + + private void processFinishOperationCmd(APDU apdu) { + short cmd = finishOperationCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + data[OP_HANDLE] = KMArray.cast(cmd).get((short) 0); + data[INPUT_DATA] = KMArray.cast(cmd).get((short) 1); + data[SIGNATURE] = KMArray.cast(cmd).get((short) 2); + data[HW_TOKEN] = KMArray.cast(cmd).get((short) 3); + data[VERIFICATION_TOKEN] = KMArray.cast(cmd).get((short) 4); + data[CONFIRMATION_TOKEN] = KMArray.cast(cmd).get((short) 5); + // Check Operation Handle + KMOperationState op = findOperation(data[OP_HANDLE]); + if (op == null) { + KMException.throwIt(KMError.INVALID_OPERATION_HANDLE); + } + // Authorize the finish operation + authorizeUpdateFinishOperation(op, scratchPad); + switch (op.getPurpose()) { + case KMType.SIGN: + finishTrustedConfirmationOperation(op); + case KMType.VERIFY: + finishSigningVerifyingOperation(op, scratchPad); + break; + case KMType.ENCRYPT: + finishEncryptOperation(op, scratchPad); + break; + case KMType.DECRYPT: + finishDecryptOperation(op, scratchPad); + break; + case KMType.AGREE_KEY: + finishKeyAgreementOperation(op, scratchPad); + break; + } + if (data[OUTPUT_DATA] == KMType.INVALID_VALUE) { + data[OUTPUT_DATA] = KMByteBlob.instance((short) 0); + } + // Remove the operation handle + releaseOperation(op); + + // make response + short resp = KMArray.instance((short) 2); + KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(resp).add((short) 1, data[OUTPUT_DATA]); + sendOutgoing(apdu, resp); + } + + private void finishEncryptOperation(KMOperationState op, byte[] scratchPad) { + if (op.getAlgorithm() != KMType.AES && op.getAlgorithm() != KMType.DES) { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + finishAesDesOperation(op); + } + + private void finishDecryptOperation(KMOperationState op, byte[] scratchPad) { + short len = KMByteBlob.cast(data[INPUT_DATA]).length(); + switch (op.getAlgorithm()) { + case KMType.RSA: + // Fill the scratch pad with zero + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + if (op.getPadding() == KMType.PADDING_NONE && len != 256) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + len = + op.getOperation() + .finish( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + len, + scratchPad, + (short) 0); + + data[OUTPUT_DATA] = KMByteBlob.instance(scratchPad, (short) 0, len); + break; + case KMType.AES: + case KMType.DES: + finishAesDesOperation(op); + break; + } + } + + private void finishAesDesOperation(KMOperationState op) { + short len = KMByteBlob.cast(data[INPUT_DATA]).length(); + short blockSize = AES_BLOCK_SIZE; + if (op.getAlgorithm() == KMType.DES) { + blockSize = DES_BLOCK_SIZE; + } + + if (op.getPurpose() == KMType.DECRYPT + && len > 0 + && (op.getBlockMode() == KMType.ECB || op.getBlockMode() == KMType.CBC) + && ((short) (len % blockSize) != 0)) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + + if (op.getBlockMode() == KMType.GCM) { + if (op.getPurpose() == KMType.DECRYPT && (len < (short) (op.getMacLength() / 8))) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + if (op.isAesGcmUpdateAllowed()) { + op.setAesGcmUpdateComplete(); + } + // Get the output size + len = op.getOperation().getAESGCMOutputSize(len, (short) (op.getMacLength() / 8)); + } + // If padding i.e. pkcs7 then add padding to right + // Output data can at most one block size more the input data in case of pkcs7 encryption + // In case of gcm we will allocate extra memory of the size equal to blocksize. + data[OUTPUT_DATA] = KMByteBlob.instance((short) (len + 2 * blockSize)); + try { + len = + op.getOperation() + .finish( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length(), + KMByteBlob.cast(data[OUTPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[OUTPUT_DATA]).getStartOff()); + } catch (CryptoException e) { + if (e.getReason() == CryptoException.ILLEGAL_USE) { + // As per VTS, zero length input on AES/DES with PADDING_NONE Should return a zero length + // output. But JavaCard fails with CryptoException.ILLEGAL_USE if no input data is + // provided via update() method. So ignore this exception in case if all below conditions + // are satisfied and simply return empty output. + // 1. padding mode is PADDING_NONE. + // 2. No input message is processed in update(). + // 3. Zero length input data is passed in finish operation. + if ((op.getPadding() == KMType.PADDING_NONE) + && !op.isInputMsgProcessed() + && (KMByteBlob.cast(data[INPUT_DATA]).length() == 0)) { + len = 0; + } else { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + } + } + KMByteBlob.cast(data[OUTPUT_DATA]).setLength(len); + } + + private void finishKeyAgreementOperation(KMOperationState op, byte[] scratchPad) { + try { + KMAsn1Parser pkcs8 = KMAsn1Parser.instance(); + short blob = pkcs8.decodeEcSubjectPublicKeyInfo(data[INPUT_DATA]); + short len = + op.getOperation() + .finish( + KMByteBlob.cast(blob).getBuffer(), + KMByteBlob.cast(blob).getStartOff(), + KMByteBlob.cast(blob).length(), + scratchPad, + (short) 0); + data[OUTPUT_DATA] = KMByteBlob.instance((short) 32); + Util.arrayCopyNonAtomic( + scratchPad, + (short) 0, + KMByteBlob.cast(data[OUTPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[OUTPUT_DATA]).getStartOff(), + len); + } catch (CryptoException e) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + } + + private void finishSigningVerifyingOperation(KMOperationState op, byte[] scratchPad) { + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + switch (op.getAlgorithm()) { + case KMType.RSA: + // If there is no padding we can treat signing as a RSA decryption operation. + try { + if (op.getPurpose() == KMType.SIGN) { + // len of signature will be 256 bytes - but it can be less then 256 bytes + short len = + op.getOperation() + .sign( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length(), + scratchPad, + (short) 0); + // Maximum output size of signature is 256 bytes. - the signature will always be + // positive + data[OUTPUT_DATA] = KMByteBlob.instance((short) 256); + Util.arrayCopyNonAtomic( + scratchPad, + (short) 0, + KMByteBlob.cast(data[OUTPUT_DATA]).getBuffer(), + (short) (KMByteBlob.cast(data[OUTPUT_DATA]).getStartOff() + 256 - len), + len); + } else { + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + } catch (CryptoException e) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + break; + case KMType.EC: + short len = KMByteBlob.cast(data[INPUT_DATA]).length(); + // If DIGEST NONE then truncate the input data to 32 bytes. + if (op.getDigest() == KMType.DIGEST_NONE && len > 32) { + len = 32; + } + if (op.getPurpose() == KMType.SIGN) { + // len of signature will be 512 bits i.e. 64 bytes + len = + op.getOperation() + .sign( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + len, + scratchPad, + (short) 0); + data[OUTPUT_DATA] = KMByteBlob.instance(scratchPad, (short) 0, len); + } else { + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + break; + case KMType.HMAC: + // As per Keymaster HAL documentation, the length of the Hmac output can + // be decided by using TAG_MAC_LENGTH in Keyparameters. But there is no + // such provision to control the length of the Hmac output using JavaCard + // crypto APIs and the current implementation always returns 32 bytes + // length of Hmac output. So to provide support to TAG_MAC_LENGTH + // feature, we truncate the output signature to TAG_MAC_LENGTH and return + // the truncated signature back to the caller. At the time of verfication + // we again compute the signature of the plain text input, truncate it to + // TAG_MAC_LENGTH and compare it with the input signature for + // verification. + op.getOperation() + .sign( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length(), + scratchPad, + (short) 0); + if (op.getPurpose() == KMType.SIGN) { + // Copy only signature of mac length size. + data[OUTPUT_DATA] = + KMByteBlob.instance(scratchPad, (short) 0, (short) (op.getMacLength() / 8)); + } else if (op.getPurpose() == KMType.VERIFY) { + if ((KMByteBlob.cast(data[SIGNATURE]).length() < (MIN_HMAC_LENGTH_BITS / 8)) + || KMByteBlob.cast(data[SIGNATURE]).length() > (SHA256_DIGEST_LEN_BITS / 8)) { + KMException.throwIt(KMError.UNSUPPORTED_MAC_LENGTH); + } + if ((KMByteBlob.cast(data[SIGNATURE]).length() < (short) (op.getMinMacLength() / 8))) { + KMException.throwIt(KMError.INVALID_MAC_LENGTH); + } + + if (0 + != Util.arrayCompare( + scratchPad, + (short) 0, + KMByteBlob.cast(data[SIGNATURE]).getBuffer(), + KMByteBlob.cast(data[SIGNATURE]).getStartOff(), + KMByteBlob.cast(data[SIGNATURE]).length())) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + data[OUTPUT_DATA] = KMByteBlob.instance((short) 0); + } + break; + default: // This is should never happen + KMException.throwIt(KMError.OPERATION_CANCELLED); + break; + } + } + + private void authorizeUpdateFinishOperation(KMOperationState op, byte[] scratchPad) { + // If one time user Authentication is required + if (op.isSecureUserIdReqd() && !op.isAuthTimeoutValidated()) { + // Validate Verification Token. + validateVerificationToken(data[VERIFICATION_TOKEN], scratchPad); + // validate operation handle. + short ptr = KMVerificationToken.cast(data[VERIFICATION_TOKEN]).getChallenge(); + if (KMInteger.compare(ptr, op.getHandle()) != 0) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + tmpVariables[0] = op.getAuthTime(); + tmpVariables[2] = KMVerificationToken.cast(data[VERIFICATION_TOKEN]).getTimestamp(); + if (tmpVariables[2] == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + if (KMInteger.compare(tmpVariables[0], tmpVariables[2]) < 0) { + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); + } + op.setAuthTimeoutValidated(true); + } else if (op.isAuthPerOperationReqd()) { // If Auth per operation is required + if (!validateHwToken(data[HW_TOKEN], scratchPad)) { + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); + } + tmpVariables[0] = KMHardwareAuthToken.cast(data[HW_TOKEN]).getChallenge(); + if (KMInteger.compare(data[OP_HANDLE], tmpVariables[0]) != 0) { + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); + } + if (!authTokenMatches(op.getUserSecureId(), op.getAuthType(), scratchPad)) { + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); + } + } + } + + private void authorizeKeyUsageForCount(byte[] scratchPad) { + short scratchPadOff = 0; + Util.arrayFillNonAtomic(scratchPad, scratchPadOff, (short) 12, (byte) 0); + + short usageLimitBufLen = + KMIntegerTag.getValue( + scratchPad, + scratchPadOff, + KMType.UINT_TAG, + KMType.MAX_USES_PER_BOOT, + data[HW_PARAMETERS]); + + if (usageLimitBufLen == KMType.INVALID_VALUE) { + return; + } + + if (usageLimitBufLen > 4) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + + if (kmDataStore.isAuthTagPersisted(data[AUTH_TAG])) { + // Get current counter, update and increment it. + short len = + kmDataStore.getRateLimitedKeyCount( + data[AUTH_TAG], scratchPad, (short) (scratchPadOff + 4)); + if (len != 4) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + if (0 + >= KMInteger.unsignedByteArrayCompare( + scratchPad, scratchPadOff, scratchPad, (short) (scratchPadOff + 4), (short) 4)) { + KMException.throwIt(KMError.KEY_MAX_OPS_EXCEEDED); + } + // Increment the counter. + Util.arrayFillNonAtomic(scratchPad, scratchPadOff, len, (byte) 0); + Util.setShort(scratchPad, (short) (scratchPadOff + 2), (short) 1); + KMUtils.add( + scratchPad, + scratchPadOff, + (short) (scratchPadOff + len), + (short) (scratchPadOff + len * 2)); + + kmDataStore.setRateLimitedKeyCount( + data[AUTH_TAG], scratchPad, (short) (scratchPadOff + len * 2), len); + } else { + // Persist auth tag. + if (!kmDataStore.persistAuthTag(data[AUTH_TAG])) { + KMException.throwIt(KMError.TOO_MANY_OPERATIONS); + } + } + } + + private void authorizeDeviceUnlock(byte[] scratchPad) { + // If device is locked and key characteristics requires unlocked device then check whether + // HW auth token has correct timestamp. + short ptr = + KMKeyParameters.findTag( + KMType.BOOL_TAG, KMType.UNLOCKED_DEVICE_REQUIRED, data[HW_PARAMETERS]); + + if (ptr != KMType.INVALID_VALUE && kmDataStore.getDeviceLock()) { + if (data[HW_TOKEN] == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.DEVICE_LOCKED); + } + ptr = KMHardwareAuthToken.cast(data[HW_TOKEN]).getTimestamp(); + // Check if the current auth time stamp is greater than device locked time stamp + short ts = kmDataStore.getDeviceTimeStamp(); + if (KMInteger.compare(ptr, ts) <= 0) { + KMException.throwIt(KMError.DEVICE_LOCKED); + } + // Now check if the device unlock requires password only authentication and whether + // auth token is generated through password authentication or not. + if (kmDataStore.getDeviceLockPasswordOnly()) { + ptr = KMHardwareAuthToken.cast(data[HW_TOKEN]).getHwAuthenticatorType(); + ptr = KMEnum.cast(ptr).getVal(); + if (((byte) ptr & KMType.PASSWORD) == 0) { + KMException.throwIt(KMError.DEVICE_LOCKED); + } + } + // Unlock the device + // repository.deviceLockedFlag = false; + kmDataStore.setDeviceLock(false); + kmDataStore.clearDeviceLockTimeStamp(); + } + } + + private boolean verifyVerificationTokenMacInBigEndian(short verToken, byte[] scratchPad) { + // concatenation length will be 37 + length of verified parameters list - which + // is typically empty + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + // Add "Auth Verification" - 17 bytes. + Util.arrayCopyNonAtomic( + authVerification, (short) 0, scratchPad, (short) 0, (short) authVerification.length); + short len = (short) authVerification.length; + // concatenate challenge - 8 bytes + short ptr = KMVerificationToken.cast(verToken).getChallenge(); + KMInteger.cast(ptr) + .value( + scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length()))); + len += KMInteger.UINT_64; + // concatenate timestamp -8 bytes + ptr = KMVerificationToken.cast(verToken).getTimestamp(); + KMInteger.cast(ptr) + .value( + scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length()))); + len += KMInteger.UINT_64; + // concatenate security level - 4 bytes + scratchPad[(short) (len + 3)] = TRUSTED_ENVIRONMENT; + len += KMInteger.UINT_32; + // hmac the data + ptr = KMVerificationToken.cast(verToken).getMac(); + + return seProvider.hmacVerify( + kmDataStore.getComputedHmacKey(), + scratchPad, + (short) 0, + len, + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()); + } + + private void validateVerificationToken(short verToken, byte[] scratchPad) { + short ptr = KMVerificationToken.cast(verToken).getMac(); + // If mac length is zero then token is empty. + if (KMByteBlob.cast(ptr).length() == 0) { + KMException.throwIt(KMError.INVALID_MAC_LENGTH); + } + if (!verifyVerificationTokenMacInBigEndian(verToken, scratchPad)) { + // Throw Exception if none of the combination works. + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + } + + private short updateOperationCmd(APDU apdu) { + short cmd = KMArray.instance((short) 4); + // Arguments + KMArray.cast(cmd).add((short) 0, KMInteger.exp()); + KMArray.cast(cmd).add((short) 1, KMByteBlob.exp()); + short authToken = KMHardwareAuthToken.exp(); + KMArray.cast(cmd).add((short) 2, authToken); + short verToken = KMVerificationToken.exp(); + KMArray.cast(cmd).add((short) 3, verToken); + return receiveIncoming(apdu, cmd); + } + + private void processUpdateOperationCmd(APDU apdu) { + short cmd = updateOperationCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + data[OP_HANDLE] = KMArray.cast(cmd).get((short) 0); + data[INPUT_DATA] = KMArray.cast(cmd).get((short) 1); + data[HW_TOKEN] = KMArray.cast(cmd).get((short) 2); + data[VERIFICATION_TOKEN] = KMArray.cast(cmd).get((short) 3); + + // Input data must be present even if it is zero length. + if (data[INPUT_DATA] == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + + // Check Operation Handle and get op state + // Check Operation Handle + KMOperationState op = findOperation(data[OP_HANDLE]); + if (op == null) { + KMException.throwIt(KMError.INVALID_OPERATION_HANDLE); + } + // authorize the update operation + authorizeUpdateFinishOperation(op, scratchPad); + + if (op.getPurpose() == KMType.SIGN || op.getPurpose() == KMType.VERIFY) { + // update the data. + op.getOperation() + .update( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length()); + // update trusted confirmation operation + updateTrustedConfirmationOperation(op); + + data[OUTPUT_DATA] = KMType.INVALID_VALUE; + } else if (op.getPurpose() == KMType.ENCRYPT || op.getPurpose() == KMType.DECRYPT) { + // Update for encrypt/decrypt using RSA will not be supported because to do this op state + // will have to buffer the data - so reject the update if it is rsa algorithm. + if (op.getAlgorithm() == KMType.RSA) { + KMException.throwIt(KMError.OPERATION_CANCELLED); + } + short len = KMByteBlob.cast(data[INPUT_DATA]).length(); + short blockSize = DES_BLOCK_SIZE; + if (op.getAlgorithm() == KMType.AES) { + blockSize = AES_BLOCK_SIZE; + if (op.getBlockMode() == KMType.GCM) { + // if input data present + if (len > 0) { + // no more future updateAAD allowed if input data present. + if (op.isAesGcmUpdateAllowed()) { + op.setAesGcmUpdateComplete(); + } + } + } + } + // Allocate output buffer as input data is already block aligned + data[OUTPUT_DATA] = KMByteBlob.instance((short) (len + 2 * blockSize)); + // Otherwise just update the data. + // HAL consumes all the input and maintains a buffered data inside it. So the + // applet sends the inputConsumed length as same as the input length. + try { + len = + op.getOperation() + .update( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length(), + KMByteBlob.cast(data[OUTPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[OUTPUT_DATA]).getStartOff()); + } catch (CryptoException e) { + KMException.throwIt(KMError.INVALID_TAG); + } + if (KMByteBlob.cast(data[INPUT_DATA]).length() > 0) { + // This flag is used to denote that an input data of length > 0 is received and processed + // successfully in update command. This flag is later used in the finish operation + // to handle a particular use case, where a zero length input data on AES/DES algorithm + // with PADDING_NONE should return a zero length output with OK response. + op.setProcessedInputMsg(true); + } + // Adjust the Output data if it is not equal to input data. + // This happens in case of JCardSim provider. + KMByteBlob.cast(data[OUTPUT_DATA]).setLength(len); + } + + if (data[OUTPUT_DATA] == KMType.INVALID_VALUE) { + data[OUTPUT_DATA] = KMByteBlob.instance((short) 0); + } + // Persist if there are any updates. + // op.persist(); + // make response + short resp = KMArray.instance((short) 2); + KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(resp).add((short) 1, data[OUTPUT_DATA]); + sendOutgoing(apdu, resp); + } + + private short updateAadOperationCmd(APDU apdu) { + short cmd = KMArray.instance((short) 4); + KMArray.cast(cmd).add((short) 0, KMInteger.exp()); + KMArray.cast(cmd).add((short) 1, KMByteBlob.exp()); + short authToken = KMHardwareAuthToken.exp(); + KMArray.cast(cmd).add((short) 2, authToken); + short verToken = KMVerificationToken.exp(); + KMArray.cast(cmd).add((short) 3, verToken); + return receiveIncoming(apdu, cmd); + } + + private void processUpdateAadOperationCmd(APDU apdu) { + short cmd = updateAadOperationCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + data[OP_HANDLE] = KMArray.cast(cmd).get((short) 0); + data[INPUT_DATA] = KMArray.cast(cmd).get((short) 1); + data[HW_TOKEN] = KMArray.cast(cmd).get((short) 2); + data[VERIFICATION_TOKEN] = KMArray.cast(cmd).get((short) 3); + + // Input data must be present even if it is zero length. + if (data[INPUT_DATA] == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // Check Operation Handle and get op state + // Check Operation Handle + KMOperationState op = findOperation(data[OP_HANDLE]); + if (op == null) { + KMException.throwIt(KMError.INVALID_OPERATION_HANDLE); + } + if (op.getAlgorithm() != KMType.AES) { + KMException.throwIt(KMError.INCOMPATIBLE_ALGORITHM); + } + if (op.getBlockMode() != KMType.GCM) { + KMException.throwIt(KMError.INCOMPATIBLE_BLOCK_MODE); + } + if (!op.isAesGcmUpdateAllowed()) { + KMException.throwIt(KMError.INVALID_TAG); + } + if (op.getPurpose() != KMType.ENCRYPT && op.getPurpose() != KMType.DECRYPT) { + KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); + } + // authorize the update operation + authorizeUpdateFinishOperation(op, scratchPad); + try { + op.getOperation() + .updateAAD( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length()); + } catch (CryptoException exp) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + // make response + short resp = KMArray.instance((short) 1); + KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); + sendOutgoing(apdu, resp); + } + + private short beginOperationCmd(APDU apdu) { + short cmd = KMArray.instance((short) 4); + // Arguments + short params = KMKeyParameters.expAny(); + KMArray.cast(cmd).add((short) 0, KMEnum.instance(KMType.PURPOSE)); + KMArray.cast(cmd).add((short) 1, KMByteBlob.exp()); + KMArray.cast(cmd).add((short) 2, params); + short authToken = KMHardwareAuthToken.exp(); + KMArray.cast(cmd).add((short) 3, authToken); + return receiveIncoming(apdu, cmd); + } + + private void processBeginOperationCmd(APDU apdu) { + // Receive the incoming request fully from the host into buffer. + short cmd = beginOperationCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + short purpose = KMArray.cast(cmd).get((short) 0); + data[KEY_BLOB] = KMArray.cast(cmd).get((short) 1); + data[KEY_PARAMETERS] = KMArray.cast(cmd).get((short) 2); + data[HW_TOKEN] = KMArray.cast(cmd).get((short) 3); + purpose = KMEnum.cast(purpose).getVal(); + // Check for app id and app data. + data[APP_ID] = getApplicationId(data[KEY_PARAMETERS]); + data[APP_DATA] = getApplicationData(data[KEY_PARAMETERS]); + // Check if key requires upgrade. The KeyBlob is parsed inside isKeyUpgradeRequired + // function itself. + if (isKeyUpgradeRequired(data[KEY_BLOB], data[APP_ID], data[APP_DATA], scratchPad)) { + KMException.throwIt(KMError.KEY_REQUIRES_UPGRADE); + } + KMTag.assertPresence( + data[SB_PARAMETERS], KMType.ENUM_TAG, KMType.ALGORITHM, KMError.UNSUPPORTED_ALGORITHM); + short algorithm = KMEnumTag.getValue(KMType.ALGORITHM, data[SB_PARAMETERS]); + // If Blob usage tag is present in key characteristics then it should be standalone. + if (KMTag.isPresent(data[SB_PARAMETERS], KMType.ENUM_TAG, KMType.BLOB_USAGE_REQ)) { + if (KMEnumTag.getValue(KMType.BLOB_USAGE_REQ, data[SB_PARAMETERS]) != KMType.STANDALONE) { + KMException.throwIt(KMError.UNSUPPORTED_TAG); + } + } + + // Generate a random number for operation handle + short buf = KMByteBlob.instance(KMOperationState.OPERATION_HANDLE_SIZE); + generateUniqueOperationHandle( + KMByteBlob.cast(buf).getBuffer(), + KMByteBlob.cast(buf).getStartOff(), + KMByteBlob.cast(buf).length()); + /* opHandle is a KMInteger and is encoded as KMInteger when it is returned back. */ + short opHandle = + KMInteger.instance( + KMByteBlob.cast(buf).getBuffer(), + KMByteBlob.cast(buf).getStartOff(), + KMByteBlob.cast(buf).length()); + KMOperationState op = reserveOperation(algorithm, opHandle); + if (op == null) { + KMException.throwIt(KMError.TOO_MANY_OPERATIONS); + } + data[OP_HANDLE] = op.getHandle(); + op.setPurpose((byte) purpose); + op.setKeySize(KMByteBlob.cast(data[SECRET]).length()); + authorizeAndBeginOperation(op, scratchPad); + switch (op.getPurpose()) { + case KMType.SIGN: + beginTrustedConfirmationOperation(op); + case KMType.VERIFY: + beginSignVerifyOperation(op); + break; + case KMType.ENCRYPT: + case KMType.DECRYPT: + beginCipherOperation(op); + break; + case KMType.AGREE_KEY: + beginKeyAgreementOperation(op); + break; + default: + KMException.throwIt(KMError.UNIMPLEMENTED); + break; + } + short iv = KMType.INVALID_VALUE; + // If the data[IV] is required to be returned. + // As per VTS, for the decryption operation don't send the iv back. + if (data[IV] != KMType.INVALID_VALUE + && op.getPurpose() != KMType.DECRYPT + && op.getBlockMode() != KMType.ECB) { + iv = KMArray.instance((short) 1); + if (op.getAlgorithm() == KMType.DES && op.getBlockMode() == KMType.CBC) { + // For AES/DES we are generate an random iv of length 16 bytes. + // While sending the iv back for DES/CBC mode of opeation only send + // 8 bytes back. + short ivBlob = KMByteBlob.instance((short) 8); + Util.arrayCopy( + KMByteBlob.cast(data[IV]).getBuffer(), + KMByteBlob.cast(data[IV]).getStartOff(), + KMByteBlob.cast(ivBlob).getBuffer(), + KMByteBlob.cast(ivBlob).getStartOff(), + (short) 8); + data[IV] = ivBlob; + } + KMArray.cast(iv).add((short) 0, KMByteTag.instance(KMType.NONCE, data[IV])); + } else { + iv = KMArray.instance((short) 0); + } + short macLen = 0; + if (op.getMacLength() != KMType.INVALID_VALUE) { + macLen = (short) (op.getMacLength() / 8); + } + short params = KMKeyParameters.instance(iv); + short resp = KMArray.instance((short) 5); + KMArray.cast(resp).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(resp).add((short) 1, params); + KMArray.cast(resp).add((short) 2, data[OP_HANDLE]); + KMArray.cast(resp).add((short) 3, KMInteger.uint_8(op.getBufferingMode())); + KMArray.cast(resp).add((short) 4, KMInteger.uint_16(macLen)); + sendOutgoing(apdu, resp); + } + + private void authorizePurpose(KMOperationState op) { + switch (op.getAlgorithm()) { + case KMType.AES: + case KMType.DES: + if (op.getPurpose() == KMType.SIGN + || op.getPurpose() == KMType.VERIFY + || op.getPurpose() == KMType.AGREE_KEY) { + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + break; + case KMType.EC: + if (op.getPurpose() == KMType.ENCRYPT || op.getPurpose() == KMType.DECRYPT) { + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + break; + case KMType.HMAC: + if (op.getPurpose() == KMType.ENCRYPT + || op.getPurpose() == KMType.DECRYPT + || op.getPurpose() == KMType.AGREE_KEY) { + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + break; + case KMType.RSA: + if (op.getPurpose() == KMType.AGREE_KEY) { + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + break; + default: + break; + } + if (!KMEnumArrayTag.contains(KMType.PURPOSE, op.getPurpose(), data[HW_PARAMETERS])) { + KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); + } + } + + private void authorizeDigest(KMOperationState op) { + short digests = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.DIGEST, data[HW_PARAMETERS]); + op.setDigest(KMType.DIGEST_NONE); + short param = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.DIGEST, data[KEY_PARAMETERS]); + if (param != KMType.INVALID_VALUE) { + if (KMEnumArrayTag.cast(param).length() != 1) { + KMException.throwIt(KMError.UNSUPPORTED_DIGEST); + } + param = KMEnumArrayTag.cast(param).get((short) 0); + if (!KMEnumArrayTag.cast(digests).contains(param)) { + KMException.throwIt(KMError.INCOMPATIBLE_DIGEST); + } + op.setDigest((byte) param); + } else if (KMEnumArrayTag.contains( + KMType.PADDING, KMType.RSA_PKCS1_1_5_SIGN, data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.UNSUPPORTED_DIGEST); + } + short paramPadding = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PADDING, data[KEY_PARAMETERS]); + if (paramPadding != KMType.INVALID_VALUE) { + if (KMEnumArrayTag.cast(paramPadding).length() != 1) { + // TODO vts fails because it expects UNSUPPORTED_PADDING_MODE + KMException.throwIt(KMError.UNSUPPORTED_PADDING_MODE); + } + paramPadding = KMEnumArrayTag.cast(paramPadding).get((short) 0); + } + switch (op.getAlgorithm()) { + case KMType.RSA: + if ((paramPadding == KMType.RSA_OAEP || paramPadding == KMType.RSA_PSS) + && param == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.UNSUPPORTED_DIGEST); + } + break; + case KMType.EC: + case KMType.HMAC: + if ((param == KMType.INVALID_VALUE && op.getPurpose() != KMType.AGREE_KEY) + || !isDigestSupported(op.getAlgorithm(), op.getDigest())) { + KMException.throwIt(KMError.UNSUPPORTED_DIGEST); + } + break; + default: + break; + } + } + + private void authorizePadding(KMOperationState op) { + short paddings = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PADDING, data[HW_PARAMETERS]); + op.setPadding(KMType.PADDING_NONE); + short param = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PADDING, data[KEY_PARAMETERS]); + if (param != KMType.INVALID_VALUE) { + if (KMEnumArrayTag.cast(param).length() != 1) { + KMException.throwIt(KMError.UNSUPPORTED_PADDING_MODE); + } + param = KMEnumArrayTag.cast(param).get((short) 0); + if (!KMEnumArrayTag.cast(paddings).contains(param)) { + KMException.throwIt(KMError.INCOMPATIBLE_PADDING_MODE); + } + } + switch (op.getAlgorithm()) { + case KMType.RSA: + if (param == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.UNSUPPORTED_PADDING_MODE); + } + if ((op.getPurpose() == KMType.SIGN || op.getPurpose() == KMType.VERIFY) + && param != KMType.PADDING_NONE + && param != KMType.RSA_PSS + && param != KMType.RSA_PKCS1_1_5_SIGN) { + KMException.throwIt(KMError.UNSUPPORTED_PADDING_MODE); + } + if ((op.getPurpose() == KMType.ENCRYPT || op.getPurpose() == KMType.DECRYPT) + && param != KMType.PADDING_NONE + && param != KMType.RSA_OAEP + && param != KMType.RSA_PKCS1_1_5_ENCRYPT) { + KMException.throwIt(KMError.UNSUPPORTED_PADDING_MODE); + } + + if (param == KMType.PADDING_NONE && op.getDigest() != KMType.DIGEST_NONE) { + KMException.throwIt(KMError.INCOMPATIBLE_DIGEST); + } + if ((param == KMType.RSA_OAEP || param == KMType.RSA_PSS) + && op.getDigest() == KMType.DIGEST_NONE) { + KMException.throwIt(KMError.INCOMPATIBLE_DIGEST); + } + if (op.getPurpose() == KMType.SIGN + || op.getPurpose() == KMType.VERIFY + || param == KMType.RSA_OAEP) { + // Digest is mandatory in these cases. + if (!isDigestSupported(op.getAlgorithm(), op.getDigest())) { + KMException.throwIt(KMError.UNSUPPORTED_DIGEST); + } + } + if (param == KMType.RSA_OAEP) { + short mgfDigest = + KMKeyParameters.findTag( + KMType.ENUM_ARRAY_TAG, KMType.RSA_OAEP_MGF_DIGEST, data[KEY_PARAMETERS]); + if (mgfDigest != KMType.INVALID_VALUE) { + if (KMEnumArrayTag.cast(mgfDigest).length() != 1) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + mgfDigest = KMEnumArrayTag.cast(mgfDigest).get((short) 0); + if (mgfDigest == KMType.DIGEST_NONE) { + KMException.throwIt(KMError.UNSUPPORTED_MGF_DIGEST); + } + + } else { + mgfDigest = KMType.SHA1; + } + short mgfDigestHwParams = + KMKeyParameters.findTag( + KMType.ENUM_ARRAY_TAG, KMType.RSA_OAEP_MGF_DIGEST, data[HW_PARAMETERS]); + if ((mgfDigestHwParams != KMType.INVALID_VALUE) + && (!KMEnumArrayTag.cast(mgfDigestHwParams).contains(mgfDigest))) { + KMException.throwIt(KMError.INCOMPATIBLE_MGF_DIGEST); + } + if (mgfDigest != KMType.SHA1 && mgfDigest != KMType.SHA2_256) { + KMException.throwIt(KMError.UNSUPPORTED_MGF_DIGEST); + } + op.setMgfDigest((byte) mgfDigest); + } + op.setPadding((byte) param); + break; + case KMType.DES: + case KMType.AES: + if (param == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.UNSUPPORTED_PADDING_MODE); + } + op.setPadding((byte) param); + break; + default: + break; + } + } + + private void authorizeBlockModeAndMacLength(KMOperationState op) { + short param = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.BLOCK_MODE, data[KEY_PARAMETERS]); + if (param != KMType.INVALID_VALUE) { + if (KMEnumArrayTag.cast(param).length() != 1) { + KMException.throwIt(KMError.UNSUPPORTED_BLOCK_MODE); + } + param = KMEnumArrayTag.cast(param).get((short) 0); + } + if (KMType.AES == op.getAlgorithm() || KMType.DES == op.getAlgorithm()) { + if (!KMEnumArrayTag.contains(KMType.BLOCK_MODE, param, data[HW_PARAMETERS])) { + KMException.throwIt(KMError.INCOMPATIBLE_BLOCK_MODE); + } + } + short macLen = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.MAC_LENGTH, data[KEY_PARAMETERS]); + switch (op.getAlgorithm()) { + case KMType.AES: + // Validate the block mode. + switch (param) { + case KMType.ECB: + case KMType.CBC: + case KMType.CTR: + case KMType.GCM: + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_BLOCK_MODE); + } + if (param == KMType.GCM) { + if (op.getPadding() != KMType.PADDING_NONE || op.getPadding() == KMType.PKCS7) { + KMException.throwIt(KMError.INCOMPATIBLE_PADDING_MODE); + } + if (macLen == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.MISSING_MAC_LENGTH); + } + short minMacLen = + KMIntegerTag.getShortValue( + KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, data[HW_PARAMETERS]); + if (minMacLen == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + if (macLen % 8 != 0 + || macLen > MAX_GCM_TAG_LENGTH_BITS + || macLen < MIN_GCM_TAG_LENGTH_BITS) { + KMException.throwIt(KMError.UNSUPPORTED_MAC_LENGTH); + } + if (macLen < minMacLen) { + KMException.throwIt(KMError.INVALID_MAC_LENGTH); + } + op.setMacLength(macLen); + } + if (param == KMType.CTR) { + if (op.getPadding() != KMType.PADDING_NONE || op.getPadding() == KMType.PKCS7) { + KMException.throwIt(KMError.INCOMPATIBLE_PADDING_MODE); + } + } + break; + case KMType.DES: + // Validate the block mode. + switch (param) { + case KMType.ECB: + case KMType.CBC: + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_BLOCK_MODE); + } + if (param == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + break; + case KMType.HMAC: + short minMacLen = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, data[HW_PARAMETERS]); + if (minMacLen == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + op.setMinMacLength(minMacLen); + if (macLen == KMType.INVALID_VALUE) { + if (op.getPurpose() == KMType.SIGN) { + KMException.throwIt(KMError.MISSING_MAC_LENGTH); + } + } else { + // MAC length may not be specified for verify. + if (op.getPurpose() == KMType.VERIFY) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + if (macLen % 8 != 0 || macLen > SHA256_DIGEST_LEN_BITS || macLen < MIN_HMAC_LENGTH_BITS) { + KMException.throwIt(KMError.UNSUPPORTED_MAC_LENGTH); + } + if (macLen < minMacLen) { + KMException.throwIt(KMError.INVALID_MAC_LENGTH); + } + op.setMacLength(macLen); + } + break; + default: + break; + } + op.setBlockMode((byte) param); + } + + private void authorizeAndBeginOperation(KMOperationState op, byte[] scratchPad) { + authorizePurpose(op); + authorizeDigest(op); + authorizePadding(op); + authorizeBlockModeAndMacLength(op); + if (!validateHwToken(data[HW_TOKEN], scratchPad)) { + data[HW_TOKEN] = KMType.INVALID_VALUE; + } + authorizeUserSecureIdAuthTimeout(op, scratchPad); + authorizeDeviceUnlock(scratchPad); + authorizeKeyUsageForCount(scratchPad); + + KMTag.assertAbsence( + data[HW_PARAMETERS], KMType.BOOL_TAG, KMType.BOOTLOADER_ONLY, KMError.INVALID_KEY_BLOB); + + // Validate early boot + // VTS expects error code EARLY_BOOT_ONLY during begin operation if early boot ended tag is + // present + if (kmDataStore.getEarlyBootEndedStatus()) { + KMTag.assertAbsence( + data[HW_PARAMETERS], KMType.BOOL_TAG, KMType.EARLY_BOOT_ONLY, KMError.EARLY_BOOT_ENDED); + } + + // Authorize Caller Nonce - if caller nonce absent in key char and nonce present in + // key params then fail if it is not a Decrypt operation + data[IV] = KMType.INVALID_VALUE; + + if (!KMTag.isPresent(data[HW_PARAMETERS], KMType.BOOL_TAG, KMType.CALLER_NONCE) + && KMTag.isPresent(data[KEY_PARAMETERS], KMType.BYTES_TAG, KMType.NONCE) + && op.getPurpose() != KMType.DECRYPT) { + KMException.throwIt(KMError.CALLER_NONCE_PROHIBITED); + } + + short nonce = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.NONCE, data[KEY_PARAMETERS]); + // If Nonce is present then check whether the size of nonce is correct. + if (nonce != KMType.INVALID_VALUE) { + data[IV] = KMByteTag.cast(nonce).getValue(); + // For CBC mode - iv must be 8 bytes + if (op.getBlockMode() == KMType.CBC + && op.getAlgorithm() == KMType.DES + && KMByteBlob.cast(data[IV]).length() != 8) { + KMException.throwIt(KMError.INVALID_NONCE); + } + + // For GCM mode - IV must be 12 bytes + if (KMByteBlob.cast(data[IV]).length() != 12 && op.getBlockMode() == KMType.GCM) { + KMException.throwIt(KMError.INVALID_NONCE); + } + + // For AES CBC and CTR modes IV must be 16 bytes + if ((op.getBlockMode() == KMType.CBC || op.getBlockMode() == KMType.CTR) + && op.getAlgorithm() == KMType.AES + && KMByteBlob.cast(data[IV]).length() != 16) { + KMException.throwIt(KMError.INVALID_NONCE); + } + } else if (op.getAlgorithm() == KMType.AES || op.getAlgorithm() == KMType.DES) { + + // For symmetric decryption iv is required + if (op.getPurpose() == KMType.DECRYPT + && (op.getBlockMode() == KMType.CBC + || op.getBlockMode() == KMType.GCM + || op.getBlockMode() == KMType.CTR)) { + KMException.throwIt(KMError.MISSING_NONCE); + } else if (op.getBlockMode() == KMType.ECB) { + // For ECB we create zero length nonce + data[IV] = KMByteBlob.instance((short) 0); + } else if (op.getPurpose() == KMType.ENCRYPT) { + + // For encrypt mode if nonce is absent then create random nonce of correct length + byte ivLen = 16; + if (op.getBlockMode() == KMType.GCM) { + ivLen = 12; + } else if (op.getAlgorithm() == KMType.DES) { + ivLen = 8; + } + data[IV] = KMByteBlob.instance(ivLen); + seProvider.newRandomNumber( + KMByteBlob.cast(data[IV]).getBuffer(), + KMByteBlob.cast(data[IV]).getStartOff(), + KMByteBlob.cast(data[IV]).length()); + } + } + } + + private void beginKeyAgreementOperation(KMOperationState op) { + if (op.getAlgorithm() != KMType.EC) { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + + op.setOperation( + seProvider.initAsymmetricOperation( + (byte) op.getPurpose(), + (byte) op.getAlgorithm(), + (byte) op.getPadding(), + (byte) op.getDigest(), + KMType.DIGEST_NONE, /* No MGF1 Digest */ + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + null, + (short) 0, + (short) 0)); + } + + private void beginCipherOperation(KMOperationState op) { + switch (op.getAlgorithm()) { + case KMType.RSA: + try { + if (op.getPurpose() == KMType.DECRYPT) { + op.setOperation( + seProvider.initAsymmetricOperation( + (byte) op.getPurpose(), + (byte) op.getAlgorithm(), + (byte) op.getPadding(), + (byte) op.getDigest(), + (byte) op.getMgfDigest(), + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + KMByteBlob.cast(data[PUB_KEY]).getBuffer(), + KMByteBlob.cast(data[PUB_KEY]).getStartOff(), + KMByteBlob.cast(data[PUB_KEY]).length())); + } else { + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + } catch (CryptoException exp) { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + break; + case KMType.AES: + case KMType.DES: + if (op.getBlockMode() == KMType.GCM) { + op.setAesGcmUpdateStart(); + } + try { + op.setOperation( + seProvider.initSymmetricOperation( + (byte) op.getPurpose(), + (byte) op.getAlgorithm(), + (byte) op.getDigest(), + (byte) op.getPadding(), + (byte) op.getBlockMode(), + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + KMByteBlob.cast(data[IV]).getBuffer(), + KMByteBlob.cast(data[IV]).getStartOff(), + KMByteBlob.cast(data[IV]).length(), + op.getMacLength())); + } catch (CryptoException exception) { + if (exception.getReason() == CryptoException.ILLEGAL_VALUE) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } else if (exception.getReason() == CryptoException.NO_SUCH_ALGORITHM) { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + } + } + } + + private void beginTrustedConfirmationOperation(KMOperationState op) { + // Check for trusted confirmation - if required then set the signer in op state. + if (KMKeyParameters.findTag( + KMType.BOOL_TAG, KMType.TRUSTED_CONFIRMATION_REQUIRED, data[HW_PARAMETERS]) + != KMType.INVALID_VALUE) { + + op.setTrustedConfirmationSigner( + seProvider.initTrustedConfirmationSymmetricOperation(kmDataStore.getComputedHmacKey())); + + op.getTrustedConfirmationSigner() + .update(confirmationToken, (short) 0, (short) confirmationToken.length); + } + } + + private void beginSignVerifyOperation(KMOperationState op) { + switch (op.getAlgorithm()) { + case KMType.RSA: + try { + if (op.getPurpose() == KMType.SIGN) { + op.setOperation( + seProvider.initAsymmetricOperation( + (byte) op.getPurpose(), + (byte) op.getAlgorithm(), + (byte) op.getPadding(), + (byte) op.getDigest(), + KMType.DIGEST_NONE, /* No MGF Digest */ + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + KMByteBlob.cast(data[PUB_KEY]).getBuffer(), + KMByteBlob.cast(data[PUB_KEY]).getStartOff(), + KMByteBlob.cast(data[PUB_KEY]).length())); + } else { + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + } catch (CryptoException exp) { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + break; + case KMType.EC: + try { + if (op.getPurpose() == KMType.SIGN) { + op.setOperation( + seProvider.initAsymmetricOperation( + (byte) op.getPurpose(), + (byte) op.getAlgorithm(), + (byte) op.getPadding(), + (byte) op.getDigest(), + KMType.DIGEST_NONE, /* No MGF Digest */ + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + null, + (short) 0, + (short) 0)); + } else { + KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); + } + } catch (CryptoException exp) { + // Javacard does not support NO digest based signing. + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + break; + case KMType.HMAC: + // As per Keymaster HAL documentation, the length of the Hmac output can + // be decided by using TAG_MAC_LENGTH in Keyparameters. But there is no + // such provision to control the length of the Hmac output using JavaCard + // crypto APIs and the current implementation always returns 32 bytes + // length of Hmac output. So to provide support to TAG_MAC_LENGTH + // feature, we truncate the output signature to TAG_MAC_LENGTH and return + // the truncated signature back to the caller. At the time of verfication + // we again compute the signature of the plain text input, truncate it to + // TAG_MAC_LENGTH and compare it with the input signature for + // verification. So this is the reason we are using KMType.SIGN directly + // instead of using op.getPurpose(). + try { + op.setOperation( + seProvider.initSymmetricOperation( + (byte) KMType.SIGN, + (byte) op.getAlgorithm(), + (byte) op.getDigest(), + (byte) op.getPadding(), + (byte) op.getBlockMode(), + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + null, + (short) 0, + (short) 0, + (short) 0)); + } catch (CryptoException exp) { + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + } + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + break; + } + } + + private boolean isHwAuthTokenContainsMatchingSecureId(short hwAuthToken, short secureUserIdsObj) { + short secureUserId = KMHardwareAuthToken.cast(hwAuthToken).getUserId(); + if (!KMInteger.cast(secureUserId).isZero()) { + if (KMIntegerArrayTag.cast(secureUserIdsObj).contains(secureUserId)) { + return true; + } + } + + short authenticatorId = KMHardwareAuthToken.cast(hwAuthToken).getAuthenticatorId(); + if (!KMInteger.cast(authenticatorId).isZero()) { + if (KMIntegerArrayTag.cast(secureUserIdsObj).contains(authenticatorId)) { + return true; + } + } + return false; + } + + private boolean authTokenMatches(short userSecureIdsPtr, short authType, byte[] scratchPad) { + if (data[HW_TOKEN] == KMType.INVALID_VALUE) { + return false; + } + if (!isHwAuthTokenContainsMatchingSecureId(data[HW_TOKEN], userSecureIdsPtr)) { + return false; + } + // check auth type + tmpVariables[2] = KMHardwareAuthToken.cast(data[HW_TOKEN]).getHwAuthenticatorType(); + tmpVariables[2] = KMEnum.cast(tmpVariables[2]).getVal(); + if (((byte) tmpVariables[2] & (byte) authType) == 0) { + return false; + } + return true; + } + + private void authorizeUserSecureIdAuthTimeout(KMOperationState op, byte[] scratchPad) { + short authTime; + short authType; + // Authorize User Secure Id and Auth timeout + short userSecureIdPtr = + KMKeyParameters.findTag(KMType.ULONG_ARRAY_TAG, KMType.USER_SECURE_ID, data[HW_PARAMETERS]); + if (userSecureIdPtr != KMType.INVALID_VALUE) { + // Authentication required. + if (KMType.INVALID_VALUE + != KMKeyParameters.findTag( + KMType.BOOL_TAG, KMType.NO_AUTH_REQUIRED, data[HW_PARAMETERS])) { + // Key has both USER_SECURE_ID and NO_AUTH_REQUIRED + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + // authenticator type must be provided. + if (KMType.INVALID_VALUE + == (authType = KMEnumTag.getValue(KMType.USER_AUTH_TYPE, data[HW_PARAMETERS]))) { + // Authentication required, but no auth type found. + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); + } + + short authTimeoutTagPtr = + KMKeyParameters.findTag(KMType.UINT_TAG, KMType.AUTH_TIMEOUT, data[HW_PARAMETERS]); + if (authTimeoutTagPtr != KMType.INVALID_VALUE) { + // authenticate user + if (!authTokenMatches(userSecureIdPtr, authType, scratchPad)) { + KMException.throwIt(KMError.KEY_USER_NOT_AUTHENTICATED); + } + + authTimeoutTagPtr = + KMKeyParameters.findTag( + KMType.ULONG_TAG, KMType.AUTH_TIMEOUT_MILLIS, data[CUSTOM_TAGS]); + if (authTimeoutTagPtr == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + authTime = KMIntegerTag.cast(authTimeoutTagPtr).getValue(); + // set the one time auth + op.setOneTimeAuthReqd(true); + // set the authentication time stamp in operation state + authTime = + addIntegers( + authTime, KMHardwareAuthToken.cast(data[HW_TOKEN]).getTimestamp(), scratchPad); + op.setAuthTime( + KMInteger.cast(authTime).getBuffer(), KMInteger.cast(authTime).getStartOff()); + // auth time validation will happen in update or finish + op.setAuthTimeoutValidated(false); + } else { + // auth per operation required + // store user secure id and authType in OperationState. + op.setUserSecureId(userSecureIdPtr); + op.setAuthType((byte) authType); + // set flags + op.setOneTimeAuthReqd(false); + op.setAuthPerOperationReqd(true); + } + } + } + + private boolean verifyHwTokenMacInBigEndian(short hwToken, byte[] scratchPad) { + // The challenge, userId and authenticatorId, authenticatorType and timestamp + // are in network order (big-endian). + short len = 0; + // add 0 + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + len = 1; + // concatenate challenge - 8 bytes + short ptr = KMHardwareAuthToken.cast(hwToken).getChallenge(); + KMInteger.cast(ptr) + .value( + scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length()))); + len += KMInteger.UINT_64; + // concatenate user id - 8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getUserId(); + KMInteger.cast(ptr) + .value( + scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length()))); + len += KMInteger.UINT_64; + // concatenate authenticator id - 8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getAuthenticatorId(); + KMInteger.cast(ptr) + .value( + scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length()))); + len += KMInteger.UINT_64; + // concatenate authenticator type - 4 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getHwAuthenticatorType(); + scratchPad[(short) (len + 3)] = KMEnum.cast(ptr).getVal(); + len += KMInteger.UINT_32; + // concatenate timestamp -8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getTimestamp(); + KMInteger.cast(ptr) + .value( + scratchPad, (short) (len + (short) (KMInteger.UINT_64 - KMInteger.cast(ptr).length()))); + len += KMInteger.UINT_64; + + ptr = KMHardwareAuthToken.cast(hwToken).getMac(); + + return seProvider.hmacVerify( + kmDataStore.getComputedHmacKey(), + scratchPad, + (short) 0, + len, + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()); + } + + private boolean verifyHwTokenMacInLittleEndian(short hwToken, byte[] scratchPad) { + // The challenge, userId and authenticatorId values are in little endian order, + // but authenticatorType and timestamp are in network order (big-endian). + short len = 0; + // add 0 + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); + len = 1; + // concatenate challenge - 8 bytes + short ptr = KMHardwareAuthToken.cast(hwToken).getChallenge(); + KMInteger.cast(ptr).toLittleEndian(scratchPad, len); + len += KMInteger.UINT_64; + // concatenate user id - 8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getUserId(); + KMInteger.cast(ptr).toLittleEndian(scratchPad, len); + len += KMInteger.UINT_64; + // concatenate authenticator id - 8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getAuthenticatorId(); + KMInteger.cast(ptr).toLittleEndian(scratchPad, len); + len += KMInteger.UINT_64; + // concatenate authenticator type - 4 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getHwAuthenticatorType(); + scratchPad[(short) (len + 3)] = KMEnum.cast(ptr).getVal(); + len += KMInteger.UINT_32; + // concatenate timestamp - 8 bytes + ptr = KMHardwareAuthToken.cast(hwToken).getTimestamp(); + KMInteger.cast(ptr) + .value(scratchPad, (short) (len + (short) (8 - KMInteger.cast(ptr).length()))); + len += KMInteger.UINT_64; + + ptr = KMHardwareAuthToken.cast(hwToken).getMac(); + + return seProvider.hmacVerify( + kmDataStore.getComputedHmacKey(), + scratchPad, + (short) 0, + len, + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()); + } + + private boolean validateHwToken(short hwToken, byte[] scratchPad) { + // CBOR Encoding is always big endian + short ptr = KMHardwareAuthToken.cast(hwToken).getMac(); + // If mac length is zero then token is empty. + if (KMByteBlob.cast(ptr).length() == 0) { + return false; + } + if (KMConfigurations.TEE_MACHINE_TYPE == KMConfigurations.LITTLE_ENDIAN) { + return verifyHwTokenMacInLittleEndian(hwToken, scratchPad); + } else { + return verifyHwTokenMacInBigEndian(hwToken, scratchPad); + } + } + + private short importKeyCmd(APDU apdu) { + short cmd = KMArray.instance((short) 6); + // Arguments + short params = KMKeyParameters.expAny(); + KMArray.cast(cmd).add((short) 0, params); + KMArray.cast(cmd).add((short) 1, KMEnum.instance(KMType.KEY_FORMAT)); + KMArray.cast(cmd).add((short) 2, KMByteBlob.exp()); + KMArray.cast(cmd).add((short) 3, KMByteBlob.exp()); // attest key + KMArray.cast(cmd).add((short) 4, params); // attest key params + KMArray.cast(cmd).add((short) 5, KMByteBlob.exp()); // issuer + return receiveIncoming(apdu, cmd); + } + + private void processImportKeyCmd(APDU apdu) { + // Receive the incoming request fully from the host into buffer. + short cmd = importKeyCmd(apdu); + byte[] scratchPad = apdu.getBuffer(); + data[KEY_PARAMETERS] = KMArray.cast(cmd).get((short) 0); + short keyFmt = KMArray.cast(cmd).get((short) 1); + data[IMPORTED_KEY_BLOB] = KMArray.cast(cmd).get((short) 2); + data[ATTEST_KEY_BLOB] = KMArray.cast(cmd).get((short) 3); + data[ATTEST_KEY_PARAMS] = KMArray.cast(cmd).get((short) 4); + data[ATTEST_KEY_ISSUER] = KMArray.cast(cmd).get((short) 5); + keyFmt = KMEnum.cast(keyFmt).getVal(); + + data[CERTIFICATE] = KMArray.instance((short) 0); // by default the cert is empty. + data[ORIGIN] = KMType.IMPORTED; + // ID_IMEI should be present if ID_SECOND_IMEI is present + short attIdTag = + KMKeyParameters.findTag( + KMType.BYTES_TAG, KMType.ATTESTATION_ID_SECOND_IMEI, data[KEY_PARAMETERS]); + if (attIdTag != KMType.INVALID_VALUE) { + KMTag.assertPresence( + data[KEY_PARAMETERS], + KMType.BYTES_TAG, + KMType.ATTESTATION_ID_IMEI, + KMError.CANNOT_ATTEST_IDS); + } + importKey(apdu, keyFmt, scratchPad); + } + + private void validateImportKey(short params, short keyFmt) { + short attKeyPurpose = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, params); + // ATTEST_KEY cannot be combined with any other purpose. + if (attKeyPurpose != KMType.INVALID_VALUE + && KMEnumArrayTag.cast(attKeyPurpose).contains(KMType.ATTEST_KEY) + && KMEnumArrayTag.cast(attKeyPurpose).length() > 1) { + KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); + } + // Rollback protection not supported + KMTag.assertAbsence( + params, + KMType.BOOL_TAG, + KMType.ROLLBACK_RESISTANCE, + KMError.ROLLBACK_RESISTANCE_UNAVAILABLE); + // As per specification, Early boot keys may not be imported at all, if Tag::EARLY_BOOT_ONLY is + // provided to IKeyMintDevice::importKey + KMTag.assertAbsence(params, KMType.BOOL_TAG, KMType.EARLY_BOOT_ONLY, KMError.EARLY_BOOT_ENDED); + // Check if the tags are supported. + if (KMKeyParameters.hasUnsupportedTags(params)) { + KMException.throwIt(KMError.UNSUPPORTED_TAG); + } + // Algorithm must be present + KMTag.assertPresence(params, KMType.ENUM_TAG, KMType.ALGORITHM, KMError.INVALID_ARGUMENT); + short alg = KMEnumTag.getValue(KMType.ALGORITHM, params); + // key format must be raw if aes, des or hmac and pkcs8 for rsa and ec. + if ((alg == KMType.AES || alg == KMType.DES || alg == KMType.HMAC) && keyFmt != KMType.RAW) { + KMException.throwIt(KMError.UNIMPLEMENTED); + } + if ((alg == KMType.RSA || alg == KMType.EC) && keyFmt != KMType.PKCS8) { + KMException.throwIt(KMError.UNIMPLEMENTED); + } + } + + private void importKey(APDU apdu, short keyFmt, byte[] scratchPad) { + validateImportKey(data[KEY_PARAMETERS], keyFmt); + // Check algorithm and dispatch to appropriate handler. + short alg = KMEnumTag.getValue(KMType.ALGORITHM, data[KEY_PARAMETERS]); + switch (alg) { + case KMType.RSA: + importRSAKey(scratchPad); + break; + case KMType.AES: + importAESKey(scratchPad); + break; + case KMType.DES: + importTDESKey(scratchPad); + break; + case KMType.HMAC: + importHmacKey(scratchPad); + break; + case KMType.EC: + importECKeys(scratchPad); + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + break; + } + makeKeyCharacteristics(scratchPad); + KMAttestationCert cert = + generateAttestation(data[ATTEST_KEY_BLOB], data[ATTEST_KEY_PARAMS], scratchPad); + createEncryptedKeyBlob(scratchPad); + sendOutgoing(apdu, cert, data[CERTIFICATE], data[KEY_BLOB], data[KEY_CHARACTERISTICS]); + } + + private void importECKeys(byte[] scratchPad) { + // Decode key material + KMAsn1Parser pkcs8 = KMAsn1Parser.instance(); + short keyBlob = pkcs8.decodeEc(data[IMPORTED_KEY_BLOB]); + data[PUB_KEY] = KMArray.cast(keyBlob).get((short) 0); + data[SECRET] = KMArray.cast(keyBlob).get((short) 1); + // initialize 256 bit p256 key for given private key and public key. + short index = 0; + // check whether the key size tag is present in key parameters. + short SecretLen = (short) (KMByteBlob.length(data[SECRET]) * 8); + short keySize = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + if (keySize != KMType.INVALID_VALUE) { + // As per NIST.SP.800-186 page 9, secret for 256 curve should be between + // 256-383 + if (((256 <= SecretLen) && (383 >= SecretLen)) ^ keySize == 256) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + if (keySize != 256) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + } else { + if ((256 > SecretLen) || (383 < SecretLen)) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + // add the key size to scratchPad + keySize = KMInteger.uint_16((short) 256); + keySize = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, keySize); + Util.setShort(scratchPad, index, keySize); + index += 2; + } + // check the curve if present in key parameters. + short curve = KMEnumTag.getValue(KMType.ECCURVE, data[KEY_PARAMETERS]); + if (curve != KMType.INVALID_VALUE) { + // As per NIST.SP.800-186 page 9, secret length for 256 curve should be between + // 256-383 + if (((256 <= SecretLen) && (383 >= SecretLen)) ^ curve == KMType.P_256) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + if (curve != KMType.P_256) { + KMException.throwIt(KMError.UNSUPPORTED_EC_CURVE); + } + } else { + if ((256 > SecretLen) || (383 < SecretLen)) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + // add the curve to scratchPad + curve = KMEnumTag.instance(KMType.ECCURVE, KMType.P_256); + Util.setShort(scratchPad, index, curve); + index += 2; + } + + // Check whether key can be created + seProvider.importAsymmetricKey( + KMType.EC, + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + KMByteBlob.cast(data[PUB_KEY]).getBuffer(), + KMByteBlob.cast(data[PUB_KEY]).getStartOff(), + KMByteBlob.cast(data[PUB_KEY]).length()); + + // add scratch pad to key parameters + updateKeyParameters(scratchPad, index); + data[KEY_BLOB] = createKeyBlobInstance(ASYM_KEY_TYPE); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PUB_KEY, data[PUB_KEY]); + } + + private void importHmacKey(byte[] scratchPad) { + // Get Key + data[SECRET] = data[IMPORTED_KEY_BLOB]; + // create HMAC key of up to 512 bit + short index = 0; // index in scratchPad for update params + // check the keysize tag if present in key parameters. + short keysize = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + if (keysize != KMType.INVALID_VALUE) { + if (!(keysize >= 64 && keysize <= 512 && keysize % 8 == 0)) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + if (keysize != (short) (KMByteBlob.length(data[SECRET]) * 8)) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + } else { + // add the key size to scratchPad + keysize = (short) (KMByteBlob.length(data[SECRET]) * 8); + if (!(keysize >= 64 && keysize <= 512 && keysize % 8 == 0)) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + keysize = KMInteger.uint_16(keysize); + short keySizeTag = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, keysize); + Util.setShort(scratchPad, index, keySizeTag); + index += 2; + } + // Check whether key can be created + seProvider.importSymmetricKey( + KMType.HMAC, + keysize, + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length()); + + // update the key parameters list + updateKeyParameters(scratchPad, index); + // validate HMAC Key parameters + validateHmacKey(); + data[KEY_BLOB] = createKeyBlobInstance(SYM_KEY_TYPE); + } + + private void importTDESKey(byte[] scratchPad) { + // Decode Key Material + data[SECRET] = data[IMPORTED_KEY_BLOB]; + short index = 0; // index in scratchPad for update params + // check the keysize tag if present in key parameters. + short keysize = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + if (keysize != KMType.INVALID_VALUE) { + if (keysize != 168) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + if (192 != (short) (8 * KMByteBlob.length(data[SECRET]))) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + } else { + keysize = (short) (KMByteBlob.length(data[SECRET]) * 8); + if (keysize != 192) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + // add the key size to scratchPad + keysize = KMInteger.uint_16((short) 168); + short keysizeTag = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, keysize); + Util.setShort(scratchPad, index, keysizeTag); + index += 2; + } + // Read Minimum Mac length - it must not be present + KMTag.assertAbsence( + data[KEY_PARAMETERS], KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, KMError.INVALID_TAG); + // Check whether key can be created + seProvider.importSymmetricKey( + KMType.DES, + keysize, + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length()); + // update the key parameters list + updateKeyParameters(scratchPad, index); + data[KEY_BLOB] = createKeyBlobInstance(SYM_KEY_TYPE); + } + + private void validateAesKeySize(short keySizeBits) { + if (keySizeBits != 128 && keySizeBits != 256) { + KMException.throwIt(KMError.UNSUPPORTED_KEY_SIZE); + } + } + + private void importAESKey(byte[] scratchPad) { + // Get Key + data[SECRET] = data[IMPORTED_KEY_BLOB]; + // create 128 or 256 bit AES key + short index = 0; // index in scratchPad for update params + // check the keysize tag if present in key parameters. + short keysize = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + if (keysize != KMType.INVALID_VALUE) { + if (keysize != (short) (8 * KMByteBlob.length(data[SECRET]))) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + validateAesKeySize(keysize); + } else { + // add the key size to scratchPad + keysize = (short) (8 * KMByteBlob.cast(data[SECRET]).length()); + validateAesKeySize(keysize); + keysize = KMInteger.uint_16(keysize); + short keysizeTag = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, keysize); + Util.setShort(scratchPad, index, keysizeTag); + index += 2; + } + // Check whether key can be created + seProvider.importSymmetricKey( + KMType.AES, + keysize, + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length()); + + // update the key parameters list + updateKeyParameters(scratchPad, index); + // validate AES Key parameters + validateAESKey(); + data[KEY_BLOB] = createKeyBlobInstance(SYM_KEY_TYPE); + } + + private void importRSAKey(byte[] scratchPad) { + // Decode key material + KMAsn1Parser pkcs8 = KMAsn1Parser.instance(); + short keyblob = pkcs8.decodeRsa(data[IMPORTED_KEY_BLOB]); + data[PUB_KEY] = KMArray.cast(keyblob).get((short) 0); + short pubKeyExp = KMArray.cast(keyblob).get((short) 1); + data[SECRET] = KMArray.cast(keyblob).get((short) 2); + if (F4.length != KMByteBlob.cast(pubKeyExp).length()) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + if (Util.arrayCompare( + F4, + (short) 0, + KMByteBlob.cast(pubKeyExp).getBuffer(), + KMByteBlob.cast(pubKeyExp).getStartOff(), + (short) F4.length) + != 0) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + short index = 0; // index in scratchPad for update parameters. + // validate public exponent if present in key params - it must be 0x010001 + short len = + KMIntegerTag.getValue( + scratchPad, + (short) 10, // using offset 10 as first 10 bytes reserved for update params + KMType.ULONG_TAG, + KMType.RSA_PUBLIC_EXPONENT, + data[KEY_PARAMETERS]); + if (len != KMTag.INVALID_VALUE) { + if (len != 4 + || Util.getShort(scratchPad, (short) 10) != 0x01 + || Util.getShort(scratchPad, (short) 12) != 0x01) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + } else { + // add public exponent to scratchPad + Util.setShort(scratchPad, (short) 10, (short) 0x01); + Util.setShort(scratchPad, (short) 12, (short) 0x01); + pubKeyExp = KMInteger.uint_32(scratchPad, (short) 10); + pubKeyExp = KMIntegerTag.instance(KMType.ULONG_TAG, KMType.RSA_PUBLIC_EXPONENT, pubKeyExp); + Util.setShort(scratchPad, index, pubKeyExp); + index += 2; + } + + // check the keysize tag if present in key parameters. + short keysize = + KMIntegerTag.getShortValue(KMType.UINT_TAG, KMType.KEYSIZE, data[KEY_PARAMETERS]); + short kSize = (short) (KMByteBlob.length(data[PUB_KEY]) * 8); + if (keysize != KMType.INVALID_VALUE) { + if (keysize != 2048 || (keysize != kSize)) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + } else { + if (2048 != kSize) { + KMException.throwIt(KMError.IMPORT_PARAMETER_MISMATCH); + } + // add the key size to scratchPad + keysize = KMInteger.uint_16((short) 2048); + keysize = KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, keysize); + Util.setShort(scratchPad, index, keysize); + index += 2; + } + + // Check whether key can be created + seProvider.importAsymmetricKey( + KMType.RSA, + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + KMByteBlob.cast(data[PUB_KEY]).getBuffer(), + KMByteBlob.cast(data[PUB_KEY]).getStartOff(), + KMByteBlob.cast(data[PUB_KEY]).length()); + + // update the key parameters list + updateKeyParameters(scratchPad, index); + // validate RSA Key parameters + validateRSAKey(scratchPad); + data[KEY_BLOB] = createKeyBlobInstance(ASYM_KEY_TYPE); + KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PUB_KEY, data[PUB_KEY]); + } + + private void updateKeyParameters(byte[] newParams, short len) { + if (len == 0) { + return; // nothing to update + } + // Create Update Param array and copy current params + short params = KMKeyParameters.cast(data[KEY_PARAMETERS]).getVals(); + len = (short) (KMArray.cast(params).length() + (short) (len / 2)); + short updatedParams = KMArray.instance(len); // update params + + len = KMArray.cast(params).length(); + short index = 0; + + // copy the existing key parameters to updated array + while (index < len) { + short tag = KMArray.cast(params).get(index); + KMArray.cast(updatedParams).add(index, tag); + index++; + } + + // copy new parameters to updated array + len = KMArray.cast(updatedParams).length(); + short newParamIndex = 0; // index in ptrArr + while (index < len) { + short tag = Util.getShort(newParams, newParamIndex); + KMArray.cast(updatedParams).add(index, tag); + index++; + newParamIndex += 2; + } + // replace with updated key parameters. + data[KEY_PARAMETERS] = KMKeyParameters.instance(updatedParams); + } + + private short initStrongBoxCmd(APDU apdu) { + short cmd = KMArray.instance((short) 3); + KMArray.cast(cmd).add((short) 0, KMInteger.exp()); // OS version + KMArray.cast(cmd).add((short) 1, KMInteger.exp()); // OS patch level + KMArray.cast(cmd).add((short) 2, KMInteger.exp()); // Vendor patch level + return receiveIncoming(apdu, cmd); + } + + // This command is executed to set the boot parameters. + // releaseAllOperations has to be called on every boot, so + // it is called from inside initStrongBoxCmd. Later in future if + // initStrongBoxCmd is removed, then make sure that releaseAllOperations + // is moved to a place where it is called on every boot. + private void processInitStrongBoxCmd(APDU apdu) { + short cmd = initStrongBoxCmd(apdu); + + short osVersion = KMArray.cast(cmd).get((short) 0); + short osPatchLevel = KMArray.cast(cmd).get((short) 1); + short vendorPatchLevel = KMArray.cast(cmd).get((short) 2); + setOsVersion(osVersion); + setOsPatchLevel(osPatchLevel); + setVendorPatchLevel(vendorPatchLevel); + kmDataStore.setDeviceBootStatus(KMKeymintDataStore.SET_SYSTEM_PROPERTIES_SUCCESS); + } + + public void reboot() { + // flag to maintain early boot ended state + kmDataStore.setEarlyBootEndedStatus(false); + // Clear all the operation state. + releaseAllOperations(); + // Hmac is cleared, so generate a new Hmac nonce. + initHmacNonceAndSeed(); + // Clear all auth tags. + kmDataStore.removeAllAuthTags(); + } + + protected void initSystemBootParams( + short osVersion, short osPatchLevel, short vendorPatchLevel, short bootPatchLevel) { + osVersion = KMInteger.uint_16(osVersion); + osPatchLevel = KMInteger.uint_16(osPatchLevel); + vendorPatchLevel = KMInteger.uint_16((short) vendorPatchLevel); + setOsVersion(osVersion); + setOsPatchLevel(osPatchLevel); + setVendorPatchLevel(vendorPatchLevel); + } + + protected void setOsVersion(short version) { + kmDataStore.setOsVersion( + KMInteger.cast(version).getBuffer(), + KMInteger.cast(version).getStartOff(), + KMInteger.cast(version).length()); + } + + protected void setOsPatchLevel(short patch) { + kmDataStore.setOsPatch( + KMInteger.cast(patch).getBuffer(), + KMInteger.cast(patch).getStartOff(), + KMInteger.cast(patch).length()); + } + + protected void setVendorPatchLevel(short patch) { + kmDataStore.setVendorPatchLevel( + KMInteger.cast(patch).getBuffer(), + KMInteger.cast(patch).getStartOff(), + KMInteger.cast(patch).length()); + } + + private short generateKeyCmd(APDU apdu) { + short params = KMKeyParameters.expAny(); + short blob = KMByteBlob.exp(); + // Array of expected arguments + short cmd = KMArray.instance((short) 4); + KMArray.cast(cmd).add((short) 0, params); // key params + KMArray.cast(cmd).add((short) 1, blob); // attest key + KMArray.cast(cmd).add((short) 2, params); // attest key params + KMArray.cast(cmd).add((short) 3, blob); // issuer + return receiveIncoming(apdu, cmd); + } + + private void processGenerateKey(APDU apdu) { + // Receive the incoming request fully from the host into buffer. + short cmd = generateKeyCmd(apdu); + // Re-purpose the apdu buffer as scratch pad. + byte[] scratchPad = apdu.getBuffer(); + data[KEY_PARAMETERS] = KMArray.cast(cmd).get((short) 0); + data[ATTEST_KEY_BLOB] = KMArray.cast(cmd).get((short) 1); + data[ATTEST_KEY_PARAMS] = KMArray.cast(cmd).get((short) 2); + data[ATTEST_KEY_ISSUER] = KMArray.cast(cmd).get((short) 3); + data[CERTIFICATE] = KMType.INVALID_VALUE; // by default the cert is empty. + // ROLLBACK_RESISTANCE not supported. + KMTag.assertAbsence( + data[KEY_PARAMETERS], + KMType.BOOL_TAG, + KMType.ROLLBACK_RESISTANCE, + KMError.ROLLBACK_RESISTANCE_UNAVAILABLE); + + // Algorithm must be present + KMTag.assertPresence( + data[KEY_PARAMETERS], KMType.ENUM_TAG, KMType.ALGORITHM, KMError.INVALID_ARGUMENT); + + // Check if the tags are supported. + if (KMKeyParameters.hasUnsupportedTags(data[KEY_PARAMETERS])) { + KMException.throwIt(KMError.UNSUPPORTED_TAG); + } + + // ID_IMEI should be present if ID_SECOND_IMEI is present + short attIdTag = + KMKeyParameters.findTag( + KMType.BYTES_TAG, KMType.ATTESTATION_ID_SECOND_IMEI, data[KEY_PARAMETERS]); + if (attIdTag != KMType.INVALID_VALUE) { + KMTag.assertPresence( + data[KEY_PARAMETERS], + KMType.BYTES_TAG, + KMType.ATTESTATION_ID_IMEI, + KMError.CANNOT_ATTEST_IDS); + } + + short attKeyPurpose = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, data[KEY_PARAMETERS]); + // ATTEST_KEY cannot be combined with any other purpose. + if (attKeyPurpose != KMType.INVALID_VALUE + && KMEnumArrayTag.cast(attKeyPurpose).contains(KMType.ATTEST_KEY) + && KMEnumArrayTag.cast(attKeyPurpose).length() > 1) { + KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); + } + short alg = KMEnumTag.getValue(KMType.ALGORITHM, data[KEY_PARAMETERS]); + // Check algorithm and dispatch to appropriate handler. + switch (alg) { + case KMType.RSA: + generateRSAKey(scratchPad); + break; + case KMType.AES: + generateAESKey(scratchPad); + break; + case KMType.DES: + generateTDESKey(scratchPad); + break; + case KMType.HMAC: + generateHmacKey(scratchPad); + break; + case KMType.EC: + generateECKeys(scratchPad); + break; + default: + KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); + break; + } + // create key blob and associated attestation. + data[ORIGIN] = KMType.GENERATED; + makeKeyCharacteristics(scratchPad); + // construct the certificate and place the encoded data in data[CERTIFICATE] + KMAttestationCert cert = + generateAttestation(data[ATTEST_KEY_BLOB], data[ATTEST_KEY_PARAMS], scratchPad); + createEncryptedKeyBlob(scratchPad); + sendOutgoing(apdu, cert, data[CERTIFICATE], data[KEY_BLOB], data[KEY_CHARACTERISTICS]); + } + + private short getApplicationId(short params) { + short appId = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_ID, params); + if (appId != KMTag.INVALID_VALUE) { + appId = KMByteTag.cast(appId).getValue(); + if (KMByteBlob.cast(appId).length() == 0) { + // Treat empty as INVALID. + return KMType.INVALID_VALUE; + } + } + return appId; + } + + private short getApplicationData(short params) { + short appData = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_DATA, params); + if (appData != KMTag.INVALID_VALUE) { + appData = KMByteTag.cast(appData).getValue(); + if (KMByteBlob.cast(appData).length() == 0) { + // Treat empty as INVALID. + return KMType.INVALID_VALUE; + } + } + return appData; + } + + private short getAttestationMode(short attKeyBlob, short attChallenge) { + short alg = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, data[KEY_PARAMETERS]); + short mode = KMType.NO_CERT; + if (KMEnumTag.cast(alg).getValue() != KMType.RSA + && KMEnumTag.cast(alg).getValue() != KMType.EC) { + return mode; + } + // If attestation keyblob preset + if (attKeyBlob != KMType.INVALID_VALUE && KMByteBlob.cast(attKeyBlob).length() > 0) { + // No attestation challenge present then it is an error + if (attChallenge == KMType.INVALID_VALUE || KMByteBlob.cast(attChallenge).length() <= 0) { + KMException.throwIt(KMError.ATTESTATION_CHALLENGE_MISSING); + } else { + mode = KMType.ATTESTATION_CERT; + } + } else { // no attestation key blob + // Attestation challenge present then it is an error because no factory provisioned attest key + if (attChallenge != KMType.INVALID_VALUE && KMByteBlob.cast(attChallenge).length() > 0) { + KMException.throwIt(KMError.ATTESTATION_KEYS_NOT_PROVISIONED); + } else if (KMEnumArrayTag.contains(KMType.PURPOSE, KMType.ATTEST_KEY, data[HW_PARAMETERS]) + || KMEnumArrayTag.contains(KMType.PURPOSE, KMType.SIGN, data[HW_PARAMETERS])) { + // The Purpose value can be read from either data[HW_PARAMETERS] or data[KEY_PARAMETERS] + // as the values will be same, and they are cryptographically bound. + mode = KMType.SELF_SIGNED_CERT; + } else { + mode = KMType.FAKE_CERT; + } + } + return mode; + } + + private KMAttestationCert generateAttestation( + short attKeyBlob, short attKeyParam, byte[] scratchPad) { + // 1) If attestation key is present and attestation challenge is absent then it is an error. + // 2) If attestation key is absent and attestation challenge is present then it is an error as + // factory provisioned attestation key is not supported. + // 3) If both are present and issuer is absent or attest key purpose is not ATTEST_KEY then it + // is an error. + // 4) If the generated/imported keys are RSA or EC then validity period must be specified. + // Device Unique Attestation is not supported. + short heapStart = repository.getHeapIndex(); + KMTag.assertAbsence( + data[KEY_PARAMETERS], + KMType.BOOL_TAG, + KMType.DEVICE_UNIQUE_ATTESTATION, + KMError.CANNOT_ATTEST_IDS); + // Read attestation challenge if present + short attChallenge = + KMKeyParameters.findTag( + KMType.BYTES_TAG, KMType.ATTESTATION_CHALLENGE, data[KEY_PARAMETERS]); + if (attChallenge != KMType.INVALID_VALUE) { + attChallenge = KMByteTag.cast(attChallenge).getValue(); + } + // No attestation required for symmetric keys + short mode = getAttestationMode(attKeyBlob, attChallenge); + KMAttestationCert cert = null; + + switch (mode) { + case KMType.ATTESTATION_CERT: + cert = + makeAttestationCert( + attKeyBlob, attKeyParam, attChallenge, data[ATTEST_KEY_ISSUER], scratchPad); + break; + case KMType.SELF_SIGNED_CERT: + cert = makeSelfSignedCert(data[SECRET], data[PUB_KEY], mode, scratchPad); + break; + case KMType.FAKE_CERT: + // Generate certificate with no signature. + cert = makeSelfSignedCert(KMType.INVALID_VALUE, data[PUB_KEY], mode, scratchPad); + break; + default: + data[CERTIFICATE] = KMType.INVALID_VALUE; + return null; + } + // Certificate Data is converted to cbor and written to the end of the stack. + short certData = repository.allocReclaimableMemory(MAX_CERT_SIZE); + // Leave first 4 bytes for Array header and ByteBlob header. + cert.buffer(repository.getHeap(), (short) (certData + 4), (short) (MAX_CERT_SIZE - 4)); + // Build the certificate - this will sign the cert + cert.build(); + // Certificate is now built so the data in the heap starting from heapStart to the current + // heap index can be reused. So resetting the heap index to heapStart. + repository.setHeapIndex(heapStart); + data[CERTIFICATE] = certData; + return cert; + } + + // Encodes KeyCharacteristics at the end of the heap + private void encodeKeyCharacteristics(short keyChars) { + byte[] buffer = repository.getHeap(); + short prevReclaimIndex = repository.getHeapReclaimIndex(); + short ptr = repository.allocReclaimableMemory(MAX_KEY_CHARS_SIZE); + short len = encoder.encode(keyChars, buffer, ptr, prevReclaimIndex, MAX_KEY_CHARS_SIZE); + // shift the encoded KeyCharacteristics data towards the right till the data[CERTIFICATE] + // offset. + Util.arrayCopyNonAtomic(buffer, ptr, buffer, (short) (ptr + (MAX_KEY_CHARS_SIZE - len)), len); + // Reclaim the unused memory. + repository.reclaimMemory((short) (MAX_KEY_CHARS_SIZE - len)); + } + + // Encodes KeyBlob at the end of the heap + private void encodeKeyBlob(short keyBlobPtr) { + // allocate reclaimable memory. + byte[] buffer = repository.getHeap(); + short prevReclaimIndex = repository.getHeapReclaimIndex(); + short top = repository.allocReclaimableMemory(MAX_KEYBLOB_SIZE); + short keyBlob = encoder.encode(keyBlobPtr, buffer, top, prevReclaimIndex, MAX_KEYBLOB_SIZE); + Util.arrayCopyNonAtomic( + repository.getHeap(), + top, + repository.getHeap(), + (short) (top + MAX_KEYBLOB_SIZE - keyBlob), + keyBlob); + short newTop = (short) (top + MAX_KEYBLOB_SIZE - keyBlob); + // Encode the KeyBlob array inside a ByteString. Get the length of + // the ByteString header. + short encodedBytesLength = encoder.getEncodedBytesLength(keyBlob); + newTop -= encodedBytesLength; + encoder.encodeByteBlobHeader(keyBlob, buffer, newTop, encodedBytesLength); + // Reclaim unused memory. + repository.reclaimMemory((short) (newTop - top)); + } + + private short readKeyBlobVersion(short keyBlob) { + short version = KMType.INVALID_VALUE; + try { + version = + decoder.readKeyblobVersion( + KMByteBlob.cast(keyBlob).getBuffer(), + KMByteBlob.cast(keyBlob).getStartOff(), + KMByteBlob.cast(keyBlob).length()); + if (version == KMType.INVALID_VALUE) { + // If Version is not present. Then it is either an old KeyBlob or + // corrupted KeyBlob. + version = 0; + } else { + version = KMInteger.cast(version).getShort(); + if (version > KEYBLOB_CURRENT_VERSION || version < 0) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + } + } catch (Exception e) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + return version; + } + + private void readKeyBlobParams(short version, short parsedKeyBlob) { + data[KEY_BLOB] = parsedKeyBlob; + // initialize data + switch (version) { + case (short) 0: + data[SECRET] = KMArray.cast(parsedKeyBlob).get((short) 0); + data[NONCE] = KMArray.cast(parsedKeyBlob).get((short) 1); + data[AUTH_TAG] = KMArray.cast(parsedKeyBlob).get((short) 2); + data[KEY_CHARACTERISTICS] = KMArray.cast(parsedKeyBlob).get((short) 3); + data[PUB_KEY] = KMType.INVALID_VALUE; + if (KMArray.cast(parsedKeyBlob).length() == ASYM_KEY_BLOB_SIZE_V0) { + data[PUB_KEY] = KMArray.cast(parsedKeyBlob).get((short) 4); + } + // Set the data[KEY_BLOB_VERSION_DATA_OFFSET] with integer value of 0 so + // that it will used at later point of time. + data[KEY_BLOB_VERSION_DATA_OFFSET] = KMInteger.uint_8((byte) 0); + break; + case (short) 1: + data[KEY_BLOB_VERSION_DATA_OFFSET] = KMArray.cast(parsedKeyBlob).get((short) 0); + data[SECRET] = KMArray.cast(parsedKeyBlob).get((short) 1); + data[NONCE] = KMArray.cast(parsedKeyBlob).get((short) 2); + data[AUTH_TAG] = KMArray.cast(parsedKeyBlob).get((short) 3); + data[KEY_CHARACTERISTICS] = KMArray.cast(parsedKeyBlob).get((short) 4); + data[PUB_KEY] = KMType.INVALID_VALUE; + if (KMArray.cast(parsedKeyBlob).length() == ASYM_KEY_BLOB_SIZE_V1) { + data[PUB_KEY] = KMArray.cast(parsedKeyBlob).get((short) 5); + } + break; + case (short) 2: + case (short) 3: + data[SECRET] = KMArray.cast(parsedKeyBlob).get(KEY_BLOB_SECRET); + data[NONCE] = KMArray.cast(parsedKeyBlob).get(KEY_BLOB_NONCE); + data[AUTH_TAG] = KMArray.cast(parsedKeyBlob).get(KEY_BLOB_AUTH_TAG); + data[KEY_CHARACTERISTICS] = KMArray.cast(parsedKeyBlob).get(KEY_BLOB_PARAMS); + data[KEY_BLOB_VERSION_DATA_OFFSET] = + KMArray.cast(parsedKeyBlob).get(KEY_BLOB_VERSION_OFFSET); + data[CUSTOM_TAGS] = KMArray.cast(parsedKeyBlob).get(KEY_BLOB_CUSTOM_TAGS); + data[PUB_KEY] = KMType.INVALID_VALUE; + if (KMArray.cast(parsedKeyBlob).length() == ASYM_KEY_BLOB_SIZE_V2_V3) { + data[PUB_KEY] = KMArray.cast(parsedKeyBlob).get(KEY_BLOB_PUB_KEY); + } + break; + default: + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + } + + // Decode the KeyBlob from CBOR structures to the sub types of KMType. + private void decodeKeyBlob(short version, short keyBlob) { + // Decode KeyBlob and read the KeyBlob params based on the version. + short parsedBlob = + decoder.decodeArray( + createKeyBlobExp(version), + KMByteBlob.cast(keyBlob).getBuffer(), + KMByteBlob.cast(keyBlob).getStartOff(), + KMByteBlob.cast(keyBlob).length()); + short minArraySize = 0; + switch (version) { + case 0: + minArraySize = SYM_KEY_BLOB_SIZE_V0; + break; + case 1: + minArraySize = SYM_KEY_BLOB_SIZE_V1; + break; + case 2: + case 3: + minArraySize = SYM_KEY_BLOB_SIZE_V2_V3; + break; + default: + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + ; + // KeyBlob size should not be less than the minimum KeyBlob size. + if (KMArray.cast(parsedBlob).length() < minArraySize) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + readKeyBlobParams(version, parsedBlob); + } + + // Decrypts the secret key in the KeyBlob. The secret can be a Symmetric or Asymmetric key. + private void processDecryptSecret(short version, short appId, short appData, byte[] scratchPad) { + data[TEE_PARAMETERS] = KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getTeeEnforced(); + data[SB_PARAMETERS] = + KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getStrongboxEnforced(); + data[SW_PARAMETERS] = + KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getKeystoreEnforced(); + data[HW_PARAMETERS] = KMKeyParameters.makeHwEnforced(data[SB_PARAMETERS], data[TEE_PARAMETERS]); + + data[HIDDEN_PARAMETERS] = KMKeyParameters.makeHidden(appId, appData, data[ROT], scratchPad); + // Decrypt Secret and verify auth tag + decryptSecret(scratchPad, version); + short keyBlobSecretOff = 0; + switch (version) { + case 0: + // V0 KeyBlob + // KEY_BLOB = [ + // SECRET, + // NONCE, + // AUTH_TAG, + // KEY_CHARACTERISTICS, + // PUBKEY + // ] + keyBlobSecretOff = (short) 0; + break; + case 1: + // V1 KeyBlob + // KEY_BLOB = [ + // VERSION, + // SECRET, + // NONCE, + // AUTH_TAG, + // KEY_CHARACTERISTICS, + // PUBKEY + // ] + keyBlobSecretOff = (short) 1; + break; + case 2: + case 3: + // V2 KeyBlob + // KEY_BLOB = [ + // VERSION, + // SECRET, + // NONCE, + // AUTH_TAG, + // KEY_CHARACTERISTICS, + // CUSTOM_TAGS, + // PUBKEY + // ] + keyBlobSecretOff = KEY_BLOB_SECRET; + break; + default: + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + ; + KMArray.cast(data[KEY_BLOB]).add(keyBlobSecretOff, data[SECRET]); + } + + private void parseEncryptedKeyBlob( + short keyBlob, short appId, short appData, byte[] scratchPad, short version) { + // make root of trust blob + data[ROT] = readROT(scratchPad, version); + if (data[ROT] == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + try { + decodeKeyBlob(version, keyBlob); + processDecryptSecret(version, appId, appData, scratchPad); + } catch (Exception e) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + } + + private void decryptSecret(byte[] scratchPad, short version) { + // derive master key - stored in derivedKey + short len; + short authDataOff = 0; + short authDataLen = 0; + byte[] authDataBuff = null; + switch (version) { + case 3: + len = deriveKey(scratchPad); + break; + + case 2: + case 1: + case 0: + makeAuthData(version, scratchPad); + len = deriveKeyForOldKeyBlobs(scratchPad); + authDataBuff = repository.getHeap(); + authDataOff = data[AUTH_DATA]; + authDataLen = data[AUTH_DATA_LENGTH]; + break; + default: + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + if (!seProvider.aesGCMDecrypt( + KMByteBlob.cast(data[DERIVED_KEY]).getBuffer(), + KMByteBlob.cast(data[DERIVED_KEY]).getStartOff(), + KMByteBlob.cast(data[DERIVED_KEY]).length(), + KMByteBlob.cast(data[SECRET]).getBuffer(), + KMByteBlob.cast(data[SECRET]).getStartOff(), + KMByteBlob.cast(data[SECRET]).length(), + scratchPad, + (short) 0, + KMByteBlob.cast(data[NONCE]).getBuffer(), + KMByteBlob.cast(data[NONCE]).getStartOff(), + KMByteBlob.cast(data[NONCE]).length(), + authDataBuff, + authDataOff, + authDataLen, + KMByteBlob.cast(data[AUTH_TAG]).getBuffer(), + KMByteBlob.cast(data[AUTH_TAG]).getStartOff(), + KMByteBlob.cast(data[AUTH_TAG]).length())) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + // Copy the decrypted secret + data[SECRET] = + KMByteBlob.instance(scratchPad, (short) 0, KMByteBlob.cast(data[SECRET]).length()); + } + + private short addIntegers(short authTime, short timeStamp, byte[] scratchPad) { + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 24, (byte) 0); + Util.arrayCopyNonAtomic( + KMInteger.cast(authTime).getBuffer(), + KMInteger.cast(authTime).getStartOff(), + scratchPad, + (short) (8 - KMInteger.cast(timeStamp).length()), + KMInteger.cast(timeStamp).length()); + + // Copy timestamp to scratchpad + Util.arrayCopyNonAtomic( + KMInteger.cast(timeStamp).getBuffer(), + KMInteger.cast(timeStamp).getStartOff(), + scratchPad, + (short) (16 - KMInteger.cast(timeStamp).length()), + KMInteger.cast(timeStamp).length()); + + // add authTime in millis to timestamp. + KMUtils.add(scratchPad, (short) 0, (short) 8, (short) 16); + return KMInteger.uint_64(scratchPad, (short) 16); + } + + public void powerReset() { + // TODO handle power reset signal. + releaseAllOperations(); + resetWrappingKey(); + } + + private void updateTrustedConfirmationOperation(KMOperationState op) { + if (op.isTrustedConfirmationRequired()) { + op.getTrustedConfirmationSigner() + .update( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length()); + } + } + + private void finishTrustedConfirmationOperation(KMOperationState op) { + // Perform trusted confirmation if required + if (op.isTrustedConfirmationRequired()) { + if (0 == KMByteBlob.cast(data[CONFIRMATION_TOKEN]).length()) { + KMException.throwIt(KMError.NO_USER_CONFIRMATION); + } + + boolean verified = + op.getTrustedConfirmationSigner() + .verify( + KMByteBlob.cast(data[INPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[INPUT_DATA]).getStartOff(), + KMByteBlob.cast(data[INPUT_DATA]).length(), + KMByteBlob.cast(data[CONFIRMATION_TOKEN]).getBuffer(), + KMByteBlob.cast(data[CONFIRMATION_TOKEN]).getStartOff(), + KMByteBlob.cast(data[CONFIRMATION_TOKEN]).length()); + if (!verified) { + KMException.throwIt(KMError.NO_USER_CONFIRMATION); + } + } + } + + private boolean isDigestSupported(short alg, short digest) { + switch (alg) { + case KMType.RSA: + case KMType.EC: + if (digest != KMType.DIGEST_NONE && digest != KMType.SHA2_256) { + return false; + } + break; + case KMType.HMAC: + if (digest != KMType.SHA2_256) { + return false; + } + break; + default: + break; + } + return true; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMKeymintDataStore.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMKeymintDataStore.java new file mode 100644 index 0000000..65117eb --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMKeymintDataStore.java @@ -0,0 +1,1075 @@ +package com.android.javacard.keymaster; + +import com.android.javacard.seprovider.KMDataStoreConstants; +import com.android.javacard.seprovider.KMException; +import com.android.javacard.seprovider.KMKey; +import com.android.javacard.seprovider.KMSEProvider; +import com.android.javacard.seprovider.KMUpgradable; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; +import org.globalplatform.upgrade.Element; + +/** + * This is a storage class which helps in storing the provisioned data, ROT, OS version, Boot patch + * level, Vendor Patchlevel, HMAC nonce, computed shared secret, 8 auth tags, device-locked, + * device-locked timestamp and device-locked password only. Only the provisioned data is restored + * back during applet upgrades and the remaining data is flushed. + */ +public class KMKeymintDataStore implements KMUpgradable { + + // Data table configuration + public static final short KM_APPLET_PACKAGE_VERSION_1 = 0x0100; + public static final short KM_APPLET_PACKAGE_VERSION_2 = 0x0200; + public static final short KM_APPLET_PACKAGE_VERSION_3 = 0x0300; + public static final short KM_APPLET_PACKAGE_VERSION_4 = 0x0400; + public static final byte DATA_INDEX_SIZE = 17; + public static final byte DATA_INDEX_ENTRY_SIZE = 4; + public static final byte DATA_INDEX_ENTRY_LENGTH = 0; + public static final byte DATA_INDEX_ENTRY_OFFSET = 2; + public static final short DATA_MEM_SIZE = 300; + // Data table offsets + public static final byte HMAC_NONCE = 0; + public static final byte BOOT_OS_VERSION = 1; + public static final byte BOOT_OS_PATCH_LEVEL = 2; + public static final byte VENDOR_PATCH_LEVEL = 3; + public static final byte DEVICE_LOCKED_TIME = 4; + public static final byte DEVICE_LOCKED = 5; + public static final byte DEVICE_LOCKED_PASSWORD_ONLY = 6; + // Total 8 auth tags, so the next offset is AUTH_TAG_1 + 8 + public static final byte AUTH_TAG_1 = 7; + public static final byte DEVICE_STATUS_FLAG = 15; + public static final byte EARLY_BOOT_ENDED_FLAG = 16; + // Data Item sizes + public static final byte HMAC_SEED_NONCE_SIZE = 32; + public static final byte COMPUTED_HMAC_KEY_SIZE = 32; + public static final byte OS_VERSION_SIZE = 4; + public static final byte OS_PATCH_SIZE = 4; + public static final byte VENDOR_PATCH_SIZE = 4; + public static final byte DEVICE_LOCK_TS_SIZE = 8; + public static final byte MAX_BLOB_STORAGE = 8; + public static final byte AUTH_TAG_LENGTH = 16; + public static final byte AUTH_TAG_COUNTER_SIZE = 4; + public static final byte AUTH_TAG_ENTRY_SIZE = (AUTH_TAG_LENGTH + AUTH_TAG_COUNTER_SIZE + 1); + // Device boot states. Applet starts executing the + // core commands once all the states are set. The commands + // that are allowed irrespective of these states are: + // All the provision commands + // INS_GET_HW_INFO_CMD + // INS_ADD_RNG_ENTROPY_CMD + // INS_COMPUTE_SHARED_HMAC_CMD + // INS_GET_HMAC_SHARING_PARAM_CMD + public static final byte SET_BOOT_PARAMS_SUCCESS = 0x01; + public static final byte SET_SYSTEM_PROPERTIES_SUCCESS = 0x02; + public static final byte NEGOTIATED_SHARED_SECRET_SUCCESS = 0x04; + // Old Data table offsets + private static final byte OLD_PROVISIONED_STATUS_OFFSET = 18; + private static final byte SHARED_SECRET_KEY_SIZE = 32; + private static final byte DEVICE_STATUS_FLAG_SIZE = 1; + private static final short UDS_CERT_CHAIN_MAX_SIZE = 2500; // First 2 bytes for length. + private static final short DICE_CERT_CHAIN_MAX_SIZE = 512; + private static KMKeymintDataStore kmDataStore; + // Secure Boot Mode + public byte secureBootMode; + // Data - originally was in repository + private byte[] attIdBrand; + private byte[] attIdDevice; + private byte[] attIdProduct; + private byte[] attIdSerial; + private byte[] attIdImei; + private byte[] attIdSecondImei; + private byte[] attIdMeId; + private byte[] attIdManufacturer; + private byte[] attIdModel; + // Boot parameters + private byte[] verifiedHash; + private byte[] bootKey; + private byte[] bootPatchLevel; + private boolean deviceBootLocked; + private short bootState; + // Challenge for Root of trust + private byte[] challenge; + + /* + * Applets upgrading to KeyMint3.0 may not have the second imei provisioned. + * So this flag is used to ignore the SECOND_IMEI tag if the previous Applet's + * KeyMint version is less than 3.0. + */ + public boolean ignoreSecondImei; + private short dataIndex; + private byte[] dataTable; + private KMSEProvider seProvider; + private KMRepository repository; + private byte[] udsCertChain; + private byte[] diceCertChain; + private KMKey masterKey; + private KMKey deviceUniqueKeyPair; + private KMKey preSharedKey; + private KMKey computedHmacKey; + private KMKey rkpMacKey; + private byte[] oemRootPublicKey; + private short provisionStatus; + + public KMKeymintDataStore(KMSEProvider provider, KMRepository repo) { + seProvider = provider; + repository = repo; + boolean isUpgrading = provider.isUpgrading(); + initDataTable(); + // Initialize the device locked status + if (!isUpgrading) { + udsCertChain = new byte[UDS_CERT_CHAIN_MAX_SIZE]; + diceCertChain = new byte[DICE_CERT_CHAIN_MAX_SIZE]; + oemRootPublicKey = new byte[65]; + } + setDeviceLockPasswordOnly(false); + setDeviceLock(false); + kmDataStore = this; + } + + public static KMKeymintDataStore instance() { + return kmDataStore; + } + + private void initDataTable() { + if (dataTable == null) { + dataTable = new byte[DATA_MEM_SIZE]; + dataIndex = (short) (DATA_INDEX_SIZE * DATA_INDEX_ENTRY_SIZE); + } + } + + private short dataAlloc(short length) { + if (((short) (dataIndex + length)) > dataTable.length) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + dataIndex += length; + return (short) (dataIndex - length); + } + + private void clearDataEntry(short id) { + id = (short) (id * DATA_INDEX_ENTRY_SIZE); + short dataLen = Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_LENGTH)); + if (dataLen != 0) { + short dataPtr = Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_OFFSET)); + JCSystem.beginTransaction(); + Util.arrayFillNonAtomic(dataTable, dataPtr, dataLen, (byte) 0); + JCSystem.commitTransaction(); + } + } + + private void writeDataEntry(short id, byte[] buf, short offset, short len) { + short dataPtr; + id = (short) (id * DATA_INDEX_ENTRY_SIZE); + short dataLen = Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_LENGTH)); + if (dataLen == 0) { + dataPtr = dataAlloc(len); + JCSystem.beginTransaction(); + Util.setShort(dataTable, (short) (id + DATA_INDEX_ENTRY_OFFSET), dataPtr); + Util.setShort(dataTable, (short) (id + DATA_INDEX_ENTRY_LENGTH), len); + Util.arrayCopyNonAtomic(buf, offset, dataTable, dataPtr, len); + JCSystem.commitTransaction(); + } else { + if (len != dataLen) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + dataPtr = Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_OFFSET)); + JCSystem.beginTransaction(); + Util.arrayCopyNonAtomic(buf, offset, dataTable, dataPtr, len); + JCSystem.commitTransaction(); + } + } + + private short readDataEntry(short id, byte[] buf, short offset) { + id = (short) (id * DATA_INDEX_ENTRY_SIZE); + short len = Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_LENGTH)); + if (len != 0) { + Util.arrayCopyNonAtomic( + dataTable, + Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_OFFSET)), + buf, + offset, + len); + } + return len; + } + + private short readDataEntry(byte[] dataTable, short id, byte[] buf, short offset) { + id = (short) (id * DATA_INDEX_ENTRY_SIZE); + short len = Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_LENGTH)); + if (len != 0) { + Util.arrayCopyNonAtomic( + dataTable, + Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_OFFSET)), + buf, + offset, + len); + } + return len; + } + + private short dataLength(short id) { + id = (short) (id * DATA_INDEX_ENTRY_SIZE); + return Util.getShort(dataTable, (short) (id + DATA_INDEX_ENTRY_LENGTH)); + } + + public short readData(short id) { + short len = dataLength(id); + if (len != 0) { + short blob = KMByteBlob.instance(dataLength(id)); + readDataEntry(id, KMByteBlob.cast(blob).getBuffer(), KMByteBlob.cast(blob).getStartOff()); + return blob; + } + return KMType.INVALID_VALUE; + } + + public short getHmacNonce() { + return readData(HMAC_NONCE); + } + + public short getOsVersion() { + short blob = readData(BOOT_OS_VERSION); + if (blob == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_DATA); + } + return KMInteger.uint_32( + KMByteBlob.cast(blob).getBuffer(), KMByteBlob.cast(blob).getStartOff()); + } + + public short getVendorPatchLevel() { + short blob = readData(VENDOR_PATCH_LEVEL); + if (blob == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_DATA); + } + return KMInteger.uint_32( + KMByteBlob.cast(blob).getBuffer(), KMByteBlob.cast(blob).getStartOff()); + } + + public short getOsPatch() { + short blob = readData(BOOT_OS_PATCH_LEVEL); + if (blob == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_DATA); + } + return KMInteger.uint_32( + KMByteBlob.cast(blob).getBuffer(), KMByteBlob.cast(blob).getStartOff()); + } + + private boolean readBoolean(short id) { + short blob = readData(id); + if (blob == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_DATA); + } + return (byte) ((repository.getHeap())[KMByteBlob.cast(blob).getStartOff()]) == 0x01; + } + + public boolean getDeviceLock() { + return readBoolean(DEVICE_LOCKED); + } + + public void setDeviceLock(boolean flag) { + writeBoolean(DEVICE_LOCKED, flag); + } + + public boolean getDeviceLockPasswordOnly() { + return readBoolean(DEVICE_LOCKED_PASSWORD_ONLY); + } + + public void setDeviceLockPasswordOnly(boolean flag) { + writeBoolean(DEVICE_LOCKED_PASSWORD_ONLY, flag); + } + + public boolean getEarlyBootEndedStatus() { + return readBoolean(EARLY_BOOT_ENDED_FLAG); + } + + public void setEarlyBootEndedStatus(boolean flag) { + writeBoolean(EARLY_BOOT_ENDED_FLAG, flag); + } + + public short getDeviceTimeStamp() { + short blob = readData(DEVICE_LOCKED_TIME); + if (blob == KMType.INVALID_VALUE) { + KMException.throwIt(KMError.INVALID_DATA); + } + return KMInteger.uint_64( + KMByteBlob.cast(blob).getBuffer(), KMByteBlob.cast(blob).getStartOff()); + } + + public void setOsVersion(byte[] buf, short start, short len) { + if (len != OS_VERSION_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + writeDataEntry(BOOT_OS_VERSION, buf, start, len); + } + + public void setVendorPatchLevel(byte[] buf, short start, short len) { + if (len != VENDOR_PATCH_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + writeDataEntry(VENDOR_PATCH_LEVEL, buf, start, len); + } + + private void writeBoolean(short id, boolean flag) { + short start = repository.alloc((short) 1); + if (flag) { + (repository.getHeap())[start] = (byte) 0x01; + } else { + (repository.getHeap())[start] = (byte) 0x00; + } + writeDataEntry(id, repository.getHeap(), start, (short) 1); + } + + public void setDeviceLockTimestamp(byte[] buf, short start, short len) { + if (len != DEVICE_LOCK_TS_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + writeDataEntry(DEVICE_LOCKED_TIME, buf, start, len); + } + + public void clearDeviceBootStatus() { + clearDataEntry(DEVICE_STATUS_FLAG); + } + + public void setDeviceBootStatus(byte initStatus) { + short offset = repository.allocReclaimableMemory(DEVICE_STATUS_FLAG_SIZE); + byte[] buf = repository.getHeap(); + getDeviceBootStatus(buf, offset); + buf[offset] |= initStatus; + writeDataEntry(DEVICE_STATUS_FLAG, buf, offset, DEVICE_STATUS_FLAG_SIZE); + repository.reclaimMemory(DEVICE_STATUS_FLAG_SIZE); + } + + public boolean isDeviceReady() { + boolean result = false; + short offset = repository.allocReclaimableMemory(DEVICE_STATUS_FLAG_SIZE); + byte[] buf = repository.getHeap(); + getDeviceBootStatus(buf, offset); + byte bootCompleteStatus = + (SET_BOOT_PARAMS_SUCCESS + | SET_SYSTEM_PROPERTIES_SUCCESS + | NEGOTIATED_SHARED_SECRET_SUCCESS); + if (bootCompleteStatus == (buf[offset] & bootCompleteStatus)) { + result = true; + } + repository.reclaimMemory(DEVICE_STATUS_FLAG_SIZE); + return result; + } + + public short getDeviceBootStatus(byte[] scratchpad, short offset) { + scratchpad[offset] = 0; + return readDataEntry(DEVICE_STATUS_FLAG, scratchpad, offset); + } + + public void clearDeviceLockTimeStamp() { + clearDataEntry(DEVICE_LOCKED_TIME); + } + + public void setOsPatch(byte[] buf, short start, short len) { + if (len != OS_PATCH_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + writeDataEntry(BOOT_OS_PATCH_LEVEL, buf, start, len); + } + + private boolean isAuthTagSlotAvailable(short tagId, byte[] buf, short offset) { + readDataEntry(tagId, buf, offset); + return (0 == buf[offset]); + } + + public void initHmacNonce(byte[] nonce, short offset, short len) { + if (len != HMAC_SEED_NONCE_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + writeDataEntry(HMAC_NONCE, nonce, offset, len); + } + + public void clearHmacNonce() { + clearDataEntry(HMAC_NONCE); + } + + public boolean persistAuthTag(short authTag) { + + if (KMByteBlob.cast(authTag).length() != AUTH_TAG_LENGTH) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + + short authTagEntry = repository.alloc(AUTH_TAG_ENTRY_SIZE); + short scratchPadOff = repository.alloc(AUTH_TAG_ENTRY_SIZE); + byte[] scratchPad = repository.getHeap(); + writeAuthTagState(repository.getHeap(), authTagEntry, (byte) 1); + Util.arrayCopyNonAtomic( + KMByteBlob.cast(authTag).getBuffer(), + KMByteBlob.cast(authTag).getStartOff(), + repository.getHeap(), + (short) (authTagEntry + 1), + AUTH_TAG_LENGTH); + Util.setShort( + repository.getHeap(), (short) (authTagEntry + AUTH_TAG_LENGTH + 1 + 2), (short) 1); + short index = 0; + while (index < MAX_BLOB_STORAGE) { + if ((dataLength((short) (index + AUTH_TAG_1)) == 0) + || isAuthTagSlotAvailable((short) (index + AUTH_TAG_1), scratchPad, scratchPadOff)) { + + writeDataEntry( + (short) (index + AUTH_TAG_1), repository.getHeap(), authTagEntry, AUTH_TAG_ENTRY_SIZE); + return true; + } + index++; + } + return false; + } + + public void removeAllAuthTags() { + short index = 0; + while (index < MAX_BLOB_STORAGE) { + clearDataEntry((short) (index + AUTH_TAG_1)); + index++; + } + } + + public boolean isAuthTagPersisted(short authTag) { + return (KMType.INVALID_VALUE != findTag(authTag)); + } + + private short findTag(short authTag) { + if (KMByteBlob.cast(authTag).length() != AUTH_TAG_LENGTH) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + short index = 0; + short found; + short offset = repository.alloc(AUTH_TAG_ENTRY_SIZE); + while (index < MAX_BLOB_STORAGE) { + if (dataLength((short) (index + AUTH_TAG_1)) != 0) { + readDataEntry((short) (index + AUTH_TAG_1), repository.getHeap(), offset); + found = + Util.arrayCompare( + repository.getHeap(), + (short) (offset + 1), + KMByteBlob.cast(authTag).getBuffer(), + KMByteBlob.cast(authTag).getStartOff(), + AUTH_TAG_LENGTH); + if (found == 0) { + return (short) (index + AUTH_TAG_1); + } + } + index++; + } + return KMType.INVALID_VALUE; + } + + public short getRateLimitedKeyCount(short authTag, byte[] out, short outOff) { + short tag = findTag(authTag); + short blob; + if (tag != KMType.INVALID_VALUE) { + blob = readData(tag); + Util.arrayCopyNonAtomic( + KMByteBlob.cast(blob).getBuffer(), + (short) (KMByteBlob.cast(blob).getStartOff() + AUTH_TAG_LENGTH + 1), + out, + outOff, + AUTH_TAG_COUNTER_SIZE); + return AUTH_TAG_COUNTER_SIZE; + } + return (short) 0; + } + + public void setRateLimitedKeyCount(short authTag, byte[] buf, short off, short len) { + short tag = findTag(authTag); + if (tag != KMType.INVALID_VALUE) { + short dataPtr = readData(tag); + Util.arrayCopyNonAtomic( + buf, + off, + KMByteBlob.cast(dataPtr).getBuffer(), + (short) (KMByteBlob.cast(dataPtr).getStartOff() + AUTH_TAG_LENGTH + 1), + len); + writeDataEntry( + tag, + KMByteBlob.cast(dataPtr).getBuffer(), + KMByteBlob.cast(dataPtr).getStartOff(), + KMByteBlob.cast(dataPtr).length()); + } + } + + public void persistUdsCertChain(byte[] buf, short offset, short len) { + // Input buffer contains encoded Uds certificate chain as shown below. + // UdsDKSignatures = { + // + SignerName => DKCertChain + // } + // SignerName = tstr + // DKCertChain = [ + // 2* Certificate // Root -> Leaf. Root is the vendo r + // // self-signed cert, leaf contains DK_pu b + // ] + // Certificate = COSE_Sign1 of a public key + if ((short) (len + 2) > UDS_CERT_CHAIN_MAX_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + JCSystem.beginTransaction(); + Util.setShort(udsCertChain, (short) 0, (short) len); + Util.arrayCopyNonAtomic(buf, offset, udsCertChain, (short) 2, len); + JCSystem.commitTransaction(); + } + + public short getUdsCertChainLength() { + return Util.getShort(udsCertChain, (short) 0); + } + + public byte[] getUdsCertChain() { + return udsCertChain; + } + + public byte[] getDiceCertificateChain() { + return diceCertChain; + } + + public void persistBootCertificateChain(byte[] buf, short offset, short len) { + if ((short) (len + 2) > DICE_CERT_CHAIN_MAX_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + JCSystem.beginTransaction(); + Util.setShort(diceCertChain, (short) 0, (short) len); + Util.arrayCopyNonAtomic(buf, offset, diceCertChain, (short) 2, len); + JCSystem.commitTransaction(); + } + + private void writeAuthTagState(byte[] buf, short offset, byte state) { + buf[offset] = state; + } + + // The master key should only be generated during applet installation and + // during a device factory reset event. + public KMKey createMasterKey(short keySizeBits) { + if (masterKey == null) { + masterKey = seProvider.createMasterKey(masterKey, keySizeBits); + } + return (KMKey) masterKey; + } + + public KMKey regenerateMasterKey() { + return seProvider.createMasterKey(masterKey, KMKeymasterApplet.MASTER_KEY_SIZE); + } + + public KMKey getMasterKey() { + return masterKey; + } + + public void createPresharedKey(byte[] keyData, short offset, short length) { + if (length != SHARED_SECRET_KEY_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + if (preSharedKey == null) { + preSharedKey = seProvider.createPreSharedKey(preSharedKey, keyData, offset, length); + } + } + + public KMKey getPresharedKey() { + if (preSharedKey == null) { + KMException.throwIt(KMError.INVALID_DATA); + } + return preSharedKey; + } + + public void createComputedHmacKey(byte[] keyData, short offset, short length) { + if (length != COMPUTED_HMAC_KEY_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + if (computedHmacKey == null) { + computedHmacKey = seProvider.createComputedHmacKey(computedHmacKey, keyData, offset, length); + } else { + seProvider.createComputedHmacKey(computedHmacKey, keyData, offset, length); + } + } + + public KMKey getComputedHmacKey() { + if (computedHmacKey == null) { + KMException.throwIt(KMError.INVALID_DATA); + } + return computedHmacKey; + } + + public KMKey createRkpDeviceUniqueKeyPair( + byte[] pubKey, + short pubKeyOff, + short pubKeyLen, + byte[] privKey, + short privKeyOff, + short privKeyLen) { + if (deviceUniqueKeyPair == null) { + deviceUniqueKeyPair = + seProvider.createRkpDeviceUniqueKeyPair( + deviceUniqueKeyPair, pubKey, pubKeyOff, pubKeyLen, privKey, privKeyOff, privKeyLen); + } else { + seProvider.createRkpDeviceUniqueKeyPair( + deviceUniqueKeyPair, pubKey, pubKeyOff, pubKeyLen, privKey, privKeyOff, privKeyLen); + } + return deviceUniqueKeyPair; + } + + public KMKey getRkpDeviceUniqueKeyPair() { + return ((KMKey) deviceUniqueKeyPair); + } + + public void createRkpMacKey(byte[] keydata, short offset, short length) { + if (rkpMacKey == null) { + rkpMacKey = seProvider.createRkpMacKey(rkpMacKey, keydata, offset, length); + } else { + seProvider.createRkpMacKey(rkpMacKey, keydata, offset, length); + } + } + + public KMKey getRkpMacKey() { + if (rkpMacKey == null) { + KMException.throwIt(KMError.INVALID_DATA); + } + return rkpMacKey; + } + + public short getAttestationId(short tag, byte[] buffer, short start) { + byte[] attestId = null; + switch (tag) { + // Attestation Id Brand + case KMType.ATTESTATION_ID_BRAND: + attestId = attIdBrand; + break; + // Attestation Id Device + case KMType.ATTESTATION_ID_DEVICE: + attestId = attIdDevice; + break; + // Attestation Id Product + case KMType.ATTESTATION_ID_PRODUCT: + attestId = attIdProduct; + break; + // Attestation Id Serial + case KMType.ATTESTATION_ID_SERIAL: + attestId = attIdSerial; + break; + // Attestation Id IMEI + case KMType.ATTESTATION_ID_IMEI: + attestId = attIdImei; + break; + // Attestation Id SECOND IMEI + case KMType.ATTESTATION_ID_SECOND_IMEI: + attestId = attIdSecondImei; + break; + // Attestation Id MEID + case KMType.ATTESTATION_ID_MEID: + attestId = attIdMeId; + break; + // Attestation Id Manufacturer + case KMType.ATTESTATION_ID_MANUFACTURER: + attestId = attIdManufacturer; + break; + // Attestation Id Model + case KMType.ATTESTATION_ID_MODEL: + attestId = attIdModel; + break; + } + if (attestId == null) { + /* Ignore the SECOND_IMEI tag if the previous Applet's KeyMint version is less than 3.0 and + * no SECOND_IMEI is provisioned. + */ + if (kmDataStore.ignoreSecondImei && tag == KMType.ATTESTATION_ID_SECOND_IMEI) { + return (short) 0; + } + KMException.throwIt(KMError.CANNOT_ATTEST_IDS); + } + Util.arrayCopyNonAtomic(attestId, (short) 0, buffer, start, (short) attestId.length); + return (short) attestId.length; + } + + public void setAttestationId(short tag, byte[] buffer, short start, short length) { + switch (tag) { + // Attestation Id Brand + case KMType.ATTESTATION_ID_BRAND: + JCSystem.beginTransaction(); + attIdBrand = new byte[length]; + Util.arrayCopyNonAtomic(buffer, (short) start, attIdBrand, (short) 0, length); + JCSystem.commitTransaction(); + break; + // Attestation Id Device + case KMType.ATTESTATION_ID_DEVICE: + JCSystem.beginTransaction(); + attIdDevice = new byte[length]; + Util.arrayCopyNonAtomic(buffer, (short) start, attIdDevice, (short) 0, length); + JCSystem.commitTransaction(); + break; + // Attestation Id Product + case KMType.ATTESTATION_ID_PRODUCT: + JCSystem.beginTransaction(); + attIdProduct = new byte[length]; + Util.arrayCopyNonAtomic(buffer, (short) start, attIdProduct, (short) 0, length); + JCSystem.commitTransaction(); + break; + // Attestation Id Serial + case KMType.ATTESTATION_ID_SERIAL: + JCSystem.beginTransaction(); + attIdSerial = new byte[length]; + Util.arrayCopyNonAtomic(buffer, (short) start, attIdSerial, (short) 0, length); + JCSystem.commitTransaction(); + break; + // Attestation Id IMEI + case KMType.ATTESTATION_ID_IMEI: + JCSystem.beginTransaction(); + attIdImei = new byte[length]; + Util.arrayCopyNonAtomic(buffer, (short) start, attIdImei, (short) 0, length); + JCSystem.commitTransaction(); + break; + // Attestation Id SECOND IMEI + case KMType.ATTESTATION_ID_SECOND_IMEI: + JCSystem.beginTransaction(); + attIdSecondImei = new byte[length]; + Util.arrayCopyNonAtomic(buffer, (short) start, attIdSecondImei, (short) 0, length); + JCSystem.commitTransaction(); + break; + // Attestation Id MEID + case KMType.ATTESTATION_ID_MEID: + JCSystem.beginTransaction(); + attIdMeId = new byte[length]; + Util.arrayCopyNonAtomic(buffer, (short) start, attIdMeId, (short) 0, length); + JCSystem.commitTransaction(); + break; + // Attestation Id Manufacturer + case KMType.ATTESTATION_ID_MANUFACTURER: + JCSystem.beginTransaction(); + attIdManufacturer = new byte[length]; + Util.arrayCopyNonAtomic(buffer, (short) start, attIdManufacturer, (short) 0, length); + JCSystem.commitTransaction(); + break; + // Attestation Id Model + case KMType.ATTESTATION_ID_MODEL: + JCSystem.beginTransaction(); + attIdModel = new byte[length]; + Util.arrayCopyNonAtomic(buffer, (short) start, attIdModel, (short) 0, length); + JCSystem.commitTransaction(); + break; + } + } + + public void deleteAttestationIds() { + attIdBrand = null; + attIdDevice = null; + attIdProduct = null; + attIdSerial = null; + attIdImei = null; + attIdSecondImei = null; + attIdMeId = null; + attIdManufacturer = null; + attIdModel = null; + // Trigger garbage collection. + JCSystem.requestObjectDeletion(); + } + + public short getVerifiedBootHash(byte[] buffer, short start) { + if (verifiedHash == null) { + KMException.throwIt(KMError.INVALID_DATA); + } + Util.arrayCopyNonAtomic(verifiedHash, (short) 0, buffer, start, (short) verifiedHash.length); + return (short) verifiedHash.length; + } + + public short getBootKey(byte[] buffer, short start) { + if (bootKey == null) { + KMException.throwIt(KMError.INVALID_DATA); + } + Util.arrayCopyNonAtomic(bootKey, (short) 0, buffer, start, (short) bootKey.length); + return (short) bootKey.length; + } + + public short getBootState() { + return bootState; + } + + public void setBootState(short state) { + bootState = state; + } + + public boolean isDeviceBootLocked() { + return deviceBootLocked; + } + + public short getBootPatchLevel() { + if (bootPatchLevel == null) { + KMException.throwIt(KMError.INVALID_DATA); + } + return KMInteger.uint_32(bootPatchLevel, (short) 0); + } + + public void setVerifiedBootHash(byte[] buffer, short start, short length) { + if (verifiedHash == null) { + verifiedHash = new byte[32]; + } + if (length != KMKeymasterApplet.VERIFIED_BOOT_HASH_SIZE) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + Util.arrayCopy(buffer, start, verifiedHash, (short) 0, (short) 32); + } + + public void setBootKey(byte[] buffer, short start, short length) { + if (bootKey == null) { + bootKey = new byte[32]; + } + if (length != KMKeymasterApplet.VERIFIED_BOOT_KEY_SIZE) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + Util.arrayCopy(buffer, start, bootKey, (short) 0, (short) 32); + } + + public void setDeviceLocked(boolean state) { + deviceBootLocked = state; + } + + public void setBootPatchLevel(byte[] buffer, short start, short length) { + if (bootPatchLevel == null) { + bootPatchLevel = new byte[4]; + } + if (length > 4 || length < 0) { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + Util.arrayCopy(buffer, start, bootPatchLevel, (short) 0, (short) length); + } + + public void setChallenge(byte[] buf, short start, short length) { + if (challenge == null) { + challenge = new byte[16]; + } + if (length != 16) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + Util.arrayCopy(buf, start, challenge, (short) 0, (short) length); + } + + public short getChallenge(byte[] buffer, short start) { + if (challenge == null) { + KMException.throwIt(KMError.INVALID_DATA); + } + Util.arrayCopyNonAtomic(challenge, (short) 0, buffer, start, (short) challenge.length); + return (short) challenge.length; + } + + public boolean isProvisionLocked() { + if (0 != (provisionStatus & KMKeymasterApplet.PROVISION_STATUS_PROVISIONING_LOCKED)) { + return true; + } + return false; + } + + public short getProvisionStatus() { + return provisionStatus; + } + + public void setProvisionStatus(short pStatus) { + JCSystem.beginTransaction(); + provisionStatus |= pStatus; + JCSystem.commitTransaction(); + } + + public void unlockProvision() { + JCSystem.beginTransaction(); + provisionStatus &= ~KMKeymasterApplet.PROVISION_STATUS_PROVISIONING_LOCKED; + JCSystem.commitTransaction(); + } + + public void persistOEMRootPublicKey(byte[] inBuff, short inOffset, short inLength) { + if (inLength != 65) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + if (oemRootPublicKey == null) { + oemRootPublicKey = new byte[65]; + } + Util.arrayCopy(inBuff, inOffset, oemRootPublicKey, (short) 0, inLength); + } + + public byte[] getOEMRootPublicKey() { + if (oemRootPublicKey == null) { + KMException.throwIt(KMError.INVALID_DATA); + } + return oemRootPublicKey; + } + + @Override + public void onSave(Element element) { + // Prmitives + element.write(provisionStatus); + element.write(secureBootMode); + element.write(ignoreSecondImei); + // Objects + element.write(attIdBrand); + element.write(attIdDevice); + element.write(attIdProduct); + element.write(attIdSerial); + element.write(attIdImei); + element.write(attIdSecondImei); + element.write(attIdMeId); + element.write(attIdManufacturer); + element.write(attIdModel); + element.write(udsCertChain); + element.write(diceCertChain); + element.write(oemRootPublicKey); + + // Key Objects + seProvider.onSave(element, KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY, masterKey); + seProvider.onSave(element, KMDataStoreConstants.INTERFACE_TYPE_PRE_SHARED_KEY, preSharedKey); + seProvider.onSave( + element, KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR, deviceUniqueKeyPair); + seProvider.onSave(element, KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY, rkpMacKey); + } + + @Override + public void onRestore(Element element, short oldVersion, short currentVersion) { + if (oldVersion <= KM_APPLET_PACKAGE_VERSION_1) { + // 1.0 to 4.0 Upgrade happens here. + handlePreviousVersionUpgrade(element); + return; + } else if (oldVersion == KM_APPLET_PACKAGE_VERSION_2) { + handleUpgrade(element, oldVersion); + JCSystem.beginTransaction(); + // While upgrading Secure Boot Mode flag from 2.0 to 4.0, implementations + // have to update the secureBootMode with the correct input. + secureBootMode = 0; + provisionStatus |= KMKeymasterApplet.PROVISION_STATUS_SECURE_BOOT_MODE; + // Applet package Versions till 2 had CoseSign1 for additionalCertificateChain. + // From package version 3, the additionalCertificateChain is in X.509 format. + // So Unreference the old address and allocate new persistent memory. + udsCertChain = new byte[UDS_CERT_CHAIN_MAX_SIZE]; + JCSystem.commitTransaction(); + // Request for ObjectDeletion for unreferenced address of additionalCertChain. + JCSystem.requestObjectDeletion(); + return; + } + handleUpgrade(element, oldVersion); + } + + private void handlePreviousVersionUpgrade(Element element) { + // set ignore Imei flag to true. + ignoreSecondImei = true; + // Read Primitives + // restore old data table index + short oldDataIndex = element.readShort(); + element.readBoolean(); // pop deviceBootLocked + element.readShort(); // pop bootState + + // Read Objects + // restore old data table + byte[] oldDataTable = (byte[]) element.readObject(); + + attIdBrand = (byte[]) element.readObject(); + attIdDevice = (byte[]) element.readObject(); + attIdProduct = (byte[]) element.readObject(); + attIdSerial = (byte[]) element.readObject(); + attIdImei = (byte[]) element.readObject(); + attIdMeId = (byte[]) element.readObject(); + attIdManufacturer = (byte[]) element.readObject(); + attIdModel = (byte[]) element.readObject(); + element.readObject(); // pop verifiedHash + element.readObject(); // pop bootKey + element.readObject(); // pop bootPatchLevel + udsCertChain = (byte[]) element.readObject(); + diceCertChain = (byte[]) element.readObject(); + + // Read Key Objects + masterKey = (KMKey) seProvider.onRestore(element); + seProvider.onRestore(element); // pop computedHmacKey + preSharedKey = (KMKey) seProvider.onRestore(element); + deviceUniqueKeyPair = (KMKey) seProvider.onRestore(element); + rkpMacKey = (KMKey) seProvider.onRestore(element); + handleProvisionStatusUpgrade(oldDataTable, oldDataIndex); + } + + private void handleUpgrade(Element element, short oldVersion) { + // Read Primitives + provisionStatus = element.readShort(); + if (oldVersion >= KM_APPLET_PACKAGE_VERSION_3) { + secureBootMode = element.readByte(); + } + /* check if KeyMint is upgrading from older HAL version to KM300 + * and set the ignore second Imei flag + */ + if (oldVersion < KM_APPLET_PACKAGE_VERSION_4) { + ignoreSecondImei = true; + } else { + ignoreSecondImei = element.readBoolean(); + } + // Read Objects + attIdBrand = (byte[]) element.readObject(); + attIdDevice = (byte[]) element.readObject(); + attIdProduct = (byte[]) element.readObject(); + attIdSerial = (byte[]) element.readObject(); + attIdImei = (byte[]) element.readObject(); + if (oldVersion >= KM_APPLET_PACKAGE_VERSION_4) { + attIdSecondImei = (byte[]) element.readObject(); + } + attIdMeId = (byte[]) element.readObject(); + attIdManufacturer = (byte[]) element.readObject(); + attIdModel = (byte[]) element.readObject(); + udsCertChain = (byte[]) element.readObject(); + diceCertChain = (byte[]) element.readObject(); + oemRootPublicKey = (byte[]) element.readObject(); + // Read Key Objects + masterKey = (KMKey) seProvider.onRestore(element); + preSharedKey = (KMKey) seProvider.onRestore(element); + deviceUniqueKeyPair = (KMKey) seProvider.onRestore(element); + rkpMacKey = (KMKey) seProvider.onRestore(element); + } + + public void getProvisionStatus(byte[] dataTable, byte[] scratchpad, short offset) { + Util.setShort(scratchpad, offset, (short) 0); + readDataEntry(dataTable, OLD_PROVISIONED_STATUS_OFFSET, scratchpad, offset); + } + + void handleProvisionStatusUpgrade(byte[] dataTable, short dataTableIndex) { + short dInex = repository.allocReclaimableMemory((short) 2); + byte data[] = repository.getHeap(); + getProvisionStatus(dataTable, data, dInex); + short pStatus = (short) (data[dInex] & 0x00ff); + if (KMKeymasterApplet.PROVISION_STATUS_PROVISIONING_LOCKED + == (pStatus & KMKeymasterApplet.PROVISION_STATUS_PROVISIONING_LOCKED)) { + pStatus |= + KMKeymasterApplet.PROVISION_STATUS_SE_LOCKED + | KMKeymasterApplet.PROVISION_STATUS_SECURE_BOOT_MODE; + } + JCSystem.beginTransaction(); + // While upgrading Secure Boot Mode flag from 1.0 to 4.0, implementations + // have to update the secureBootMode with the correct input. + secureBootMode = 0; + provisionStatus = pStatus; + // Applet package Versions till 2 had CoseSign1 for additionalCertificateChain. + // From package version 3, the additionalCertificateChain is in X.509 format. + // So Unreference the old address and allocate new persistent memory. + udsCertChain = new byte[UDS_CERT_CHAIN_MAX_SIZE]; + JCSystem.commitTransaction(); + repository.reclaimMemory((short) 2); + // Request object deletion for unreferenced address for additionalCertChain + JCSystem.requestObjectDeletion(); + } + + @Override + public short getBackupPrimitiveByteCount() { + // provisionStatus - 2 bytes + // secureBootMode - 1 byte + // Flag for ignore second Imei- 1 byte + return (short) + (4 + + seProvider.getBackupPrimitiveByteCount(KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY) + + seProvider.getBackupPrimitiveByteCount( + KMDataStoreConstants.INTERFACE_TYPE_PRE_SHARED_KEY) + + seProvider.getBackupPrimitiveByteCount( + KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR) + + seProvider.getBackupPrimitiveByteCount( + KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY)); + } + + @Override + public short getBackupObjectCount() { + // AttestationIds - 9 + // UdsCertificateChain - 1 + // diceCertificateChain - 1 + // oemRootPublicKey - 1 + return (short) + (12 + + seProvider.getBackupObjectCount(KMDataStoreConstants.INTERFACE_TYPE_MASTER_KEY) + + seProvider.getBackupObjectCount(KMDataStoreConstants.INTERFACE_TYPE_PRE_SHARED_KEY) + + seProvider.getBackupObjectCount( + KMDataStoreConstants.INTERFACE_TYPE_DEVICE_UNIQUE_KEY_PAIR) + + seProvider.getBackupObjectCount(KMDataStoreConstants.INTERFACE_TYPE_RKP_MAC_KEY)); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMMap.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMMap.java new file mode 100644 index 0000000..2418204 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMMap.java @@ -0,0 +1,209 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMMap represents an array of a KMType key and a KMType value. Map is the sequence of pairs. Each + * pair is one or more sub-types of KMType. The KMMap instance maps to the CBOR type map. KMMap is a + * KMType and it further extends the value field in TLV_HEADER as MAP_HEADER struct{ short + * subType;short length;} followed by a sequence of pairs. Each pair contains a key and a value as + * short pointers to KMType instances. + */ +public class KMMap extends KMType { + + public static final short ANY_MAP_LENGTH = 0x1000; + private static final byte MAP_HEADER_SIZE = 4; + private static KMMap prototype; + + private KMMap() {} + + private static KMMap proto(short ptr) { + if (prototype == null) { + prototype = new KMMap(); + } + instanceTable[KM_MAP_OFFSET] = ptr; + return prototype; + } + + public static short exp() { + short ptr = instance(MAP_TYPE, (short) MAP_HEADER_SIZE); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), (short) 0); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), ANY_MAP_LENGTH); + return ptr; + } + + public static short instance(short length) { + short ptr = KMType.instance(MAP_TYPE, (short) (MAP_HEADER_SIZE + (length * 4))); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), (short) 0); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), length); + return ptr; + } + + public static short instance(short length, byte type) { + short ptr = instance(length); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), type); + return ptr; + } + + public static KMMap cast(short ptr) { + if (heap[ptr] != MAP_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public void add(short index, short keyPtr, short valPtr) { + short len = length(); + if (index >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + short keyIndex = + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index * 4)); + Util.setShort(heap, keyIndex, keyPtr); + Util.setShort(heap, (short) (keyIndex + 2), valPtr); + } + + public short getKey(short index) { + short len = length(); + if (index >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + return Util.getShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index * 4))); + } + + public short getKeyValue(short index) { + short len = length(); + if (index >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + return Util.getShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index * 4 + 2))); + } + + public void swap(short index1, short index2) { + short len = length(); + if (index1 >= len || index2 >= len) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + // Swap keys + short indexPtr1 = + Util.getShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index1 * 4))); + short indexPtr2 = + Util.getShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index2 * 4))); + Util.setShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index1 * 4)), + indexPtr2); + Util.setShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index2 * 4)), + indexPtr1); + + // Swap Values + indexPtr1 = + Util.getShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index1 * 4 + 2))); + indexPtr2 = + Util.getShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index2 * 4 + 2))); + Util.setShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index1 * 4 + 2)), + indexPtr2); + Util.setShort( + heap, + (short) + (instanceTable[KM_MAP_OFFSET] + + TLV_HEADER_SIZE + + MAP_HEADER_SIZE + + (short) (index2 * 4 + 2)), + indexPtr1); + } + + public void canonicalize() { + KMCoseMap.canonicalize(instanceTable[KM_MAP_OFFSET], length()); + } + + public short containedType() { + return Util.getShort(heap, (short) (instanceTable[KM_MAP_OFFSET] + TLV_HEADER_SIZE)); + } + + public short getStartOff() { + return (short) (instanceTable[KM_MAP_OFFSET] + TLV_HEADER_SIZE + MAP_HEADER_SIZE); + } + + public short length() { + return Util.getShort(heap, (short) (instanceTable[KM_MAP_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public byte[] getBuffer() { + return heap; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMNInteger.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMNInteger.java new file mode 100644 index 0000000..3a6404e --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMNInteger.java @@ -0,0 +1,134 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * Represents 8-bit, 16-bit, 32-bit and 64-bit signed integer. It corresponds to CBOR int type. + * struct{byte NEG_INTEGER_TYPE; short length; 4 or 8 bytes of value} + */ +public class KMNInteger extends KMInteger { + + public static final byte SIGNED_MASK = (byte) 0x80; + private static KMNInteger prototype; + + private KMNInteger() {} + + private static KMNInteger proto(short ptr) { + if (prototype == null) { + prototype = new KMNInteger(); + } + instanceTable[KM_NEG_INTEGER_OFFSET] = ptr; + return prototype; + } + + public static short exp() { + return KMType.exp(NEG_INTEGER_TYPE); + } + + // return an empty integer instance + public static short instance(short length) { + if ((length <= 0) || (length > 8)) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + if (length > 4) { + length = KMInteger.UINT_64; + } else { + length = KMInteger.UINT_32; + } + return KMType.instance(NEG_INTEGER_TYPE, length); + } + + public static short instance(byte[] num, short srcOff, short length) { + if (length > 8) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + if (length == 1) { + return uint_8(num[srcOff]); + } else if (length == 2) { + return uint_16(Util.getShort(num, srcOff)); + } else if (length == 4) { + return uint_32(num, srcOff); + } else { + return uint_64(num, srcOff); + } + } + + public static KMNInteger cast(short ptr) { + byte[] heap = repository.getHeap(); + if (heap[ptr] != NEG_INTEGER_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + 1)) == INVALID_VALUE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + // create integer and copy byte value + public static short uint_8(byte num) { + if (num >= 0) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(KMInteger.UINT_32); + heap[(short) (ptr + TLV_HEADER_SIZE + 3)] = num; + return ptr; + } + + // create integer and copy short value + public static short uint_16(short num) { + if (num >= 0) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(KMInteger.UINT_32); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), num); + return ptr; + } + + // create integer and copy integer value + public static short uint_32(byte[] num, short offset) { + if (!isSignedInteger(num, offset)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(KMInteger.UINT_32); + Util.arrayCopy(num, offset, heap, (short) (ptr + TLV_HEADER_SIZE), KMInteger.UINT_32); + return ptr; + } + + // create integer and copy integer value + public static short uint_64(byte[] num, short offset) { + if (!isSignedInteger(num, offset)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = instance(KMInteger.UINT_64); + Util.arrayCopy(num, offset, heap, (short) (ptr + TLV_HEADER_SIZE), KMInteger.UINT_64); + return ptr; + } + + public static boolean isSignedInteger(byte[] num, short offset) { + byte val = num[offset]; + return SIGNED_MASK == (val & SIGNED_MASK); + } + + @Override + protected short getBaseOffset() { + return instanceTable[KM_NEG_INTEGER_OFFSET]; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMOperationState.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMOperationState.java new file mode 100644 index 0000000..2a53acd --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMOperationState.java @@ -0,0 +1,354 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import com.android.javacard.seprovider.KMException; +import com.android.javacard.seprovider.KMOperation; +import javacard.framework.JCSystem; +import javacard.framework.Util; + +/** + * KMOperationState is the container of an active operation started by beginOperation function. This + * operation state is persisted by the applet in non volatile memory. However, this state is not + * retained if applet is upgraded. There will be four operation state records maintained i.e. only + * four active operations are supported at any given time. + */ +public class KMOperationState { + + // sizes + public static final byte OPERATION_HANDLE_SIZE = 8; + public static final byte DATA_SIZE = 11; + public static final byte AUTH_TIME_SIZE = 8; + // Secure user ids 5 * 8 = 40 bytes ( Considering Maximum 5 SECURE USER IDs) + // First two bytes are reserved to store number of secure ids. So total 42 bytes. + public static final byte USER_SECURE_IDS_SIZE = 42; + // byte type + private static final byte ALG = 0; + private static final byte PURPOSE = 1; + private static final byte PADDING = 2; + private static final byte BLOCK_MODE = 3; + private static final byte DIGEST = 4; + private static final byte FLAGS = 5; + private static final byte KEY_SIZE = 6; + private static final byte MAC_LENGTH = 7; + private static final byte MGF_DIGEST = 8; + private static final byte AUTH_TYPE = 9; + private static final byte MIN_MAC_LENGTH = 10; + private static final byte OPERATION = 0; + private static final byte HMAC_SIGNER_OPERATION = 1; + // Flag masks + private static final byte AUTH_PER_OP_REQD = 1; + private static final byte SECURE_USER_ID_REQD = 2; + private static final byte AUTH_TIMEOUT_VALIDATED = 4; + private static final byte AES_GCM_UPDATE_ALLOWED = 8; + private static final byte PROCESSED_INPUT_MSG = 16; + // Max user secure ids. + private static final byte MAX_SECURE_USER_IDS = 5; + + // Object References + private byte[] opHandle; + private byte[] authTime; + private byte[] userSecureIds; + private short[] data; + private Object[] operations; + + public KMOperationState() { + opHandle = JCSystem.makeTransientByteArray(OPERATION_HANDLE_SIZE, JCSystem.CLEAR_ON_RESET); + authTime = JCSystem.makeTransientByteArray(AUTH_TIME_SIZE, JCSystem.CLEAR_ON_RESET); + data = JCSystem.makeTransientShortArray(DATA_SIZE, JCSystem.CLEAR_ON_RESET); + operations = JCSystem.makeTransientObjectArray((short) 2, JCSystem.CLEAR_ON_RESET); + userSecureIds = JCSystem.makeTransientByteArray(USER_SECURE_IDS_SIZE, JCSystem.CLEAR_ON_RESET); + reset(); + } + + public void reset() { + byte index = 0; + while (index < DATA_SIZE) { + data[index] = KMType.INVALID_VALUE; + index++; + } + Util.arrayFillNonAtomic(opHandle, (short) 0, OPERATION_HANDLE_SIZE, (byte) 0); + Util.arrayFillNonAtomic(authTime, (short) 0, AUTH_TIME_SIZE, (byte) 0); + + if (null != operations[OPERATION]) { + ((KMOperation) operations[OPERATION]).abort(); + } + operations[OPERATION] = null; + + if (null != operations[HMAC_SIGNER_OPERATION]) { + ((KMOperation) operations[HMAC_SIGNER_OPERATION]).abort(); + } + operations[HMAC_SIGNER_OPERATION] = null; + } + + public short compare(byte[] handle, short start, short len) { + return Util.arrayCompare(handle, start, opHandle, (short) 0, (short) opHandle.length); + } + + public short getKeySize() { + return data[KEY_SIZE]; + } + + public void setKeySize(short keySize) { + data[KEY_SIZE] = keySize; + } + + public short getHandle() { + return KMInteger.uint_64(opHandle, (short) 0); + } + + public void setHandle(byte[] buf, short start, short len) { + Util.arrayCopyNonAtomic(buf, start, opHandle, (short) 0, (short) opHandle.length); + } + + public short getPurpose() { + return data[PURPOSE]; + } + + public void setPurpose(short purpose) { + data[PURPOSE] = purpose; + } + + public boolean isInputMsgProcessed() { + return (data[FLAGS] & PROCESSED_INPUT_MSG) != 0; + } + + public KMOperation getOperation() { + return (KMOperation) operations[OPERATION]; + } + + public void setOperation(KMOperation op) { + operations[OPERATION] = op; + } + + public boolean isAuthPerOperationReqd() { + return (data[FLAGS] & AUTH_PER_OP_REQD) != 0; + } + + public void setAuthPerOperationReqd(boolean flag) { + if (flag) { + data[FLAGS] = (short) (data[FLAGS] | AUTH_PER_OP_REQD); + } else { + data[FLAGS] = (short) (data[FLAGS] & (~AUTH_PER_OP_REQD)); + } + } + + public boolean isAuthTimeoutValidated() { + return (data[FLAGS] & AUTH_TIMEOUT_VALIDATED) != 0; + } + + public void setAuthTimeoutValidated(boolean flag) { + if (flag) { + data[FLAGS] = (byte) (data[FLAGS] | AUTH_TIMEOUT_VALIDATED); + } else { + data[FLAGS] = (byte) (data[FLAGS] & (~AUTH_TIMEOUT_VALIDATED)); + } + } + + public boolean isSecureUserIdReqd() { + return (data[FLAGS] & SECURE_USER_ID_REQD) != 0; + } + + public short getAuthTime() { + return KMInteger.uint_64(authTime, (short) 0); + } + + public void setAuthTime(byte[] timeBuf, short start) { + Util.arrayCopyNonAtomic(timeBuf, start, authTime, (short) 0, AUTH_TIME_SIZE); + } + + public void setProcessedInputMsg(boolean flag) { + if (flag) { + data[FLAGS] = (byte) (data[FLAGS] | PROCESSED_INPUT_MSG); + } else { + data[FLAGS] = (byte) (data[FLAGS] & (~PROCESSED_INPUT_MSG)); + } + } + + public void setOneTimeAuthReqd(boolean flag) { + if (flag) { + data[FLAGS] = (short) (data[FLAGS] | SECURE_USER_ID_REQD); + } else { + data[FLAGS] = (short) (data[FLAGS] & (~SECURE_USER_ID_REQD)); + } + } + + public short getAuthType() { + return data[AUTH_TYPE]; + } + + public void setAuthType(byte authType) { + data[AUTH_TYPE] = authType; + } + + public short getUserSecureId() { + short offset = 0; + short length = Util.getShort(userSecureIds, offset); + offset += 2; + if (length == 0) { + return KMType.INVALID_VALUE; + } + short arrObj = KMArray.instance(length); + short index = 0; + short obj; + while (index < length) { + obj = KMInteger.instance(userSecureIds, (short) (offset + index * 8), (short) 8); + KMArray.cast(arrObj).add(index, obj); + index++; + } + return KMIntegerArrayTag.instance(KMType.ULONG_ARRAY_TAG, KMType.USER_SECURE_ID, arrObj); + } + + public void setUserSecureId(short integerArrayPtr) { + short length = KMIntegerArrayTag.cast(integerArrayPtr).length(); + if (length > MAX_SECURE_USER_IDS) { + KMException.throwIt(KMError.INVALID_KEY_BLOB); + } + Util.arrayFillNonAtomic(userSecureIds, (short) 0, USER_SECURE_IDS_SIZE, (byte) 0); + short index = 0; + short obj; + short offset = 0; + offset = Util.setShort(userSecureIds, offset, length); + while (index < length) { + obj = KMIntegerArrayTag.cast(integerArrayPtr).get(index); + Util.arrayCopyNonAtomic( + KMInteger.cast(obj).getBuffer(), + KMInteger.cast(obj).getStartOff(), + userSecureIds, + (short) (8 - KMInteger.cast(obj).length() + offset + 8 * index), + KMInteger.cast(obj).length()); + index++; + } + } + + public short getAlgorithm() { + return data[ALG]; + } + + public void setAlgorithm(short algorithm) { + data[ALG] = algorithm; + } + + public short getPadding() { + return data[PADDING]; + } + + public void setPadding(short padding) { + data[PADDING] = padding; + } + + public short getBlockMode() { + return data[BLOCK_MODE]; + } + + public void setBlockMode(short blockMode) { + data[BLOCK_MODE] = blockMode; + } + + public short getDigest() { + return data[DIGEST]; + } + + public void setDigest(byte digest) { + data[DIGEST] = digest; + } + + public short getMgfDigest() { + return data[MGF_DIGEST]; + } + + public void setMgfDigest(byte mgfDigest) { + data[MGF_DIGEST] = mgfDigest; + } + + public boolean isAesGcmUpdateAllowed() { + return (data[FLAGS] & AES_GCM_UPDATE_ALLOWED) != 0; + } + + public void setAesGcmUpdateComplete() { + data[FLAGS] = (byte) (data[FLAGS] & (~AES_GCM_UPDATE_ALLOWED)); + } + + public void setAesGcmUpdateStart() { + data[FLAGS] = (byte) (data[FLAGS] | AES_GCM_UPDATE_ALLOWED); + } + + public short getMinMacLength() { + return data[MIN_MAC_LENGTH]; + } + + public void setMinMacLength(short length) { + data[MIN_MAC_LENGTH] = length; + } + + public short getMacLength() { + return data[MAC_LENGTH]; + } + + public void setMacLength(short length) { + data[MAC_LENGTH] = length; + } + + public byte getBufferingMode() { + short alg = getAlgorithm(); + short purpose = getPurpose(); + short digest = getDigest(); + short padding = getPadding(); + short blockMode = getBlockMode(); + + if (alg == KMType.RSA + && ((digest == KMType.DIGEST_NONE && purpose == KMType.SIGN) + || purpose == KMType.DECRYPT)) { + return KMType.BUF_RSA_DECRYPT_OR_NO_DIGEST; + } + + if (alg == KMType.EC && digest == KMType.DIGEST_NONE && purpose == KMType.SIGN) { + return KMType.BUF_EC_NO_DIGEST; + } + + switch (alg) { + case KMType.AES: + if (purpose == KMType.ENCRYPT && padding == KMType.PKCS7) { + return KMType.BUF_AES_ENCRYPT_PKCS7_BLOCK_ALIGN; + } else if (purpose == KMType.DECRYPT && padding == KMType.PKCS7) { + return KMType.BUF_AES_DECRYPT_PKCS7_BLOCK_ALIGN; + } else if (purpose == KMType.DECRYPT && blockMode == KMType.GCM) { + return KMType.BUF_AES_GCM_DECRYPT_BLOCK_ALIGN; + } + break; + case KMType.DES: + if (purpose == KMType.ENCRYPT && padding == KMType.PKCS7) { + return KMType.BUF_DES_ENCRYPT_PKCS7_BLOCK_ALIGN; + } else if (purpose == KMType.DECRYPT && padding == KMType.PKCS7) { + return KMType.BUF_DES_DECRYPT_PKCS7_BLOCK_ALIGN; + } + } + return KMType.BUF_NONE; + } + + public KMOperation getTrustedConfirmationSigner() { + return (KMOperation) operations[HMAC_SIGNER_OPERATION]; + } + + public void setTrustedConfirmationSigner(KMOperation hmacSignerOp) { + operations[HMAC_SIGNER_OPERATION] = hmacSignerOp; + } + + public boolean isTrustedConfirmationRequired() { + return operations[HMAC_SIGNER_OPERATION] != null; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMRemotelyProvisionedComponentDevice.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMRemotelyProvisionedComponentDevice.java new file mode 100644 index 0000000..d69534d --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMRemotelyProvisionedComponentDevice.java @@ -0,0 +1,1396 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.keymaster; + +import com.android.javacard.seprovider.KMException; +import com.android.javacard.seprovider.KMKey; +import com.android.javacard.seprovider.KMOperation; +import com.android.javacard.seprovider.KMSEProvider; +import javacard.framework.APDU; +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; + +/** + * This class handles remote key provisioning. Generates an RKP key and generates a certificate + * signing request(CSR). The generation of CSR is divided among multiple functions to the save the + * memory inside the Applet. The set of functions to be called sequentially in the order to complete + * the process of generating the CSR are processBeginSendData, processUpdateKey, + * processUpdateEekChain, processUpdateChallenge, processFinishSendData, and getResponse. + * ProcessUpdateKey is called Ntimes, where N is the number of keys. Similarly, getResponse is + * called multiple times till the client receives the response completely. + */ +public class KMRemotelyProvisionedComponentDevice { + + // Below are the device info labels + // The string "brand" in hex + public static final byte[] BRAND = {0x62, 0x72, 0x61, 0x6E, 0x64}; + // The string "manufacturer" in hex + public static final byte[] MANUFACTURER = { + 0x6D, 0x61, 0x6E, 0x75, 0x66, 0x61, 0x63, 0x74, 0x75, 0x72, 0x65, 0x72 + }; + // The string "product" in hex + public static final byte[] PRODUCT = {0x70, 0x72, 0x6F, 0x64, 0x75, 0x63, 0x74}; + // The string "model" in hex + public static final byte[] MODEL = {0x6D, 0x6F, 0x64, 0x65, 0x6C}; + // // The string "device" in hex + public static final byte[] DEVICE = {0x64, 0x65, 0x76, 0x69, 0x63, 0x65}; + // The string "vb_state" in hex + public static final byte[] VB_STATE = {0x76, 0x62, 0x5F, 0x73, 0x74, 0x61, 0x74, 0x65}; + // The string "bootloader_state" in hex. + public static final byte[] BOOTLOADER_STATE = { + 0x62, 0x6F, 0x6F, 0x74, 0x6C, 0x6F, 0x61, 0x64, 0x65, 0x72, 0x5F, 0x73, 0x74, 0x61, 0x74, 0x65 + }; + // The string "vb_meta_digest" in hdex. + public static final byte[] VB_META_DIGEST = { + 0X76, 0X62, 0X6D, 0X65, 0X74, 0X61, 0X5F, 0X64, 0X69, 0X67, 0X65, 0X73, 0X74 + }; + // The string "os_version" in hex. + public static final byte[] OS_VERSION = { + 0x6F, 0x73, 0x5F, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E + }; + // The string "system_patch_level" in hex. + public static final byte[] SYSTEM_PATCH_LEVEL = { + 0x73, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x5F, 0x70, 0x61, 0x74, 0x63, 0x68, 0x5F, 0x6C, 0x65, 0x76, + 0x65, 0x6C + }; + // The string "boot_patch_level" in hex. + public static final byte[] BOOT_PATCH_LEVEL = { + 0x62, 0x6F, 0x6F, 0x74, 0x5F, 0x70, 0x61, 0x74, 0x63, 0x68, 0x5F, 0x6C, 0x65, 0x76, 0x65, 0x6C + }; + // The string "vendor_patch_level" in hex. + public static final byte[] VENDOR_PATCH_LEVEL = { + 0x76, 0x65, 0x6E, 0x64, 0x6F, 0x72, 0x5F, 0x70, 0x61, 0x74, 0x63, 0x68, 0x5F, 0x6C, 0x65, 0x76, + 0x65, 0x6C + }; + // The string "version" in hex. + public static final byte[] DEVICE_INFO_VERSION = {0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E}; + // The string "security_level" in hex. + public static final byte[] SECURITY_LEVEL = { + 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x5F, 0x6C, 0x65, 0x76, 0x65, 0x6C + }; + // The string "fused" in hex. + public static final byte[] FUSED = {0x66, 0x75, 0x73, 0x65, 0x64}; + // The string "cert_type" in hex + public static final byte[] CERT_TYPE = {0x63, 0x65, 0x72, 0x74, 0x5F, 0x74, 0x79, 0x70, 0x65}; + // Below are the Verified boot state values + // The string "green" in hex. + public static final byte[] VB_STATE_GREEN = {0x67, 0x72, 0x65, 0x65, 0x6E}; + // The string "yellow" in hex. + public static final byte[] VB_STATE_YELLOW = {0x79, 0x65, 0x6C, 0x6C, 0x6F, 0x77}; + // The string "orange" in hex. + public static final byte[] VB_STATE_ORANGE = {0x6F, 0x72, 0x61, 0x6E, 0x67, 0x65}; + // The string "red" in hex. + public static final byte[] VB_STATE_RED = {0x72, 0x65, 0x64}; + // Below are the boot loader state values + // The string "unlocked" in hex. + public static final byte[] UNLOCKED = {0x75, 0x6E, 0x6C, 0x6F, 0x63, 0x6B, 0x65, 0x64}; + // The string "locked" in hex. + public static final byte[] LOCKED = {0x6C, 0x6F, 0x63, 0x6B, 0x65, 0x64}; + // Device info CDDL schema version + public static final byte DI_SCHEMA_VERSION = 2; + // The string "strongbox" in hex. + public static final byte[] DI_SECURITY_LEVEL = { + 0x73, 0x74, 0x72, 0x6F, 0x6E, 0x67, 0x62, 0x6F, 0x78 + }; + // The string "keymint" in hex. + public static final byte[] DI_CERT_TYPE = {0x6B, 0x65, 0x79, 0x6D, 0x69, 0x6E, 0x74}; + // Represents each element size inside the data buffer. Each element has two entries + // 1) Length of the element and 2) offset of the element in the data buffer. + public static final byte DATA_INDEX_ENTRY_SIZE = 4; + // It is the offset, which represents the position where the element is present + // in the data buffer. + public static final byte DATA_INDEX_ENTRY_OFFSET = 2; + // Flag to denote TRUE + private static final byte TRUE = 0x01; + // Flag to denote FALSE + private static final byte FALSE = 0x00; + // RKP hardware info Version + private static final byte RKP_VERSION = 0x03; + // RKP supportedNumKeysInCsr + private static final byte MIN_SUPPORTED_NUM_KEYS_IN_CSR = 20; + // The CsrPayload CDDL Schema version. + private static final byte CSR_PAYLOAD_CDDL_SCHEMA_VERSION = 3; + // Boot params + // Below constants used to denote the type of the boot parameters. Note that these + // constants are only defined to be used internally. + private static final byte OS_VERSION_ID = 0x00; + private static final byte SYSTEM_PATCH_LEVEL_ID = 0x01; + private static final byte BOOT_PATCH_LEVEL_ID = 0x02; + private static final byte VENDOR_PATCH_LEVEL_ID = 0x03; + // Configurable flag to denote if UDS certificate chain is supported in the + // RKP server. + private static final boolean IS_UDS_SUPPORTED_IN_RKP_SERVER = true; + // Denotes COSE Integer lengths less than or equal to 23. + private static final byte TINY_PAYLOAD = 0x17; + // Denotes COSE Integer with short lengths. + private static final short SHORT_PAYLOAD = 0x100; + // The maximum possible output buffer. + private static final short MAX_SEND_DATA = 512; + // The string "Google Strongbox KeyMint 3" in hex. + private static final byte[] uniqueId = { + 0x47, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x20, 0x53, 0x74, 0x72, 0x6f, 0x6e, 0x67, 0x62, 0x6f, 0x78, + 0x20, 0x4b, 0x65, 0x79, 0x4d, 0x69, 0x6e, 0x74, 0x20, 0x33 + }; + // Flag to denote more response is available to the clients. + private static final byte MORE_DATA = 0x01; + // Flag to denote no response is available to the clients. + private static final byte NO_DATA = 0x00; + // Below are the response processing states. + private static final byte START_PROCESSING = 0x00; + private static final byte PROCESSING_DICE_CERTS_IN_PROGRESS = 0x02; + private static final byte PROCESSING_DICE_CERTS_COMPLETE = 0x04; + private static final byte PROCESSING_UDS_CERTS_IN_PROGRESS = 0x08; + private static final byte PROCESSING_UDS_CERTS_COMPLETE = 0x0A; + // The data table size. + private static final short DATA_SIZE = 512; + // Number of entries in the data table. + private static final byte DATA_INDEX_SIZE = 6; + // Below are the data table offsets. + private static final byte TOTAL_KEYS_TO_SIGN = 0; + private static final byte KEYS_TO_SIGN_COUNT = 1; + private static final byte GENERATE_CSR_PHASE = 2; + private static final byte RESPONSE_PROCESSING_STATE = 3; + private static final byte UDS_PROCESSED_LENGTH = 4; + private static final byte DICE_PROCESSED_LENGTH = 5; + + // Below are some of the sizes defined in the data table. + // The size of the Ephemeral Mac key used to sign the rkp public key. + private static final byte EPHEMERAL_MAC_KEY_SIZE = 32; + // The size of short types. + private static final byte SHORT_SIZE = 2; + // The size of byte types + private static final byte BYTE_SIZE = 1; + // Below are the different processing stages for generateCSR. + // BEGIN - It is the initial stage where the process is initialized and construction of + // MacedPublickeys are initiated. + // UPDATE - Challenge, EEK and RKP keys are sent to the applet for further process. + // FINISH - MacedPublicKeys are constructed and construction of protected data is initiated. + // GET_UDS_CERTS_RESPONSE - Constructed the UDSCerts in the response. + private static final byte BEGIN = 0x01; + private static final byte UPDATE = 0x02; + private static final byte FINISH = 0x04; + private static final byte GET_UDS_CERTS_RESPONSE = 0x06; + + // RKP mac key size + private static final byte RKP_MAC_KEY_SIZE = 32; + // RKP CDDL Schema version + private static final byte RKP_AUTHENTICATE_CDDL_SCHEMA_VERSION = 1; + // The maximum size of the encoded buffer size. + private static final short MAX_ENCODED_BUF_SIZE = 1024; + // Used to hold the temporary results. + public short[] rkpTmpVariables; + // Data table to hold the entries at the initial stages of generateCSR and which are used + // at later stages to construct the response data. + private byte[] data; + // Instance of the CBOR encoder. + private KMEncoder encoder; + // Instance of the CBOR decoder. + private KMDecoder decoder; + // Instance of the KMRepository for memory management. + private KMRepository repository; + // Instance of the provider for cyrpto operations. + private KMSEProvider seProvider; + // Instance of the KMKeymintDataStore to save or retrieve the data. + private KMKeymintDataStore storeDataInst; + // Holds the KMOperation instance. This is used to do multi part update operations. + private Object[] operation; + // Holds the current index in the data table. + private short[] dataIndex; + + public KMRemotelyProvisionedComponentDevice( + KMEncoder encoder, + KMDecoder decoder, + KMRepository repository, + KMSEProvider seProvider, + KMKeymintDataStore storeDInst) { + this.encoder = encoder; + this.decoder = decoder; + this.repository = repository; + this.seProvider = seProvider; + this.storeDataInst = storeDInst; + rkpTmpVariables = JCSystem.makeTransientShortArray((short) 32, JCSystem.CLEAR_ON_RESET); + data = JCSystem.makeTransientByteArray(DATA_SIZE, JCSystem.CLEAR_ON_RESET); + operation = JCSystem.makeTransientObjectArray((short) 1, JCSystem.CLEAR_ON_RESET); + dataIndex = JCSystem.makeTransientShortArray((short) 1, JCSystem.CLEAR_ON_RESET); + // Initialize RKP mac key + if (!seProvider.isUpgrading()) { + short offset = repository.allocReclaimableMemory((short) RKP_MAC_KEY_SIZE); + byte[] buffer = repository.getHeap(); + seProvider.getTrueRandomNumber(buffer, offset, RKP_MAC_KEY_SIZE); + storeDataInst.createRkpMacKey(buffer, offset, RKP_MAC_KEY_SIZE); + repository.reclaimMemory(RKP_MAC_KEY_SIZE); + } + operation[0] = null; + } + + private void initializeDataTable() { + clearDataTable(); + releaseOperation(); + dataIndex[0] = (short) (DATA_INDEX_SIZE * DATA_INDEX_ENTRY_SIZE); + } + + private short dataAlloc(short length) { + if ((short) (dataIndex[0] + length) > (short) data.length) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + dataIndex[0] += length; + return (short) (dataIndex[0] - length); + } + + private void clearDataTable() { + Util.arrayFillNonAtomic(data, (short) 0, (short) data.length, (byte) 0x00); + dataIndex[0] = 0x00; + } + + private void releaseOperation() { + if (operation[0] != null) { + ((KMOperation) operation[0]).abort(); + operation[0] = null; + } + } + + private short createEntry(short index, short length) { + index = (short) (index * DATA_INDEX_ENTRY_SIZE); + short ptr = dataAlloc(length); + Util.setShort(data, index, length); + Util.setShort(data, (short) (index + DATA_INDEX_ENTRY_OFFSET), ptr); + return ptr; + } + + private short getEntry(short index) { + index = (short) (index * DATA_INDEX_ENTRY_SIZE); + return Util.getShort(data, (short) (index + DATA_INDEX_ENTRY_OFFSET)); + } + + private void processGetRkpHwInfoCmd(APDU apdu) { + // Make the response + // Author name - Google. + short respPtr = KMArray.instance((short) 6); + KMArray resp = KMArray.cast(respPtr); + resp.add((short) 0, KMInteger.uint_16(KMError.OK)); + resp.add((short) 1, KMInteger.uint_16(RKP_VERSION)); + resp.add( + (short) 2, + KMByteBlob.instance( + KMKeymasterApplet.Google, (short) 0, (short) KMKeymasterApplet.Google.length)); + // This field is no longer used in version 3 + resp.add((short) 3, KMInteger.uint_8(KMType.RKP_CURVE_NONE)); + resp.add((short) 4, KMByteBlob.instance(uniqueId, (short) 0, (short) uniqueId.length)); + resp.add((short) 5, KMInteger.uint_16(MIN_SUPPORTED_NUM_KEYS_IN_CSR)); + KMKeymasterApplet.sendOutgoing(apdu, respPtr); + } + + /** + * This function generates an EC key pair with attest key as purpose and creates an encrypted key + * blob. It then generates a COSEMac message which includes the ECDSA public key. + */ + public void processGenerateRkpKey(APDU apdu) { + short arr = KMArray.instance((short) 1); + KMArray.cast(arr).add((short) 0, KMSimpleValue.exp()); + arr = KMKeymasterApplet.receiveIncoming(apdu, arr); + // Re-purpose the apdu buffer as scratch pad. + byte[] scratchPad = apdu.getBuffer(); + // test mode flag. + boolean testMode = + (KMSimpleValue.TRUE == KMSimpleValue.cast(KMArray.cast(arr).get((short) 0)).getValue()); + KMKeymasterApplet.generateRkpKey(scratchPad, getEcAttestKeyParameters()); + short pubKey = KMKeymasterApplet.getPubKey(); + short coseMac0 = constructCoseMacForRkpKey(testMode, scratchPad, pubKey); + // Encode the COSE_MAC0 object + arr = KMArray.instance((short) 3); + KMArray.cast(arr).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(arr).add((short) 1, coseMac0); + KMArray.cast(arr).add((short) 2, KMKeymasterApplet.getPivateKey()); + KMKeymasterApplet.sendOutgoing(apdu, arr); + } + + public short getHeaderLen(short length) { + if (length <= TINY_PAYLOAD) { + return (short) 1; + } else if (length < SHORT_PAYLOAD) { + return (short) 2; + } else { + return (short) 3; + } + } + + public void constructPartialSignedData( + byte[] scratchPad, + short coseKeysCount, + short totalCoseKeysLen, + short challengeByteBlob, + short deviceInfo, + short versionPtr, + short certTypePtr) { + // Initialize ECDSA operation + initECDSAOperation(); + + short versionLength = encoder.getEncodedLength(versionPtr); + short certTypeLen = encoder.getEncodedLength(certTypePtr); + short challengeLen = (short) KMByteBlob.cast(challengeByteBlob).length(); + if (challengeLen < 16 || challengeLen > 64) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + short challengeHeaderLen = encoder.getEncodedBytesLength(challengeLen); + short deviceInfoLen = encoder.getEncodedLength(deviceInfo); + + // Calculate the keysToSign length + // keysToSignLen = coseKeysArrayHeaderLen + totalCoseKeysLen + short coseKeysArrHeaderLen = getHeaderLen(coseKeysCount); + short keysToSignLen = (short) (coseKeysArrHeaderLen + totalCoseKeysLen); + + // Calculate the payload array header len + /* + * paylaodArrHeaderLen is Array of 2 elements that occupies 1 byte. + * SignedData = [challenge, AuthenticatedRequest<CsrPayload>] + */ + short paylaodArrHeaderLen = 1; + /* + * csrPaylaodArrHeaderLen is Array of 4 elements that occupies 1 byte. + * CsrPayload = [version: 3, CertificateType, DeviceInfo, KeysToSign] + */ + short csrPaylaodArrHeaderLen = 1; + short csrPayloadLen = + (short) + (csrPaylaodArrHeaderLen + versionLength + certTypeLen + deviceInfoLen + keysToSignLen); + short csrPaylaodByteHeaderLen = encoder.getEncodedBytesLength(csrPayloadLen); + short payloadLen = + (short) + (paylaodArrHeaderLen + + challengeHeaderLen + + challengeLen + + csrPaylaodByteHeaderLen + + csrPaylaodArrHeaderLen + + versionLength + + certTypeLen + + deviceInfoLen + + keysToSignLen); + + // Empty aad + short aad = KMByteBlob.instance(scratchPad, (short) 0, (short) 0); + + /* construct protected header */ + short protectedHeaders = + KMCose.constructHeaders( + rkpTmpVariables, + KMNInteger.uint_8(KMCose.COSE_ALG_ES256), + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE); + protectedHeaders = + KMKeymasterApplet.encodeToApduBuffer( + protectedHeaders, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + protectedHeaders = KMByteBlob.instance(scratchPad, (short) 0, protectedHeaders); + + // Construct partial signature + short signStructure = + KMCose.constructCoseSignStructure(protectedHeaders, aad, KMType.INVALID_VALUE); + short partialSignStructureLen = + KMKeymasterApplet.encodeToApduBuffer( + signStructure, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + ((KMOperation) operation[0]).update(scratchPad, (short) 0, partialSignStructureLen); + + // Add payload Byte Header + short prevReclaimIndex = repository.getHeapReclaimIndex(); + byte[] heap = repository.getHeap(); + short heapIndex = repository.allocReclaimableMemory(MAX_ENCODED_BUF_SIZE); + short byteBlobHeaderLen = encoder.encodeByteBlobHeader(payloadLen, heap, heapIndex, (short) 3); + ((KMOperation) operation[0]).update(heap, heapIndex, byteBlobHeaderLen); + + short arr = KMArray.instance((short) 2); + KMArray.cast(arr).add((short) 0, challengeByteBlob); + KMArray.cast(arr).add((short) 1, KMType.INVALID_VALUE); + short payloadArrayLen = encoder.encode(arr, heap, heapIndex, prevReclaimIndex); + ((KMOperation) operation[0]).update(heap, heapIndex, payloadArrayLen); + + byteBlobHeaderLen = encoder.encodeByteBlobHeader(csrPayloadLen, heap, heapIndex, (short) 3); + ((KMOperation) operation[0]).update(heap, heapIndex, byteBlobHeaderLen); + + // Construct partial csr payload array + arr = KMArray.instance((short) 4); + KMArray.cast(arr).add((short) 0, versionPtr); + KMArray.cast(arr).add((short) 1, certTypePtr); + KMArray.cast(arr).add((short) 2, deviceInfo); + KMArray.cast(arr).add((short) 3, KMType.INVALID_VALUE); + short partialCsrPayloadArrayLen = encoder.encode(arr, heap, heapIndex, prevReclaimIndex); + ((KMOperation) operation[0]).update(heap, heapIndex, partialCsrPayloadArrayLen); + + // Encode keysToSign Array Header length + short keysToSignArrayHeaderLen = + encoder.encodeArrayHeader(coseKeysCount, heap, heapIndex, (short) 3); + ((KMOperation) operation[0]).update(heap, heapIndex, keysToSignArrayHeaderLen); + repository.reclaimMemory(MAX_ENCODED_BUF_SIZE); + } + + /** + * This is the first command of the generateCSR. + * Input: + * 1) Number of RKP keys. + * 2) Total size of the encoded CoseKeys (Each RKP key is represented in CoseKey) + * 3) Challenge + * Process: + * 1) creates device info, initializes version and cert type. + * 2) Initialize the ECDSA operation with the deviceUniqueKeyPair and do partial sign of the + * CsrPayload with the initial input data received. A Multipart update on ECDSA is + * called on each updateKey command in the second stage. + * 3) Store the number of RKP keys in the temporary data buffer. + * 4) Update the phase of the generateCSR function to BEGIN. + * 5) generates RKP device info + * Response: + * 1) Send OK response. + * 2) Encoded device info + * 3) CSR payload CDDL schema version + * 4) Cert type + * + * @param apdu Input apdu + */ + public void processBeginSendData(APDU apdu) throws Exception { + try { + initializeDataTable(); + short arr = KMArray.instance((short) 3); + KMArray.cast(arr).add((short) 0, KMInteger.exp()); // Array length + KMArray.cast(arr).add((short) 1, KMInteger.exp()); // Total length of the encoded CoseKeys. + KMArray.cast(arr).add((short) 2, KMByteBlob.exp()); // challenge + arr = KMKeymasterApplet.receiveIncoming(apdu, arr); + // Re-purpose the apdu buffer as scratch pad. + byte[] scratchPad = apdu.getBuffer(); + short deviceInfo = createDeviceInfo(scratchPad); + short versionPtr = KMInteger.uint_16(CSR_PAYLOAD_CDDL_SCHEMA_VERSION); + short certTypePtr = + KMTextString.instance(DI_CERT_TYPE, (short) 0, (short) DI_CERT_TYPE.length); + + constructPartialSignedData( + scratchPad, + KMInteger.cast(KMArray.cast(arr).get((short) 0)).getShort(), + KMInteger.cast(KMArray.cast(arr).get((short) 1)).getShort(), + KMArray.cast(arr).get((short) 2), + deviceInfo, + versionPtr, + certTypePtr); + // Store the total keys in data table. + short dataEntryIndex = createEntry(TOTAL_KEYS_TO_SIGN, SHORT_SIZE); + Util.setShort( + data, dataEntryIndex, KMInteger.cast(KMArray.cast(arr).get((short) 0)).getShort()); + // Store the current csr status, which is BEGIN. + createEntry(GENERATE_CSR_PHASE, BYTE_SIZE); + updateState(BEGIN); + if (0 == KMInteger.cast(KMArray.cast(arr).get((short) 0)).getShort()) { + updateState(UPDATE); + } + short prevReclaimIndex = repository.getHeapReclaimIndex(); + short offset = repository.allocReclaimableMemory(MAX_ENCODED_BUF_SIZE); + short length = + encoder.encode( + deviceInfo, repository.getHeap(), offset, prevReclaimIndex, MAX_ENCODED_BUF_SIZE); + short encodedDeviceInfo = KMByteBlob.instance(repository.getHeap(), offset, length); + // release memory + repository.reclaimMemory(MAX_ENCODED_BUF_SIZE); + // Send response. + short array = KMArray.instance((short) 4); + KMArray.cast(array).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(array).add((short) 1, encodedDeviceInfo); + KMArray.cast(array).add((short) 2, versionPtr); + KMArray.cast(array).add((short) 3, certTypePtr); + KMKeymasterApplet.sendOutgoing(apdu, array); + } catch (Exception e) { + clearDataTable(); + releaseOperation(); + throw e; + } + } + + /** + * This is the second command of the generateCSR. This command will be called in a loop + * for the number of keys. + * Input: + * CoseMac0 containing the RKP Key + * Process: + * 1) Validate the phase of generateCSR. Prior state should be either BEGIN or UPDATE. + * 2) Validate the number of RKP Keys received against the value received in the first command. + * 3) Validate the CoseMac0 structure and extract the RKP Key. + * 4) Do Multipart ECDSA update operation with the input as RKP key. + * 5) Update the number of keys received count into the data buffer. + * 6) Update the phase of the generateCSR function to UPDATE. + * Response: + * 1) Send OK response. + * 2) encoded Cose Key + * @param apdu Input apdu + */ + public void processUpdateKey(APDU apdu) throws Exception { + try { + // The prior state can be BEGIN or UPDATE + validateState((byte) (BEGIN | UPDATE)); + validateKeysToSignCount(); + short headers = KMCoseHeaders.exp(); + short arrInst = KMArray.instance((short) 4); + short byteBlobExp = KMByteBlob.exp(); + KMArray.cast(arrInst).add((short) 0, byteBlobExp); + KMArray.cast(arrInst).add((short) 1, headers); + KMArray.cast(arrInst).add((short) 2, byteBlobExp); + KMArray.cast(arrInst).add((short) 3, byteBlobExp); + short arr = KMArray.exp(arrInst); + arr = KMKeymasterApplet.receiveIncoming(apdu, arr); + arrInst = KMArray.cast(arr).get((short) 0); + // Re-purpose the apdu buffer as scratch pad. + byte[] scratchPad = apdu.getBuffer(); + + // Validate and extract the CoseKey from CoseMac0 message. + short coseKey = validateAndExtractPublicKey(arrInst, scratchPad); + // Encode CoseKey + short length = + KMKeymasterApplet.encodeToApduBuffer( + coseKey, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + // Do ECDSA update with input as encoded CoseKey. + ((KMOperation) operation[0]).update(scratchPad, (short) 0, length); + short encodedCoseKey = KMByteBlob.instance(scratchPad, (short) 0, length); + + // Increment the count each time this function gets executed. + // Store the count in data table. + short dataEntryIndex = getEntry(KEYS_TO_SIGN_COUNT); + if (dataEntryIndex == 0) { + dataEntryIndex = createEntry(KEYS_TO_SIGN_COUNT, SHORT_SIZE); + } + length = Util.getShort(data, dataEntryIndex); + Util.setShort(data, dataEntryIndex, ++length); + // Update the csr state + updateState(UPDATE); + // Send response. + short array = KMArray.instance((short) 2); + KMArray.cast(array).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(array).add((short) 1, encodedCoseKey); + KMKeymasterApplet.sendOutgoing(apdu, array); + } catch (Exception e) { + clearDataTable(); + releaseOperation(); + throw e; + } + } + + /** + * This is the third command of generateCSR. + * Input: + * No input data. + * Process: + * 1) Validate the phase of generateCSR. Prior state should be UPDATE. + * 2) Check if all the RKP keys are received, if not, throw exception. + * 3) Finalize the ECDSA operation and get the signature, signature of SignedDataSigStruct + * where SignedDataSigStruct is + * [context: "Signature1", + * protected: bstr .cbor {1 : AlgorithmEdDSA / AlgorithmES256}, + * external_aad: bstr .size 0, + * payload: bstr .cbor [challenge, + * bstr .cbor CsrPayload = [version, CertificateType, DeviceInfo, KeysToSign] + * ] + * ] + * 4) Construct protected header data. + * 5) Update the phase of the generateCSR function to FINISH. + * Response: + * OK + * protectedHeader - SignedData protected header + * Signature of SignedDataSigStruct + * Version - The AuthenticatedRequest CDDL Schema version. + * Flag to represent there is more data to retrieve. + * @param apdu Input apdu. + */ + public void processFinishSendData(APDU apdu) throws Exception { + try { + // The prior state should be UPDATE. + validateState(UPDATE); + byte[] scratchPad = apdu.getBuffer(); + if (data[getEntry(TOTAL_KEYS_TO_SIGN)] != data[getEntry(KEYS_TO_SIGN_COUNT)]) { + // Mismatch in the number of keys sent. + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + // PubKeysToSignMac + short empty = repository.alloc((short) 0); + short len = + ((KMOperation) operation[0]) + .sign(repository.getHeap(), (short) empty, (short) 0, scratchPad, (short) 0); + releaseOperation(); + short signatureData = KMByteBlob.instance(scratchPad, (short) 0, len); + len = KMAsn1Parser.instance().decodeEcdsa256Signature(signatureData, scratchPad, (short) 0); + + signatureData = KMByteBlob.instance(scratchPad, (short) 0, len); + + /* construct protected header */ + short protectedHeaders = + KMCose.constructHeaders( + rkpTmpVariables, + KMNInteger.uint_8(KMCose.COSE_ALG_ES256), + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE); + protectedHeaders = + KMKeymasterApplet.encodeToApduBuffer( + protectedHeaders, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + protectedHeaders = KMByteBlob.instance(scratchPad, (short) 0, protectedHeaders); + + updateState(FINISH); + short arr = KMArray.instance((short) 5); + KMArray.cast(arr).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(arr).add((short) 1, protectedHeaders); + KMArray.cast(arr).add((short) 2, signatureData); + KMArray.cast(arr).add((short) 3, KMInteger.uint_8(RKP_AUTHENTICATE_CDDL_SCHEMA_VERSION)); + KMArray.cast(arr).add((short) 4, KMInteger.uint_8(MORE_DATA)); + KMKeymasterApplet.sendOutgoing(apdu, arr); + } catch (Exception e) { + clearDataTable(); + releaseOperation(); + throw e; + } + } + + /** + * This is the fourth command of generateCSR. This command is called multiple times by the + * HAL until complete UdsCerts are received. On each call, a chunk of 512 bytes data is sent. + * Input: + * No input data. + * Process: + * 1) Validate the phase of generateCSR. Prior state should be FINISH. + * 2) checks if Uds cert is present and sends the certs in chunks of 512 bytes. + * 3) Update the phase of the generateCSR function to GET_UDS_CERTS_RESPONSE. In-case of + * a) No Uds certs present and + * b) When last chunk of Uds cert is sent + * Response: + * OK + * Uds cert data + * Flag to represent there is more data to retrieve. + * @param apdu Input apdu. + */ + public void processGetUdsCerts(APDU apdu) throws Exception { + try { + // The prior state should be FINISH. + validateState((byte) (FINISH)); + short len; + byte moreData; + byte[] scratchPad = apdu.getBuffer(); + if (!isUdsCertsChainPresent()) { + createEntry(RESPONSE_PROCESSING_STATE, BYTE_SIZE); + updateState(GET_UDS_CERTS_RESPONSE); + moreData = NO_DATA; + scratchPad[0] = (byte) 0xA0; // CBOR Encoded empty map is A0 + len = 1; + } else { + len = processUdsCertificateChain(scratchPad); + moreData = MORE_DATA; + byte state = getCurrentOutputProcessingState(); + switch (state) { + case PROCESSING_UDS_CERTS_IN_PROGRESS: + moreData = MORE_DATA; + break; + case PROCESSING_UDS_CERTS_COMPLETE: + updateState(GET_UDS_CERTS_RESPONSE); + moreData = NO_DATA; + break; + default: + KMException.throwIt(KMError.INVALID_STATE); + } + } + short data = KMByteBlob.instance(scratchPad, (short) 0, len); + short arr = KMArray.instance((short) 3); + KMArray.cast(arr).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(arr).add((short) 1, data); + // represents there is more output to retrieve + KMArray.cast(arr).add((short) 2, KMInteger.uint_8(moreData)); + KMKeymasterApplet.sendOutgoing(apdu, arr); + } catch (Exception e) { + clearDataTable(); + throw e; + } + } + + /** + * This is the fifth command of generateCSR. This command is called multiple times by the + * HAL until complete Dice cert chain is received. On each call, a chunk of 512 bytes data is sent. + * Input: + * No input data. + * Process: + * 1) Validate the phase of generateCSR. Prior state should be GET_UDS_CERTS_RESPONSE. + * 2) Sends the Dice cert chain data in chunks of 512 bytes. + * + * After receiving a complete dice cert chain in HAL, Hal constructs the final CSR using the output data + * returned from all the 5 generateCSR commands in Applet. + * + * Response: + * OK + * Dice cert chain data + * Flag to represent there is more data to retrieve. + * @param apdu Input apdu. + */ + public void processGetDiceCertChain(APDU apdu) throws Exception { + try { + // The prior state should be GET_UDS_CERTS_RESPONSE. + validateState((byte) (GET_UDS_CERTS_RESPONSE)); + byte[] scratchPad = apdu.getBuffer(); + short len = 0; + len = processDiceCertChain(scratchPad); + byte moreData = MORE_DATA; + byte state = getCurrentOutputProcessingState(); + switch (state) { + case PROCESSING_DICE_CERTS_IN_PROGRESS: + moreData = MORE_DATA; + break; + case PROCESSING_DICE_CERTS_COMPLETE: + moreData = NO_DATA; + clearDataTable(); + break; + default: + KMException.throwIt(KMError.INVALID_STATE); + } + short data = KMByteBlob.instance(scratchPad, (short) 0, len); + short arr = KMArray.instance((short) 3); + KMArray.cast(arr).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(arr).add((short) 1, data); + // represents there is more output to retrieve + KMArray.cast(arr).add((short) 2, KMInteger.uint_8(moreData)); + KMKeymasterApplet.sendOutgoing(apdu, arr); + } catch (Exception e) { + clearDataTable(); + throw e; + } + } + + private boolean isUdsCertsChainPresent() { + if (!IS_UDS_SUPPORTED_IN_RKP_SERVER || (storeDataInst.getUdsCertChainLength() == 0)) { + return false; + } + return true; + } + + public void process(short ins, APDU apdu) throws Exception { + switch (ins) { + case KMKeymasterApplet.INS_GET_RKP_HARDWARE_INFO: + processGetRkpHwInfoCmd(apdu); + break; + case KMKeymasterApplet.INS_GENERATE_RKP_KEY_CMD: + processGenerateRkpKey(apdu); + break; + case KMKeymasterApplet.INS_BEGIN_SEND_DATA_CMD: + processBeginSendData(apdu); + break; + case KMKeymasterApplet.INS_UPDATE_KEY_CMD: + processUpdateKey(apdu); + break; + case KMKeymasterApplet.INS_FINISH_SEND_DATA_CMD: + processFinishSendData(apdu); + break; + case KMKeymasterApplet.INS_GET_UDS_CERTS_CMD: + processGetUdsCerts(apdu); + break; + case KMKeymasterApplet.INS_GET_DICE_CERT_CHAIN_CMD: + processGetDiceCertChain(apdu); + break; + default: + ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); + } + } + + private byte getCurrentOutputProcessingState() { + short index = getEntry(RESPONSE_PROCESSING_STATE); + if (index == 0) { + return START_PROCESSING; + } + return data[index]; + } + + private void updateOutputProcessingState(byte state) { + short dataEntryIndex = getEntry(RESPONSE_PROCESSING_STATE); + data[dataEntryIndex] = state; + } + + /** + * Validates the CoseMac message and extracts the CoseKey from it. + * + * @param coseMacPtr CoseMac instance to be validated. + * @param scratchPad Scratch buffer used to store temp results. + * @return CoseKey instance. + */ + private short validateAndExtractPublicKey(short coseMacPtr, byte[] scratchPad) { + // Version 3 removes the need to have testMode in function calls + boolean testMode = false; + // Exp for KMCoseHeaders + short coseHeadersExp = KMCoseHeaders.exp(); + // Exp for coseky + short coseKeyExp = KMCoseKey.exp(); + + // validate protected Headers + short ptr = KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_PROTECTED_PARAMS_OFFSET); + ptr = + decoder.decode( + coseHeadersExp, + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()); + + if (!KMCoseHeaders.cast(ptr) + .isDataValid(rkpTmpVariables, KMCose.COSE_ALG_HMAC_256, KMType.INVALID_VALUE)) { + KMException.throwIt(KMError.STATUS_FAILED); + } + + // Validate payload. + ptr = KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_PAYLOAD_OFFSET); + ptr = + decoder.decode( + coseKeyExp, + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + KMByteBlob.cast(ptr).length()); + + if (!KMCoseKey.cast(ptr) + .isDataValid( + rkpTmpVariables, + KMCose.COSE_KEY_TYPE_EC2, + KMType.INVALID_VALUE, + KMCose.COSE_ALG_ES256, + KMCose.COSE_ECCURVE_256)) { + KMException.throwIt(KMError.STATUS_FAILED); + } + + boolean isTestKey = KMCoseKey.cast(ptr).isTestKey(); + if (isTestKey && !testMode) { + KMException.throwIt(KMError.STATUS_TEST_KEY_IN_PRODUCTION_REQUEST); + } else if (!isTestKey && testMode) { + KMException.throwIt(KMError.STATUS_PRODUCTION_KEY_IN_TEST_REQUEST); + } + + // Compute CoseMac Structure and compare the macs. + short macStructure = + KMCose.constructCoseMacStructure( + KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_PROTECTED_PARAMS_OFFSET), + KMByteBlob.instance((short) 0), + KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_PAYLOAD_OFFSET)); + short encodedLen = + KMKeymasterApplet.encodeToApduBuffer( + macStructure, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + + short hmacLen = + rkpHmacSign(testMode, scratchPad, (short) 0, encodedLen, scratchPad, encodedLen); + + if (hmacLen + != KMByteBlob.cast(KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_TAG_OFFSET)).length()) { + KMException.throwIt(KMError.STATUS_INVALID_MAC); + } + + if (0 + != Util.arrayCompare( + scratchPad, + encodedLen, + KMByteBlob.cast(KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_TAG_OFFSET)).getBuffer(), + KMByteBlob.cast(KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_TAG_OFFSET)) + .getStartOff(), + hmacLen)) { + KMException.throwIt(KMError.STATUS_INVALID_MAC); + } + return ptr; + } + + private void validateKeysToSignCount() { + short index = getEntry(KEYS_TO_SIGN_COUNT); + short keysToSignCount = 0; + if (index != 0) { + keysToSignCount = Util.getShort(data, index); + } + if (Util.getShort(data, getEntry(TOTAL_KEYS_TO_SIGN)) <= keysToSignCount) { + // Mismatch in the number of keys sent. + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + } + + private void validateState(byte expectedState) { + short dataEntryIndex = getEntry(GENERATE_CSR_PHASE); + if (0 == (data[dataEntryIndex] & expectedState)) { + KMException.throwIt(KMError.INVALID_STATE); + } + } + + private void updateState(byte state) { + short dataEntryIndex = getEntry(GENERATE_CSR_PHASE); + if (dataEntryIndex == 0) { + KMException.throwIt(KMError.INVALID_STATE); + } + data[dataEntryIndex] = state; + } + + /* + * Create DeviceInfo structure as specified in the RKPV3.0 specification. + */ + private short createDeviceInfo(byte[] scratchpad) { + // Device Info Key Value pairs. + for (short i = 0; i < 32; i++) { + rkpTmpVariables[i] = KMType.INVALID_VALUE; + } + short dataOffset = 2; + rkpTmpVariables[0] = dataOffset; + rkpTmpVariables[1] = 0; + short metaOffset = 0; + updateItem( + rkpTmpVariables, + metaOffset, + BRAND, + getAttestationId(KMType.ATTESTATION_ID_BRAND, scratchpad)); + updateItem( + rkpTmpVariables, + metaOffset, + MANUFACTURER, + getAttestationId(KMType.ATTESTATION_ID_MANUFACTURER, scratchpad)); + updateItem( + rkpTmpVariables, + metaOffset, + PRODUCT, + getAttestationId(KMType.ATTESTATION_ID_PRODUCT, scratchpad)); + updateItem( + rkpTmpVariables, + metaOffset, + MODEL, + getAttestationId(KMType.ATTESTATION_ID_MODEL, scratchpad)); + updateItem( + rkpTmpVariables, + metaOffset, + DEVICE, + getAttestationId(KMType.ATTESTATION_ID_DEVICE, scratchpad)); + updateItem(rkpTmpVariables, metaOffset, VB_STATE, getVbState()); + updateItem(rkpTmpVariables, metaOffset, BOOTLOADER_STATE, getBootloaderState()); + updateItem(rkpTmpVariables, metaOffset, VB_META_DIGEST, getVerifiedBootHash(scratchpad)); + updateItem(rkpTmpVariables, metaOffset, OS_VERSION, getBootParams(OS_VERSION_ID, scratchpad)); + updateItem( + rkpTmpVariables, + metaOffset, + SYSTEM_PATCH_LEVEL, + getBootParams(SYSTEM_PATCH_LEVEL_ID, scratchpad)); + updateItem( + rkpTmpVariables, + metaOffset, + BOOT_PATCH_LEVEL, + getBootParams(BOOT_PATCH_LEVEL_ID, scratchpad)); + updateItem( + rkpTmpVariables, + metaOffset, + VENDOR_PATCH_LEVEL, + getBootParams(VENDOR_PATCH_LEVEL_ID, scratchpad)); + updateItem( + rkpTmpVariables, + metaOffset, + SECURITY_LEVEL, + KMTextString.instance(DI_SECURITY_LEVEL, (short) 0, (short) DI_SECURITY_LEVEL.length)); + updateItem(rkpTmpVariables, metaOffset, FUSED, KMInteger.uint_8(storeDataInst.secureBootMode)); + // Create device info map. + short map = KMMap.instance(rkpTmpVariables[1]); + short mapIndex = 0; + short index = 2; + while (index < (short) 32) { + if (rkpTmpVariables[index] != KMType.INVALID_VALUE) { + KMMap.cast(map) + .add(mapIndex++, rkpTmpVariables[index], rkpTmpVariables[(short) (index + 1)]); + } + index += 2; + } + KMMap.cast(map).canonicalize(); + return map; + } + + // Below 6 methods are helper methods to create device info structure. + // ---------------------------------------------------------------------------- + + /** + * Update the item inside the device info structure. + * + * @param deviceIds Device Info structure to be updated. + * @param metaOffset Out parameter meta information. Offset 0 is index and Offset 1 is length. + * @param item Key info to be updated. + * @param value value to be updated. + */ + private void updateItem(short[] deviceIds, short metaOffset, byte[] item, short value) { + if (KMType.INVALID_VALUE != value) { + deviceIds[deviceIds[metaOffset]++] = + KMTextString.instance(item, (short) 0, (short) item.length); + deviceIds[deviceIds[metaOffset]++] = value; + deviceIds[(short) (metaOffset + 1)]++; + } + } + + private short getAttestationId(short attestId, byte[] scratchpad) { + short attIdTagLen = storeDataInst.getAttestationId(attestId, scratchpad, (short) 0); + if (attIdTagLen == 0) { + KMException.throwIt(KMError.INVALID_STATE); + } + return KMTextString.instance(scratchpad, (short) 0, attIdTagLen); + } + + private short getVerifiedBootHash(byte[] scratchPad) { + short len = storeDataInst.getVerifiedBootHash(scratchPad, (short) 0); + if (len == 0) { + KMException.throwIt(KMError.INVALID_STATE); + } + return KMByteBlob.instance(scratchPad, (short) 0, len); + } + + private short getBootloaderState() { + short bootloaderState; + if (storeDataInst.isDeviceBootLocked()) { + bootloaderState = KMTextString.instance(LOCKED, (short) 0, (short) LOCKED.length); + } else { + bootloaderState = KMTextString.instance(UNLOCKED, (short) 0, (short) UNLOCKED.length); + } + return bootloaderState; + } + + private short getVbState() { + short state = storeDataInst.getBootState(); + short vbState = KMType.INVALID_VALUE; + if (state == KMType.VERIFIED_BOOT) { + vbState = KMTextString.instance(VB_STATE_GREEN, (short) 0, (short) VB_STATE_GREEN.length); + } else if (state == KMType.SELF_SIGNED_BOOT) { + vbState = KMTextString.instance(VB_STATE_YELLOW, (short) 0, (short) VB_STATE_YELLOW.length); + } else if (state == KMType.UNVERIFIED_BOOT) { + vbState = KMTextString.instance(VB_STATE_ORANGE, (short) 0, (short) VB_STATE_ORANGE.length); + } else if (state == KMType.FAILED_BOOT) { + vbState = KMTextString.instance(VB_STATE_RED, (short) 0, (short) VB_STATE_RED.length); + } + return vbState; + } + + private short converIntegerToTextString(short intPtr, byte[] scratchPad) { + // Prepare Hex Values + short index = 1; + scratchPad[0] = 0x30; // Ascii 0 + while (index < 10) { + scratchPad[index] = (byte) (scratchPad[(short) (index - 1)] + 1); + index++; + } + scratchPad[index++] = 0x41; // Ascii 'A' + while (index < 16) { + scratchPad[index] = (byte) (scratchPad[(short) (index - 1)] + 1); + index++; + } + + short intLen = KMInteger.cast(intPtr).length(); + short intOffset = KMInteger.cast(intPtr).getStartOff(); + byte[] buf = repository.getHeap(); + short tsPtr = KMTextString.instance((short) (intLen * 2)); + short tsStartOff = KMTextString.cast(tsPtr).getStartOff(); + index = 0; + byte nibble; + while (index < intLen) { + nibble = (byte) ((byte) (buf[intOffset] >> 4) & (byte) 0x0F); + buf[tsStartOff] = scratchPad[nibble]; + nibble = (byte) (buf[intOffset] & 0x0F); + buf[(short) (tsStartOff + 1)] = scratchPad[nibble]; + index++; + intOffset++; + tsStartOff += 2; + } + return tsPtr; + } + + private short getBootParams(byte bootParam, byte[] scratchPad) { + short value = KMType.INVALID_VALUE; + switch (bootParam) { + case OS_VERSION_ID: + value = storeDataInst.getOsVersion(); + break; + case SYSTEM_PATCH_LEVEL_ID: + value = storeDataInst.getOsPatch(); + break; + case BOOT_PATCH_LEVEL_ID: + value = storeDataInst.getBootPatchLevel(); + break; + case VENDOR_PATCH_LEVEL_ID: + value = storeDataInst.getVendorPatchLevel(); + break; + default: + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // Convert Integer to Text String for OS_VERSION. + if (bootParam == OS_VERSION_ID) { + value = converIntegerToTextString(value, scratchPad); + } + return value; + } + // ---------------------------------------------------------------------------- + + // ---------------------------------------------------------------------------- + private void initECDSAOperation() { + KMKey deviceUniqueKeyPair = storeDataInst.getRkpDeviceUniqueKeyPair(); + operation[0] = + seProvider.getRkpOperation( + KMType.SIGN, + KMType.EC, + KMType.SHA2_256, + KMType.PADDING_NONE, + (byte) 0, + deviceUniqueKeyPair, + null, + (short) 0, + (short) 0, + (short) 0); + if (operation[0] == null) { + KMException.throwIt(KMError.STATUS_FAILED); + } + } + + private short getResponseProcessedLength(short index) { + short dataEntryIndex = getEntry(index); + if (dataEntryIndex == 0) { + dataEntryIndex = createEntry(index, SHORT_SIZE); + Util.setShort(data, dataEntryIndex, (short) 0); + return (short) 0; + } + return Util.getShort(data, dataEntryIndex); + } + + private void updateResponseProcessedLength(short index, short processedLen) { + short dataEntryIndex = getEntry(index); + Util.setShort(data, dataEntryIndex, processedLen); + } + + private short processUdsCertificateChain(byte[] scratchPad) { + byte[] persistedData = storeDataInst.getUdsCertChain(); + short totalUccLen = Util.getShort(persistedData, (short) 0); + createEntry(RESPONSE_PROCESSING_STATE, BYTE_SIZE); + if (totalUccLen == 0) { + // No Uds certificate chain present. + updateOutputProcessingState(PROCESSING_UDS_CERTS_COMPLETE); + return 0; + } + short processedLen = getResponseProcessedLength(UDS_PROCESSED_LENGTH); + short lengthToSend = (short) (totalUccLen - processedLen); + if (lengthToSend > MAX_SEND_DATA) { + lengthToSend = MAX_SEND_DATA; + } + Util.arrayCopyNonAtomic( + persistedData, (short) (2 + processedLen), scratchPad, (short) 0, lengthToSend); + + processedLen += lengthToSend; + updateResponseProcessedLength(UDS_PROCESSED_LENGTH, processedLen); + // Update the output processing state. + updateOutputProcessingState( + (processedLen == totalUccLen) + ? PROCESSING_UDS_CERTS_COMPLETE + : PROCESSING_UDS_CERTS_IN_PROGRESS); + return lengthToSend; + } + + // Dice cert chain for STRONGBOX has chain length of 2. So it can be returned in a single go. + private short processDiceCertChain(byte[] scratchPad) { + byte[] diceCertChain = storeDataInst.getDiceCertificateChain(); + short totalDccLen = Util.getShort(diceCertChain, (short) 0); + if (totalDccLen == 0) { + // No Uds certificate chain present. + updateOutputProcessingState(PROCESSING_DICE_CERTS_COMPLETE); + return 0; + } + short processedLen = getResponseProcessedLength(DICE_PROCESSED_LENGTH); + short lengthToSend = (short) (totalDccLen - processedLen); + if (lengthToSend > MAX_SEND_DATA) { + lengthToSend = MAX_SEND_DATA; + } + Util.arrayCopyNonAtomic( + diceCertChain, (short) (2 + processedLen), scratchPad, (short) 0, lengthToSend); + + processedLen += lengthToSend; + updateResponseProcessedLength(DICE_PROCESSED_LENGTH, processedLen); + // Update the output processing state. + updateOutputProcessingState( + (processedLen == totalDccLen) + ? PROCESSING_DICE_CERTS_COMPLETE + : PROCESSING_DICE_CERTS_IN_PROGRESS); + return lengthToSend; + } + + private short constructCoseMacForRkpKey(boolean testMode, byte[] scratchPad, short pubKey) { + // prepare cosekey + short coseKey = + KMCose.constructCoseKey( + rkpTmpVariables, + KMInteger.uint_8(KMCose.COSE_KEY_TYPE_EC2), + KMType.INVALID_VALUE, + KMNInteger.uint_8(KMCose.COSE_ALG_ES256), + KMInteger.uint_8(KMCose.COSE_ECCURVE_256), + KMByteBlob.cast(pubKey).getBuffer(), + KMByteBlob.cast(pubKey).getStartOff(), + KMByteBlob.cast(pubKey).length(), + KMType.INVALID_VALUE, + testMode); + // Encode the cose key and make it as payload. + short len = + KMKeymasterApplet.encodeToApduBuffer( + coseKey, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + short payload = KMByteBlob.instance(scratchPad, (short) 0, len); + // Prepare protected header, which is required to construct the COSE_MAC0 + short headerPtr = + KMCose.constructHeaders( + rkpTmpVariables, + KMInteger.uint_8(KMCose.COSE_ALG_HMAC_256), + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE); + // Encode the protected header as byte blob. + len = + KMKeymasterApplet.encodeToApduBuffer( + headerPtr, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + short protectedHeader = KMByteBlob.instance(scratchPad, (short) 0, len); + // create MAC_Structure + short macStructure = + KMCose.constructCoseMacStructure(protectedHeader, KMByteBlob.instance((short) 0), payload); + // Encode the Mac_structure and do HMAC_Sign to produce the tag for COSE_MAC0 + len = + KMKeymasterApplet.encodeToApduBuffer( + macStructure, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + // HMAC Sign. + short hmacLen = rkpHmacSign(testMode, scratchPad, (short) 0, len, scratchPad, len); + // Create COSE_MAC0 object + short coseMac0 = + KMCose.constructCoseMac0( + protectedHeader, + KMCoseHeaders.instance(KMArray.instance((short) 0)), + payload, + KMByteBlob.instance(scratchPad, len, hmacLen)); + len = + KMKeymasterApplet.encodeToApduBuffer( + coseMac0, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + return KMByteBlob.instance(scratchPad, (short) 0, len); + } + + private short getEcAttestKeyParameters() { + short tagIndex = 0; + short arrPtr = KMArray.instance((short) 6); + // Key size - 256 + short keySize = + KMIntegerTag.instance(KMType.UINT_TAG, KMType.KEYSIZE, KMInteger.uint_16((short) 256)); + // Digest - SHA256 + short byteBlob = KMByteBlob.instance((short) 1); + KMByteBlob.cast(byteBlob).add((short) 0, KMType.SHA2_256); + short digest = KMEnumArrayTag.instance(KMType.DIGEST, byteBlob); + // Purpose - Attest + byteBlob = KMByteBlob.instance((short) 1); + KMByteBlob.cast(byteBlob).add((short) 0, KMType.ATTEST_KEY); + short purpose = KMEnumArrayTag.instance(KMType.PURPOSE, byteBlob); + + KMArray.cast(arrPtr).add(tagIndex++, purpose); + // Algorithm - EC + KMArray.cast(arrPtr).add(tagIndex++, KMEnumTag.instance(KMType.ALGORITHM, KMType.EC)); + KMArray.cast(arrPtr).add(tagIndex++, keySize); + KMArray.cast(arrPtr).add(tagIndex++, digest); + // Curve - P256 + KMArray.cast(arrPtr).add(tagIndex++, KMEnumTag.instance(KMType.ECCURVE, KMType.P_256)); + // No Authentication is required to use this key. + KMArray.cast(arrPtr).add(tagIndex, KMBoolTag.instance(KMType.NO_AUTH_REQUIRED)); + return KMKeyParameters.instance(arrPtr); + } + + private boolean isSignedByte(byte b) { + return ((b & 0x0080) != 0); + } + + private short writeIntegerHeader(short valueLen, byte[] data, short offset) { + // write length + data[offset] = (byte) valueLen; + // write INTEGER tag + offset--; + data[offset] = 0x02; + return offset; + } + + private short writeSequenceHeader(short valueLen, byte[] data, short offset) { + // write length + data[offset] = (byte) valueLen; + // write INTEGER tag + offset--; + data[offset] = 0x30; + return offset; + } + + private short writeSignatureData( + byte[] input, short inputOff, short inputlen, byte[] output, short offset) { + Util.arrayCopyNonAtomic(input, inputOff, output, offset, inputlen); + if (isSignedByte(input[inputOff])) { + offset--; + output[offset] = (byte) 0; + } + return offset; + } + + public short encodeES256CoseSignSignature( + byte[] input, short offset, short len, byte[] scratchPad, short scratchPadOff) { + // SEQ [ INTEGER(r), INTEGER(s)] + // write from bottom to the top + if (len != 64) { + KMException.throwIt(KMError.INVALID_DATA); + } + short maxTotalLen = 72; + short end = (short) (scratchPadOff + maxTotalLen); + // write s. + short start = (short) (end - 32); + start = writeSignatureData(input, (short) (offset + 32), (short) 32, scratchPad, start); + // write length and header + short length = (short) (end - start); + start--; + start = writeIntegerHeader(length, scratchPad, start); + // write r + short rEnd = start; + start = (short) (start - 32); + start = writeSignatureData(input, offset, (short) 32, scratchPad, start); + // write length and header + length = (short) (rEnd - start); + start--; + start = writeIntegerHeader(length, scratchPad, start); + // write length and sequence header + length = (short) (end - start); + start--; + start = writeSequenceHeader(length, scratchPad, start); + length = (short) (end - start); + if (start > scratchPadOff) { + // re adjust the buffer + Util.arrayCopyNonAtomic(scratchPad, start, scratchPad, scratchPadOff, length); + } + return length; + } + + private short rkpHmacSign( + boolean testMode, + byte[] data, + short dataStart, + short dataLength, + byte[] signature, + short signatureStart) { + short result; + if (testMode) { + short macKey = KMByteBlob.instance(EPHEMERAL_MAC_KEY_SIZE); + Util.arrayFillNonAtomic( + KMByteBlob.cast(macKey).getBuffer(), + KMByteBlob.cast(macKey).getStartOff(), + EPHEMERAL_MAC_KEY_SIZE, + (byte) 0); + result = + seProvider.hmacSign( + KMByteBlob.cast(macKey).getBuffer(), + KMByteBlob.cast(macKey).getStartOff(), + EPHEMERAL_MAC_KEY_SIZE, + data, + dataStart, + dataLength, + signature, + signatureStart); + } else { + result = + seProvider.hmacSign( + storeDataInst.getRkpMacKey(), data, dataStart, dataLength, signature, signatureStart); + } + return result; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMRepository.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMRepository.java new file mode 100644 index 0000000..9fd2406 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMRepository.java @@ -0,0 +1,130 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; + +/** + * KMRepository class manages volatile memory usage by the applet. Note the repository is only used + * by applet and it is not intended to be used by seProvider. + */ +public class KMRepository { + + // The maximum available heap memory. + public static final short HEAP_SIZE = 10000; + // Index pointing from the back of heap. + private static short[] reclaimIndex; + // Singleton instance + private static KMRepository repository; + // Heap buffer + private byte[] heap; + // Index to the heap buffer. + private short[] heapIndex; + + public KMRepository(boolean isUpgrading) { + heap = JCSystem.makeTransientByteArray(HEAP_SIZE, JCSystem.CLEAR_ON_RESET); + heapIndex = JCSystem.makeTransientShortArray((short) 1, JCSystem.CLEAR_ON_RESET); + reclaimIndex = JCSystem.makeTransientShortArray((short) 1, JCSystem.CLEAR_ON_RESET); + reclaimIndex[0] = HEAP_SIZE; + repository = this; + } + + public static KMRepository instance() { + return repository; + } + + public void onUninstall() { + // Javacard Runtime environment cleans up the data. + + } + + public void onProcess() {} + + public void clean() { + Util.arrayFillNonAtomic(heap, (short) 0, HEAP_SIZE, (byte) 0); + heapIndex[0] = 0; + reclaimIndex[0] = HEAP_SIZE; + } + + public void onDeselect() {} + + public void onSelect() { + // If write through caching is implemented then this method will restore the data into cache + } + + // This function uses memory from the back of the heap(transient memory). Call + // reclaimMemory function immediately after the use. + public short allocReclaimableMemory(short length) { + if ((((short) (reclaimIndex[0] - length)) <= heapIndex[0]) || (length >= HEAP_SIZE / 2)) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + reclaimIndex[0] -= length; + return reclaimIndex[0]; + } + + // Reclaims the memory back. + public void reclaimMemory(short length) { + if (reclaimIndex[0] < heapIndex[0]) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + Util.arrayFillNonAtomic(heap, reclaimIndex[0], length, (byte) 0); + reclaimIndex[0] += length; + } + + public short allocAvailableMemory() { + if (heapIndex[0] >= heap.length) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short index = heapIndex[0]; + heapIndex[0] = reclaimIndex[0]; + return index; + } + + public short alloc(short length) { + if ((((short) (heapIndex[0] + length)) > heap.length) + || (((short) (heapIndex[0] + length)) > reclaimIndex[0])) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + heapIndex[0] += length; + return (short) (heapIndex[0] - length); + } + + public byte[] getHeap() { + return heap; + } + + public short getHeapIndex() { + return heapIndex[0]; + } + + // Use this function to reset the heapIndex to its previous state. + // Some of the data might be lost so use it carefully. + public void setHeapIndex(short offset) { + if (offset > heapIndex[0] || offset < 0) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + Util.arrayFillNonAtomic(heap, offset, (short) (heapIndex[0] - offset), (byte) 0); + heapIndex[0] = offset; + } + + public short getHeapReclaimIndex() { + return reclaimIndex[0]; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMSemanticTag.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMSemanticTag.java new file mode 100644 index 0000000..07b2675 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMSemanticTag.java @@ -0,0 +1,80 @@ +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMSemanticTag corresponds to CBOR type of tagged item. The structure is defined as struct{byte + * SEMANTIC_TAG_TYPE; short length; tag, short ptr }. Tag is INTEGER_TYPE and the possible values + * are defined here https://www.rfc-editor.org/rfc/rfc7049#section-2.4 + */ +public class KMSemanticTag extends KMType { + + public static final short COSE_MAC_SEMANTIC_TAG = (short) 0x0011; + public static final short ROT_SEMANTIC_TAG = (short) 0x9C41; + private static KMSemanticTag prototype; + + private KMSemanticTag() {} + + private static KMSemanticTag proto(short ptr) { + if (prototype == null) { + prototype = new KMSemanticTag(); + } + instanceTable[KM_SEMANTIC_TAG_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp(short valuePtr) { + short ptr = KMType.instance(SEMANTIC_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), KMInteger.exp()); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), valuePtr); + return ptr; + } + + public static KMSemanticTag cast(short ptr) { + if (heap[ptr] != SEMANTIC_TAG_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static short instance(short tag, short value) { + if (!isSemanticTagSupported(tag)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + // The maximum tag size can be UINT32. Currently, we support + // only two tags which are short. + short ptr = KMType.instance(SEMANTIC_TAG_TYPE, (short) 6); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2), tag); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE + 4), value); + return ptr; + } + + private static boolean isSemanticTagSupported(short tag) { + tag = KMInteger.cast(tag).getShort(); + switch (tag) { + case COSE_MAC_SEMANTIC_TAG: + case ROT_SEMANTIC_TAG: + break; + default: + return false; + } + return true; + } + + public short length() { + return Util.getShort(heap, (short) (instanceTable[KM_SEMANTIC_TAG_OFFSET] + 1)); + } + + public short getKeyPtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_SEMANTIC_TAG_OFFSET] + TLV_HEADER_SIZE + 2)); + } + + public short getValuePtr() { + return Util.getShort( + heap, (short) (instanceTable[KM_SEMANTIC_TAG_OFFSET] + TLV_HEADER_SIZE + 4)); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMSimpleValue.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMSimpleValue.java new file mode 100644 index 0000000..6dffd73 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMSimpleValue.java @@ -0,0 +1,71 @@ +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMSimpleValue corresponds to CBOR type of Simple value. It holds either true, false or NULL + * values. The structure is defined as struct{byte SIMPLE_VALUE_TYPE; short length; simple value } + */ +public class KMSimpleValue extends KMType { + + public static final byte FALSE = (byte) 20; + public static final byte TRUE = (byte) 21; + public static final byte NULL = (byte) 22; + private static KMSimpleValue prototype; + + private KMSimpleValue() {} + + private static KMSimpleValue proto(short ptr) { + if (prototype == null) { + prototype = new KMSimpleValue(); + } + instanceTable[KM_SIMPLE_VALUE_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + return KMType.exp(SIMPLE_VALUE_TYPE); + } + + public static KMSimpleValue cast(short ptr) { + if (heap[ptr] != SIMPLE_VALUE_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (!isSimpleValueValid(heap[(short) (ptr + 3)])) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public static short instance(byte value) { + if (!isSimpleValueValid(value)) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short ptr = KMType.instance(SIMPLE_VALUE_TYPE, (short) 1); + heap[(short) (ptr + 3)] = value; + return ptr; + } + + private static boolean isSimpleValueValid(byte value) { + switch (value) { + case TRUE: + case FALSE: + case NULL: + break; + default: + return false; + } + return true; + } + + public short length() { + return Util.getShort(heap, (short) (instanceTable[KM_SIMPLE_VALUE_OFFSET] + 1)); + } + + public byte getValue() { + return heap[(short) (instanceTable[KM_SIMPLE_VALUE_OFFSET] + 3)]; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMTag.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMTag.java new file mode 100644 index 0000000..3033a70 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMTag.java @@ -0,0 +1,102 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import com.android.javacard.seprovider.KMException; +import javacard.framework.Util; + +/** + * This class represents a tag as defined by keymaster hal specifications. It is composed of key + * value pair. The key consists of short tag type e.g. KMType.ENUM and short tag key e.g. + * KMType.ALGORITHM. The key is encoded as uint CBOR type with 4 bytes. This is followed by value + * which can be any CBOR type based on key. struct{byte tag=KMType.TAG_TYPE, short length, value) + * where value is subtype of KMTag i.e. struct{short tagType=one of tag types declared in KMType , + * short tagKey=one of the tag keys declared in KMType, value} where value is one of the sub-types + * of KMType. + */ +public class KMTag extends KMType { + + public static short getTagType(short ptr) { + return Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + } + + public static short getKey(short ptr) { + return Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE + 2)); + } + + public static void assertPresence(short params, short tagType, short tagKey, short error) { + if (!isPresent(params, tagType, tagKey)) { + KMException.throwIt(error); + } + } + + public static void assertAbsence(short params, short tagType, short tagKey, short error) { + if (isPresent(params, tagType, tagKey)) { + KMException.throwIt(error); + } + } + + public static boolean isPresent(short params, short tagType, short tagKey) { + short tag = KMKeyParameters.findTag(tagType, tagKey, params); + return tag != KMType.INVALID_VALUE; + } + + public static boolean isEqual(short params, short tagType, short tagKey, short value) { + switch (tagType) { + case KMType.ENUM_TAG: + return KMEnumTag.getValue(tagKey, params) == value; + case KMType.UINT_TAG: + case KMType.DATE_TAG: + case KMType.ULONG_TAG: + return KMIntegerTag.isEqual(params, tagType, tagKey, value); + case KMType.ENUM_ARRAY_TAG: + return KMEnumArrayTag.contains(tagKey, value, params); + case KMType.UINT_ARRAY_TAG: + case KMType.ULONG_ARRAY_TAG: + return KMIntegerArrayTag.contains(tagKey, value, params); + } + return false; + } + + public static void assertTrue(boolean condition, short error) { + if (!condition) { + KMException.throwIt(error); + } + } + + public static boolean isValidPublicExponent(short params) { + short pubExp = KMKeyParameters.findTag(KMType.ULONG_TAG, KMType.RSA_PUBLIC_EXPONENT, params); + if (pubExp == KMType.INVALID_VALUE) { + return false; + } + pubExp = KMIntegerTag.cast(pubExp).getValue(); + if (!(KMInteger.cast(pubExp).getShort() == 0x01 + && KMInteger.cast(pubExp).getSignificantShort() == 0x01)) { + return false; + } + return true; + } + + public static boolean isValidKeySize(short params) { + short keysize = KMKeyParameters.findTag(KMType.UINT_TAG, KMType.KEYSIZE, params); + if (keysize == KMType.INVALID_VALUE) { + return false; + } + short alg = KMEnumTag.getValue(KMType.ALGORITHM, params); + return KMIntegerTag.cast(keysize).isValidKeySize((byte) alg); + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMTextString.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMTextString.java new file mode 100644 index 0000000..80aebd2 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMTextString.java @@ -0,0 +1,80 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMTextString represents contiguous block of bytes. It corresponds to CBOR type of Text String. It + * extends KMByteBlob by specifying value field as zero or more sequence of bytes. struct{ byte + * TEXT_STR_TYPE; short length; sequence of bytes} + */ +public class KMTextString extends KMByteBlob { + + private static byte OFFSET_SIZE = 2; + + private static KMTextString prototype; + + private KMTextString() {} + + private static KMTextString proto(short ptr) { + if (prototype == null) { + prototype = new KMTextString(); + } + instanceTable[KM_TEXT_STRING_OFFSET] = ptr; + return prototype; + } + + // pointer to an empty instance used as expression + public static short exp() { + return KMType.exp(TEXT_STRING_TYPE); + } + + // return an empty byte blob instance + public static short instance(short length) { + short ptr = KMType.instance(TEXT_STRING_TYPE, (short) (length + OFFSET_SIZE)); + Util.setShort( + heap, (short) (ptr + TLV_HEADER_SIZE), (short) (ptr + TLV_HEADER_SIZE + OFFSET_SIZE)); + Util.setShort(heap, (short) (ptr + 1), length); + return ptr; + } + + // byte blob from existing buf + public static short instance(byte[] buf, short startOff, short length) { + short ptr = instance(length); + Util.arrayCopyNonAtomic( + buf, startOff, heap, (short) (ptr + TLV_HEADER_SIZE + OFFSET_SIZE), length); + return ptr; + } + + // cast the ptr to KMTextString + public static KMTextString cast(short ptr) { + if (heap[ptr] != TEXT_STRING_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + if (Util.getShort(heap, (short) (ptr + 1)) == INVALID_VALUE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + protected short getBaseOffset() { + return instanceTable[KM_TEXT_STRING_OFFSET]; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMType.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMType.java new file mode 100644 index 0000000..dc89604 --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMType.java @@ -0,0 +1,409 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.JCSystem; +import javacard.framework.Util; + +/** + * This class declares all types, tag types, and tag keys. It also establishes basic structure of + * any KMType i.e. struct{byte type, short length, value} where value can any of the KMType. Also, + * KMType refers to transient memory heap in the repository. Finally KMType's subtypes are singleton + * prototype objects which just cast the structure over contiguous memory buffer. + */ +public abstract class KMType { + + public static final short INVALID_VALUE = (short) 0x8000; + // Types + public static final byte BYTE_BLOB_TYPE = 0x01; + public static final byte INTEGER_TYPE = 0x02; + public static final byte ENUM_TYPE = 0x03; + public static final byte TAG_TYPE = 0x04; + public static final byte ARRAY_TYPE = 0x05; + public static final byte KEY_PARAM_TYPE = 0x06; + public static final byte KEY_CHAR_TYPE = 0x07; + public static final byte HW_AUTH_TOKEN_TYPE = 0x08; + public static final byte VERIFICATION_TOKEN_TYPE = 0x09; + public static final byte HMAC_SHARING_PARAM_TYPE = 0x0A; + public static final byte X509_CERT = 0x0B; + public static final byte NEG_INTEGER_TYPE = 0x0C; + public static final byte TEXT_STRING_TYPE = 0x0D; + public static final byte MAP_TYPE = 0x0E; + public static final byte COSE_KEY_TYPE = 0x0F; + public static final byte COSE_PAIR_TAG_TYPE = 0x10; + public static final byte COSE_PAIR_INT_TAG_TYPE = 0x20; + public static final byte COSE_PAIR_NEG_INT_TAG_TYPE = 0x30; + public static final byte COSE_PAIR_BYTE_BLOB_TAG_TYPE = 0x40; + public static final byte COSE_PAIR_COSE_KEY_TAG_TYPE = 0x60; + public static final byte COSE_PAIR_SIMPLE_VALUE_TAG_TYPE = 0x70; + public static final byte COSE_PAIR_TEXT_STR_TAG_TYPE = (byte) 0x80; + public static final byte SIMPLE_VALUE_TYPE = (byte) 0x90; + public static final byte COSE_HEADERS_TYPE = (byte) 0xA0; + public static final byte COSE_CERT_PAYLOAD_TYPE = (byte) 0xB0; + public static final byte SEMANTIC_TAG_TYPE = (byte) 0xC0; + // Tag Types + public static final short INVALID_TAG = 0x0000; + public static final short ENUM_TAG = 0x1000; + public static final short ENUM_ARRAY_TAG = 0x2000; + public static final short UINT_TAG = 0x3000; + public static final short UINT_ARRAY_TAG = 0x4000; + public static final short ULONG_TAG = 0x5000; + public static final short DATE_TAG = 0x6000; + public static final short BOOL_TAG = 0x7000; + public static final short BIGNUM_TAG = (short) 0x8000; + public static final short BYTES_TAG = (short) 0x9000; + public static final short ULONG_ARRAY_TAG = (short) 0xA000; + public static final short TAG_TYPE_MASK = (short) 0xF000; + + // Enum Tag + // Internal tags + public static final short RULE = 0x7FFF; + public static final byte IGNORE_INVALID_TAGS = 0x00; + public static final byte FAIL_ON_INVALID_TAGS = 0x01; + + // Algorithm Enum Tag key and values + public static final short ALGORITHM = 0x0002; + public static final byte RSA = 0x01; + public static final byte DES = 0x21; + public static final byte EC = 0x03; + public static final byte AES = 0x20; + public static final byte HMAC = (byte) 0x80; + + // EcCurve Enum Tag key and values. + public static final short ECCURVE = 0x000A; + public static final byte P_224 = 0x00; + public static final byte P_256 = 0x01; + public static final byte P_384 = 0x02; + public static final byte P_521 = 0x03; + public static final byte CURVE_25519 = 0x04; + + // KeyBlobUsageRequirements Enum Tag key and values. + public static final short BLOB_USAGE_REQ = 0x012D; + public static final byte STANDALONE = 0x00; + public static final byte REQUIRES_FILE_SYSTEM = 0x01; + + // HardwareAuthenticatorType Enum Tag key and values. + public static final short USER_AUTH_TYPE = 0x01F8; + public static final byte USER_AUTH_NONE = 0x00; + public static final byte PASSWORD = 0x01; + public static final byte FINGERPRINT = 0x02; + public static final byte BOTH = 0x03; + // have to be power of 2 + public static final byte ANY = (byte) 0xFF; + + // Origin Enum Tag key and values. + public static final short ORIGIN = 0x02BE; + public static final byte GENERATED = 0x00; + public static final byte DERIVED = 0x01; + public static final byte IMPORTED = 0x02; + public static final byte UNKNOWN = 0x03; + public static final byte SECURELY_IMPORTED = 0x04; + + // Hardware Type tag key and values + public static final short HARDWARE_TYPE = 0x0130; + public static final byte SOFTWARE = 0x00; + public static final byte TRUSTED_ENVIRONMENT = 0x01; + public static final byte STRONGBOX = 0x02; + + // No Tag + // Derivation Function - No Tag defined + public static final short KEY_DERIVATION_FUNCTION = (short) 0xF001; + public static final byte DERIVATION_NONE = 0x00; + public static final byte RFC5869_SHA256 = 0x01; + public static final byte ISO18033_2_KDF1_SHA1 = 0x02; + public static final byte ISO18033_2_KDF1_SHA256 = 0x03; + public static final byte ISO18033_2_KDF2_SHA1 = 0x04; + public static final byte ISO18033_2_KDF2_SHA256 = 0x05; + + // KeyFormat - No Tag defined. + public static final short KEY_FORMAT = (short) 0xF002; + public static final byte X509 = 0x00; + public static final byte PKCS8 = 0x01; + public static final byte RAW = 0x03; + + // Verified Boot State + public static final short VERIFIED_BOOT_STATE = (short) 0xF003; + public static final byte VERIFIED_BOOT = 0x00; + public static final byte SELF_SIGNED_BOOT = 0x01; + public static final byte UNVERIFIED_BOOT = 0x02; + public static final byte FAILED_BOOT = 0x03; + + // Device Locked + public static final short DEVICE_LOCKED = (short) 0xF006; + public static final byte DEVICE_LOCKED_TRUE = 0x01; + public static final byte DEVICE_LOCKED_FALSE = 0x00; + + // Enum Array Tag + // Purpose + public static final short PURPOSE = 0x0001; + public static final byte ENCRYPT = 0x00; + public static final byte DECRYPT = 0x01; + public static final byte SIGN = 0x02; + public static final byte VERIFY = 0x03; + public static final byte DERIVE_KEY = 0x04; + public static final byte WRAP_KEY = 0x05; + public static final byte AGREE_KEY = 0x06; + public static final byte ATTEST_KEY = (byte) 0x07; + // Block mode + public static final short BLOCK_MODE = 0x0004; + public static final byte ECB = 0x01; + public static final byte CBC = 0x02; + public static final byte CTR = 0x03; + public static final byte GCM = 0x20; + + // Digest + public static final short DIGEST = 0x0005; + public static final byte DIGEST_NONE = 0x00; + public static final byte MD5 = 0x01; + public static final byte SHA1 = 0x02; + public static final byte SHA2_224 = 0x03; + public static final byte SHA2_256 = 0x04; + public static final byte SHA2_384 = 0x05; + public static final byte SHA2_512 = 0x06; + + // Padding mode + public static final short PADDING = 0x0006; + public static final byte PADDING_NONE = 0x01; + public static final byte RSA_OAEP = 0x02; + public static final byte RSA_PSS = 0x03; + public static final byte RSA_PKCS1_1_5_ENCRYPT = 0x04; + public static final byte RSA_PKCS1_1_5_SIGN = 0x05; + public static final byte PKCS7 = 0x40; + + // OAEP MGF Digests - only SHA-1 is supported in Javacard + public static final short RSA_OAEP_MGF_DIGEST = 0xCB; + + // Integer Tag - UINT, ULONG and DATE + // UINT tags + // Keysize + public static final short KEYSIZE = 0x0003; + // Min Mac Length + public static final short MIN_MAC_LENGTH = 0x0008; + // Min Seconds between OPS + public static final short MIN_SEC_BETWEEN_OPS = 0x0193; + // Max Uses per Boot + public static final short MAX_USES_PER_BOOT = 0x0194; + // UserId + public static final short USERID = 0x01F5; + // Auth Timeout + public static final short AUTH_TIMEOUT = 0x01F9; + // Auth Timeout in Milliseconds + public static final short AUTH_TIMEOUT_MILLIS = 0x7FFF; + // OS Version + public static final short OS_VERSION = 0x02C1; + // OS Patch Level + public static final short OS_PATCH_LEVEL = 0x02C2; + // Vendor Patch Level + public static final short VENDOR_PATCH_LEVEL = 0x02CE; + // Boot Patch Level + public static final short BOOT_PATCH_LEVEL = 0x02CF; + // Mac Length + public static final short MAC_LENGTH = 0x03EB; + // Usage Count Limit + public static final short USAGE_COUNT_LIMIT = 0x195; + + // ULONG tags + // RSA Public Exponent + public static final short RSA_PUBLIC_EXPONENT = 0x00C8; + + // DATE tags + public static final short ACTIVE_DATETIME = 0x0190; + public static final short ORIGINATION_EXPIRE_DATETIME = 0x0191; + public static final short USAGE_EXPIRE_DATETIME = 0x0192; + public static final short CREATION_DATETIME = 0x02BD; + ; + public static final short CERTIFICATE_NOT_BEFORE = 0x03F0; + public static final short CERTIFICATE_NOT_AFTER = 0x03F1; + // Integer Array Tags - ULONG_REP and UINT_REP. + // User Secure Id + public static final short USER_SECURE_ID = (short) 0x01F6; + + // Boolean Tag + // Caller Nonce + public static final short CALLER_NONCE = (short) 0x0007; + // Include Unique Id + public static final short INCLUDE_UNIQUE_ID = (short) 0x00CA; + // Bootloader Only + public static final short BOOTLOADER_ONLY = (short) 0x012E; + // Rollback Resistance + public static final short ROLLBACK_RESISTANCE = (short) 0x012F; + // No Auth Required + public static final short NO_AUTH_REQUIRED = (short) 0x01F7; + // Allow While On Body + public static final short ALLOW_WHILE_ON_BODY = (short) 0x01FA; + // Max Boot Level + public static final short MAX_BOOT_LEVEL = (short) 0x03F2; + // Trusted User Presence Required + public static final short TRUSTED_USER_PRESENCE_REQUIRED = (short) 0x01FB; + // Trusted Confirmation Required + public static final short TRUSTED_CONFIRMATION_REQUIRED = (short) 0x01FC; + // Unlocked Device Required + public static final short UNLOCKED_DEVICE_REQUIRED = (short) 0x01FD; + // Reset Since Id Rotation + public static final short RESET_SINCE_ID_ROTATION = (short) 0x03EC; + // Early boot ended. + public static final short EARLY_BOOT_ONLY = (short) 0x0131; + // Device unique attestation. + public static final short DEVICE_UNIQUE_ATTESTATION = (short) 0x02D0; + + // Byte Tag + // Application Id + public static final short APPLICATION_ID = (short) 0x0259; + // Application Data + public static final short APPLICATION_DATA = (short) 0x02BC; + // Root Of Trust + public static final short ROOT_OF_TRUST = (short) 0x02C0; + // Unique Id + public static final short UNIQUE_ID = (short) 0x02C3; + // Attestation Challenge + public static final short ATTESTATION_CHALLENGE = (short) 0x02C4; + // Attestation Application Id + public static final short ATTESTATION_APPLICATION_ID = (short) 0x02C5; + // Attestation Id Brand + public static final short ATTESTATION_ID_BRAND = (short) 0x02C6; + // Attestation Id Device + public static final short ATTESTATION_ID_DEVICE = (short) 0x02C7; + // Attestation Id Product + public static final short ATTESTATION_ID_PRODUCT = (short) 0x02C8; + // Attestation Id Serial + public static final short ATTESTATION_ID_SERIAL = (short) 0x02C9; + // Attestation Id IMEI + public static final short ATTESTATION_ID_IMEI = (short) 0x02CA; + // Attestation Id SECOND IMEI + public static final short ATTESTATION_ID_SECOND_IMEI = (short) 0x02D3; + // Attestation Id MEID + public static final short ATTESTATION_ID_MEID = (short) 0x02CB; + // Attestation Id Manufacturer + public static final short ATTESTATION_ID_MANUFACTURER = (short) 0x02CC; + // Attestation Id Model + public static final short ATTESTATION_ID_MODEL = (short) 0x02CD; + // Associated Data + public static final short ASSOCIATED_DATA = (short) 0x03E8; + // Nonce + public static final short NONCE = (short) 0x03E9; + // Confirmation Token + public static final short CONFIRMATION_TOKEN = (short) 0x03ED; + // Serial Number - this is a big num but in applet we handle it as byte blob + public static final short CERTIFICATE_SERIAL_NUM = (short) 0x03EE; + // Subject Name + public static final short CERTIFICATE_SUBJECT_NAME = (short) 0x03EF; + + public static final short LENGTH_FROM_PDU = (short) 0xFFFF; + + public static final byte NO_VALUE = (byte) 0xff; + // Support Curves for Eek Chain validation. + public static final byte RKP_CURVE_NONE = 0; + // Type offsets. + public static final byte KM_TYPE_BASE_OFFSET = 0; + public static final byte KM_ARRAY_OFFSET = KM_TYPE_BASE_OFFSET; + public static final byte KM_BOOL_TAG_OFFSET = KM_TYPE_BASE_OFFSET + 1; + public static final byte KM_BYTE_BLOB_OFFSET = KM_TYPE_BASE_OFFSET + 2; + public static final byte KM_BYTE_TAG_OFFSET = KM_TYPE_BASE_OFFSET + 3; + public static final byte KM_ENUM_OFFSET = KM_TYPE_BASE_OFFSET + 4; + public static final byte KM_ENUM_ARRAY_TAG_OFFSET = KM_TYPE_BASE_OFFSET + 5; + public static final byte KM_ENUM_TAG_OFFSET = KM_TYPE_BASE_OFFSET + 6; + public static final byte KM_HARDWARE_AUTH_TOKEN_OFFSET = KM_TYPE_BASE_OFFSET + 7; + public static final byte KM_HMAC_SHARING_PARAMETERS_OFFSET = KM_TYPE_BASE_OFFSET + 8; + public static final byte KM_INTEGER_OFFSET = KM_TYPE_BASE_OFFSET + 9; + public static final byte KM_INTEGER_ARRAY_TAG_OFFSET = KM_TYPE_BASE_OFFSET + 10; + public static final byte KM_INTEGER_TAG_OFFSET = KM_TYPE_BASE_OFFSET + 11; + public static final byte KM_KEY_CHARACTERISTICS_OFFSET = KM_TYPE_BASE_OFFSET + 12; + public static final byte KM_KEY_PARAMETERS_OFFSET = KM_TYPE_BASE_OFFSET + 13; + public static final byte KM_VERIFICATION_TOKEN_OFFSET = KM_TYPE_BASE_OFFSET + 14; + public static final byte KM_NEG_INTEGER_OFFSET = KM_TYPE_BASE_OFFSET + 15; + public static final byte KM_TEXT_STRING_OFFSET = KM_TYPE_BASE_OFFSET + 16; + public static final byte KM_MAP_OFFSET = KM_TYPE_BASE_OFFSET + 17; + public static final byte KM_COSE_KEY_OFFSET = KM_TYPE_BASE_OFFSET + 18; + public static final byte KM_COSE_KEY_INT_VAL_OFFSET = KM_TYPE_BASE_OFFSET + 19; + public static final byte KM_COSE_KEY_NINT_VAL_OFFSET = KM_TYPE_BASE_OFFSET + 20; + public static final byte KM_COSE_KEY_BYTE_BLOB_VAL_OFFSET = KM_TYPE_BASE_OFFSET + 21; + public static final byte KM_COSE_KEY_COSE_KEY_VAL_OFFSET = KM_TYPE_BASE_OFFSET + 22; + public static final byte KM_COSE_KEY_SIMPLE_VAL_OFFSET = KM_TYPE_BASE_OFFSET + 23; + public static final byte KM_SIMPLE_VALUE_OFFSET = KM_TYPE_BASE_OFFSET + 24; + public static final byte KM_COSE_HEADERS_OFFSET = KM_TYPE_BASE_OFFSET + 25; + public static final byte KM_COSE_KEY_TXT_STR_VAL_OFFSET = KM_TYPE_BASE_OFFSET + 26; + public static final byte KM_COSE_CERT_PAYLOAD_OFFSET = KM_TYPE_BASE_OFFSET + 27; + public static final byte KM_BIGNUM_TAG_OFFSET = KM_TYPE_BASE_OFFSET + 28; + public static final byte KM_SEMANTIC_TAG_OFFSET = KM_TYPE_BASE_OFFSET + 29; + + // Attestation types + public static final byte NO_CERT = 0; + public static final byte ATTESTATION_CERT = 1; + public static final byte SELF_SIGNED_CERT = 2; + public static final byte FAKE_CERT = 3; + // Buffering Mode + public static final byte BUF_NONE = 0; + public static final byte BUF_RSA_DECRYPT_OR_NO_DIGEST = 1; + public static final byte BUF_EC_NO_DIGEST = 2; + public static final byte BUF_AES_ENCRYPT_PKCS7_BLOCK_ALIGN = 3; + public static final byte BUF_AES_DECRYPT_PKCS7_BLOCK_ALIGN = 4; + public static final byte BUF_DES_ENCRYPT_PKCS7_BLOCK_ALIGN = 5; + public static final byte BUF_DES_DECRYPT_PKCS7_BLOCK_ALIGN = 6; + public static final byte BUF_AES_GCM_DECRYPT_BLOCK_ALIGN = 7; + + // MAX ApplicationID or Application Data size + public static final byte MAX_APP_ID_APP_DATA_SIZE = 64; + // Max attestation challenge size. + public static final short MAX_ATTESTATION_CHALLENGE_SIZE = 128; + // Max certificate serial size. + public static final byte MAX_CERTIFICATE_SERIAL_SIZE = 20; + // Attestation Application ID + public static final short MAX_ATTESTATION_APP_ID_SIZE = 1024; + // Instance table + public static final byte INSTANCE_TABLE_SIZE = 30; + protected static final byte TLV_HEADER_SIZE = 3; + protected static KMRepository repository; + protected static byte[] heap; + protected static short[] instanceTable; + + public static void initialize() { + instanceTable = JCSystem.makeTransientShortArray(INSTANCE_TABLE_SIZE, JCSystem.CLEAR_ON_RESET); + KMType.repository = KMRepository.instance(); + KMType.heap = repository.getHeap(); + } + + public static byte getType(short ptr) { + return heap[ptr]; + } + + public static short length(short ptr) { + return Util.getShort(heap, (short) (ptr + 1)); + } + + public static short getValue(short ptr) { + return Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + } + + protected static short instance(byte type, short length) { + if (length < 0) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + short ptr = repository.alloc((short) (length + TLV_HEADER_SIZE)); + heap[ptr] = type; + Util.setShort(heap, (short) (ptr + 1), length); + return ptr; + } + + protected static short exp(byte type) { + short ptr = repository.alloc(TLV_HEADER_SIZE); + heap[ptr] = type; + Util.setShort(heap, (short) (ptr + 1), INVALID_VALUE); + return ptr; + } +} diff --git a/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMVerificationToken.java b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMVerificationToken.java new file mode 100644 index 0000000..590e73a --- /dev/null +++ b/ready_se/google/keymint/KM300/Applet/src/com/android/javacard/keymaster/KMVerificationToken.java @@ -0,0 +1,129 @@ +/* + * Copyright(C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMVerificationToken represents VerificationToken structure from android keymaster hal + * specifications. It corresponds to CBOR array type. struct{byte type=VERIFICATION_TOKEN_TYPE; + * short length=2; short arrayPtr} where arrayPtr is a pointer to ordered array with following + * elements: {KMInteger Challenge; KMInteger Timestamp; KMByteBlob PARAMETERS_VERIFIED; + * SecurityLevel level; KMByteBlob Mac}. + */ +public class KMVerificationToken extends KMType { + + public static final byte CHALLENGE = 0x00; + public static final byte TIMESTAMP = 0x01; + public static final byte MAC = 0x02; + + private static KMVerificationToken prototype; + + private KMVerificationToken() {} + + public static short exp() { + short arrPtr = KMArray.instance((short) 3); + KMArray arr = KMArray.cast(arrPtr); + arr.add(CHALLENGE, KMInteger.exp()); + arr.add(TIMESTAMP, KMInteger.exp()); + arr.add(MAC, KMByteBlob.exp()); + return instance(arrPtr); + } + + private static KMVerificationToken proto(short ptr) { + if (prototype == null) { + prototype = new KMVerificationToken(); + } + KMType.instanceTable[KM_VERIFICATION_TOKEN_OFFSET] = ptr; + return prototype; + } + + public static short instance() { + short arrPtr = KMArray.instance((short) 3); + KMArray arr = KMArray.cast(arrPtr); + arr.add(CHALLENGE, KMInteger.uint_16((short) 0)); + arr.add(TIMESTAMP, KMInteger.uint_16((short) 0)); + arr.add(MAC, KMByteBlob.instance((short) 0)); + return instance(arrPtr); + } + + public static short instance(short vals) { + KMArray arr = KMArray.cast(vals); + if (arr.length() != 3) { + ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); + } + short ptr = KMType.instance(VERIFICATION_TOKEN_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMVerificationToken cast(short ptr) { + if (heap[ptr] != VERIFICATION_TOKEN_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (heap[arrPtr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + public short getVals() { + return Util.getShort( + heap, (short) (KMType.instanceTable[KM_VERIFICATION_TOKEN_OFFSET] + TLV_HEADER_SIZE)); + } + + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); + } + + public short getChallenge() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(CHALLENGE); + } + + public void setChallenge(short vals) { + KMInteger.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(CHALLENGE, vals); + } + + public short getTimestamp() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(TIMESTAMP); + } + + public void setTimestamp(short vals) { + KMInteger.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(TIMESTAMP, vals); + } + + public short getMac() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).get(MAC); + } + + public void setMac(short vals) { + KMByteBlob.cast(vals); + short arrPtr = getVals(); + KMArray.cast(arrPtr).add(MAC, vals); + } +} diff --git a/ready_se/google/keymint/KM300/HAL/.clang-format b/ready_se/google/keymint/KM300/HAL/.clang-format new file mode 100644 index 0000000..b0dc94c --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/.clang-format @@ -0,0 +1,10 @@ +BasedOnStyle: LLVM +IndentWidth: 4 +UseTab: Never +BreakBeforeBraces: Attach +AllowShortFunctionsOnASingleLine: Inline +AllowShortIfStatementsOnASingleLine: true +IndentCaseLabels: false +ColumnLimit: 100 +PointerBindsToType: true +SpacesBeforeTrailingComments: 2 diff --git a/ready_se/google/keymint/KM300/HAL/Android.bp b/ready_se/google/keymint/KM300/HAL/Android.bp new file mode 100644 index 0000000..fd38d10 --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/Android.bp @@ -0,0 +1,117 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +package { + default_applicable_licenses: [ + "external_libese_ready_se_google_keymint_KM300_HAL_license", + ], +} + +// Added automatically by a large-scale-change +// See: http://go/android-license-faq +license { + name: "external_libese_ready_se_google_keymint_KM300_HAL_license", + visibility: [":__subpackages__"], + license_kinds: [ + "SPDX-license-identifier-Apache-2.0", + ], + license_text: [ + "LICENSE", + ], +} + +cc_library { + name: "libjc_keymint3", + defaults: [ + "keymaster_defaults", + "keymint_use_latest_hal_aidl_ndk_shared", + ], + srcs: [ + "CborConverter.cpp", + "JavacardKeyMintDevice.cpp", + "JavacardKeyMintOperation.cpp", + "JavacardRemotelyProvisionedComponentDevice.cpp", + "JavacardSecureElement.cpp", + "JavacardSharedSecret.cpp", + "keymint_utils.cpp", + ], + cflags: ["-O0"], + shared_libs: [ + "android.hardware.security.secureclock-V1-ndk", + "android.hardware.security.sharedsecret-V1-ndk", + "android.hardware.security.rkp-V3-ndk", + "lib_android_keymaster_keymint_utils", + "libbase", + "libcppbor_external", + "libkeymaster_portable", + "libkeymaster_messages", + "libsoft_attestation_cert", + "liblog", + "libcrypto", + "libcutils", + "libjc_keymint_transport", + "libbinder_ndk", + ], + export_include_dirs: [ + ".", + ], + vendor_available: true, +} + +cc_binary { + name: "android.hardware.security.keymint3-service.strongbox", + relative_install_path: "hw", + init_rc: ["android.hardware.security.keymint3-service.strongbox.rc"], + vintf_fragments: [ + "android.hardware.security.keymint3-service.strongbox.xml", + "android.hardware.security.sharedsecret3-service.strongbox.xml", + ], + vendor: true, + cflags: [ + "-Wall", + "-Wextra", + ], + defaults: [ + "keymint_use_latest_hal_aidl_ndk_shared", + ], + shared_libs: [ + "android.hardware.security.sharedsecret-V1-ndk", + "lib_android_keymaster_keymint_utils", + "android.hardware.security.rkp-V3-ndk", + "libbase", + "libbinder_ndk", + "libcppbor_external", + "libcrypto", + "libkeymaster_portable", + "libjc_keymint3", + "libjc_keymint_transport", + "liblog", + "libutils", + "android.se.omapi-V1-ndk", + ], + srcs: [ + "service.cpp", + ], + required: [ + "android.hardware.hardware_keystore.jc-strongbox-keymint3.xml", + ], +} + +prebuilt_etc { + name: "android.hardware.hardware_keystore.jc-strongbox-keymint3.xml", + sub_dir: "permissions", + vendor: true, + src: "android.hardware.hardware_keystore.jc-strongbox-keymint3.xml", +} diff --git a/ready_se/google/keymint/KM300/HAL/CborConverter.cpp b/ready_se/google/keymint/KM300/HAL/CborConverter.cpp new file mode 100644 index 0000000..7d0fc23 --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/CborConverter.cpp @@ -0,0 +1,521 @@ +/* + ** + ** Copyright 2020, The Android Open Source Project + ** + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** + ** http://www.apache.org/licenses/LICENSE-2.0 + ** + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + */ + +#include "CborConverter.h" + +#include <map> +#include <string> + +#include <android-base/logging.h> + +#include <KeyMintUtils.h> + +namespace keymint::javacard { +using ::aidl::android::hardware::security::keymint::KeyParameterValue; +using ::aidl::android::hardware::security::keymint::SecurityLevel; +using ::aidl::android::hardware::security::keymint::km_utils::kmParam2Aidl; +using ::aidl::android::hardware::security::keymint::km_utils::legacy_enum_conversion; +using ::aidl::android::hardware::security::keymint::km_utils::typeFromTag; + +constexpr int SB_ENFORCED = 0; +constexpr int TEE_ENFORCED = 1; +constexpr int SW_ENFORCED = 2; + +namespace { + +template <KeyParameterValue::Tag aidl_tag> +std::optional<uint32_t> aidlEnumVal2Uint32(const KeyParameterValue& value) { + return (value.getTag() == aidl_tag) + ? std::optional(static_cast<uint32_t>(value.get<aidl_tag>())) + : std::nullopt; +} + +std::optional<uint32_t> aidlEnumParam2Uint32(const KeyParameter& param) { + auto tag = legacy_enum_conversion(param.tag); + switch (tag) { + case KM_TAG_PURPOSE: + return aidlEnumVal2Uint32<KeyParameterValue::keyPurpose>(param.value); + case KM_TAG_ALGORITHM: + return aidlEnumVal2Uint32<KeyParameterValue::algorithm>(param.value); + case KM_TAG_BLOCK_MODE: + return aidlEnumVal2Uint32<KeyParameterValue::blockMode>(param.value); + case KM_TAG_DIGEST: + case KM_TAG_RSA_OAEP_MGF_DIGEST: + return aidlEnumVal2Uint32<KeyParameterValue::digest>(param.value); + case KM_TAG_PADDING: + return aidlEnumVal2Uint32<KeyParameterValue::paddingMode>(param.value); + case KM_TAG_EC_CURVE: + return aidlEnumVal2Uint32<KeyParameterValue::ecCurve>(param.value); + case KM_TAG_USER_AUTH_TYPE: + return aidlEnumVal2Uint32<KeyParameterValue::hardwareAuthenticatorType>(param.value); + case KM_TAG_ORIGIN: + return aidlEnumVal2Uint32<KeyParameterValue::origin>(param.value); + case KM_TAG_BLOB_USAGE_REQUIREMENTS: + case KM_TAG_KDF: + default: + CHECK(false) << "Unknown or unused enum tag: Something is broken"; + return std::nullopt; + } +} + +} // namespace + +bool CborConverter::addAttestationKey(Array& array, + const std::optional<AttestationKey>& attestationKey) { + if (attestationKey.has_value()) { + array.add(Bstr(attestationKey->keyBlob)); + addKeyparameters(array, attestationKey->attestKeyParams); + array.add(Bstr(attestationKey->issuerSubjectName)); + } else { + array.add(std::move(Bstr(vector<uint8_t>(0)))); + array.add(std::move(Map())); + array.add(std::move(Bstr(vector<uint8_t>(0)))); + } + return true; +} + +bool CborConverter::addKeyparameters(Array& array, const vector<KeyParameter>& keyParams) { + Map map; + std::map<uint64_t, vector<uint8_t>> enum_repetition; + std::map<uint64_t, Array> uint_repetition; + for (auto& param : keyParams) { + auto tag = legacy_enum_conversion(param.tag); + switch (typeFromTag(tag)) { + case KM_ENUM: { + auto paramEnum = aidlEnumParam2Uint32(param); + if (paramEnum.has_value()) { + map.add(static_cast<uint64_t>(tag), *paramEnum); + } + break; + } + case KM_UINT: + if (param.value.getTag() == KeyParameterValue::integer) { + auto intVal = param.value.get<KeyParameterValue::integer>(); + map.add(static_cast<uint64_t>(tag), intVal); + } + break; + case KM_UINT_REP: + if (param.value.getTag() == KeyParameterValue::integer) { + auto intVal = param.value.get<KeyParameterValue::integer>(); + uint_repetition[static_cast<uint64_t>(tag)].add(intVal); + } + break; + case KM_ENUM_REP: { + auto paramEnumRep = aidlEnumParam2Uint32(param); + if (paramEnumRep.has_value()) { + enum_repetition[static_cast<uint64_t>(tag)].push_back(*paramEnumRep); + } + break; + } + case KM_ULONG: + if (param.value.getTag() == KeyParameterValue::longInteger) { + auto longVal = param.value.get<KeyParameterValue::longInteger>(); + map.add(static_cast<uint64_t>(tag), longVal); + } + break; + case KM_ULONG_REP: + if (param.value.getTag() == KeyParameterValue::longInteger) { + auto longVal = param.value.get<KeyParameterValue::longInteger>(); + uint_repetition[static_cast<uint64_t>(tag & 0x00000000ffffffff)].add(longVal); + } + break; + case KM_DATE: + if (param.value.getTag() == KeyParameterValue::dateTime) { + auto dateVal = param.value.get<KeyParameterValue::dateTime>(); + map.add(static_cast<uint64_t>(tag), dateVal); + } + break; + case KM_BOOL: + map.add(static_cast<uint64_t>(tag), 1 /* true */); + break; + case KM_BIGNUM: + case KM_BYTES: + if (param.value.getTag() == KeyParameterValue::blob) { + const auto& value = param.value.get<KeyParameterValue::blob>(); + map.add(static_cast<uint64_t>(tag & 0x00000000ffffffff), value); + } + break; + case KM_INVALID: + break; + } + } + + for (auto const& [key, val] : enum_repetition) { + Bstr bstr(val); + map.add(key, std::move(bstr)); + } + + for (auto& [key, val] : uint_repetition) { + map.add(key, std::move(val)); + } + array.add(std::move(map)); + return true; +} + +// Array of three maps +std::optional<vector<KeyCharacteristics>> +CborConverter::getKeyCharacteristics(const unique_ptr<Item>& item, const uint32_t pos) { + vector<KeyCharacteristics> keyCharacteristics; + auto arrayItem = getItemAtPos(item, pos); + if (!arrayItem || (MajorType::ARRAY != getType(arrayItem.value()))) { + return std::nullopt; + } + KeyCharacteristics swEnf{SecurityLevel::KEYSTORE, {}}; + KeyCharacteristics teeEnf{SecurityLevel::TRUSTED_ENVIRONMENT, {}}; + KeyCharacteristics sbEnf{SecurityLevel::STRONGBOX, {}}; + + auto optSbEnf = getKeyParameters(arrayItem.value(), SB_ENFORCED); + if (!optSbEnf) { + return std::nullopt; + } + sbEnf.authorizations = std::move(optSbEnf.value()); + auto optTeeEnf = getKeyParameters(arrayItem.value(), TEE_ENFORCED); + if (!optTeeEnf) { + return std::nullopt; + } + teeEnf.authorizations = std::move(optTeeEnf.value()); + auto optSwEnf = getKeyParameters(arrayItem.value(), SW_ENFORCED); + if (!optSwEnf) { + return std::nullopt; + } + swEnf.authorizations = std::move(optSwEnf.value()); + // VTS will fail if the authorizations list is empty. + if (!sbEnf.authorizations.empty()) keyCharacteristics.push_back(std::move(sbEnf)); + if (!teeEnf.authorizations.empty()) keyCharacteristics.push_back(std::move(teeEnf)); + if (!swEnf.authorizations.empty()) keyCharacteristics.push_back(std::move(swEnf)); + return keyCharacteristics; +} + +std::optional<std::vector<KeyParameter>> CborConverter::getKeyParameter( + const std::pair<const std::unique_ptr<Item>&, const std::unique_ptr<Item>&> pair) { + std::vector<KeyParameter> keyParams; + keymaster_tag_t key; + auto optValue = getUint64(pair.first); + if (!optValue) { + return std::nullopt; + } + key = static_cast<keymaster_tag_t>(optValue.value()); + switch (keymaster_tag_get_type(key)) { + case KM_ENUM_REP: { + /* ENUM_REP contains values encoded in a Byte string */ + const Bstr* bstr = pair.second.get()->asBstr(); + if (bstr == nullptr) { + return std::nullopt; + } + for (auto bchar : bstr->value()) { + keymaster_key_param_t keyParam; + keyParam.tag = key; + keyParam.enumerated = bchar; + keyParams.push_back(kmParam2Aidl(keyParam)); + } + return keyParams; + } + case KM_ENUM: { + keymaster_key_param_t keyParam; + keyParam.tag = key; + if (!(optValue = getUint64(pair.second))) { + return std::nullopt; + } + keyParam.enumerated = static_cast<uint32_t>(optValue.value()); + keyParams.push_back(kmParam2Aidl(keyParam)); + return keyParams; + } + case KM_UINT: { + keymaster_key_param_t keyParam; + keyParam.tag = key; + if (!(optValue = getUint64(pair.second))) { + return std::nullopt; + } + keyParam.integer = static_cast<uint32_t>(optValue.value()); + keyParams.push_back(kmParam2Aidl(keyParam)); + return keyParams; + } + case KM_ULONG: { + keymaster_key_param_t keyParam; + keyParam.tag = key; + if (!(optValue = getUint64(pair.second))) { + return std::nullopt; + } + keyParam.long_integer = optValue.value(); + keyParams.push_back(kmParam2Aidl(keyParam)); + return keyParams; + } + case KM_UINT_REP: { + /* UINT_REP contains values encoded in a Array */ + Array* array = const_cast<Array*>(pair.second.get()->asArray()); + if (array == nullptr) return std::nullopt; + for (int i = 0; i < array->size(); i++) { + keymaster_key_param_t keyParam; + keyParam.tag = key; + const std::unique_ptr<Item>& item = array->get(i); + if (!(optValue = getUint64(item))) { + return std::nullopt; + } + keyParam.integer = static_cast<uint32_t>(optValue.value()); + keyParams.push_back(kmParam2Aidl(keyParam)); + } + return keyParams; + } + case KM_ULONG_REP: { + /* ULONG_REP contains values encoded in a Array */ + Array* array = const_cast<Array*>(pair.second.get()->asArray()); + if (array == nullptr) return std::nullopt; + for (int i = 0; i < array->size(); i++) { + keymaster_key_param_t keyParam; + keyParam.tag = key; + const std::unique_ptr<Item>& item = array->get(i); + if (!(optValue = getUint64(item))) { + return std::nullopt; + } + keyParam.long_integer = optValue.value(); + keyParams.push_back(kmParam2Aidl(keyParam)); + } + return keyParams; + } + case KM_DATE: { + keymaster_key_param_t keyParam; + keyParam.tag = key; + if (!(optValue = getUint64(pair.second))) { + return std::nullopt; + } + keyParam.date_time = optValue.value(); + keyParams.push_back(kmParam2Aidl(keyParam)); + return keyParams; + } + case KM_BOOL: { + keymaster_key_param_t keyParam; + keyParam.tag = key; + if (!(optValue = getUint64(pair.second))) { + return std::nullopt; + } + // If a tag with this type is present, the value is true. If absent, false. + keyParam.boolean = true; + keyParams.push_back(kmParam2Aidl(keyParam)); + return keyParams; + } + case KM_BIGNUM: + case KM_BYTES: { + keymaster_key_param_t keyParam; + keyParam.tag = key; + const Bstr* bstr = pair.second.get()->asBstr(); + if (bstr == nullptr) return std::nullopt; + keyParam.blob.data = bstr->value().data(); + keyParam.blob.data_length = bstr->value().size(); + keyParams.push_back(kmParam2Aidl(keyParam)); + return keyParams; + } + case KM_INVALID: + break; + } + return std::nullopt; +} + +// array of a blobs +std::optional<vector<Certificate>> +CborConverter::getCertificateChain(const std::unique_ptr<Item>& item, const uint32_t pos) { + vector<Certificate> certChain; + auto arrayItem = getItemAtPos(item, pos); + if (!arrayItem || (MajorType::ARRAY != getType(arrayItem.value()))) return std::nullopt; + + const Array* arr = arrayItem.value().get()->asArray(); + for (int i = 0; i < arr->size(); i++) { + Certificate cert; + auto optTemp = getByteArrayVec(arrayItem.value(), i); + if (!optTemp) return std::nullopt; + cert.encodedCertificate = std::move(optTemp.value()); + certChain.push_back(std::move(cert)); + } + return certChain; +} + +std::optional<string> CborConverter::getTextStr(const unique_ptr<Item>& item, const uint32_t pos) { + auto textStrItem = getItemAtPos(item, pos); + if (!textStrItem || (MajorType::TSTR != getType(textStrItem.value()))) { + return std::nullopt; + } + const Tstr* tstr = textStrItem.value().get()->asTstr(); + return tstr->value(); +} + +std::optional<string> CborConverter::getByteArrayStr(const unique_ptr<Item>& item, + const uint32_t pos) { + auto optTemp = getByteArrayVec(item, pos); + if (!optTemp) { + return std::nullopt; + } + std::string str(optTemp->begin(), optTemp->end()); + return str; +} + +std::optional<std::vector<uint8_t>> CborConverter::getByteArrayVec(const unique_ptr<Item>& item, + const uint32_t pos) { + auto strItem = getItemAtPos(item, pos); + if (!strItem || (MajorType::BSTR != getType(strItem.value()))) { + return std::nullopt; + } + const Bstr* bstr = strItem.value().get()->asBstr(); + return bstr->value(); +} + +std::optional<SharedSecretParameters> +CborConverter::getSharedSecretParameters(const unique_ptr<Item>& item, const uint32_t pos) { + SharedSecretParameters params; + // Array [seed, nonce] + auto arrayItem = getItemAtPos(item, pos); + if (!arrayItem || (MajorType::ARRAY != getType(arrayItem.value()))) { + return std::nullopt; + } + auto optSeed = getByteArrayVec(arrayItem.value(), 0); + auto optNonce = getByteArrayVec(arrayItem.value(), 1); + if (!optSeed || !optNonce) { + return std::nullopt; + } + params.seed = std::move(optSeed.value()); + params.nonce = std::move(optNonce.value()); + return params; +} + +bool CborConverter::addSharedSecretParameters(Array& array, + const vector<SharedSecretParameters>& params) { + Array cborParamsVec; + for (auto param : params) { + Array cborParam; + cborParam.add(Bstr(param.seed)); + cborParam.add(Bstr(param.nonce)); + cborParamsVec.add(std::move(cborParam)); + } + array.add(std::move(cborParamsVec)); + return true; +} + +bool CborConverter::addTimeStampToken(Array& array, const TimeStampToken& token) { + Array vToken; + vToken.add(static_cast<uint64_t>(token.challenge)); + vToken.add(static_cast<uint64_t>(token.timestamp.milliSeconds)); + vToken.add((std::vector<uint8_t>(token.mac))); + array.add(std::move(vToken)); + return true; +} + +bool CborConverter::addHardwareAuthToken(Array& array, const HardwareAuthToken& authToken) { + + Array hwAuthToken; + hwAuthToken.add(static_cast<uint64_t>(authToken.challenge)); + hwAuthToken.add(static_cast<uint64_t>(authToken.userId)); + hwAuthToken.add(static_cast<uint64_t>(authToken.authenticatorId)); + hwAuthToken.add(static_cast<uint64_t>(authToken.authenticatorType)); + hwAuthToken.add(static_cast<uint64_t>(authToken.timestamp.milliSeconds)); + hwAuthToken.add((std::vector<uint8_t>(authToken.mac))); + array.add(std::move(hwAuthToken)); + return true; +} + +std::optional<TimeStampToken> CborConverter::getTimeStampToken(const unique_ptr<Item>& item, + const uint32_t pos) { + TimeStampToken token; + // {challenge, timestamp, Mac} + auto optChallenge = getUint64(item, pos); + auto optTimestampMillis = getUint64(item, pos + 1); + auto optTemp = getByteArrayVec(item, pos + 2); + if (!optChallenge || !optTimestampMillis || !optTemp) { + return std::nullopt; + } + token.mac = std::move(optTemp.value()); + token.challenge = static_cast<long>(std::move(optChallenge.value())); + token.timestamp.milliSeconds = static_cast<long>(std::move(optTimestampMillis.value())); + return token; +} + +std::optional<Array> CborConverter::getArrayItem(const std::unique_ptr<Item>& item, + const uint32_t pos) { + Array array; + auto arrayItem = getItemAtPos(item, pos); + if (!arrayItem || (MajorType::ARRAY != getType(arrayItem.value()))) { + return std::nullopt; + } + array = std::move(*(arrayItem.value().get()->asArray())); + return array; +} + +std::optional<Map> CborConverter::getMapItem(const std::unique_ptr<Item>& item, + const uint32_t pos) { + Map map; + auto mapItem = getItemAtPos(item, pos); + if (!mapItem || (MajorType::MAP != getType(mapItem.value()))) { + return std::nullopt; + } + map = std::move(*(mapItem.value().get()->asMap())); + return map; +} + +std::optional<vector<KeyParameter>> CborConverter::getKeyParameters(const unique_ptr<Item>& item, + const uint32_t pos) { + vector<KeyParameter> params; + auto mapItem = getItemAtPos(item, pos); + if (!mapItem || (MajorType::MAP != getType(mapItem.value()))) return std::nullopt; + const Map* map = mapItem.value().get()->asMap(); + size_t mapSize = map->size(); + for (int i = 0; i < mapSize; i++) { + auto optKeyParams = getKeyParameter((*map)[i]); + if (optKeyParams) { + params.insert(params.end(), optKeyParams->begin(), optKeyParams->end()); + } else { + return std::nullopt; + } + } + return params; +} + +std::tuple<std::unique_ptr<Item>, keymaster_error_t> +CborConverter::decodeData(const std::vector<uint8_t>& response) { + auto [item, pos, message] = cppbor::parse(response); + if (!item || MajorType::ARRAY != getType(item)) { + return {nullptr, KM_ERROR_UNKNOWN_ERROR}; + } + auto optErrorCode = getErrorCode(item, 0); + if (!optErrorCode) { + return {nullptr, KM_ERROR_UNKNOWN_ERROR}; + } + return {std::move(item), optErrorCode.value()}; +} + +std::optional<keymaster_error_t> +CborConverter::getErrorCode(const std::unique_ptr<cppbor::Item>& item, const uint32_t pos) { + auto optErrorVal = getUint64(item, pos); + if (!optErrorVal) { + return std::nullopt; + } + return static_cast<keymaster_error_t>(0 - optErrorVal.value()); +} + +std::optional<uint64_t> CborConverter::getUint64(const unique_ptr<Item>& item) { + if ((item == nullptr) || (MajorType::UINT != getType(item))) { + return std::nullopt; + } + const Uint* uintVal = item.get()->asUint(); + return uintVal->unsignedValue(); +} + +std::optional<uint64_t> CborConverter::getUint64(const unique_ptr<Item>& item, const uint32_t pos) { + auto intItem = getItemAtPos(item, pos); + if (!intItem) { + return std::nullopt; + } + return getUint64(intItem.value()); +} + +} // namespace keymint::javacard diff --git a/ready_se/google/keymint/KM300/HAL/CborConverter.h b/ready_se/google/keymint/KM300/HAL/CborConverter.h new file mode 100644 index 0000000..d594350 --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/CborConverter.h @@ -0,0 +1,142 @@ +/* + ** + ** Copyright 2020, The Android Open Source Project + ** + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** + ** http://www.apache.org/licenses/LICENSE-2.0 + ** + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + */ +#pragma once + +#include <iostream> +#include <memory> +#include <numeric> +#include <vector> + +#include <cppbor.h> +#include <cppbor_parse.h> + +#include <aidl/android/hardware/security/keymint/Certificate.h> +#include <aidl/android/hardware/security/keymint/IKeyMintDevice.h> +#include <aidl/android/hardware/security/secureclock/TimeStampToken.h> +#include <aidl/android/hardware/security/sharedsecret/ISharedSecret.h> + +#include <keymaster/android_keymaster_messages.h> + +namespace keymint::javacard { +using aidl::android::hardware::security::keymint::AttestationKey; +using aidl::android::hardware::security::keymint::Certificate; +using aidl::android::hardware::security::keymint::HardwareAuthToken; +using aidl::android::hardware::security::keymint::KeyCharacteristics; +using aidl::android::hardware::security::keymint::KeyParameter; +using aidl::android::hardware::security::secureclock::TimeStampToken; +using aidl::android::hardware::security::sharedsecret::SharedSecretParameters; +using cppbor::Array; +using cppbor::Bstr; +using cppbor::EncodedItem; +using cppbor::Item; +using cppbor::MajorType; +using cppbor::Map; +using cppbor::Nint; +using cppbor::Tstr; +using cppbor::Uint; +using std::string; +using std::unique_ptr; +using std::vector; + +class CborConverter { + public: + CborConverter() = default; + + ~CborConverter() = default; + + std::tuple<std::unique_ptr<Item>, keymaster_error_t> + decodeData(const std::vector<uint8_t>& response); + + std::optional<uint64_t> getUint64(const unique_ptr<Item>& item); + + std::optional<uint64_t> getUint64(const unique_ptr<Item>& item, const uint32_t pos); + + std::optional<SharedSecretParameters> + getSharedSecretParameters(const std::unique_ptr<Item>& item, const uint32_t pos); + + std::optional<string> getByteArrayStr(const unique_ptr<Item>& item, const uint32_t pos); + + std::optional<string> getTextStr(const unique_ptr<Item>& item, const uint32_t pos); + + std::optional<std::vector<uint8_t>> getByteArrayVec(const unique_ptr<Item>& item, + const uint32_t pos); + + std::optional<vector<KeyParameter>> getKeyParameters(const unique_ptr<Item>& item, + const uint32_t pos); + + bool addKeyparameters(Array& array, const vector<KeyParameter>& keyParams); + + bool addAttestationKey(Array& array, const std::optional<AttestationKey>& attestationKey); + + bool addHardwareAuthToken(Array& array, const HardwareAuthToken& authToken); + + bool addSharedSecretParameters(Array& array, const vector<SharedSecretParameters>& params); + + std::optional<TimeStampToken> getTimeStampToken(const std::unique_ptr<Item>& item, + const uint32_t pos); + + std::optional<vector<KeyCharacteristics>> + getKeyCharacteristics(const std::unique_ptr<Item>& item, const uint32_t pos); + + std::optional<vector<Certificate>> getCertificateChain(const std::unique_ptr<Item>& item, + const uint32_t pos); + + std::optional<vector<vector<uint8_t>>> getMultiByteArray(const unique_ptr<Item>& item, + const uint32_t pos); + + bool addTimeStampToken(Array& array, const TimeStampToken& token); + + std::optional<Map> getMapItem(const std::unique_ptr<Item>& item, const uint32_t pos); + + std::optional<Array> getArrayItem(const std::unique_ptr<Item>& item, const uint32_t pos); + + std::optional<keymaster_error_t> getErrorCode(const std::unique_ptr<Item>& item, + const uint32_t pos); + + private: + /** + * Get the type of the Item pointer. + */ + inline MajorType getType(const unique_ptr<Item>& item) { return item.get()->type(); } + + /** + * Construct Keyparameter structure from the pair of key and value. If TagType is ENUM_REP the + * value contains binary string. If TagType is UINT_REP or ULONG_REP the value contains Array of + * unsigned integers. + */ + std::optional<std::vector<KeyParameter>> getKeyParameter( + const std::pair<const std::unique_ptr<Item>&, const std::unique_ptr<Item>&> pair); + + /** + * Get the sub item pointer from the root item pointer at the given position. + */ + inline std::optional<unique_ptr<Item>> getItemAtPos(const unique_ptr<Item>& item, + const uint32_t pos) { + Array* arr = nullptr; + + if (MajorType::ARRAY != getType(item)) { + return std::nullopt; + } + arr = const_cast<Array*>(item.get()->asArray()); + if (arr->size() < (pos + 1)) { + return std::nullopt; + } + return std::move((*arr)[pos]); + } +}; + +} // namespace keymint::javacard diff --git a/ready_se/google/keymint/KM300/HAL/JavacardKeyMintDevice.cpp b/ready_se/google/keymint/KM300/HAL/JavacardKeyMintDevice.cpp new file mode 100644 index 0000000..bd68b48 --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/JavacardKeyMintDevice.cpp @@ -0,0 +1,454 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "javacard.keymint.device.strongbox-impl" + +#include "JavacardKeyMintDevice.h" + +#include <regex.h> + +#include <algorithm> +#include <iostream> +#include <iterator> +#include <memory> +#include <string> +#include <vector> + +#include <KeyMintUtils.h> +#include <android-base/logging.h> +#include <android-base/properties.h> +#include <hardware/hw_auth_token.h> +#include <keymaster/android_keymaster_messages.h> +#include <keymaster/wrapped_key.h> + +#include "JavacardKeyMintOperation.h" +#include "JavacardSharedSecret.h" + +namespace aidl::android::hardware::security::keymint { +using cppbor::Bstr; +using cppbor::EncodedItem; +using cppbor::Uint; +using ::keymaster::AuthorizationSet; +using ::keymaster::dup_buffer; +using ::keymaster::KeymasterBlob; +using ::keymaster::KeymasterKeyBlob; +using ::keymint::javacard::Instruction; +using std::string; + +ScopedAStatus JavacardKeyMintDevice::defaultHwInfo(KeyMintHardwareInfo* info) { + info->versionNumber = 2; + info->keyMintAuthorName = "Google"; + info->keyMintName = "JavacardKeymintDevice"; + info->securityLevel = securitylevel_; + info->timestampTokenRequired = true; + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::getHardwareInfo(KeyMintHardwareInfo* info) { + auto [item, err] = card_->sendRequest(Instruction::INS_GET_HW_INFO_CMD); + std::optional<string> optKeyMintName; + std::optional<string> optKeyMintAuthorName; + std::optional<uint64_t> optSecLevel; + std::optional<uint64_t> optVersion; + std::optional<uint64_t> optTsRequired; + if (err != KM_ERROR_OK || !(optVersion = cbor_.getUint64(item, 1)) || + !(optSecLevel = cbor_.getUint64(item, 2)) || + !(optKeyMintName = cbor_.getByteArrayStr(item, 3)) || + !(optKeyMintAuthorName = cbor_.getByteArrayStr(item, 4)) || + !(optTsRequired = cbor_.getUint64(item, 5))) { + LOG(ERROR) << "Error in response of getHardwareInfo."; + LOG(INFO) << "Returning defaultHwInfo in getHardwareInfo."; + return defaultHwInfo(info); + } + card_->initializeJavacard(); + info->keyMintName = std::move(optKeyMintName.value()); + info->keyMintAuthorName = std::move(optKeyMintAuthorName.value()); + info->timestampTokenRequired = (optTsRequired.value() == 1); + info->securityLevel = static_cast<SecurityLevel>(std::move(optSecLevel.value())); + info->versionNumber = static_cast<int32_t>(std::move(optVersion.value())); + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::generateKey(const vector<KeyParameter>& keyParams, + const optional<AttestationKey>& attestationKey, + KeyCreationResult* creationResult) { + cppbor::Array array; + // add key params + cbor_.addKeyparameters(array, keyParams); + // add attestation key if any + cbor_.addAttestationKey(array, attestationKey); + auto [item, err] = card_->sendRequest(Instruction::INS_GENERATE_KEY_CMD, array); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending generateKey."; + return km_utils::kmError2ScopedAStatus(err); + } + auto optKeyBlob = cbor_.getByteArrayVec(item, 1); + auto optKeyChars = cbor_.getKeyCharacteristics(item, 2); + auto optCertChain = cbor_.getCertificateChain(item, 3); + if (!optKeyBlob || !optKeyChars || !optCertChain) { + LOG(ERROR) << "Error in decoding og response in generateKey."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + creationResult->keyCharacteristics = std::move(optKeyChars.value()); + creationResult->certificateChain = std::move(optCertChain.value()); + creationResult->keyBlob = std::move(optKeyBlob.value()); + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::addRngEntropy(const vector<uint8_t>& data) { + cppbor::Array request; + // add key data + request.add(Bstr(data)); + auto [item, err] = card_->sendRequest(Instruction::INS_ADD_RNG_ENTROPY_CMD, request); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending addRngEntropy."; + return km_utils::kmError2ScopedAStatus(err); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::importKey(const vector<KeyParameter>& keyParams, + KeyFormat keyFormat, const vector<uint8_t>& keyData, + const optional<AttestationKey>& attestationKey, + KeyCreationResult* creationResult) { + + cppbor::Array request; + // add key params + cbor_.addKeyparameters(request, keyParams); + // add key format + request.add(Uint(static_cast<uint8_t>(keyFormat))); + // add key data + request.add(Bstr(keyData)); + // add attestation key if any + cbor_.addAttestationKey(request, attestationKey); + + auto [item, err] = card_->sendRequest(Instruction::INS_IMPORT_KEY_CMD, request); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending data in importKey."; + return km_utils::kmError2ScopedAStatus(err); + } + auto optKeyBlob = cbor_.getByteArrayVec(item, 1); + auto optKeyChars = cbor_.getKeyCharacteristics(item, 2); + auto optCertChain = cbor_.getCertificateChain(item, 3); + if (!optKeyBlob || !optKeyChars || !optCertChain) { + LOG(ERROR) << "Error in decoding response in importKey."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + creationResult->keyCharacteristics = std::move(optKeyChars.value()); + creationResult->certificateChain = std::move(optCertChain.value()); + creationResult->keyBlob = std::move(optKeyBlob.value()); + return ScopedAStatus::ok(); +} + +// import wrapped key is divided into 2 stage operation. +ScopedAStatus JavacardKeyMintDevice::importWrappedKey(const vector<uint8_t>& wrappedKeyData, + const vector<uint8_t>& wrappingKeyBlob, + const vector<uint8_t>& maskingKey, + const vector<KeyParameter>& unwrappingParams, + int64_t passwordSid, int64_t biometricSid, + KeyCreationResult* creationResult) { + cppbor::Array request; + std::unique_ptr<Item> item; + vector<uint8_t> keyBlob; + std::vector<uint8_t> response; + vector<KeyCharacteristics> keyCharacteristics; + std::vector<uint8_t> iv; + std::vector<uint8_t> transitKey; + std::vector<uint8_t> secureKey; + std::vector<uint8_t> tag; + vector<KeyParameter> authList; + KeyFormat keyFormat; + std::vector<uint8_t> wrappedKeyDescription; + keymaster_error_t errorCode = parseWrappedKey(wrappedKeyData, iv, transitKey, secureKey, tag, + authList, keyFormat, wrappedKeyDescription); + if (errorCode != KM_ERROR_OK) { + LOG(ERROR) << "Error in parse wrapped key in importWrappedKey."; + return km_utils::kmError2ScopedAStatus(errorCode); + } + + // begin import + std::tie(item, errorCode) = + sendBeginImportWrappedKeyCmd(transitKey, wrappingKeyBlob, maskingKey, unwrappingParams); + if (errorCode != KM_ERROR_OK) { + LOG(ERROR) << "Error in send begin import wrapped key in importWrappedKey."; + return km_utils::kmError2ScopedAStatus(errorCode); + } + // Finish the import + std::tie(item, errorCode) = sendFinishImportWrappedKeyCmd( + authList, keyFormat, secureKey, tag, iv, wrappedKeyDescription, passwordSid, biometricSid); + if (errorCode != KM_ERROR_OK) { + LOG(ERROR) << "Error in send finish import wrapped key in importWrappedKey."; + return km_utils::kmError2ScopedAStatus(errorCode); + } + auto optKeyBlob = cbor_.getByteArrayVec(item, 1); + auto optKeyChars = cbor_.getKeyCharacteristics(item, 2); + auto optCertChain = cbor_.getCertificateChain(item, 3); + if (!optKeyBlob || !optKeyChars || !optCertChain) { + LOG(ERROR) << "Error in decoding the response in importWrappedKey."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + creationResult->keyCharacteristics = std::move(optKeyChars.value()); + creationResult->certificateChain = std::move(optCertChain.value()); + creationResult->keyBlob = std::move(optKeyBlob.value()); + return ScopedAStatus::ok(); +} + +std::tuple<std::unique_ptr<Item>, keymaster_error_t> +JavacardKeyMintDevice::sendBeginImportWrappedKeyCmd(const std::vector<uint8_t>& transitKey, + const std::vector<uint8_t>& wrappingKeyBlob, + const std::vector<uint8_t>& maskingKey, + const vector<KeyParameter>& unwrappingParams) { + Array request; + request.add(std::vector<uint8_t>(transitKey)); + request.add(std::vector<uint8_t>(wrappingKeyBlob)); + request.add(std::vector<uint8_t>(maskingKey)); + cbor_.addKeyparameters(request, unwrappingParams); + return card_->sendRequest(Instruction::INS_BEGIN_IMPORT_WRAPPED_KEY_CMD, request); +} + +std::tuple<std::unique_ptr<Item>, keymaster_error_t> +JavacardKeyMintDevice::sendFinishImportWrappedKeyCmd( + const vector<KeyParameter>& keyParams, KeyFormat keyFormat, + const std::vector<uint8_t>& secureKey, const std::vector<uint8_t>& tag, + const std::vector<uint8_t>& iv, const std::vector<uint8_t>& wrappedKeyDescription, + int64_t passwordSid, int64_t biometricSid) { + Array request; + cbor_.addKeyparameters(request, keyParams); + request.add(static_cast<uint64_t>(keyFormat)); + request.add(std::vector<uint8_t>(secureKey)); + request.add(std::vector<uint8_t>(tag)); + request.add(std::vector<uint8_t>(iv)); + request.add(std::vector<uint8_t>(wrappedKeyDescription)); + request.add(Uint(passwordSid)); + request.add(Uint(biometricSid)); + return card_->sendRequest(Instruction::INS_FINISH_IMPORT_WRAPPED_KEY_CMD, request); +} + +ScopedAStatus JavacardKeyMintDevice::upgradeKey(const vector<uint8_t>& keyBlobToUpgrade, + const vector<KeyParameter>& upgradeParams, + vector<uint8_t>* keyBlob) { + cppbor::Array request; + // add key blob + request.add(Bstr(keyBlobToUpgrade)); + // add key params + cbor_.addKeyparameters(request, upgradeParams); + auto [item, err] = card_->sendRequest(Instruction::INS_UPGRADE_KEY_CMD, request); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in upgradeKey."; + return km_utils::kmError2ScopedAStatus(err); + } + auto optKeyBlob = cbor_.getByteArrayVec(item, 1); + if (!optKeyBlob) { + LOG(ERROR) << "Error in decoding the response in upgradeKey."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + *keyBlob = std::move(optKeyBlob.value()); + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::deleteKey(const vector<uint8_t>& keyBlob) { + Array request; + request.add(Bstr(keyBlob)); + auto [item, err] = card_->sendRequest(Instruction::INS_DELETE_KEY_CMD, request); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in deleteKey."; + return km_utils::kmError2ScopedAStatus(err); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::deleteAllKeys() { + auto [item, err] = card_->sendRequest(Instruction::INS_DELETE_ALL_KEYS_CMD); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in deleteAllKeys."; + return km_utils::kmError2ScopedAStatus(err); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::destroyAttestationIds() { + auto [item, err] = card_->sendRequest(Instruction::INS_DESTROY_ATT_IDS_CMD); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in destroyAttestationIds."; + return km_utils::kmError2ScopedAStatus(err); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::begin(KeyPurpose purpose, const std::vector<uint8_t>& keyBlob, + const std::vector<KeyParameter>& params, + const std::optional<HardwareAuthToken>& authToken, + BeginResult* result) { + + cppbor::Array array; + std::vector<uint8_t> response; + // make request + array.add(Uint(static_cast<uint64_t>(purpose))); + array.add(Bstr(keyBlob)); + cbor_.addKeyparameters(array, params); + HardwareAuthToken token = authToken.value_or(HardwareAuthToken()); + cbor_.addHardwareAuthToken(array, token); + + // Send earlyBootEnded if there is any pending earlybootEnded event. + auto retErr = card_->sendEarlyBootEndedEvent(false); + if (retErr != KM_ERROR_OK) { + return km_utils::kmError2ScopedAStatus(retErr); + ; + } + + auto [item, err] = card_->sendRequest(Instruction::INS_BEGIN_OPERATION_CMD, array); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in begin."; + return km_utils::kmError2ScopedAStatus(err); + } + // return the result + auto keyParams = cbor_.getKeyParameters(item, 1); + auto optOpHandle = cbor_.getUint64(item, 2); + auto optBufMode = cbor_.getUint64(item, 3); + auto optMacLength = cbor_.getUint64(item, 4); + + if (!keyParams || !optOpHandle || !optBufMode || !optMacLength) { + LOG(ERROR) << "Error in decoding the response in begin."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + result->params = std::move(keyParams.value()); + result->challenge = optOpHandle.value(); + result->operation = ndk::SharedRefBase::make<JavacardKeyMintOperation>( + static_cast<keymaster_operation_handle_t>(optOpHandle.value()), + static_cast<BufferingMode>(optBufMode.value()), optMacLength.value(), card_); + return ScopedAStatus::ok(); +} + +ScopedAStatus +JavacardKeyMintDevice::deviceLocked(bool passwordOnly, + const std::optional<TimeStampToken>& timestampToken) { + Array request; + int8_t password = 1; + if (!passwordOnly) { + password = 0; + } + request.add(Uint(password)); + cbor_.addTimeStampToken(request, timestampToken.value_or(TimeStampToken())); + auto [item, err] = card_->sendRequest(Instruction::INS_DEVICE_LOCKED_CMD, request); + if (err != KM_ERROR_OK) { + return km_utils::kmError2ScopedAStatus(err); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::earlyBootEnded() { + auto err = card_->sendEarlyBootEndedEvent(true); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending earlyBootEndedEvent."; + return km_utils::kmError2ScopedAStatus(err); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::getKeyCharacteristics( + const std::vector<uint8_t>& keyBlob, const std::vector<uint8_t>& appId, + const std::vector<uint8_t>& appData, std::vector<KeyCharacteristics>* result) { + cppbor::Array request; + request.add(vector<uint8_t>(keyBlob)); + request.add(vector<uint8_t>(appId)); + request.add(vector<uint8_t>(appData)); + auto [item, err] = card_->sendRequest(Instruction::INS_GET_KEY_CHARACTERISTICS_CMD, request); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in getKeyCharacteristics."; + return km_utils::kmError2ScopedAStatus(err); + } + auto optKeyChars = cbor_.getKeyCharacteristics(item, 1); + if (!optKeyChars) { + LOG(ERROR) << "Error in sending in upgradeKey."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + *result = std::move(optKeyChars.value()); + return ScopedAStatus::ok(); +} + +keymaster_error_t +JavacardKeyMintDevice::parseWrappedKey(const vector<uint8_t>& wrappedKeyData, + std::vector<uint8_t>& iv, std::vector<uint8_t>& transitKey, + std::vector<uint8_t>& secureKey, std::vector<uint8_t>& tag, + vector<KeyParameter>& authList, KeyFormat& keyFormat, + std::vector<uint8_t>& wrappedKeyDescription) { + KeymasterBlob kmIv; + KeymasterKeyBlob kmTransitKey; + KeymasterKeyBlob kmSecureKey; + KeymasterBlob kmTag; + AuthorizationSet authSet; + keymaster_key_format_t kmKeyFormat; + KeymasterBlob kmWrappedKeyDescription; + + size_t keyDataLen = wrappedKeyData.size(); + uint8_t* keyData = dup_buffer(wrappedKeyData.data(), keyDataLen); + keymaster_key_blob_t keyMaterial = {keyData, keyDataLen}; + keymaster_error_t error = + parse_wrapped_key(KeymasterKeyBlob(keyMaterial), &kmIv, &kmTransitKey, &kmSecureKey, &kmTag, + &authSet, &kmKeyFormat, &kmWrappedKeyDescription); + if (error != KM_ERROR_OK) { + LOG(ERROR) << "Error parsing wrapped key."; + return error; + } + iv = km_utils::kmBlob2vector(kmIv); + transitKey = km_utils::kmBlob2vector(kmTransitKey); + secureKey = km_utils::kmBlob2vector(kmSecureKey); + tag = km_utils::kmBlob2vector(kmTag); + authList = km_utils::kmParamSet2Aidl(authSet); + keyFormat = static_cast<KeyFormat>(kmKeyFormat); + wrappedKeyDescription = km_utils::kmBlob2vector(kmWrappedKeyDescription); + return KM_ERROR_OK; +} + +ScopedAStatus JavacardKeyMintDevice::convertStorageKeyToEphemeral( + const std::vector<uint8_t>& /* storageKeyBlob */, + std::vector<uint8_t>* /* ephemeralKeyBlob */) { + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNIMPLEMENTED); +} + +ScopedAStatus JavacardKeyMintDevice::getRootOfTrustChallenge(array<uint8_t, 16>* challenge) { + auto [item, err] = card_->sendRequest(Instruction::INS_GET_ROT_CHALLENGE_CMD); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in getRootOfTrustChallenge."; + return km_utils::kmError2ScopedAStatus(err); + } + auto optChallenge = cbor_.getByteArrayVec(item, 1); + if (!optChallenge) { + LOG(ERROR) << "Error in sending in upgradeKey."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + std::move(optChallenge->begin(), optChallenge->begin() + 16, challenge->begin()); + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintDevice::getRootOfTrust(const array<uint8_t, 16>& /*challenge*/, + vector<uint8_t>* /*rootOfTrust*/) { + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNIMPLEMENTED); +} + +ScopedAStatus JavacardKeyMintDevice::sendRootOfTrust(const vector<uint8_t>& rootOfTrust) { + cppbor::Array request; + request.add(EncodedItem(rootOfTrust)); // taggedItem. + auto [item, err] = card_->sendRequest(Instruction::INS_SEND_ROT_DATA_CMD, request); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in sendRootOfTrust."; + return km_utils::kmError2ScopedAStatus(err); + } + LOG(INFO) << "JavacardKeyMintDevice::sendRootOfTrust success"; + return ScopedAStatus::ok(); +} + +} // namespace aidl::android::hardware::security::keymint diff --git a/ready_se/google/keymint/KM300/HAL/JavacardKeyMintDevice.h b/ready_se/google/keymint/KM300/HAL/JavacardKeyMintDevice.h new file mode 100644 index 0000000..adf0f7d --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/JavacardKeyMintDevice.h @@ -0,0 +1,124 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <aidl/android/hardware/security/keymint/BnKeyMintDevice.h> +#include <aidl/android/hardware/security/keymint/BnKeyMintOperation.h> +#include <aidl/android/hardware/security/keymint/HardwareAuthToken.h> +#include <aidl/android/hardware/security/sharedsecret/SharedSecretParameters.h> + +#include "CborConverter.h" +#include "JavacardSecureElement.h" + +namespace aidl::android::hardware::security::keymint { +using cppbor::Item; +using ::keymint::javacard::CborConverter; +using ::keymint::javacard::JavacardSecureElement; +using ndk::ScopedAStatus; +using secureclock::TimeStampToken; +using std::array; +using std::optional; +using std::shared_ptr; +using std::vector; + +class JavacardKeyMintDevice : public BnKeyMintDevice { + public: + explicit JavacardKeyMintDevice(shared_ptr<JavacardSecureElement> card) + : securitylevel_(SecurityLevel::STRONGBOX), card_(card) { + card_->initializeJavacard(); + } + virtual ~JavacardKeyMintDevice() {} + + ScopedAStatus getHardwareInfo(KeyMintHardwareInfo* info) override; + + ScopedAStatus addRngEntropy(const vector<uint8_t>& data) override; + + ScopedAStatus generateKey(const vector<KeyParameter>& keyParams, + const optional<AttestationKey>& attestationKey, + KeyCreationResult* creationResult) override; + + ScopedAStatus importKey(const vector<KeyParameter>& keyParams, KeyFormat keyFormat, + const vector<uint8_t>& keyData, + const optional<AttestationKey>& attestationKey, + KeyCreationResult* creationResult) override; + + ScopedAStatus importWrappedKey(const vector<uint8_t>& wrappedKeyData, + const vector<uint8_t>& wrappingKeyBlob, + const vector<uint8_t>& maskingKey, + const vector<KeyParameter>& unwrappingParams, + int64_t passwordSid, int64_t biometricSid, + KeyCreationResult* creationResult) override; + + ScopedAStatus upgradeKey(const vector<uint8_t>& keyBlobToUpgrade, + const vector<KeyParameter>& upgradeParams, + vector<uint8_t>* keyBlob) override; + + ScopedAStatus deleteKey(const vector<uint8_t>& keyBlob) override; + ScopedAStatus deleteAllKeys() override; + ScopedAStatus destroyAttestationIds() override; + + virtual ScopedAStatus begin(KeyPurpose in_purpose, const std::vector<uint8_t>& in_keyBlob, + const std::vector<KeyParameter>& in_params, + const std::optional<HardwareAuthToken>& in_authToken, + BeginResult* _aidl_return) override; + + ScopedAStatus deviceLocked(bool passwordOnly, + const optional<TimeStampToken>& timestampToken) override; + + ScopedAStatus earlyBootEnded() override; + + ScopedAStatus getKeyCharacteristics(const std::vector<uint8_t>& in_keyBlob, + const std::vector<uint8_t>& in_appId, + const std::vector<uint8_t>& in_appData, + std::vector<KeyCharacteristics>* _aidl_return) override; + + ScopedAStatus convertStorageKeyToEphemeral(const std::vector<uint8_t>& storageKeyBlob, + std::vector<uint8_t>* ephemeralKeyBlob) override; + + ScopedAStatus getRootOfTrustChallenge(array<uint8_t, 16>* challenge) override; + + ScopedAStatus getRootOfTrust(const array<uint8_t, 16>& challenge, + vector<uint8_t>* rootOfTrust) override; + + ScopedAStatus sendRootOfTrust(const vector<uint8_t>& rootOfTrust) override; + + private: + keymaster_error_t parseWrappedKey(const vector<uint8_t>& wrappedKeyData, + std::vector<uint8_t>& iv, std::vector<uint8_t>& transitKey, + std::vector<uint8_t>& secureKey, std::vector<uint8_t>& tag, + vector<KeyParameter>& authList, KeyFormat& keyFormat, + std::vector<uint8_t>& wrappedKeyDescription); + + std::tuple<std::unique_ptr<Item>, keymaster_error_t> sendBeginImportWrappedKeyCmd( + const std::vector<uint8_t>& transitKey, const std::vector<uint8_t>& wrappingKeyBlob, + const std::vector<uint8_t>& maskingKey, const vector<KeyParameter>& unwrappingParams); + + std::tuple<std::unique_ptr<Item>, keymaster_error_t> + sendFinishImportWrappedKeyCmd(const vector<KeyParameter>& keyParams, KeyFormat keyFormat, + const std::vector<uint8_t>& secureKey, + const std::vector<uint8_t>& tag, const std::vector<uint8_t>& iv, + const std::vector<uint8_t>& wrappedKeyDescription, + int64_t passwordSid, int64_t biometricSid); + + ScopedAStatus defaultHwInfo(KeyMintHardwareInfo* info); + + const SecurityLevel securitylevel_; + const shared_ptr<JavacardSecureElement> card_; + CborConverter cbor_; +}; + +} // namespace aidl::android::hardware::security::keymint diff --git a/ready_se/google/keymint/KM300/HAL/JavacardKeyMintOperation.cpp b/ready_se/google/keymint/KM300/HAL/JavacardKeyMintOperation.cpp new file mode 100644 index 0000000..a46f066 --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/JavacardKeyMintOperation.cpp @@ -0,0 +1,300 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "javacard.strongbox.keymint.operation-impl" + +#include "JavacardKeyMintOperation.h" + +#include <KeyMintUtils.h> +#include <aidl/android/hardware/security/keymint/ErrorCode.h> +#include <aidl/android/hardware/security/secureclock/ISecureClock.h> +#include <android-base/logging.h> + +#include "CborConverter.h" + +namespace aidl::android::hardware::security::keymint { +using cppbor::Bstr; +using cppbor::Uint; +using secureclock::TimeStampToken; + +JavacardKeyMintOperation::~JavacardKeyMintOperation() { + if (opHandle_ != 0) { + JavacardKeyMintOperation::abort(); + } +} + +ScopedAStatus JavacardKeyMintOperation::updateAad(const vector<uint8_t>& input, + const optional<HardwareAuthToken>& authToken, + const optional<TimeStampToken>& timestampToken) { + cppbor::Array request; + request.add(Uint(opHandle_)); + request.add(Bstr(input)); + cbor_.addHardwareAuthToken(request, authToken.value_or(HardwareAuthToken())); + cbor_.addTimeStampToken(request, timestampToken.value_or(TimeStampToken())); + auto [item, err] = card_->sendRequest(Instruction::INS_UPDATE_AAD_OPERATION_CMD, request); + if (err != KM_ERROR_OK) { + return km_utils::kmError2ScopedAStatus(err); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardKeyMintOperation::update(const vector<uint8_t>& input, + const optional<HardwareAuthToken>& authToken, + const optional<TimeStampToken>& timestampToken, + vector<uint8_t>* output) { + HardwareAuthToken aToken = authToken.value_or(HardwareAuthToken()); + TimeStampToken tToken = timestampToken.value_or(TimeStampToken()); + DataView view = {.buffer = {}, .data = input, .start = 0, .length = input.size()}; + keymaster_error_t err = bufferData(view); + if (err != KM_ERROR_OK) { + return km_utils::kmError2ScopedAStatus(err); + } + if (!(bufferingMode_ == BufferingMode::EC_NO_DIGEST || + bufferingMode_ == BufferingMode::RSA_DECRYPT_OR_NO_DIGEST)) { + if (view.length > MAX_CHUNK_SIZE) { + err = updateInChunks(view, aToken, tToken, output); + if (err != KM_ERROR_OK) { + return km_utils::kmError2ScopedAStatus(err); + } + } + vector<uint8_t> remaining = popNextChunk(view, view.length); + err = sendUpdate(remaining, aToken, tToken, *output); + } + return km_utils::kmError2ScopedAStatus(err); +} + +ScopedAStatus JavacardKeyMintOperation::finish(const optional<vector<uint8_t>>& input, + const optional<vector<uint8_t>>& signature, + const optional<HardwareAuthToken>& authToken, + const optional<TimeStampToken>& timestampToken, + const optional<vector<uint8_t>>& confirmationToken, + vector<uint8_t>* output) { + HardwareAuthToken aToken = authToken.value_or(HardwareAuthToken()); + TimeStampToken tToken = timestampToken.value_or(TimeStampToken()); + const vector<uint8_t> confToken = confirmationToken.value_or(vector<uint8_t>()); + const vector<uint8_t> inData = input.value_or(vector<uint8_t>()); + DataView view = {.buffer = {}, .data = inData, .start = 0, .length = inData.size()}; + const vector<uint8_t> sign = signature.value_or(vector<uint8_t>()); + if (!(bufferingMode_ == BufferingMode::EC_NO_DIGEST || + bufferingMode_ == BufferingMode::RSA_DECRYPT_OR_NO_DIGEST)) { + appendBufferedData(view); + if (view.length > MAX_CHUNK_SIZE) { + auto err = updateInChunks(view, aToken, tToken, output); + if (err != KM_ERROR_OK) { + return km_utils::kmError2ScopedAStatus(err); + } + } + } else { + keymaster_error_t err = bufferData(view); + if (err != KM_ERROR_OK) { + return km_utils::kmError2ScopedAStatus(err); + } + appendBufferedData(view); + } + vector<uint8_t> remaining = popNextChunk(view, view.length); + return km_utils::kmError2ScopedAStatus( + sendFinish(remaining, sign, aToken, tToken, confToken, *output)); +} + +ScopedAStatus JavacardKeyMintOperation::abort() { + Array request; + request.add(Uint(opHandle_)); + auto [item, err] = card_->sendRequest(Instruction::INS_ABORT_OPERATION_CMD, request); + opHandle_ = 0; + buffer_.clear(); + return km_utils::kmError2ScopedAStatus(err); +} + +void JavacardKeyMintOperation::blockAlign(DataView& view, uint16_t blockSize) { + appendBufferedData(view); + uint16_t offset = getDataViewOffset(view, blockSize); + if (view.buffer.empty() && !view.data.empty()) { + buffer_.insert(buffer_.end(), view.data.begin() + offset, view.data.end()); + } else if (view.data.empty() && !view.buffer.empty()) { + buffer_.insert(buffer_.end(), view.buffer.begin() + offset, view.buffer.end()); + } else { + if (offset < view.buffer.size()) { + buffer_.insert(buffer_.end(), view.buffer.begin() + offset, view.buffer.end()); + buffer_.insert(buffer_.end(), view.data.begin(), view.data.end()); + } else { + offset = offset - view.buffer.size(); + buffer_.insert(buffer_.end(), view.data.begin() + offset, view.data.end()); + } + } + // adjust the view length by removing the buffered data size from it. + view.length = view.length - buffer_.size(); +} + +uint16_t JavacardKeyMintOperation::getDataViewOffset(DataView& view, uint16_t blockSize) { + uint16_t offset = 0; + uint16_t remaining = 0; + switch (bufferingMode_) { + case BufferingMode::BUF_DES_DECRYPT_PKCS7_BLOCK_ALIGNED: + case BufferingMode::BUF_AES_DECRYPT_PKCS7_BLOCK_ALIGNED: + offset = ((view.length / blockSize)) * blockSize; + remaining = (view.length % blockSize); + if (offset >= blockSize && remaining == 0) { + offset -= blockSize; + } + break; + case BufferingMode::BUF_DES_ENCRYPT_PKCS7_BLOCK_ALIGNED: + case BufferingMode::BUF_AES_ENCRYPT_PKCS7_BLOCK_ALIGNED: + offset = ((view.length / blockSize)) * blockSize; + break; + case BufferingMode::BUF_AES_GCM_DECRYPT_BLOCK_ALIGNED: + if (view.length > macLength_) { + offset = (view.length - macLength_); + } + break; + default: + break; + } + return offset; +} + +keymaster_error_t JavacardKeyMintOperation::bufferData(DataView& view) { + if (view.data.empty()) return KM_ERROR_OK; // nothing to buffer + switch (bufferingMode_) { + case BufferingMode::RSA_DECRYPT_OR_NO_DIGEST: + buffer_.insert(buffer_.end(), view.data.begin(), view.data.end()); + if (buffer_.size() > RSA_BUFFER_SIZE) { + abort(); + return KM_ERROR_INVALID_INPUT_LENGTH; + } + view.start = 0; + view.length = 0; + break; + case BufferingMode::EC_NO_DIGEST: + if (buffer_.size() < EC_BUFFER_SIZE) { + buffer_.insert(buffer_.end(), view.data.begin(), view.data.end()); + // Truncate the buffered data if greater then allowed EC buffer size. + if (buffer_.size() > EC_BUFFER_SIZE) { + buffer_.erase(buffer_.begin() + EC_BUFFER_SIZE, buffer_.end()); + } + } + view.start = 0; + view.length = 0; + break; + case BufferingMode::BUF_AES_ENCRYPT_PKCS7_BLOCK_ALIGNED: + case BufferingMode::BUF_AES_DECRYPT_PKCS7_BLOCK_ALIGNED: + blockAlign(view, AES_BLOCK_SIZE); + break; + case BufferingMode::BUF_AES_GCM_DECRYPT_BLOCK_ALIGNED: + blockAlign(view, macLength_); + break; + case BufferingMode::BUF_DES_ENCRYPT_PKCS7_BLOCK_ALIGNED: + case BufferingMode::BUF_DES_DECRYPT_PKCS7_BLOCK_ALIGNED: + blockAlign(view, DES_BLOCK_SIZE); + break; + case BufferingMode::NONE: + break; + } + return KM_ERROR_OK; +} + +// Incrementally send the request using multiple updates. +keymaster_error_t JavacardKeyMintOperation::updateInChunks(DataView& view, + HardwareAuthToken& authToken, + TimeStampToken& timestampToken, + vector<uint8_t>* output) { + keymaster_error_t sendError = KM_ERROR_UNKNOWN_ERROR; + while (view.length > MAX_CHUNK_SIZE) { + vector<uint8_t> chunk = popNextChunk(view, MAX_CHUNK_SIZE); + sendError = sendUpdate(chunk, authToken, timestampToken, *output); + if (sendError != KM_ERROR_OK) { + return sendError; + } + // Clear tokens + if (!authToken.mac.empty()) authToken = HardwareAuthToken(); + if (!timestampToken.mac.empty()) timestampToken = TimeStampToken(); + } + return KM_ERROR_OK; +} + +vector<uint8_t> JavacardKeyMintOperation::popNextChunk(DataView& view, uint32_t chunkSize) { + uint32_t start = view.start; + uint32_t end = start + ((view.length < chunkSize) ? view.length : chunkSize); + vector<uint8_t> chunk; + if (start < view.buffer.size()) { + if (end < view.buffer.size()) { + chunk = {view.buffer.begin() + start, view.buffer.begin() + end}; + } else { + end = end - view.buffer.size(); + chunk = {view.buffer.begin() + start, view.buffer.end()}; + chunk.insert(chunk.end(), view.data.begin(), view.data.begin() + end); + } + } else { + start = start - view.buffer.size(); + end = end - view.buffer.size(); + chunk = {view.data.begin() + start, view.data.begin() + end}; + } + view.start = view.start + chunk.size(); + view.length = view.length - chunk.size(); + return chunk; +} + +keymaster_error_t JavacardKeyMintOperation::sendUpdate(const vector<uint8_t>& input, + const HardwareAuthToken& authToken, + const TimeStampToken& timestampToken, + vector<uint8_t>& output) { + if (input.empty()) { + return KM_ERROR_OK; + } + cppbor::Array request; + request.add(Uint(opHandle_)); + request.add(Bstr(input)); + cbor_.addHardwareAuthToken(request, authToken); + cbor_.addTimeStampToken(request, timestampToken); + auto [item, error] = card_->sendRequest(Instruction::INS_UPDATE_OPERATION_CMD, request); + if (error != KM_ERROR_OK) { + return error; + } + auto optTemp = cbor_.getByteArrayVec(item, 1); + if (!optTemp) { + return KM_ERROR_UNKNOWN_ERROR; + } + output.insert(output.end(), optTemp.value().begin(), optTemp.value().end()); + return KM_ERROR_OK; +} + +keymaster_error_t JavacardKeyMintOperation::sendFinish(const vector<uint8_t>& data, + const vector<uint8_t>& sign, + const HardwareAuthToken& authToken, + const TimeStampToken& timestampToken, + const vector<uint8_t>& confToken, + vector<uint8_t>& output) { + cppbor::Array request; + request.add(Uint(opHandle_)); + request.add(Bstr(data)); + request.add(Bstr(sign)); + cbor_.addHardwareAuthToken(request, authToken); + cbor_.addTimeStampToken(request, timestampToken); + request.add(Bstr(confToken)); + + auto [item, err] = card_->sendRequest(Instruction::INS_FINISH_OPERATION_CMD, request); + if (err != KM_ERROR_OK) { + return err; + } + auto optTemp = cbor_.getByteArrayVec(item, 1); + if (!optTemp) { + return KM_ERROR_UNKNOWN_ERROR; + } + opHandle_ = 0; + output.insert(output.end(), optTemp.value().begin(), optTemp.value().end()); + return KM_ERROR_OK; +} + +} // namespace aidl::android::hardware::security::keymint diff --git a/ready_se/google/keymint/KM300/HAL/JavacardKeyMintOperation.h b/ready_se/google/keymint/KM300/HAL/JavacardKeyMintOperation.h new file mode 100644 index 0000000..c1d967a --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/JavacardKeyMintOperation.h @@ -0,0 +1,136 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <vector> + +#include <aidl/android/hardware/security/keymint/BnKeyMintOperation.h> +#include <aidl/android/hardware/security/secureclock/ISecureClock.h> +#include <hardware/keymaster_defs.h> + +#include "CborConverter.h" +#include "JavacardSecureElement.h" + +#define AES_BLOCK_SIZE 16 +#define DES_BLOCK_SIZE 8 +#define RSA_BUFFER_SIZE 256 +#define EC_BUFFER_SIZE 32 +#define MAX_CHUNK_SIZE 256 + +namespace aidl::android::hardware::security::keymint { +using cppbor::Array; +using cppbor::Item; +using ::keymint::javacard::CborConverter; +using ::keymint::javacard::Instruction; +using ::keymint::javacard::JavacardSecureElement; +using ::ndk::ScopedAStatus; +using secureclock::TimeStampToken; +using std::optional; +using std::shared_ptr; +using std::vector; + +// Bufferig modes for update +enum class BufferingMode : int32_t { + NONE = 0, // Send everything to javacard - most of the assymteric operations + RSA_DECRYPT_OR_NO_DIGEST = + 1, // Buffer everything in update upto 256 bytes and send in finish. If + // input data is greater then 256 bytes then it is an error. Javacard + // will further check according to exact key size and crypto provider. + EC_NO_DIGEST = 2, // Buffer upto 65 bytes and then truncate. Javacard will further truncate + // upto exact keysize. + BUF_AES_ENCRYPT_PKCS7_BLOCK_ALIGNED = 3, // Buffer 16 bytes. + BUF_AES_DECRYPT_PKCS7_BLOCK_ALIGNED = 4, // Buffer 16 bytes. + BUF_DES_ENCRYPT_PKCS7_BLOCK_ALIGNED = 5, // Buffer 8 bytes. + BUF_DES_DECRYPT_PKCS7_BLOCK_ALIGNED = 6, // Buffer 8 bytes. + BUF_AES_GCM_DECRYPT_BLOCK_ALIGNED = 7, // Buffer 16 bytes. + +}; + +// The is the view in the input data being processed by update/finish funcion. + +struct DataView { + vector<uint8_t> buffer; // previously buffered data from cycle n-1 + const vector<uint8_t>& data; // current data in cycle n. + uint32_t start; // start of the view + size_t length; // length of the view +}; + +class JavacardKeyMintOperation : public BnKeyMintOperation { + public: + explicit JavacardKeyMintOperation(keymaster_operation_handle_t opHandle, + BufferingMode bufferingMode, uint16_t macLength, + shared_ptr<JavacardSecureElement> card) + : buffer_(vector<uint8_t>()), bufferingMode_(bufferingMode), macLength_(macLength), + card_(card), opHandle_(opHandle) {} + virtual ~JavacardKeyMintOperation(); + + ScopedAStatus updateAad(const vector<uint8_t>& input, + const optional<HardwareAuthToken>& authToken, + const optional<TimeStampToken>& timestampToken) override; + + ScopedAStatus update(const vector<uint8_t>& input, const optional<HardwareAuthToken>& authToken, + const optional<TimeStampToken>& timestampToken, + vector<uint8_t>* output) override; + + ScopedAStatus finish(const optional<vector<uint8_t>>& input, + const optional<vector<uint8_t>>& signature, + const optional<HardwareAuthToken>& authToken, + const optional<TimeStampToken>& timestampToken, + const optional<vector<uint8_t>>& confirmationToken, + vector<uint8_t>* output) override; + + ScopedAStatus abort() override; + + private: + vector<uint8_t> popNextChunk(DataView& view, uint32_t chunkSize); + + keymaster_error_t updateInChunks(DataView& data, HardwareAuthToken& authToken, + TimeStampToken& timestampToken, vector<uint8_t>* output); + + keymaster_error_t sendFinish(const vector<uint8_t>& data, const vector<uint8_t>& signature, + const HardwareAuthToken& authToken, + const TimeStampToken& timestampToken, + const vector<uint8_t>& confToken, vector<uint8_t>& output); + + keymaster_error_t sendUpdate(const vector<uint8_t>& data, const HardwareAuthToken& authToken, + const TimeStampToken& timestampToken, vector<uint8_t>& output); + + inline void appendBufferedData(DataView& view) { + if (!buffer_.empty()) { + view.buffer = buffer_; + view.length = view.length + buffer_.size(); + view.start = 0; + // view.buffer = insert(data.begin(), buffer_.begin(), buffer_.end()); + buffer_.clear(); + } + } + + std::tuple<std::unique_ptr<Item>, keymaster_error_t> sendRequest(Instruction ins, + Array& request); + keymaster_error_t bufferData(DataView& data); + void blockAlign(DataView& data, uint16_t blockSize); + uint16_t getDataViewOffset(DataView& view, uint16_t blockSize); + + vector<uint8_t> buffer_; + BufferingMode bufferingMode_; + uint16_t macLength_; + const shared_ptr<JavacardSecureElement> card_; + keymaster_operation_handle_t opHandle_; + CborConverter cbor_; +}; + +} // namespace aidl::android::hardware::security::keymint diff --git a/ready_se/google/keymint/KM300/HAL/JavacardRemotelyProvisionedComponentDevice.cpp b/ready_se/google/keymint/KM300/HAL/JavacardRemotelyProvisionedComponentDevice.cpp new file mode 100644 index 0000000..c79889f --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/JavacardRemotelyProvisionedComponentDevice.cpp @@ -0,0 +1,320 @@ +/* + * Copyright 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "javacard.keymint.device.rkp.strongbox-impl" + +#include "JavacardRemotelyProvisionedComponentDevice.h" + +#include <aidl/android/hardware/security/keymint/MacedPublicKey.h> + +#include <KeyMintUtils.h> +#include <android-base/logging.h> +#include <keymaster/cppcose/cppcose.h> +#include <keymaster/remote_provisioning_utils.h> + +namespace aidl::android::hardware::security::keymint { +using cppbor::Array; +using cppbor::EncodedItem; +using cppcose::kCoseMac0EntryCount; +using cppcose::kCoseMac0Payload; +using ::keymint::javacard::Instruction; +using std::string; + +// RKP error codes defined in keymint applet. +constexpr int32_t kStatusFailed = 32000; +constexpr int32_t kStatusInvalidMac = 32001; +constexpr int32_t kStatusProductionKeyInTestRequest = 32002; +constexpr int32_t kStatusTestKeyInProductionRequest = 32003; +constexpr int32_t kStatusInvalidEek = 32004; +constexpr int32_t kStatusInvalidState = 32005; + +namespace { + +keymaster_error_t translateRkpErrorCode(keymaster_error_t error) { + switch (static_cast<int32_t>(-error)) { + case kStatusFailed: + case kStatusInvalidState: + return static_cast<keymaster_error_t>(BnRemotelyProvisionedComponent::STATUS_FAILED); + case kStatusInvalidMac: + return static_cast<keymaster_error_t>(BnRemotelyProvisionedComponent::STATUS_INVALID_MAC); + case kStatusProductionKeyInTestRequest: + return static_cast<keymaster_error_t>( + BnRemotelyProvisionedComponent::STATUS_PRODUCTION_KEY_IN_TEST_REQUEST); + case kStatusTestKeyInProductionRequest: + return static_cast<keymaster_error_t>( + BnRemotelyProvisionedComponent::STATUS_TEST_KEY_IN_PRODUCTION_REQUEST); + case kStatusInvalidEek: + return static_cast<keymaster_error_t>(BnRemotelyProvisionedComponent::STATUS_INVALID_EEK); + } + return error; +} + +ScopedAStatus defaultHwInfo(RpcHardwareInfo* info) { + info->versionNumber = 3; + info->rpcAuthorName = "Google"; + info->supportedEekCurve = RpcHardwareInfo::CURVE_NONE; + info->uniqueId = "Google Strongbox KeyMint 3"; + info->supportedNumKeysInCsr = RpcHardwareInfo::MIN_SUPPORTED_NUM_KEYS_IN_CSR; + return ScopedAStatus::ok(); +} + +uint32_t coseKeyEncodedSize(const std::vector<MacedPublicKey>& keysToSign) { + uint32_t size = 0; + for (auto& macKey : keysToSign) { + auto [macedKeyItem, _, coseMacErrMsg] = cppbor::parse(macKey.macedKey); + if (!macedKeyItem || !macedKeyItem->asArray() || + macedKeyItem->asArray()->size() != kCoseMac0EntryCount) { + LOG(ERROR) << "Invalid COSE_Mac0 structure"; + return 0; + } + auto payload = macedKeyItem->asArray()->get(kCoseMac0Payload)->asBstr(); + if (!payload) return 0; + size += payload->value().size(); + } + return size; +} + +} // namespace + +ScopedAStatus JavacardRemotelyProvisionedComponentDevice::getHardwareInfo(RpcHardwareInfo* info) { + auto [item, err] = card_->sendRequest(Instruction::INS_GET_RKP_HARDWARE_INFO); + std::optional<uint64_t> optVersionNumber; + std::optional<uint64_t> optSupportedEekCurve; + std::optional<string> optRpcAuthorName; + std::optional<string> optUniqueId; + std::optional<uint64_t> optMinSupportedKeysInCsr; + if (err != KM_ERROR_OK || !(optVersionNumber = cbor_.getUint64(item, 1)) || + !(optRpcAuthorName = cbor_.getByteArrayStr(item, 2)) || + !(optSupportedEekCurve = cbor_.getUint64(item, 3)) || + !(optUniqueId = cbor_.getByteArrayStr(item, 4)) || + !(optMinSupportedKeysInCsr = cbor_.getUint64(item, 5))) { + LOG(ERROR) << "Error in response of getHardwareInfo."; + LOG(INFO) << "Returning defaultHwInfo in getHardwareInfo."; + return defaultHwInfo(info); + } + info->rpcAuthorName = std::move(optRpcAuthorName.value()); + info->versionNumber = static_cast<int32_t>(std::move(optVersionNumber.value())); + info->supportedEekCurve = static_cast<int32_t>(std::move(optSupportedEekCurve.value())); + info->uniqueId = std::move(optUniqueId.value()); + info->supportedNumKeysInCsr = static_cast<int32_t>(std::move(optMinSupportedKeysInCsr.value())); + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardRemotelyProvisionedComponentDevice::generateEcdsaP256KeyPair( + bool testMode, MacedPublicKey* macedPublicKey, std::vector<uint8_t>* privateKeyHandle) { + cppbor::Array array; + array.add(testMode); + auto [item, err] = card_->sendRequest(Instruction::INS_GENERATE_RKP_KEY_CMD, array); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending generateEcdsaP256KeyPair."; + return km_utils::kmError2ScopedAStatus(translateRkpErrorCode(err)); + } + std::optional<std::vector<uint8_t>> optMacedKey; + std::optional<std::vector<uint8_t>> optPKeyHandle; + if (!(optMacedKey = cbor_.getByteArrayVec(item, 1)) || + !(optPKeyHandle = cbor_.getByteArrayVec(item, 2))) { + LOG(ERROR) << "Error in decoding the response in generateEcdsaP256KeyPair."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + *privateKeyHandle = std::move(optPKeyHandle.value()); + macedPublicKey->macedKey = std::move(optMacedKey.value()); + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardRemotelyProvisionedComponentDevice::beginSendData( + const std::vector<MacedPublicKey>& keysToSign, const std::vector<uint8_t>& challenge, + DeviceInfo* deviceInfo, uint32_t* version, std::string* certificateType) { + uint32_t totalEncodedSize = coseKeyEncodedSize(keysToSign); + cppbor::Array array; + array.add(keysToSign.size()); + array.add(totalEncodedSize); + array.add(challenge); + auto [item, err] = card_->sendRequest(Instruction::INS_BEGIN_SEND_DATA_CMD, array); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in beginSendData."; + return km_utils::kmError2ScopedAStatus(translateRkpErrorCode(err)); + } + auto optDecodedDeviceInfo = cbor_.getByteArrayVec(item, 1); + if (!optDecodedDeviceInfo) { + LOG(ERROR) << "Error in decoding deviceInfo response in beginSendData."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + deviceInfo->deviceInfo = std::move(optDecodedDeviceInfo.value()); + auto optVersion = cbor_.getUint64(item, 2); + if (!optVersion) { + LOG(ERROR) << "Error in decoding version in beginSendData."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + *version = optVersion.value(); + auto optCertType = cbor_.getTextStr(item, 3); + if (!optCertType) { + LOG(ERROR) << "Error in decoding cert type in beginSendData."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + *certificateType = std::move(optCertType.value()); + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardRemotelyProvisionedComponentDevice::updateMacedKey( + const std::vector<MacedPublicKey>& keysToSign, Array& coseKeys) { + for (auto& macedPublicKey : keysToSign) { + cppbor::Array array; + array.add(EncodedItem(macedPublicKey.macedKey)); + auto [item, err] = card_->sendRequest(Instruction::INS_UPDATE_KEY_CMD, array); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in updateMacedKey."; + return km_utils::kmError2ScopedAStatus(translateRkpErrorCode(err)); + } + auto coseKeyData = cbor_.getByteArrayVec(item, 1); + coseKeys.add(EncodedItem(coseKeyData.value())); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardRemotelyProvisionedComponentDevice::finishSendData( + std::vector<uint8_t>& coseEncryptProtectedHeader, std::vector<uint8_t>& signature, + uint32_t& version, uint32_t& respFlag) { + auto [item, err] = card_->sendRequest(Instruction::INS_FINISH_SEND_DATA_CMD); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in finishSendData."; + return km_utils::kmError2ScopedAStatus(translateRkpErrorCode(err)); + } + auto optCEncryptProtectedHeader = cbor_.getByteArrayVec(item, 1); + auto optSignature = cbor_.getByteArrayVec(item, 2); + auto optVersion = cbor_.getUint64(item, 3); + auto optRespFlag = cbor_.getUint64(item, 4); + if (!optCEncryptProtectedHeader || !optSignature || !optVersion || !optRespFlag) { + LOG(ERROR) << "Error in decoding response in finishSendData."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + + coseEncryptProtectedHeader = std::move(optCEncryptProtectedHeader.value()); + signature.insert(signature.end(), optSignature->begin(), optSignature->end()); + version = std::move(optVersion.value()); + respFlag = std::move(optRespFlag.value()); + return ScopedAStatus::ok(); +} + +ScopedAStatus +JavacardRemotelyProvisionedComponentDevice::getDiceCertChain(std::vector<uint8_t>& diceCertChain) { + uint32_t respFlag = 0; + do { + auto [item, err] = card_->sendRequest(Instruction::INS_GET_DICE_CERT_CHAIN_CMD); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in getDiceCertChain."; + return km_utils::kmError2ScopedAStatus(translateRkpErrorCode(err)); + } + auto optDiceCertChain = cbor_.getByteArrayVec(item, 1); + auto optRespFlag = cbor_.getUint64(item, 2); + if (!optDiceCertChain || !optRespFlag) { + LOG(ERROR) << "Error in decoding response in getDiceCertChain."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + respFlag = optRespFlag.value(); + diceCertChain.insert(diceCertChain.end(), optDiceCertChain->begin(), + optDiceCertChain->end()); + } while (respFlag != 0); + return ScopedAStatus::ok(); +} + +ScopedAStatus +JavacardRemotelyProvisionedComponentDevice::getUdsCertsChain(std::vector<uint8_t>& udsCertsChain) { + uint32_t respFlag = 0; + do { + auto [item, err] = card_->sendRequest(Instruction::INS_GET_UDS_CERTS_CMD); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in getUdsCertsChain."; + return km_utils::kmError2ScopedAStatus(translateRkpErrorCode(err)); + } + auto optUdsCertData = cbor_.getByteArrayVec(item, 1); + auto optRespFlag = cbor_.getUint64(item, 2); + if (!optUdsCertData || !optRespFlag) { + LOG(ERROR) << "Error in decoding og response in getUdsCertsChain."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + respFlag = optRespFlag.value(); + udsCertsChain.insert(udsCertsChain.end(), optUdsCertData->begin(), optUdsCertData->end()); + } while (respFlag != 0); + return ScopedAStatus::ok(); +} + +ScopedAStatus JavacardRemotelyProvisionedComponentDevice::generateCertificateRequest( + bool, const std::vector<MacedPublicKey>&, const std::vector<uint8_t>&, + const std::vector<uint8_t>&, DeviceInfo*, ProtectedData*, std::vector<uint8_t>*) { + return km_utils::kmError2ScopedAStatus(static_cast<keymaster_error_t>(STATUS_REMOVED)); +} + +ScopedAStatus JavacardRemotelyProvisionedComponentDevice::generateCertificateRequestV2( + const std::vector<MacedPublicKey>& keysToSign, const std::vector<uint8_t>& challenge, + std::vector<uint8_t>* csr) { + uint32_t version; + uint32_t csrPayloadSchemaVersion; + std::string certificateType; + uint32_t respFlag; + DeviceInfo deviceInfo; + Array coseKeys; + std::vector<uint8_t> protectedHeader; + cppbor::Map coseEncryptUnProtectedHeader; + std::vector<uint8_t> signature; + std::vector<uint8_t> diceCertChain; + std::vector<uint8_t> udsCertChain; + cppbor::Array payLoad; + + auto ret = beginSendData(keysToSign, challenge, &deviceInfo, &csrPayloadSchemaVersion, + &certificateType); + if (!ret.isOk()) return ret; + + ret = updateMacedKey(keysToSign, coseKeys); + if (!ret.isOk()) return ret; + + ret = finishSendData(protectedHeader, signature, version, respFlag); + if (!ret.isOk()) return ret; + + ret = getUdsCertsChain(udsCertChain); + if (!ret.isOk()) return ret; + + ret = getDiceCertChain(diceCertChain); + if (!ret.isOk()) return ret; + + auto payload = cppbor::Array() + .add(csrPayloadSchemaVersion) + .add(certificateType) + .add(EncodedItem(deviceInfo.deviceInfo)) // deviceinfo + .add(std::move(coseKeys)) // KeysToSign + .encode(); + + auto signDataPayload = cppbor::Array() + .add(challenge) // Challenge + .add(std::move(payload)) + .encode(); + + auto signedData = cppbor::Array() + .add(std::move(protectedHeader)) + .add(cppbor::Map() /* unprotected parameters */) + .add(std::move(signDataPayload)) + .add(std::move(signature)); + + *csr = cppbor::Array() + .add(version) + .add(EncodedItem(udsCertChain)) + .add(EncodedItem(diceCertChain)) + .add(std::move(signedData)) + .encode(); + + return ScopedAStatus::ok(); +} + +} // namespace aidl::android::hardware::security::keymint diff --git a/ready_se/google/keymint/KM300/HAL/JavacardRemotelyProvisionedComponentDevice.h b/ready_se/google/keymint/KM300/HAL/JavacardRemotelyProvisionedComponentDevice.h new file mode 100644 index 0000000..5ce8cd7 --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/JavacardRemotelyProvisionedComponentDevice.h @@ -0,0 +1,80 @@ +/* + * Copyright 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <cppbor.h> + +#include <aidl/android/hardware/security/keymint/BnRemotelyProvisionedComponent.h> +#include <aidl/android/hardware/security/keymint/RpcHardwareInfo.h> +#include <aidl/android/hardware/security/keymint/SecurityLevel.h> + +#include <keymaster/UniquePtr.h> +#include <keymaster/android_keymaster.h> + +#include "CborConverter.h" +#include "JavacardSecureElement.h" + +namespace aidl::android::hardware::security::keymint { +using ::keymint::javacard::CborConverter; +using ::keymint::javacard::JavacardSecureElement; +using ndk::ScopedAStatus; +using std::shared_ptr; + +class JavacardRemotelyProvisionedComponentDevice : public BnRemotelyProvisionedComponent { + public: + explicit JavacardRemotelyProvisionedComponentDevice(shared_ptr<JavacardSecureElement> card) + : card_(card) {} + + virtual ~JavacardRemotelyProvisionedComponentDevice() = default; + + ScopedAStatus getHardwareInfo(RpcHardwareInfo* info) override; + + ScopedAStatus generateEcdsaP256KeyPair(bool testMode, MacedPublicKey* macedPublicKey, + std::vector<uint8_t>* privateKeyHandle) override; + + ScopedAStatus generateCertificateRequest(bool testMode, + const std::vector<MacedPublicKey>& keysToSign, + const std::vector<uint8_t>& endpointEncCertChain, + const std::vector<uint8_t>& challenge, + DeviceInfo* deviceInfo, ProtectedData* protectedData, + std::vector<uint8_t>* keysToSignMac) override; + + ScopedAStatus generateCertificateRequestV2(const std::vector<MacedPublicKey>& keysToSign, + const std::vector<uint8_t>& challenge, + std::vector<uint8_t>* csr) override; + + private: + ScopedAStatus beginSendData(const std::vector<MacedPublicKey>& keysToSign, + const std::vector<uint8_t>& challenge, DeviceInfo* deviceInfo, + uint32_t* version, std::string* certificateType); + + ScopedAStatus updateMacedKey(const std::vector<MacedPublicKey>& keysToSign, + cppbor::Array& coseKeys); + + ScopedAStatus finishSendData(std::vector<uint8_t>& coseEncryptProtectedHeader, + std::vector<uint8_t>& signature, uint32_t& version, + uint32_t& respFlag); + + ScopedAStatus getResponse(std::vector<uint8_t>& partialCipheredData, + cppbor::Array& recepientStructure, uint32_t& respFlag); + ScopedAStatus getDiceCertChain(std::vector<uint8_t>& diceCertChain); + ScopedAStatus getUdsCertsChain(std::vector<uint8_t>& udsCertsChain); + std::shared_ptr<JavacardSecureElement> card_; + CborConverter cbor_; +}; + +} // namespace aidl::android::hardware::security::keymint diff --git a/ready_se/google/keymint/KM300/HAL/JavacardSecureElement.cpp b/ready_se/google/keymint/KM300/HAL/JavacardSecureElement.cpp new file mode 100644 index 0000000..7c4f038 --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/JavacardSecureElement.cpp @@ -0,0 +1,157 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "javacard.keymint.device.strongbox-impl" +#include "JavacardSecureElement.h" + +#include <algorithm> +#include <iostream> +#include <iterator> +#include <memory> +#include <regex.h> +#include <string> +#include <vector> + +#include <android-base/logging.h> +#include <android-base/properties.h> +#include <keymaster/android_keymaster_messages.h> + +#include "keymint_utils.h" + +namespace keymint::javacard { + +keymaster_error_t JavacardSecureElement::initializeJavacard() { + Array request; + request.add(Uint(getOsVersion())); + request.add(Uint(getOsPatchlevel())); + request.add(Uint(getVendorPatchlevel())); + auto [item, err] = sendRequest(Instruction::INS_INIT_STRONGBOX_CMD, request); + return err; +} + +keymaster_error_t JavacardSecureElement::sendEarlyBootEndedEvent(bool eventTriggered) { + isEarlyBootEventPending |= eventTriggered; + if (!isEarlyBootEventPending) { + return KM_ERROR_OK; + } + auto [item, err] = sendRequest(Instruction::INS_EARLY_BOOT_ENDED_CMD); + if (err != KM_ERROR_OK) { + // Incase of failure cache the event and send in the next immediate request to Applet. + isEarlyBootEventPending = true; + return err; + } + isEarlyBootEventPending = false; + return KM_ERROR_OK; +} + +keymaster_error_t JavacardSecureElement::constructApduMessage(Instruction& ins, + std::vector<uint8_t>& inputData, + std::vector<uint8_t>& apduOut) { + apduOut.push_back(static_cast<uint8_t>(APDU_CLS)); // CLS + apduOut.push_back(static_cast<uint8_t>(ins)); // INS + apduOut.push_back(static_cast<uint8_t>(APDU_P1)); // P1 + apduOut.push_back(static_cast<uint8_t>(APDU_P2)); // P2 + + if (USHRT_MAX >= inputData.size()) { + // Send extended length APDU always as response size is not known to HAL. + // Case 1: Lc > 0 CLS | INS | P1 | P2 | 00 | 2 bytes of Lc | CommandData | 2 bytes of Le + // all set to 00. Case 2: Lc = 0 CLS | INS | P1 | P2 | 3 bytes of Le all set to 00. + // Extended length 3 bytes, starts with 0x00 + apduOut.push_back(static_cast<uint8_t>(0x00)); + if (inputData.size() > 0) { + apduOut.push_back(static_cast<uint8_t>(inputData.size() >> 8)); + apduOut.push_back(static_cast<uint8_t>(inputData.size() & 0xFF)); + // Data + apduOut.insert(apduOut.end(), inputData.begin(), inputData.end()); + } + // Expected length of output. + // Accepting complete length of output every time. + apduOut.push_back(static_cast<uint8_t>(0x00)); + apduOut.push_back(static_cast<uint8_t>(0x00)); + } else { + LOG(ERROR) << "Error in constructApduMessage."; + return (KM_ERROR_INVALID_INPUT_LENGTH); + } + return (KM_ERROR_OK); // success +} + +keymaster_error_t JavacardSecureElement::sendData(Instruction ins, std::vector<uint8_t>& inData, + std::vector<uint8_t>& response) { + keymaster_error_t ret = KM_ERROR_UNKNOWN_ERROR; + std::vector<uint8_t> apdu; + + ret = constructApduMessage(ins, inData, apdu); + + if (ret != KM_ERROR_OK) { + return ret; + } + + ret = transport_->sendData(apdu, response); + if (ret != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending data in sendData. " << static_cast<int>(ret); + return ret; + } + + // Response size should be greater than 2. Cbor output data followed by two bytes of APDU + // status. + if ((response.size() <= 2) || (getApduStatus(response) != APDU_RESP_STATUS_OK)) { + LOG(ERROR) << "Response of the sendData is wrong: response size = " << response.size() + << " apdu status = " << getApduStatus(response); + return (KM_ERROR_UNKNOWN_ERROR); + } + // remove the status bytes + response.pop_back(); + response.pop_back(); + return (KM_ERROR_OK); // success +} + +std::tuple<std::unique_ptr<Item>, keymaster_error_t> +JavacardSecureElement::sendRequest(Instruction ins, Array& request) { + vector<uint8_t> response; + // encode request + std::vector<uint8_t> command = request.encode(); + auto sendError = sendData(ins, command, response); + if (sendError != KM_ERROR_OK) { + return {unique_ptr<Item>(nullptr), sendError}; + } + // decode the response and send that back + return cbor_.decodeData(response); +} + +std::tuple<std::unique_ptr<Item>, keymaster_error_t> +JavacardSecureElement::sendRequest(Instruction ins, std::vector<uint8_t>& command) { + vector<uint8_t> response; + auto sendError = sendData(ins, command, response); + if (sendError != KM_ERROR_OK) { + return {unique_ptr<Item>(nullptr), sendError}; + } + // decode the response and send that back + return cbor_.decodeData(response); +} + +std::tuple<std::unique_ptr<Item>, keymaster_error_t> +JavacardSecureElement::sendRequest(Instruction ins) { + vector<uint8_t> response; + vector<uint8_t> emptyRequest; + auto sendError = sendData(ins, emptyRequest, response); + if (sendError != KM_ERROR_OK) { + return {unique_ptr<Item>(nullptr), sendError}; + } + // decode the response and send that back + return cbor_.decodeData(response); +} + +} // namespace keymint::javacard diff --git a/ready_se/google/keymint/KM300/HAL/JavacardSecureElement.h b/ready_se/google/keymint/KM300/HAL/JavacardSecureElement.h new file mode 100644 index 0000000..8ba0a44 --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/JavacardSecureElement.h @@ -0,0 +1,116 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <ITransport.h> + +#include "CborConverter.h" + +#define APDU_CLS 0x80 +#define APDU_P1 0x60 +#define APDU_P2 0x00 +#define APDU_RESP_STATUS_OK 0x9000 + +#define KEYMINT_CMD_APDU_START 0x20 + +namespace keymint::javacard { +using std::shared_ptr; +using std::vector; + +enum class Instruction { + // Keymaster commands + INS_GENERATE_KEY_CMD = KEYMINT_CMD_APDU_START + 1, + INS_IMPORT_KEY_CMD = KEYMINT_CMD_APDU_START + 2, + INS_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 3, + INS_EXPORT_KEY_CMD = KEYMINT_CMD_APDU_START + 4, + INS_ATTEST_KEY_CMD = KEYMINT_CMD_APDU_START + 5, + INS_UPGRADE_KEY_CMD = KEYMINT_CMD_APDU_START + 6, + INS_DELETE_KEY_CMD = KEYMINT_CMD_APDU_START + 7, + INS_DELETE_ALL_KEYS_CMD = KEYMINT_CMD_APDU_START + 8, + INS_ADD_RNG_ENTROPY_CMD = KEYMINT_CMD_APDU_START + 9, + INS_COMPUTE_SHARED_SECRET_CMD = KEYMINT_CMD_APDU_START + 10, + INS_DESTROY_ATT_IDS_CMD = KEYMINT_CMD_APDU_START + 11, + INS_VERIFY_AUTHORIZATION_CMD = KEYMINT_CMD_APDU_START + 12, + INS_GET_SHARED_SECRET_PARAM_CMD = KEYMINT_CMD_APDU_START + 13, + INS_GET_KEY_CHARACTERISTICS_CMD = KEYMINT_CMD_APDU_START + 14, + INS_GET_HW_INFO_CMD = KEYMINT_CMD_APDU_START + 15, + INS_BEGIN_OPERATION_CMD = KEYMINT_CMD_APDU_START + 16, + INS_UPDATE_OPERATION_CMD = KEYMINT_CMD_APDU_START + 17, + INS_FINISH_OPERATION_CMD = KEYMINT_CMD_APDU_START + 18, + INS_ABORT_OPERATION_CMD = KEYMINT_CMD_APDU_START + 19, + INS_DEVICE_LOCKED_CMD = KEYMINT_CMD_APDU_START + 20, + INS_EARLY_BOOT_ENDED_CMD = KEYMINT_CMD_APDU_START + 21, + INS_GET_CERT_CHAIN_CMD = KEYMINT_CMD_APDU_START + 22, + INS_UPDATE_AAD_OPERATION_CMD = KEYMINT_CMD_APDU_START + 23, + INS_BEGIN_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 24, + INS_FINISH_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 25, + INS_INIT_STRONGBOX_CMD = KEYMINT_CMD_APDU_START + 26, + // RKP Commands + INS_GET_RKP_HARDWARE_INFO = KEYMINT_CMD_APDU_START + 27, + INS_GENERATE_RKP_KEY_CMD = KEYMINT_CMD_APDU_START + 28, + INS_BEGIN_SEND_DATA_CMD = KEYMINT_CMD_APDU_START + 29, + INS_UPDATE_KEY_CMD = KEYMINT_CMD_APDU_START + 30, + INS_UPDATE_EEK_CHAIN_CMD = KEYMINT_CMD_APDU_START + 31, + INS_UPDATE_CHALLENGE_CMD = KEYMINT_CMD_APDU_START + 32, + INS_FINISH_SEND_DATA_CMD = KEYMINT_CMD_APDU_START + 33, + INS_GET_RESPONSE_CMD = KEYMINT_CMD_APDU_START + 34, + INS_GET_UDS_CERTS_CMD = KEYMINT_CMD_APDU_START + 35, + INS_GET_DICE_CERT_CHAIN_CMD = KEYMINT_CMD_APDU_START + 36, + // SE ROT Commands + INS_GET_ROT_CHALLENGE_CMD = KEYMINT_CMD_APDU_START + 45, + INS_GET_ROT_DATA_CMD = KEYMINT_CMD_APDU_START + 46, + INS_SEND_ROT_DATA_CMD = KEYMINT_CMD_APDU_START + 47, +}; + +class JavacardSecureElement { + public: + explicit JavacardSecureElement(shared_ptr<ITransport> transport, uint32_t osVersion, + uint32_t osPatchLevel, uint32_t vendorPatchLevel) + : transport_(transport), osVersion_(osVersion), osPatchLevel_(osPatchLevel), + vendorPatchLevel_(vendorPatchLevel), isEarlyBootEventPending(false) { + transport_->openConnection(); + } + virtual ~JavacardSecureElement() { transport_->closeConnection(); } + + std::tuple<std::unique_ptr<Item>, keymaster_error_t> sendRequest(Instruction ins, + Array& request); + std::tuple<std::unique_ptr<Item>, keymaster_error_t> sendRequest(Instruction ins); + std::tuple<std::unique_ptr<Item>, keymaster_error_t> sendRequest(Instruction ins, + std::vector<uint8_t>& command); + + keymaster_error_t sendData(Instruction ins, std::vector<uint8_t>& inData, + std::vector<uint8_t>& response); + + keymaster_error_t constructApduMessage(Instruction& ins, std::vector<uint8_t>& inputData, + std::vector<uint8_t>& apduOut); + keymaster_error_t initializeJavacard(); + keymaster_error_t sendEarlyBootEndedEvent(bool eventTriggered); + inline uint16_t getApduStatus(std::vector<uint8_t>& inputData) { + // Last two bytes are the status SW0SW1 + uint8_t SW0 = inputData.at(inputData.size() - 2); + uint8_t SW1 = inputData.at(inputData.size() - 1); + return (SW0 << 8 | SW1); + } + + shared_ptr<ITransport> transport_; + uint32_t osVersion_; + uint32_t osPatchLevel_; + uint32_t vendorPatchLevel_; + bool isEarlyBootEventPending; + CborConverter cbor_; +}; +} // namespace keymint::javacard diff --git a/ready_se/google/keymint/KM300/HAL/JavacardSharedSecret.cpp b/ready_se/google/keymint/KM300/HAL/JavacardSharedSecret.cpp new file mode 100644 index 0000000..c5cf9a2 --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/JavacardSharedSecret.cpp @@ -0,0 +1,61 @@ +#define LOG_TAG "javacard.strongbox.keymint.operation-impl" +#include "JavacardSharedSecret.h" + +#include <android-base/logging.h> + +#include <KeyMintUtils.h> + +namespace aidl::android::hardware::security::sharedsecret { +using ::keymint::javacard::Instruction; + +ScopedAStatus JavacardSharedSecret::getSharedSecretParameters(SharedSecretParameters* params) { + auto error = card_->initializeJavacard(); + if (error != KM_ERROR_OK) { + LOG(ERROR) << "Error in initializing javacard."; + return keymint::km_utils::kmError2ScopedAStatus(error); + } + auto [item, err] = card_->sendRequest(Instruction::INS_GET_SHARED_SECRET_PARAM_CMD); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in getSharedSecretParameters."; + return keymint::km_utils::kmError2ScopedAStatus(err); + } + auto optSSParams = cbor_.getSharedSecretParameters(item, 1); + if (!optSSParams) { + LOG(ERROR) << "Error in sending in getSharedSecretParameters."; + return keymint::km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + *params = std::move(optSSParams.value()); + return ScopedAStatus::ok(); +} + +ScopedAStatus +JavacardSharedSecret::computeSharedSecret(const std::vector<SharedSecretParameters>& params, + std::vector<uint8_t>* secret) { + + auto error = card_->sendEarlyBootEndedEvent(false); + if (error != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending earlyBoot event javacard."; + return keymint::km_utils::kmError2ScopedAStatus(error); + } + error = card_->initializeJavacard(); + if (error != KM_ERROR_OK) { + LOG(ERROR) << "Error in initializing javacard."; + return keymint::km_utils::kmError2ScopedAStatus(error); + } + cppbor::Array request; + cbor_.addSharedSecretParameters(request, params); + auto [item, err] = card_->sendRequest(Instruction::INS_COMPUTE_SHARED_SECRET_CMD, request); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending in computeSharedSecret."; + return keymint::km_utils::kmError2ScopedAStatus(err); + } + auto optSecret = cbor_.getByteArrayVec(item, 1); + if (!optSecret) { + LOG(ERROR) << "Error in decoding the response in computeSharedSecret."; + return keymint::km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + *secret = std::move(optSecret.value()); + return ScopedAStatus::ok(); +} + +} // namespace aidl::android::hardware::security::sharedsecret diff --git a/ready_se/google/keymint/KM300/HAL/JavacardSharedSecret.h b/ready_se/google/keymint/KM300/HAL/JavacardSharedSecret.h new file mode 100644 index 0000000..340853a --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/JavacardSharedSecret.h @@ -0,0 +1,34 @@ +#pragma once + +#include <memory> +#include <vector> + +#include <aidl/android/hardware/security/sharedsecret/BnSharedSecret.h> +#include <aidl/android/hardware/security/sharedsecret/SharedSecretParameters.h> + +#include "CborConverter.h" +#include "JavacardSecureElement.h" + +namespace aidl::android::hardware::security::sharedsecret { +using ::keymint::javacard::CborConverter; +using ::keymint::javacard::JavacardSecureElement; +using ndk::ScopedAStatus; +using std::shared_ptr; +using std::vector; + +class JavacardSharedSecret : public BnSharedSecret { + public: + explicit JavacardSharedSecret(shared_ptr<JavacardSecureElement> card) : card_(card) {} + virtual ~JavacardSharedSecret() {} + + ScopedAStatus getSharedSecretParameters(SharedSecretParameters* params) override; + + ScopedAStatus computeSharedSecret(const std::vector<SharedSecretParameters>& params, + std::vector<uint8_t>* secret) override; + + private: + shared_ptr<JavacardSecureElement> card_; + CborConverter cbor_; +}; + +} // namespace aidl::android::hardware::security::sharedsecret diff --git a/ready_se/google/keymint/KM300/HAL/LICENSE b/ready_se/google/keymint/KM300/HAL/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/ready_se/google/keymint/KM300/HAL/METADATA b/ready_se/google/keymint/KM300/HAL/METADATA new file mode 100644 index 0000000..d97975c --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/METADATA @@ -0,0 +1,3 @@ +third_party { + license_type: NOTICE +} diff --git a/ready_se/google/keymint/KM300/HAL/OWNERS b/ready_se/google/keymint/KM300/HAL/OWNERS new file mode 100644 index 0000000..0bd972b --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/OWNERS @@ -0,0 +1,3 @@ +pathakc@google.com +subrahmanyaman@google.com +avinashh@google.com diff --git a/ready_se/google/keymint/KM300/HAL/android.hardware.hardware_keystore.jc-strongbox-keymint3.xml b/ready_se/google/keymint/KM300/HAL/android.hardware.hardware_keystore.jc-strongbox-keymint3.xml new file mode 100644 index 0000000..ca49e71 --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/android.hardware.hardware_keystore.jc-strongbox-keymint3.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 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. +--> +<!-- Feature for devices with Keymaster in StrongBox. --> +<permissions> + <feature name="android.hardware.strongbox_keystore" version="300"/> + <feature name="android.hardware.keystore.app_attest_key" /> +</permissions> diff --git a/ready_se/google/keymint/KM300/HAL/android.hardware.security.keymint3-service.strongbox.rc b/ready_se/google/keymint/KM300/HAL/android.hardware.security.keymint3-service.strongbox.rc new file mode 100644 index 0000000..e1c1494 --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/android.hardware.security.keymint3-service.strongbox.rc @@ -0,0 +1,3 @@ +service vendor.keymint-strongbox /vendor/bin/hw/android.hardware.security.keymint3-service.strongbox + class early_hal + user jc_strongbox diff --git a/ready_se/google/keymint/KM300/HAL/android.hardware.security.keymint3-service.strongbox.xml b/ready_se/google/keymint/KM300/HAL/android.hardware.security.keymint3-service.strongbox.xml new file mode 100644 index 0000000..481f028 --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/android.hardware.security.keymint3-service.strongbox.xml @@ -0,0 +1,12 @@ +<manifest version="1.0" type="device"> + <hal format="aidl"> + <name>android.hardware.security.keymint</name> + <version>3</version> + <fqname>IKeyMintDevice/strongbox</fqname> + </hal> + <hal format="aidl"> + <name>android.hardware.security.keymint</name> + <version>3</version> + <fqname>IRemotelyProvisionedComponent/strongbox</fqname> + </hal> +</manifest> diff --git a/ready_se/google/keymint/KM300/HAL/android.hardware.security.sharedsecret3-service.strongbox.xml b/ready_se/google/keymint/KM300/HAL/android.hardware.security.sharedsecret3-service.strongbox.xml new file mode 100644 index 0000000..5492100 --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/android.hardware.security.sharedsecret3-service.strongbox.xml @@ -0,0 +1,6 @@ +<manifest version="1.0" type="device"> + <hal format="aidl"> + <name>android.hardware.security.sharedsecret</name> + <fqname>ISharedSecret/strongbox</fqname> + </hal> +</manifest> diff --git a/ready_se/google/keymint/KM300/HAL/keymint_utils.cpp b/ready_se/google/keymint/KM300/HAL/keymint_utils.cpp new file mode 100644 index 0000000..f613eda --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/keymint_utils.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "keymint_utils.h" + +#include <regex.h> + +#include <android-base/properties.h> + +namespace keymint::javacard { + +namespace { + +constexpr char kPlatformVersionProp[] = "ro.build.version.release"; +constexpr char kPlatformVersionRegex[] = "^([0-9]{1,2})(\\.([0-9]{1,2}))?(\\.([0-9]{1,2}))?"; +constexpr size_t kMajorVersionMatch = 1; +constexpr size_t kMinorVersionMatch = 3; +constexpr size_t kSubminorVersionMatch = 5; +constexpr size_t kPlatformVersionMatchCount = kSubminorVersionMatch + 1; + +constexpr char kPlatformPatchlevelProp[] = "ro.build.version.security_patch"; +constexpr char kVendorPatchlevelProp[] = "ro.vendor.build.security_patch"; +constexpr char kPatchlevelRegex[] = "^([0-9]{4})-([0-9]{2})-([0-9]{2})$"; +constexpr size_t kYearMatch = 1; +constexpr size_t kMonthMatch = 2; +constexpr size_t kDayMatch = 3; +constexpr size_t kPatchlevelMatchCount = kDayMatch + 1; + +uint32_t match_to_uint32(const char* expression, const regmatch_t& match) { + if (match.rm_so == -1) return 0; + + size_t len = match.rm_eo - match.rm_so; + std::string s(expression + match.rm_so, len); + return std::stoul(s); +} + +std::string wait_and_get_property(const char* prop) { + std::string prop_value; + while (!::android::base::WaitForPropertyCreation(prop)) + ; + prop_value = ::android::base::GetProperty(prop, "" /* default */); + return prop_value; +} + +uint32_t getOsVersion(const char* version_str) { + regex_t regex; + if (regcomp(®ex, kPlatformVersionRegex, REG_EXTENDED)) { + return 0; + } + + regmatch_t matches[kPlatformVersionMatchCount]; + int not_match = + regexec(®ex, version_str, kPlatformVersionMatchCount, matches, 0 /* flags */); + regfree(®ex); + if (not_match) { + return 0; + } + + uint32_t major = match_to_uint32(version_str, matches[kMajorVersionMatch]); + uint32_t minor = match_to_uint32(version_str, matches[kMinorVersionMatch]); + uint32_t subminor = match_to_uint32(version_str, matches[kSubminorVersionMatch]); + + return (major * 100 + minor) * 100 + subminor; +} + +enum class PatchlevelOutput { kYearMonthDay, kYearMonth }; + +uint32_t getPatchlevel(const char* patchlevel_str, PatchlevelOutput detail) { + regex_t regex; + if (regcomp(®ex, kPatchlevelRegex, REG_EXTENDED) != 0) { + return 0; + } + + regmatch_t matches[kPatchlevelMatchCount]; + int not_match = regexec(®ex, patchlevel_str, kPatchlevelMatchCount, matches, 0 /* flags */); + regfree(®ex); + if (not_match) { + return 0; + } + + uint32_t year = match_to_uint32(patchlevel_str, matches[kYearMatch]); + uint32_t month = match_to_uint32(patchlevel_str, matches[kMonthMatch]); + + if (month < 1 || month > 12) { + return 0; + } + + switch (detail) { + case PatchlevelOutput::kYearMonthDay: { + uint32_t day = match_to_uint32(patchlevel_str, matches[kDayMatch]); + if (day < 1 || day > 31) { + return 0; + } + return year * 10000 + month * 100 + day; + } + case PatchlevelOutput::kYearMonth: + return year * 100 + month; + } +} + +} // anonymous namespace + +uint32_t getOsVersion() { + std::string version = wait_and_get_property(kPlatformVersionProp); + return getOsVersion(version.c_str()); +} + +uint32_t getOsPatchlevel() { + std::string patchlevel = wait_and_get_property(kPlatformPatchlevelProp); + return getPatchlevel(patchlevel.c_str(), PatchlevelOutput::kYearMonth); +} + +uint32_t getVendorPatchlevel() { + std::string patchlevel = wait_and_get_property(kVendorPatchlevelProp); + return getPatchlevel(patchlevel.c_str(), PatchlevelOutput::kYearMonthDay); +} + +} // namespace keymint::javacard diff --git a/ready_se/google/keymint/KM300/HAL/keymint_utils.h b/ready_se/google/keymint/KM300/HAL/keymint_utils.h new file mode 100644 index 0000000..65cda63 --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/keymint_utils.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <string> +#include <vector> + +// #include <aidl/android/hardware/security/keymint/HardwareAuthToken.h> + +// namespace aidl::android::hardware::security::keymint { +namespace keymint::javacard { + +using std::vector; + +inline static std::vector<uint8_t> blob2vector(const uint8_t* data, const size_t length) { + std::vector<uint8_t> result(data, data + length); + return result; +} + +inline static std::vector<uint8_t> blob2vector(const std::string& value) { + vector<uint8_t> result(reinterpret_cast<const uint8_t*>(value.data()), + reinterpret_cast<const uint8_t*>(value.data()) + value.size()); + return result; +} + +// HardwareAuthToken vector2AuthToken(const vector<uint8_t>& buffer); +// vector<uint8_t> authToken2vector(const HardwareAuthToken& token); + +uint32_t getOsVersion(); +uint32_t getOsPatchlevel(); +uint32_t getVendorPatchlevel(); + +} // namespace keymint::javacard diff --git a/ready_se/google/keymint/KM300/HAL/service.cpp b/ready_se/google/keymint/KM300/HAL/service.cpp new file mode 100644 index 0000000..e83ee3d --- /dev/null +++ b/ready_se/google/keymint/KM300/HAL/service.cpp @@ -0,0 +1,95 @@ +/* + * Copyright 2020, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "javacard.strongbox-service" + +#include <aidl/android/hardware/security/keymint/SecurityLevel.h> + +#include <android-base/logging.h> +#include <android-base/properties.h> +#include <android/binder_manager.h> +#include <android/binder_process.h> + +#include "JavacardKeyMintDevice.h" +#include "JavacardRemotelyProvisionedComponentDevice.h" +#include "JavacardSecureElement.h" +#include "JavacardSharedSecret.h" +#include "OmapiTransport.h" +#include "SocketTransport.h" +#include "keymint_utils.h" + +using aidl::android::hardware::security::keymint::JavacardKeyMintDevice; +using aidl::android::hardware::security::keymint::JavacardRemotelyProvisionedComponentDevice; +using aidl::android::hardware::security::keymint::SecurityLevel; +using aidl::android::hardware::security::sharedsecret::JavacardSharedSecret; +using keymint::javacard::getOsPatchlevel; +using keymint::javacard::getOsVersion; +using keymint::javacard::getVendorPatchlevel; +using keymint::javacard::ITransport; +using keymint::javacard::JavacardSecureElement; +using keymint::javacard::OmapiTransport; +using keymint::javacard::SocketTransport; + +#define PROP_BUILD_QEMU "ro.kernel.qemu" +#define PROP_BUILD_FINGERPRINT "ro.build.fingerprint" +// Cuttlefish build fingerprint substring. +#define CUTTLEFISH_FINGERPRINT_SS "aosp_cf_" + +template <typename T, class... Args> std::shared_ptr<T> addService(Args&&... args) { + std::shared_ptr<T> ser = ndk::SharedRefBase::make<T>(std::forward<Args>(args)...); + auto instanceName = std::string(T::descriptor) + "/strongbox"; + LOG(INFO) << "adding javacard strongbox service instance: " << instanceName; + binder_status_t status = + AServiceManager_addService(ser->asBinder().get(), instanceName.c_str()); + CHECK(status == STATUS_OK); + return ser; +} + +std::shared_ptr<ITransport> getTransportInstance() { + bool isEmulator = false; + // Check if the current build is for emulator or device. + isEmulator = android::base::GetBoolProperty(PROP_BUILD_QEMU, false); + if (!isEmulator) { + std::string fingerprint = android::base::GetProperty(PROP_BUILD_FINGERPRINT, ""); + if (!fingerprint.empty()) { + if (fingerprint.find(CUTTLEFISH_FINGERPRINT_SS, 0) != std::string::npos) { + isEmulator = true; + } + } + } + + if (!isEmulator) { + return std::make_shared<OmapiTransport>(); + } else { + return std::make_shared<SocketTransport>(); + } +} + +int main() { + ABinderProcess_setThreadPoolMaxThreadCount(0); + // Javacard Secure Element + std::shared_ptr<JavacardSecureElement> card = std::make_shared<JavacardSecureElement>( + getTransportInstance(), getOsVersion(), getOsPatchlevel(), getVendorPatchlevel()); + // Add Keymint Service + addService<JavacardKeyMintDevice>(card); + // Add Shared Secret Service + addService<JavacardSharedSecret>(card); + // Add Remotely Provisioned Component Service + addService<JavacardRemotelyProvisionedComponentDevice>(card); + + ABinderProcess_joinThreadPool(); + return EXIT_FAILURE; // should not reach +} diff --git a/tools/ese_relay/ese_relay_fake.c b/tools/ese_relay/ese_relay_fake.c index f336709..99b359e 100644 --- a/tools/ese_relay/ese_relay_fake.c +++ b/tools/ese_relay/ese_relay_fake.c @@ -20,7 +20,8 @@ ESE_INCLUDE_HW(ESE_HW_FAKE); /* Minimal ATR */ -const uint8_t kAtr[] = {0x00, 0x00}; +static const uint8_t kAtrBytes[] = {0x00, 0x00}; +const uint8_t *kAtr = &kAtrBytes[0]; const size_t kAtrLength = sizeof(kAtr); const void *kEseOpenData = NULL; |