diff --git a/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java b/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java index 1561ccd7..02b9c6b5 100644 --- a/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java +++ b/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMAttestationCertImpl.java @@ -100,7 +100,7 @@ public class KMAttestationCertImpl implements KMAttestationCert { private static final byte keyUsageCertSign = (byte) 0x04; // 5th- bit private static final byte KEYMASTER_VERSION = 100; - private static final byte ATTESTATION_VERSION = 3; + private static final byte ATTESTATION_VERSION = 100; private static final byte[] pubExponent = {0x01, 0x00, 0x01}; private static final byte SERIAL_NUM = (byte) 0x01; private static final byte X509_VERSION = (byte) 0x02; @@ -138,7 +138,8 @@ public class KMAttestationCertImpl implements KMAttestationCert { private static short serialNum; private static byte certMode; - private static short certAttestKey ; + private static short certAttestKeySecret; + private static short certAttestKeyRsaPubModulus; private static boolean certRsaSign; private static final byte SERIAL_NUM_MAX_LEN = 20; private static final byte SUBJECT_NAME_MAX_LEN = 32; @@ -186,7 +187,7 @@ private static void init() { deviceLocked = 0; signPriv = 0; certMode = KMType.NO_CERT; - certAttestKey = KMType.INVALID_VALUE; + certAttestKeySecret = KMType.INVALID_VALUE; certRsaSign = true; issuer = KMType.INVALID_VALUE; subjectName = KMType.INVALID_VALUE; @@ -234,7 +235,6 @@ public KMAttestationCert notAfter(short usageExpiryTimeObj, boolean derEncoded, if (usageExpiryTimeObj != KMType.INVALID_VALUE) { // compare if the expiry time is greater then 2051 then use generalized // time format else use utc time format. - usageExpiryTimeObj = KMIntegerTag.cast(usageExpiryTimeObj).getValue(); short tmpVar = KMInteger.uint_64(KMUtils.firstJan2051, (short) 0); if (KMInteger.compare(usageExpiryTimeObj, tmpVar) >= 0) { usageExpiryTimeObj = KMUtils.convertToDate(usageExpiryTimeObj, scratchPad, @@ -514,14 +514,16 @@ private static void pushHWParams() { // Below are the allowed hardwareEnforced Authorization tags inside the attestation certificate's extension. short[] tagIds = { KMType.BOOT_PATCH_LEVEL, KMType.VENDOR_PATCH_LEVEL, + KMType.ATTESTATION_ID_MODEL, KMType.ATTESTATION_ID_MANUFACTURER, + KMType.ATTESTATION_ID_MEID, KMType.ATTESTATION_ID_IMEI, + KMType.ATTESTATION_ID_SERIAL, KMType.ATTESTATION_ID_PRODUCT, + KMType.ATTESTATION_ID_DEVICE, KMType.ATTESTATION_ID_BRAND, KMType.OS_PATCH_LEVEL, KMType.OS_VERSION, KMType.ROOT_OF_TRUST, - KMType.ORIGIN, KMType.APPLICATION_ID, - KMType.TRUSTED_CONFIRMATION_REQUIRED, - KMType.TRUSTED_USER_PRESENCE_REQUIRED, KMType.ALLOW_WHILE_ON_BODY, - KMType.AUTH_TIMEOUT, KMType.USER_AUTH_TYPE, KMType.NO_AUTH_REQUIRED, - KMType.ROLLBACK_RESISTANCE, KMType.RSA_PUBLIC_EXPONENT, - KMType.ECCURVE, KMType.PADDING, KMType.DIGEST, KMType.KEYSIZE, - KMType.ALGORITHM, KMType.PURPOSE}; + KMType.ORIGIN, KMType.AUTH_TIMEOUT, KMType.USER_AUTH_TYPE, + KMType.NO_AUTH_REQUIRED, KMType.USER_SECURE_ID, + KMType.RSA_PUBLIC_EXPONENT, KMType.ECCURVE, KMType.MIN_MAC_LENGTH, + KMType.CALLER_NONCE, KMType.PADDING, KMType.DIGEST, KMType.BLOCK_MODE, + KMType.KEYSIZE, KMType.ALGORITHM, KMType.PURPOSE}; byte index = 0; do { @@ -862,8 +864,8 @@ public short getCertLength() { return certLength; } - @Override - public void build(byte[] attBuf, short attStart, short attLength, boolean rsaSign, boolean fakeCert) { + + public void build(short attSecret, short attMod, boolean rsaSign, boolean fakeCert) { stackPtr = (short)(bufStart + bufLength); short last = stackPtr; short sigLen = 0; @@ -891,18 +893,18 @@ else if (rsaSign) { tbsStart = stackPtr; tbsLength = (short) (tbsLength - tbsStart); KMJCardSimulator provider = KMJCardSimulator.getInstance(); - if(attBuf != null){ + if(attSecret != KMType.INVALID_VALUE){ // Sign with the attestation key // The pubKey is the modulus. if (rsaSign) { sigLen = provider .rsaSign256Pkcs1( - attBuf, - attStart, - attLength, - KMByteBlob.cast(pubKey).getBuffer(), - KMByteBlob.cast(pubKey).getStartOff(), - KMByteBlob.cast(pubKey).length(), + KMByteBlob.cast(attSecret).getBuffer(), + KMByteBlob.cast(attSecret).getStartOff(), + KMByteBlob.cast(attSecret).length(), + KMByteBlob.cast(attMod).getBuffer(), + KMByteBlob.cast(attMod).getStartOff(), + KMByteBlob.cast(attMod).length(), stack, tbsStart, tbsLength, @@ -912,9 +914,9 @@ else if (rsaSign) { } else { sigLen = provider .ecSign256( - attBuf, - attStart, - attLength, + KMByteBlob.cast(attSecret).getBuffer(), + KMByteBlob.cast(attSecret).getStartOff(), + KMByteBlob.cast(attSecret).length(), stack, tbsStart, tbsLength, @@ -941,11 +943,9 @@ else if (rsaSign) { @Override public void build() { if(certMode == KMType.FAKE_CERT) { - build(null, (short) 0, (short) 0, true, true); + build(KMType.INVALID_VALUE, KMType.INVALID_VALUE, true, true); }else { - build(KMByteBlob.cast(certAttestKey).getBuffer(), - KMByteBlob.cast(certAttestKey).getStartOff(), - KMByteBlob.cast(certAttestKey).length(), certRsaSign, false); + build(certAttestKeySecret, certAttestKeyRsaPubModulus, certRsaSign, false); } } @@ -1024,10 +1024,20 @@ public boolean subjectName(short sub){ } @Override - public KMAttestationCert attestKey(short attestKey, boolean rsaSign, byte mode){ + public KMAttestationCert ecAttestKey(short attestKey, byte mode){ + certMode = mode; + certAttestKeySecret = attestKey; + certAttestKeyRsaPubModulus = KMType.INVALID_VALUE; + certRsaSign = false; + return this; + } + + @Override + public KMAttestationCert rsaAttestKey(short attestPrivExp, short attestMod, byte mode){ certMode = mode; - certAttestKey = attestKey; - certRsaSign = rsaSign; + certAttestKeySecret = attestPrivExp; + certAttestKeyRsaPubModulus = attestMod; + certRsaSign = true; return this; } diff --git a/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMECDeviceUniqueKey.java b/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMECDeviceUniqueKey.java new file mode 100644 index 00000000..bfda51bc --- /dev/null +++ b/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMECDeviceUniqueKey.java @@ -0,0 +1,53 @@ +/* + * 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.security.ECPrivateKey; +import javacard.security.ECPublicKey; +import javacard.security.KeyPair; + +public class KMECDeviceUniqueKey implements KMDeviceUniqueKey { + + private KeyPair ecKeyPair; + + @Override + public short getPublicKey(byte[] buf, short offset) { + ECPublicKey publicKey = getPublicKey(); + return publicKey.getW(buf, offset); + } + + public KMECDeviceUniqueKey(KeyPair ecPair) { + ecKeyPair = ecPair; + } + + public void setS(byte[] buffer, short offset, short length) { + ECPrivateKey ecPriv = (ECPrivateKey) ecKeyPair.getPrivate(); + ecPriv.setS(buffer, offset, length); + } + + public void setW(byte[] buffer, short offset, short length) { + ECPublicKey ecPublicKey = (ECPublicKey) ecKeyPair.getPublic(); + ecPublicKey.setW(buffer, offset, length); + } + + public ECPrivateKey getPrivateKey() { + return (ECPrivateKey) ecKeyPair.getPrivate(); + } + + public ECPublicKey getPublicKey() { + return (ECPublicKey) ecKeyPair.getPublic(); + } + +} diff --git a/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMJCardSimApplet.java b/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMJCardSimApplet.java index 0cd40184..ec36af0c 100644 --- a/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMJCardSimApplet.java +++ b/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMJCardSimApplet.java @@ -27,13 +27,14 @@ public class KMJCardSimApplet extends KMKeymasterApplet { private static final byte INS_LOCK_PROVISIONING_CMD = INS_KEYMINT_PROVIDER_APDU_START + 3; private static final byte INS_GET_PROVISION_STATUS_CMD = INS_KEYMINT_PROVIDER_APDU_START + 4; private static final byte INS_SET_BOOT_PARAMS_CMD = INS_KEYMINT_PROVIDER_APDU_START + 5; + private static final byte INS_PROVISION_DEVICE_UNIQUE_KEY_CMD = + INS_KEYMINT_PROVIDER_APDU_START + 6; + private static final byte INS_PROVISION_ADDITIONAL_CERT_CHAIN_CMD = + INS_KEYMINT_PROVIDER_APDU_START + 7; private static final byte INS_KEYMINT_PROVIDER_APDU_END = 0x1F; - private boolean locked; - KMJCardSimApplet() { super(new KMJCardSimulator()); - locked = false; setDummyBootParams(); setDummyPresharedKey(); setDummyAttestationIds(); @@ -52,65 +53,80 @@ public static void install(byte[] bArray, short bOffset, byte bLength) { @Override public void process(APDU apdu) { - // If this is select applet apdu which is selecting this applet then return - if (apdu.isISOInterindustryCLA()) { - if (selectingApplet()) { + try { + // If this is select applet apdu which is selecting this applet then return + if (apdu.isISOInterindustryCLA()) { + if (selectingApplet()) { + return; + } + } + short apduIns = validateApdu(apdu); + if (((KMJCardSimulator) seProvider).isPowerReset()) { + super.powerReset(); + } + if (((KMJCardSimulator) seProvider).isProvisionLocked()) { + switch (apduIns) { + case INS_SET_BOOT_PARAMS_CMD: + processSetBootParamsCmd(apdu); + break; + default: + super.process(apdu); + break; + } return; } - } - short apduIns = validateApdu(apdu); - if(((KMJCardSimulator)seProvider).isPowerReset()){ - super.powerReset(); - } - if (locked) { + if (apduIns == KMType.INVALID_VALUE) + return; switch (apduIns) { + case INS_PROVISION_ATTEST_IDS_CMD: + processProvisionAttestIdsCmd(apdu); + break; + case INS_PROVISION_PRESHARED_SECRET_CMD: + processProvisionPreSharedSecretCmd(apdu); + break; + case INS_GET_PROVISION_STATUS_CMD: + processGetProvisionStatusCmd(apdu); + break; + case INS_LOCK_PROVISIONING_CMD: + processLockProvisioningCmd(apdu); + break; case INS_SET_BOOT_PARAMS_CMD: processSetBootParamsCmd(apdu); break; + case INS_PROVISION_DEVICE_UNIQUE_KEY_CMD: + processProvisionDeviceUniqueKey(apdu); + break; + case INS_PROVISION_ADDITIONAL_CERT_CHAIN_CMD: + processProvisionAdditionalCertChain(apdu); + break; default: super.process(apdu); break; } - return; - } - if(apduIns == KMType.INVALID_VALUE) return; - switch (apduIns) { - case INS_PROVISION_ATTEST_IDS_CMD: - processProvisionAttestIdsCmd(apdu); - break; - case INS_PROVISION_PRESHARED_SECRET_CMD: - processProvisionPreSharedSecretCmd(apdu); - break; - case INS_GET_PROVISION_STATUS_CMD: - processGetProvisionStatusCmd(apdu); - break; - case INS_LOCK_PROVISIONING_CMD: - processLockProvisioningCmd(apdu); - break; - case INS_SET_BOOT_PARAMS_CMD: - processSetBootParamsCmd(apdu); - break; - default: - super.process(apdu); - break; + } finally { + repository.clean(); } } private void processProvisionAttestIdsCmd(APDU apdu) { - + sendError(apdu, KMError.OK); } private void processProvisionPreSharedSecretCmd(APDU apdu) { + sendError(apdu, KMError.OK); } private void processGetProvisionStatusCmd(APDU apdu) { + sendError(apdu, KMError.OK); } private void processSetBootParamsCmd(APDU apdu) { + sendError(apdu, KMError.OK); } private void processLockProvisioningCmd(APDU apdu) { - locked = true; + ((KMJCardSimulator)seProvider).setProvisionLocked(true); + sendError(apdu, KMError.OK); } private short validateApdu(APDU apdu) { @@ -143,12 +159,12 @@ private void setDummyBootParams(){ super.setOsPatchLevel(osPatchLevel); super.setVendorPatchLevel(vendorPatchLevel); - byte[] bootBlob = "00011122233344455566677788899900".getBytes(); + byte[] bootBlob = new byte[32]; short bootKey = KMByteBlob.instance(bootBlob, (short) 0, (short) bootBlob.length); short verifiedHash = KMByteBlob.instance(bootBlob, (short) 0, (short) bootBlob.length); - short bootState = KMType.VERIFIED_BOOT; + short bootState = KMType.UNVERIFIED_BOOT; ((KMJCardSimulator)seProvider).setBootPatchLevel( KMInteger.cast(bootPatchLevel).getBuffer(), @@ -166,7 +182,7 @@ private void setDummyBootParams(){ KMByteBlob.cast(verifiedHash).length()); ((KMJCardSimulator)seProvider).setBootState((byte)bootState); - ((KMJCardSimulator)seProvider).setDeviceLocked(false); + ((KMJCardSimulator)seProvider).setDeviceLocked(true); super.reboot(); } @@ -176,15 +192,95 @@ private void setDummyPresharedKey(){ } private void setDummyAttestationIds(){ - final byte[] dummy = {'D','U','M','M','Y'}; - ((KMJCardSimulator)seProvider).setAttestationId(KMType.ATTESTATION_ID_BRAND,dummy,(short)0,(short)dummy.length); - ((KMJCardSimulator)seProvider).setAttestationId(KMType.ATTESTATION_ID_IMEI,dummy,(short)0,(short)dummy.length); - ((KMJCardSimulator)seProvider).setAttestationId(KMType.ATTESTATION_ID_DEVICE,dummy,(short)0,(short)dummy.length); - ((KMJCardSimulator)seProvider).setAttestationId(KMType.ATTESTATION_ID_MEID,dummy,(short)0,(short)dummy.length); - ((KMJCardSimulator)seProvider).setAttestationId(KMType.ATTESTATION_ID_MODEL,dummy,(short)0,(short)dummy.length); - ((KMJCardSimulator)seProvider).setAttestationId(KMType.ATTESTATION_ID_MANUFACTURER,dummy,(short)0,(short)dummy.length); - ((KMJCardSimulator)seProvider).setAttestationId(KMType.ATTESTATION_ID_PRODUCT,dummy,(short)0,(short)dummy.length); - ((KMJCardSimulator)seProvider).setAttestationId(KMType.ATTESTATION_ID_SERIAL,dummy,(short)0,(short)dummy.length); + final byte[] brand = {'g','e','n','e','r','i','c'}; + final byte[] device = {'v','s','o','c','_','x','8','6','_','6','4'};//vsoc_x86_64 + final byte[] product = //aosp_cf_x86_64_phone + {'a','o','s','p','_','c','f','_','x','8','6','_','6','4','_','p','h','o','n','e'}; + final byte[] serial = {}; + final byte[] imei = {'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'}; + final byte[] meid = {'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'}; + final byte[] manufacturer = {'G','o','o','g','l', 'e'}; + final byte[] model = //"Cuttlefish x86_64 phone" + {'C','u','t','t','l', 'e','f','i','s','h',' ','x','8','6','_','6','4', + ' ','p','h','o','n','e'}; + ((KMJCardSimulator)seProvider).setAttestationId(KMType.ATTESTATION_ID_BRAND,brand,(short)0,(short)brand.length); + ((KMJCardSimulator)seProvider).setAttestationId(KMType.ATTESTATION_ID_IMEI,imei,(short)0,(short)imei.length); + ((KMJCardSimulator)seProvider).setAttestationId(KMType.ATTESTATION_ID_DEVICE,device,(short)0,(short)device.length); + ((KMJCardSimulator)seProvider).setAttestationId(KMType.ATTESTATION_ID_MEID,meid,(short)0,(short)meid.length); + ((KMJCardSimulator)seProvider).setAttestationId(KMType.ATTESTATION_ID_MODEL,model,(short)0,(short)model.length); + ((KMJCardSimulator)seProvider).setAttestationId(KMType.ATTESTATION_ID_MANUFACTURER,manufacturer,(short)0,(short)manufacturer.length); + ((KMJCardSimulator)seProvider).setAttestationId(KMType.ATTESTATION_ID_PRODUCT,product,(short)0,(short)product.length); + ((KMJCardSimulator)seProvider).setAttestationId(KMType.ATTESTATION_ID_SERIAL,serial,(short)0,(short)serial.length); + } + + private static void processProvisionDeviceUniqueKey(APDU apdu) { + // Re-purpose the apdu buffer as scratch pad. + byte[] scratchPad = apdu.getBuffer(); + short arr = KMArray.instance((short) 1); + short coseKeyExp = KMCoseKey.exp(); + KMArray.cast(arr).add((short) 0, coseKeyExp); //[ CoseKey ] + arr = receiveIncoming(apdu, arr); + // Get cose key. + short coseKey = KMArray.cast(arr).get((short) 0); + short pubKeyLen = KMCoseKey.cast(coseKey).getEcdsa256PublicKey(scratchPad, (short) 0); + short privKeyLen = KMCoseKey.cast(coseKey).getPrivateKey(scratchPad, pubKeyLen); + //Store the Device unique Key. + seProvider.createDeviceUniqueKey(false, scratchPad, (short) 0, pubKeyLen, scratchPad, + pubKeyLen, privKeyLen); + // Newly added code 30/07/2021 + short bcc = ((KMJCardSimulator) seProvider).generateBcc(false, scratchPad); + short len = KMKeymasterApplet.encodeToApduBuffer(bcc, scratchPad, (short) 0, + MAX_COSE_BUF_SIZE); + ((KMJCardSimulator) seProvider).persistBootCertificateChain(scratchPad, (short) 0, len); + sendError(apdu, KMError.OK); + } + + private static void processProvisionAdditionalCertChain(APDU apdu) { + // Prepare the expression to decode + short headers = KMCoseHeaders.exp(); + short arrInst = KMArray.instance((short) 4); + KMArray.cast(arrInst).add((short) 0, KMByteBlob.exp()); + KMArray.cast(arrInst).add((short) 1, headers); + KMArray.cast(arrInst).add((short) 2, KMByteBlob.exp()); + KMArray.cast(arrInst).add((short) 3, KMByteBlob.exp()); + short coseSignArr = KMArray.exp(arrInst); + short map = KMMap.instance((short) 1); + KMMap.cast(map).add((short) 0, KMTextString.exp(), coseSignArr); + // TODO duplicate code. + // receive incoming data and decode it. + byte[] srcBuffer = apdu.getBuffer(); + short recvLen = apdu.setIncomingAndReceive(); + short srcOffset = apdu.getOffsetCdata(); + short bufferLength = apdu.getIncomingLength(); + short bufferStartOffset = repository.allocReclaimableMemory(bufferLength); + short index = bufferStartOffset; + byte[] buffer = repository.getHeap(); + while (recvLen > 0 && ((short) (index - bufferStartOffset) < bufferLength)) { + Util.arrayCopyNonAtomic(srcBuffer, srcOffset, buffer, index, recvLen); + index += recvLen; + recvLen = apdu.receiveBytes(srcOffset); + } + // decode + map = decoder.decode(map, buffer, bufferStartOffset, bufferLength); + arrInst = KMMap.cast(map).getKeyValue((short) 0); + // Validate Additional certificate chain. + short leafCoseKey = + validateCertChain(false, KMCose.COSE_ALG_ES256, KMCose.COSE_ALG_ES256, arrInst, + srcBuffer, null); + // Compare the DK_Pub. + short pubKeyLen = KMCoseKey.cast(leafCoseKey).getEcdsa256PublicKey(srcBuffer, (short) 0); + KMDeviceUniqueKey uniqueKey = seProvider.getDeviceUniqueKey(false); + if (uniqueKey == null) + KMException.throwIt(KMError.STATUS_FAILED); + short uniqueKeyLen = uniqueKey.getPublicKey(srcBuffer, pubKeyLen); + if ((pubKeyLen != uniqueKeyLen) || + (0 != Util.arrayCompare(srcBuffer, (short) 0, srcBuffer, pubKeyLen, pubKeyLen))) { + KMException.throwIt(KMError.STATUS_FAILED); + } + seProvider.persistAdditionalCertChain(buffer, bufferStartOffset, bufferLength); + //reclaim memory + repository.reclaimMemory(bufferLength); + sendError(apdu, KMError.OK); } } diff --git a/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMJCardSimulator.java b/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMJCardSimulator.java index 27cded02..513beec7 100644 --- a/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMJCardSimulator.java +++ b/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMJCardSimulator.java @@ -40,6 +40,8 @@ import javacard.security.KeyBuilder; import javacard.security.KeyPair; import javacard.security.RSAPrivateKey; +import javacard.security.KeyAgreement; +import javacard.security.RSAPublicKey; import javacard.security.RandomData; import javacard.security.Signature; import javacardx.crypto.AEADCipher; @@ -68,6 +70,8 @@ public class KMJCardSimulator implements KMSEProvider { public static final short ENTROPY_POOL_SIZE = 16; // simulator does not support 256 bit aes keys public static final byte[] aesICV = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; private static final short CERT_CHAIN_MAX_SIZE = 2500;//First 2 bytes for length. + private static final short ADDITIONAL_CERT_CHAIN_MAX_SIZE = 1024;//First 2 bytes for length. + private static final short BCC_MAX_SIZE = 512; private static final short RSA_KEY_SIZE = 256; public static final byte POWER_RESET_FALSE = (byte)0xAA; public static final byte POWER_RESET_TRUE = (byte)0x00; @@ -76,6 +80,7 @@ public class KMJCardSimulator implements KMSEProvider { public static boolean jcardSim = false; private static Signature kdf; private static Signature hmacSignature; + private static KeyAgreement keyAgreement; private static byte[] rngCounter; private static AESKey aesRngKey; @@ -83,8 +88,12 @@ public class KMJCardSimulator implements KMSEProvider { private static byte[] entropyPool; private static byte[] rndNum; private byte[] certificateChain; + private byte[] additionalCertChain; + private byte[] bcc; private KMAESKey masterKey; private KMECPrivateKey attestationKey; + private KMECDeviceUniqueKey testKey; + private KMECDeviceUniqueKey deviceUniqueKey; private KMHmacKey preSharedKey; private boolean deviceReboot; @@ -104,6 +113,7 @@ public class KMJCardSimulator implements KMSEProvider { private byte[] bootPatchLevel; private boolean deviceBootLocked; private short bootState; + private boolean isProvisionLocked; private static KMJCardSimulator jCardSimulator = null; @@ -117,6 +127,7 @@ public KMJCardSimulator() { // Various Keys kdf = Signature.getInstance(Signature.ALG_AES_CMAC_128, false); hmacSignature = Signature.getInstance(Signature.ALG_HMAC_SHA_256, false); + keyAgreement = KeyAgreement.getInstance(KeyAgreement.ALG_EC_SVDP_DH_PLAIN, false); // RNG rndNum = JCSystem.makeTransientByteArray(MAX_RND_NUM_SIZE, JCSystem.CLEAR_ON_RESET); entropyPool = JCSystem.makeTransientByteArray(ENTROPY_POOL_SIZE, JCSystem.CLEAR_ON_RESET); @@ -131,6 +142,8 @@ public KMJCardSimulator() { // various ciphers //Allocate buffer for certificate chain. certificateChain = new byte[CERT_CHAIN_MAX_SIZE]; + additionalCertChain = new byte[ADDITIONAL_CERT_CHAIN_MAX_SIZE]; + bcc = new byte[BCC_MAX_SIZE]; jCardSimulator = this; resetFlag = new byte[1]; resetFlag[0] = (byte) POWER_RESET_FALSE; @@ -575,6 +588,61 @@ public short hmacKDF(KMMasterKey masterkey, byte[] data, short dataStart, signature, signatureStart); } + @Override + public short hkdf(byte[] ikm, short ikmOff, short ikmLen, byte[] salt, short saltOff, short saltLen, + byte[] info, short infoOff, short infoLen, byte[] out, short outOff, short outLen) { + // HMAC_extract + byte[] prk = new byte[32]; + hkdfExtract(ikm, ikmOff, ikmLen, salt, saltOff, saltLen, prk, (short) 0); + //HMAC_expand + return hkdfExpand(prk, (short) 0, (short) 32, info, infoOff, infoLen, out, outOff, outLen); + } + + private short hkdfExtract(byte[] ikm, short ikmOff, short ikmLen, byte[] salt, short saltOff, short saltLen, + byte[] out, short off) { + // https://tools.ietf.org/html/rfc5869#section-2.2 + HMACKey hmacKey = createHMACKey(salt, saltOff, saltLen); + hmacSignature.init(hmacKey, Signature.MODE_SIGN); + return hmacSignature.sign(ikm, ikmOff, ikmLen, out, off); + } + + private short hkdfExpand(byte[] prk, short prkOff, short prkLen, byte[] info, short infoOff, short infoLen, + byte[] out, short outOff, short outLen) { + // https://tools.ietf.org/html/rfc5869#section-2.3 + short digestLen = (short) 32; // SHA256 digest length. + // Calculate no of iterations N. + short n = (short) ((outLen + digestLen - 1) / digestLen); + if (n > 255) { + CryptoException.throwIt(CryptoException.ILLEGAL_VALUE); + } + HMACKey hmacKey = createHMACKey(prk, prkOff, prkLen); + byte[] previousOutput = new byte[32]; // Length of output 32. + byte[] cnt = {(byte) 0}; + short bytesCopied = 0; + short len = 0; + for (short i = 0; i < n; i++) { + cnt[0]++; + hmacSignature.init(hmacKey, Signature.MODE_SIGN); + if (i != 0) + hmacSignature.update(previousOutput, (short) 0, (short) 32); + hmacSignature.update(info, infoOff, infoLen); + len = hmacSignature.sign(cnt, (short) 0, (short) 1, previousOutput, (short) 0); + if ((short) (bytesCopied + len) > outLen) { + len = (short) (outLen - bytesCopied); + } + Util.arrayCopyNonAtomic(previousOutput, (short) 0, out, (short) (outOff + bytesCopied), len); + bytesCopied += len; + } + return outLen; + } + + @Override + public short ecdhKeyAgreement(byte[] privKey, short privKeyOff, short privKeyLen, byte[] publicKey, short publicKeyOff, + short publicKeyLen, byte[] secret, short secretOff) { + keyAgreement.init(createEcKey(privKey, privKeyOff, privKeyLen)); + return keyAgreement.generateSecret(publicKey, publicKeyOff, publicKeyLen, secret, secretOff); + } + @Override public short hmacSign(byte[] keyBuf, short keyStart, short keyLength, byte[] data, short dataStart, short dataLength, byte[] mac, short macStart) { @@ -595,7 +663,7 @@ public short rsaDecipherOAEP256(byte[] secret, short secretStart, short secretLe byte[] inputDataBuf, short inputDataStart, short inputDataLength, byte[] outputDataBuf, short outputDataStart) { KMCipher cipher = createRsaDecipher( - KMType.RSA_OAEP, KMType.SHA2_256, secret, secretStart, secretLength, modBuffer, modOff, + KMType.RSA_OAEP, KMType.SHA1, secret, secretStart, secretLength, modBuffer, modOff, modLength); return cipher.doFinal( inputDataBuf, inputDataStart, inputDataLength, outputDataBuf, outputDataStart); @@ -631,7 +699,7 @@ public KMOperation initSymmetricOperation(byte purpose, byte alg, byte digest, b @Override public KMOperation initAsymmetricOperation(byte purpose, byte alg, byte padding, byte digest, - byte[] privKeyBuf, short privKeyStart, short privKeyLength, + byte mgfDigest, byte[] privKeyBuf, short privKeyStart, short privKeyLength, byte[] pubModBuf, short pubModStart, short pubModLength) { if (alg == KMType.RSA) { switch (purpose) { @@ -650,7 +718,7 @@ public KMOperation initAsymmetricOperation(byte purpose, byte alg, byte padding, case KMType.DECRYPT: KMCipher decipher = createRsaDecipher( - padding, digest, privKeyBuf, privKeyStart, privKeyLength, pubModBuf, pubModStart, + padding, mgfDigest, privKeyBuf, privKeyStart, privKeyLength, pubModBuf, pubModStart, pubModLength); return new KMOperationImpl(decipher); default: @@ -662,6 +730,10 @@ public KMOperation initAsymmetricOperation(byte purpose, byte alg, byte padding, Signature signer = createEcSigner(digest, privKeyBuf, privKeyStart, privKeyLength); return new KMOperationImpl(signer); + case KMType.AGREE_KEY: + KeyAgreement keyAgreement = + createKeyAgreement(privKeyBuf, privKeyStart, privKeyLength); + return new KMOperationImpl(keyAgreement); default: KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); } @@ -690,7 +762,25 @@ public KMCipher createRsaDecipher(short padding, short digest, byte[] secret, sh return inst; } - private KMCipher createRsaOAEP256Cipher(byte mode, byte digest, + private MGF1ParameterSpec getMGF1ParamSpec(byte mgfDigest) { + switch (mgfDigest) { + case KMType.SHA1: + return MGF1ParameterSpec.SHA1; + case KMType.SHA2_256: + return MGF1ParameterSpec.SHA256; + case KMType.SHA2_224: + return MGF1ParameterSpec.SHA224; + case KMType.SHA2_384: + return MGF1ParameterSpec.SHA384; + case KMType.SHA2_512: + return MGF1ParameterSpec.SHA512; + default: + KMException.throwIt(KMError.UNSUPPORTED_DIGEST); + } + return null; + } + + private KMCipher createRsaOAEP256Cipher(byte mode, byte mgfDigest, byte[] secret, short secretStart, short secretLen, byte[] modBuffer, short modOff, short modLength) { // Convert byte arrays into keys @@ -711,14 +801,8 @@ private KMCipher createRsaOAEP256Cipher(byte mode, byte digest, try { KeyFactory kf = KeyFactory.getInstance("RSA"); // Create cipher with oaep padding - OAEPParameterSpec oaepSpec = null; - if (digest == KMType.SHA2_256) { - oaepSpec = new OAEPParameterSpec("SHA-256", "MGF1", - MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT); - } else { - oaepSpec = new OAEPParameterSpec("SHA1", "MGF1", - MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT); - } + OAEPParameterSpec oaepSpec = new OAEPParameterSpec("SHA-256", "MGF1", + getMGF1ParamSpec(mgfDigest), PSource.PSpecified.DEFAULT); rsaCipher = javax.crypto.Cipher.getInstance("RSA/ECB/OAEPPadding", "SunJCE"); if (mode == KMType.ENCRYPT) { RSAPublicKeySpec pubSpec = new RSAPublicKeySpec(modInt, expInt); @@ -797,6 +881,17 @@ private Signature createNoDigestSigner(short padding, return inst; } + public KeyAgreement createKeyAgreement(byte[] secret, short secretStart, + short secretLength) { + ECPrivateKey key = (ECPrivateKey) KeyBuilder + .buildKey(KeyBuilder.TYPE_EC_FP_PRIVATE, KeyBuilder.LENGTH_EC_FP_256, false); + key.setS(secret, secretStart, secretLength); + + KeyAgreement keyAgreement = KeyAgreement.getInstance(KeyAgreement.ALG_EC_SVDP_DH_PLAIN, + false); + keyAgreement.init(key); + return keyAgreement; + } public Signature createEcSigner(short digest, byte[] secret, short secretStart, short secretLength) { @@ -1204,13 +1299,118 @@ public short getAttestationKeyAlgorithm(){ return KMType.INVALID_VALUE; } - /** - * This function returns the cert chain length. - * - * @return length of the certificate chain. - */ - public short getCertificateChainLength() { - return Util.getShort(certificateChain, (short) 0); + @Override + public short getAdditionalCertChainLength() { + return Util.getShort(additionalCertChain, (short) 0); + } + + @Override + public byte[] getAdditionalCertChain() { + return additionalCertChain; + // short length = Util.getShort(additionalCertChain, (short) 0); + // Util.arrayCopyNonAtomic(additionalCertChain, (short) 2, buffer, start, length); + // return length; + } + + @Override + public short generateBcc(boolean testMode, byte[] scratchPad) { + if (!testMode && isProvisionLocked) { + KMException.throwIt(KMError.STATUS_FAILED); + } + KMDeviceUniqueKey deviceUniqueKey = getDeviceUniqueKey(testMode); + short temp = deviceUniqueKey.getPublicKey(scratchPad, (short) 0); + short coseKey = + KMCose.constructCoseKey( + KMInteger.uint_8(KMCose.COSE_KEY_TYPE_EC2), + KMType.INVALID_VALUE, + KMNInteger.uint_8(KMCose.COSE_ALG_ES256), + KMInteger.uint_8(KMCose.COSE_KEY_OP_VERIFY), + KMInteger.uint_8(KMCose.COSE_ECCURVE_256), + scratchPad, + (short) 0, + temp, + KMType.INVALID_VALUE, + false + ); + temp = KMKeymasterApplet.encodeToApduBuffer(coseKey, scratchPad, (short) 0, + KMKeymasterApplet.MAX_COSE_BUF_SIZE); + // Construct payload. + short payload = + KMCose.constructCoseCertPayload( + KMCosePairTextStringTag.instance(KMInteger.uint_8(KMCose.ISSUER), + KMTextString.instance(KMCose.TEST_ISSUER_NAME, (short) 0, + (short) KMCose.TEST_ISSUER_NAME.length)), + KMCosePairTextStringTag.instance(KMInteger.uint_8(KMCose.SUBJECT), + KMTextString.instance(KMCose.TEST_SUBJECT_NAME, (short) 0, + (short) KMCose.TEST_SUBJECT_NAME.length)), + KMCosePairByteBlobTag.instance(KMNInteger.uint_32(KMCose.SUBJECT_PUBLIC_KEY, (short) 0), + KMByteBlob.instance(scratchPad, (short) 0, temp)), + KMCosePairByteBlobTag.instance(KMNInteger.uint_32(KMCose.KEY_USAGE, (short) 0), + KMByteBlob.instance(KMCose.KEY_USAGE_SIGN, (short) 0, + (short) KMCose.KEY_USAGE_SIGN.length)) + ); + // temp temporarily holds the length of encoded cert payload. + temp = KMKeymasterApplet.encodeToApduBuffer(payload, scratchPad, (short) 0, + KMKeymasterApplet.MAX_COSE_BUF_SIZE); + payload = KMByteBlob.instance(scratchPad, (short) 0, temp); + + // protected header + short protectedHeader = + KMCose.constructHeaders(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 = + ecSign256( + deviceUniqueKey, + scratchPad, + (short) 0, + temp, + 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; + } + + public void persistBootCertificateChain(byte[] buf, short offset, short len) { + if ((short) (len + 2) > BCC_MAX_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + JCSystem.beginTransaction(); + Util.setShort(bcc, (short) 0, (short) len); + Util.arrayCopyNonAtomic(buf, offset, bcc, + (short) 2, len); + JCSystem.commitTransaction(); + } + + @Override + public byte[] getBootCertificateChain() { + return bcc; } @Override @@ -1254,12 +1454,53 @@ public short ecSign256(KMAttestationKey attestationKey, } - /** - * This operation clears the certificate chain from persistent memory. - */ - public void clearCertificateChain() { + @Override + public short ecSign256(KMDeviceUniqueKey deviceUniqueKey, byte[] inputDataBuf, short inputDataStart, + short inputDataLength, byte[] outputDataBuf, short outputDataStart) { + ECPrivateKey key = ((KMECDeviceUniqueKey) deviceUniqueKey).getPrivateKey(); + Signature signer = Signature + .getInstance(Signature.ALG_ECDSA_SHA_256, false); + signer.init(key, Signature.MODE_SIGN); + return signer.sign(inputDataBuf, inputDataStart, inputDataLength, + outputDataBuf, outputDataStart); + } + + @Override + public boolean ecVerify256(byte[] pubKey, short pubKeyOffset, short pubKeyLen, byte[] inputDataBuf, + short inputDataStart, short inputDataLength, byte[] signatureDataBuf, + short signatureDataStart, short signatureDataLen) { + + KeyPair ecKeyPair = new KeyPair(KeyPair.ALG_EC_FP, KeyBuilder.LENGTH_EC_FP_256); + ECPublicKey ecPublicKey = (ECPublicKey) ecKeyPair.getPublic(); + ecPublicKey.setW(pubKey, pubKeyOffset, pubKeyLen); + + Signature signer = Signature + .getInstance(Signature.ALG_ECDSA_SHA_256, false); + signer.init(ecPublicKey, Signature.MODE_VERIFY); + return signer.verify(inputDataBuf, inputDataStart, inputDataLength, + signatureDataBuf, signatureDataStart, signatureDataLen); + } + + + @Override + public void persistAdditionalCertChain(byte[] buf, short offset, short len) { + // Input buffer contains encoded additional certificate chain as shown below. + // AdditionalDKSignatures = { + // + SignerName => DKCertChain + // } + // SignerName = tstr + // DKCertChain = [ + // 2* Certificate // Root -> Leaf. Root is the vendo r + // // self-signed cert, leaf contains DK_pu b + // ] + // Certificate = COSE_Sign1 of a public key + if (len > ADDITIONAL_CERT_CHAIN_MAX_SIZE) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } JCSystem.beginTransaction(); - Util.arrayFillNonAtomic(certificateChain, (short) 0, CERT_CHAIN_MAX_SIZE, (byte) 0); + Util.setShort(additionalCertChain, (short) 0, (short) len); + Util.arrayCopyNonAtomic(buf, offset, additionalCertChain, + (short) 2, len); JCSystem.commitTransaction(); } @@ -1355,6 +1596,40 @@ KMAttestationKey createAttestationKey(byte[] keyData, short offset, short length return (KMAttestationKey) attestationKey; } + private KMDeviceUniqueKey createDeviceUniqueKey(KMECDeviceUniqueKey key, + byte[] pubKey, short pubKeyOff, short pubKeyLen, byte[] privKey, + short privKeyOff, short privKeyLen) { + if (key == null) { + KeyPair ecKeyPair = new KeyPair(KeyPair.ALG_EC_FP, KeyBuilder.LENGTH_EC_FP_256); + key = new KMECDeviceUniqueKey(ecKeyPair); + } + key.setS(privKey, privKeyOff, privKeyLen); + key.setW(pubKey, pubKeyOff, pubKeyLen); + return (KMDeviceUniqueKey) key; + } + + @Override + public KMDeviceUniqueKey createDeviceUniqueKey(boolean testMode, + byte[] pubKey, short pubKeyOff, short pubKeyLen, byte[] privKey, + short privKeyOff, short privKeyLen) { + KMDeviceUniqueKey key; + if (testMode) { + key = createDeviceUniqueKey(testKey, pubKey, pubKeyOff, + pubKeyLen, privKey, privKeyOff, privKeyLen); + if (testKey == null) testKey = (KMECDeviceUniqueKey) key; + } else { + key = createDeviceUniqueKey(deviceUniqueKey, pubKey, pubKeyOff, + pubKeyLen, privKey, privKeyOff, privKeyLen); + if (deviceUniqueKey == null) deviceUniqueKey = (KMECDeviceUniqueKey) key; + } + return key; + } + + @Override + public KMDeviceUniqueKey getDeviceUniqueKey(boolean testMode) { + return ((KMDeviceUniqueKey) (testMode ? testKey : deviceUniqueKey)); + } + /** * This function creates an HMACKey and initializes the key with the provided input key data. This * created key is maintained by the SEProvider. This function should be called only while @@ -1570,4 +1845,12 @@ public void setBootPatchLevel(byte[] buffer, short start, short length) { Util.arrayCopyNonAtomic(buffer, start, bootPatchLevel, (short)0, (short) 4); } + public void setProvisionLocked(boolean locked) { + isProvisionLocked = locked; + } + + public boolean isProvisionLocked() { + return isProvisionLocked; + } + } diff --git a/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMOperationImpl.java b/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMOperationImpl.java index 761b388a..a66fc6ab 100644 --- a/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMOperationImpl.java +++ b/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMOperationImpl.java @@ -15,21 +15,31 @@ */ package com.android.javacard.keymaster; +import javacard.security.KeyAgreement; import javacard.security.Signature; public class KMOperationImpl implements KMOperation { private KMCipher cipher; private Signature signature; + private KeyAgreement keyAgreement; public KMOperationImpl(KMCipher cipher) { this.cipher = cipher; this.signature = null; + this.keyAgreement = null; } public KMOperationImpl(Signature sign) { this.cipher = null; this.signature = sign; + this.keyAgreement = null; + } + + public KMOperationImpl(KeyAgreement keyAgreement) { + this.cipher = null; + this.signature = null; + this.keyAgreement = keyAgreement; } @Override @@ -48,8 +58,16 @@ public short update(byte[] inputDataBuf, short inputDataStart, short inputDataLe @Override public short finish(byte[] inputDataBuf, short inputDataStart, short inputDataLength, byte[] outputDataBuf, short outputDataStart) { - return cipher - .doFinal(inputDataBuf, inputDataStart, inputDataLength, outputDataBuf, outputDataStart); + if (cipher != null) { + return cipher + .doFinal(inputDataBuf, inputDataStart, inputDataLength, outputDataBuf, outputDataStart); + } else if (keyAgreement != null) { + return keyAgreement.generateSecret(inputDataBuf, inputDataStart, inputDataLength, + outputDataBuf, outputDataStart); + } else { + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + return 0; } @Override diff --git a/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMUtils.java b/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMUtils.java index c2b5c7f3..5b178143 100644 --- a/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMUtils.java +++ b/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMUtils.java @@ -85,14 +85,14 @@ public static short convertToDate(short time, byte[] scratchPad, (short) 8) < 0) { Util.arrayCopyNonAtomic(firstJan2020, (short) 0, scratchPad, (short) 8, (short) 8); - subtract(scratchPad, (short) 0, (short) 8, (short) 16); + subtract(scratchPad, (short) 0, (short) 8, (short) 16, (byte) 8); Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); } else { from2020 = false; Util.arrayCopyNonAtomic(firstJan2051, (short) 0, scratchPad, (short) 8, (short) 8); - subtract(scratchPad, (short) 0, (short) 8, (short) 16); + subtract(scratchPad, (short) 0, (short) 8, (short) 16, (byte) 8); Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); } @@ -133,7 +133,7 @@ public static short convertToDate(short time, byte[] scratchPad, Util.arrayCopyNonAtomic(yearMsec, (short) 0, scratchPad, (short) 8, (short) 8); } - subtract(scratchPad, (short) 0, (short) 8, (short) 16); + 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)) { @@ -183,7 +183,7 @@ public static short convertToDate(short time, byte[] scratchPad, if (KMInteger.unsignedByteArrayCompare(scratchPad, (short) 0, scratchPad, (short) 8, (short) 8) >= 0) { - subtract(scratchPad, (short) 0, (short) 8, (short) 16); + subtract(scratchPad, (short) 0, (short) 8, (short) 16, (byte) 8); Util.arrayCopyNonAtomic(scratchPad, (short) 16, scratchPad, (short) 0, (short) 8); } else { @@ -285,7 +285,7 @@ public static short divide(byte[] buf, short dividend, short divisor, // Copy remainder in the dividend and repeat. while (expCnt != 0) { if (compare(buf, dividend, divisor) >= 0) { - subtract(buf, dividend, divisor, remainder); + subtract(buf, dividend, divisor, remainder, (byte) 8); copy(buf, remainder, dividend); q = (short) (q + expCnt); } @@ -354,9 +354,9 @@ public static void add(byte[] buf, short op1, short op2, short result) { } // subtraction by borrowing. - public static void subtract(byte[] buf, short op1, short op2, short result) { + public static void subtract(byte[] buf, short op1, short op2, short result, byte sizeBytes) { byte borrow = 0; - byte index = 7; + byte index = (byte) (sizeBytes - 1); short r; short x; short y; @@ -409,4 +409,12 @@ public static short getLeapYrIndex(boolean from2020, short yrsCount) { return -1; } -} \ No newline at end of file + 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++; + } + } +} diff --git a/Applet/src/com/android/javacard/keymaster/KMArray.java b/Applet/src/com/android/javacard/keymaster/KMArray.java index c021f69f..c1540fa7 100644 --- a/Applet/src/com/android/javacard/keymaster/KMArray.java +++ b/Applet/src/com/android/javacard/keymaster/KMArray.java @@ -100,6 +100,29 @@ public short get(short index) { } 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() { diff --git a/Applet/src/com/android/javacard/keymaster/KMAttestationCert.java b/Applet/src/com/android/javacard/keymaster/KMAttestationCert.java index 2f951926..f2bab41a 100644 --- a/Applet/src/com/android/javacard/keymaster/KMAttestationCert.java +++ b/Applet/src/com/android/javacard/keymaster/KMAttestationCert.java @@ -157,20 +157,6 @@ KMAttestationCert makeUniqueId(byte[] scratchpad, short scratchPadOff, byte[] cr */ short getCertLength(); - /** - * Build the certificate. After this method the certificate is ready and signed by the - * attestation key passed in the parameter. If attBuf is null then factory set attestation key is - * used to sign the cert. If there is no factory provisioned attestation key then signature is - * left zero. - * - * @param attBuf the buffer that has attestation key - * @param start the start offset of the attestation key in the buffer - * @param length the length of the attestation key in the buffer - * @param rsa the flag which states that the attestation key is rsa, if true or ec key otherwise. - * Accordingly the certificate is signed with using rsa signature algorithm or the ecdsa - * signature algorithm. - */ - void build(byte[] attBuf, short start, short length, boolean rsa, boolean fakeCert); /** * Build a fake signed certificate. After this method executes the certificate is ready with the @@ -197,5 +183,12 @@ KMAttestationCert makeUniqueId(byte[] scratchpad, short scratchPadOff, byte[] cr * @param attestKey KMByteBlob of the key * @param mode */ - KMAttestationCert attestKey(short attestKey, boolean rsaSign, byte mode); -} \ No newline at end of file + 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/Applet/src/com/android/javacard/keymaster/KMByteBlob.java b/Applet/src/com/android/javacard/keymaster/KMByteBlob.java index e82dcac3..0d2627d9 100644 --- a/Applet/src/com/android/javacard/keymaster/KMByteBlob.java +++ b/Applet/src/com/android/javacard/keymaster/KMByteBlob.java @@ -29,7 +29,7 @@ public class KMByteBlob extends KMType { private static short OFFSET_SIZE = 2; private static KMByteBlob prototype; - private KMByteBlob() { + protected KMByteBlob() { } private static KMByteBlob proto(short ptr) { @@ -91,12 +91,12 @@ public byte get(short index) { // Get the start of blob public short getStartOff() { - return Util.getShort(heap, (short)(KMType.instanceTable[KM_BYTE_BLOB_OFFSET] + TLV_HEADER_SIZE)); + return Util.getShort(heap, (short)(getBaseOffset() + TLV_HEADER_SIZE)); } // Get the length of the blob public short length() { - return Util.getShort(heap, (short) (KMType.instanceTable[KM_BYTE_BLOB_OFFSET] + 1)); + return Util.getShort(heap, (short) (getBaseOffset() + 1)); } // Get the buffer pointer in which blob is contained. @@ -123,17 +123,18 @@ public void setValue(byte[] srcBuf, short srcStart, short srcLength) { } public boolean isValid() { - if (length() == 0) { - return false; - } - return true; + return (length() != 0); } public void setStartOff(short offset){ - Util.setShort(heap, (short)(KMType.instanceTable[KM_BYTE_BLOB_OFFSET]+TLV_HEADER_SIZE), offset); + Util.setShort(heap, (short)(getBaseOffset() + TLV_HEADER_SIZE), offset); + } + + protected short getBaseOffset() { + return instanceTable[KM_BYTE_BLOB_OFFSET]; } public void setLength(short len){ - Util.setShort(heap, (short)(KMType.instanceTable[KM_BYTE_BLOB_OFFSET]+1), len); + Util.setShort(heap, (short)(getBaseOffset() + 1), len); } } diff --git a/Applet/src/com/android/javacard/keymaster/KMCose.java b/Applet/src/com/android/javacard/keymaster/KMCose.java new file mode 100644 index 00000000..6720d8df --- /dev/null +++ b/Applet/src/com/android/javacard/keymaster/KMCose.java @@ -0,0 +1,549 @@ +/* + * 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 short COSE_SIGN1_UNPROTECTED_PARAMS_OFFSET = 1; + public static final short COSE_SIGN1_PAYLOAD_OFFSET = 2; + public static final short COSE_SIGN1_SIGNATURE_OFFSET = 3; + //COSE MAC0 + public static final short COSE_MAC0_ENTRY_COUNT = 4; + public static final short COSE_MAC0_PROTECTED_PARAMS_OFFSET = 0; + public static final short COSE_MAC0_UNPROTECTED_PARAMS_OFFSET = 1; + public static final short COSE_MAC0_PAYLOAD_OFFSET = 2; + public static final short COSE_MAC0_TAG_OFFSET = 3; + //COSE ENCRYPT + public static final short COSE_ENCRYPT_ENTRY_COUNT = 4; + public static final short COSE_ENCRYPT_STRUCTURE_ENTRY_COUNT = 3; + public static final short COSE_ENCRYPT_RECIPIENT_ENTRY_COUNT = 3; + public static final short COSE_ENCRYPT_PROTECTED_PARAMS_OFFSET = 0; + public static final short COSE_ENCRYPT_UNPROTECTED_PARAMS_OFFSET = 1; + public static final short COSE_ENCRYPT_PAYLOAD_OFFSET = 2; + public static final short COSE_ENCRYPT_RECIPIENTS_OFFSET = 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_NONCE_LENGTH = 12; + public static final short AES_GCM_TAG_SIZE = 16; + public static final short AES_GCM_KEY_SIZE = 32; + 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_KEY_OPS = 4; + 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 short COSE_KEY_MAX_SIZE = 4; + + // kdfcontext strings + public static final byte[] client = {0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74}; + public static final byte[] server = {0x73, 0x65, 0x72, 0x76, 0x65, 0x72}; + //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 + //Empty strings + public static final byte[] EMPTY_MAC_KEY = + {0x45, 0x6d, 0x70, 0x74, 0x79, 0x20, 0x4d, 0x41, 0x43, 0x20, 0x6b, 0x65, 0x79}; // "Empty MAC key" + + // Certificate payload supported keys + public static final byte ISSUER = (byte) 0x01; + public static final byte SUBJECT = (byte) 0x02; + public static final byte[] CODE_HASH = {(byte) 0xFF, (byte) 0xB8, (byte) 0xBB, (byte) 0xAF}; + public static final byte[] CODE_DESCRIPTOR = {(byte) 0xFF, (byte) 0xB8, (byte) 0xBB, (byte) 0xAE}; + public static final byte[] CONFIG_HASH = {(byte) 0xFF, (byte) 0xB8, (byte) 0xBB, (byte) 0xAD}; + public static final byte[] CONFIG_DESCRIPTOR = {(byte) 0xFF, (byte) 0xB8, (byte) 0xBB, (byte) 0xAC}; + public static final byte[] AUTHORITY_HASH = {(byte) 0xFF, (byte) 0xB8, (byte) 0xBB, (byte) 0xAB}; + public static final byte[] AUTHORITY_DESCRIPTOR = {(byte) 0xFF, (byte) 0xB8, (byte) 0xBB, (byte) 0xAA}; + public static final byte[] MODE = {(byte) 0xFF, (byte) 0xB8, (byte) 0xBB, (byte) 0xA9}; + public static final byte[] SUBJECT_PUBLIC_KEY = {(byte) 0xFF, (byte) 0xB8, (byte) 0xBB, (byte) 0xA8}; + public static final byte[] KEY_USAGE = {(byte) 0xFF, (byte) 0xB8, (byte) 0xBB, (byte) 0xA7}; + // text strings + public static final byte[] TEST_ISSUER_NAME = {(byte) 0x49, 0x73, 0x73, 0x75, 0x65, 0x72}; // "Issuer" + public static final byte[] TEST_SUBJECT_NAME = {0x53, 0x75, 0x62, 0x6A, 0x65, 0x63, 0x74}; // "Subject" + public static final byte[] KEY_USAGE_SIGN = {0x20}; // Key usage sign + public static final byte[] MAC_DERIVE_KEY_CTX = + {0x4B, 0x65, 0x79, 0x20, 0x74, 0x6F, 0x20, 0x4D, 0x41, 0x43, 0x20, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, + 0x20, 0x6B, 0x65, 0x79, 0x73}; // "Key to MAC public keys" + + /** + * 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 tags 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[] tags, boolean includeTestMode) { + short index = 0; + // var is used to calculate the length of the array. + short var = 0; + while (index < tags.length) { + if (tags[(short) (index + 1)] != KMType.INVALID_VALUE) { + tags[(short) (index + 2)] = + buildCosePairTag((byte) tags[index], tags[(short) (index + 1)]); + var++; + } + index += 3; + } + var += includeTestMode ? 1 : 0; + short arrPtr = KMArray.instance(var); + index = 0; + // var is used to index the array. + var = 0; + while (index < tags.length) { + if (tags[(short) (index + 2)] != KMType.INVALID_VALUE) { + KMArray.cast(arrPtr).add(var++, tags[(short) (index + 2)]); + } + index += 3; + } + 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 alg, short keyId, short iv, short ephemeralKey) { + short[] coseHeaderTags = { + KMCose.COSE_LABEL_ALGORITHM, alg, KMType.INVALID_VALUE, + KMCose.COSE_LABEL_KEYID, keyId, KMType.INVALID_VALUE, + KMCose.COSE_LABEL_IV, iv, KMType.INVALID_VALUE, + KMCose.COSE_LABEL_COSE_KEY, ephemeralKey, KMType.INVALID_VALUE + }; + short ptr = handleCosePairTags(coseHeaderTags, false); + ptr = KMCoseHeaders.instance(ptr); + KMCoseHeaders.cast(ptr).canonicalize(); + return ptr; + } + + /** + * Construct Recipients structure for COSE_Encrypt message. + * + * @param protectedHeaders instance of KMByteBlob which contains encoded KMCoseHeaders. + * @param unprotectedHeaders instance of KMCoseHeaders. + * @param cipherText instance of KMSimple + * @return instance of KMArray. + */ + public static short constructRecipientsStructure(short protectedHeaders, short unprotectedHeaders, + short cipherText) { + // recipients : [+COSE_recipient] + // COSE_recipient = [ + // Headers, + // ciphertext : bstr / nil, + // ? recipients : [+COSE_recipient] + // ] + short arrPtr = KMArray.instance(COSE_ENCRYPT_RECIPIENT_ENTRY_COUNT); + // 1 - protected headers + KMArray.cast(arrPtr).add((short) 0, protectedHeaders); + // 2 - unprotected headers + KMArray.cast(arrPtr).add((short) 1, unprotectedHeaders); + // 2 - payload + KMArray.cast(arrPtr).add((short) 2, cipherText); + + short recipientsArrayPtr = KMArray.instance((short) 1); + KMArray.cast(recipientsArrayPtr).add((short) 0, arrPtr); + return recipientsArrayPtr; + } + + /** + * Construct Encrypt structure required for COSE_Encrypt message. + * + * @param protectedHeader instance of KMByteBlob which wraps KMCoseHeaders. + * @param aad instance of KMByteBlob. + * @return instance of KMArray. + */ + public static short constructCoseEncryptStructure(short protectedHeader, short aad) { + // Enc_structure = [ + // context : "Encrypt" / "Encrypt0" / "Enc_Recipient" / + // "Mac_Recipient" / "Rec_Recipient", + // protected : empty_or_serialized_map, + // external_aad : bstr + // ] + short arrPtr = KMArray.instance(COSE_ENCRYPT_STRUCTURE_ENTRY_COUNT); + // 1 - protected headers + KMArray.cast(arrPtr).add((short) 0, KMTextString.instance(KMCose.ENCRYPT_CONTEXT, (short) 0, + (short) KMCose.ENCRYPT_CONTEXT.length)); + // 2 - unprotected headers + KMArray.cast(arrPtr).add((short) 1, protectedHeader); + // 2 - payload + KMArray.cast(arrPtr).add((short) 2, aad); + return arrPtr; + } + + /** + * Constructs COSE_Encrypt message. + * + * @param protectedHeader instance of KMByteBlob which wraps KMCoseHeaders. + * @param unProtectedHeader instance of KMCoseHeaders. + * @param cipherText instance of KMByteBlob containing the cipher text. + * @param recipients instance of KMArray containing the recipients instance + * @return instance of KMArray. + */ + public static short constructCoseEncrypt(short protectedHeader, short unProtectedHeader, short cipherText, + short recipients) { + // COSE_Encrypt = [ + // protectedHeader, + // unprotectedHeader, + // ciphertext : bstr / nil, + // recipients : [+COSE_recipient] + // ] + short arrPtr = KMArray.instance(KMCose.COSE_ENCRYPT_ENTRY_COUNT); + // 1 - protected headers + KMArray.cast(arrPtr).add((short) 0, protectedHeader); + // 2 - unprotected headers + KMArray.cast(arrPtr).add((short) 1, unProtectedHeader); + // 2 - payload + KMArray.cast(arrPtr).add((short) 2, cipherText); + // 3 - tag + KMArray.cast(arrPtr).add((short) 3, recipients); + return arrPtr; + } + + /** + * Constructs the instance of KMCosePair*Tag. + * + * @param key value of the key. + * @param valuePtr instance of one of KMType. + * @return instance of KMCosePair*Value object. + */ + public static short buildCosePairTag(byte key, short valuePtr) { + short type = KMType.getType(valuePtr); + short keyPtr; + if (key < 0) { + keyPtr = KMNInteger.uint_8(key); + } else { + keyPtr = KMInteger.uint_8(key); + } + switch (type) { + case KMType.INTEGER_TYPE: + return KMCosePairIntegerTag.instance(keyPtr, valuePtr); + case KMType.NEG_INTEGER_TYPE: + return KMCosePairNegIntegerTag.instance(keyPtr, valuePtr); + case KMType.BYTE_BLOB_TYPE: + return KMCosePairByteBlobTag.instance(keyPtr, valuePtr); + case KMType.TEXT_STRING_TYPE: + return KMCosePairTextStringTag.instance(keyPtr, valuePtr); + case KMType.COSE_KEY_TYPE: + return KMCosePairCoseKeyTag.instance(keyPtr, valuePtr); + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + return 0; + } + } + + /** + * Constructs a CoseKey with the provided input paramters. + * + * @param keyType Instance of the identification of the key type. + * @param keyId Instance of key identification value. + * @param keyAlg Instance of the algorithm that is used with this key. + * @param keyOps Instance of the operation that this key is used for. + * @param curve Instance of the EC curve that is used with this key. + * @param pubKey Buffer containing the public key. + * @param pubKeyOff Start offset of the buffer. + * @param pubKeyLen Length of the public key. + * @param privKeyPtr Instance of the private key. + * @param testMode Represents if key is used in test mode or production mode. + * @return Instance of the CoseKey structure. + */ + public static short constructCoseKey(short keyType, short keyId, short keyAlg, short keyOps, + short curve, byte[] pubKey, short pubKeyOff, short pubKeyLen, + short privKeyPtr, boolean testMode) { + if (pubKey[pubKeyOff] == 0x04) { // uncompressed format + pubKeyOff += 1; + pubKeyLen -= 1; + } + pubKeyLen = (short) (pubKeyLen / 2); + short xPtr = KMByteBlob.instance(pubKey, pubKeyOff, pubKeyLen); + short yPtr = KMByteBlob.instance(pubKey, (short) (pubKeyOff + pubKeyLen), pubKeyLen); + short coseKey = constructCoseKey(keyType, keyId, keyAlg, keyOps, curve, xPtr, yPtr, privKeyPtr, testMode); + KMCoseKey.cast(coseKey).canonicalize(); + return coseKey; + } + + /** + * Constructs the cose key based on input parameters supplied. All the parameters must be instantiated from + * either KMInteger or KMNInteger or KMByteblob types. + * + * @param keyType instance of KMInteger/KMNInteger which holds valid COSE key types. + * @param keyId instance of KMByteBlob which holds key identifier value. + * @param keyAlg instance of KMInteger/KMNInteger which holds valid COSE key algorithm. + * @param keyOps instance of KMInteger/KMNInteger which holds valid COSE key operations. + * @param curve instance of KMInteger/KMNInteger which holds valid COSE EC curve. + * @param pubX instance of KMByteBlob which holds EC public key's x value. + * @param pubY instance of KMByteBlob which holds EC public key's y value. + * @param priv instance of KMByteBlob which holds EC private value. + * @param includeTestKey flag which identifies whether to construct test key or production key. + * @return instance of the KMCoseKey object. + */ + public static short constructCoseKey(short keyType, short keyId, short keyAlg, short keyOps, short curve, + short pubX, short pubY, short priv, boolean includeTestKey) { + short[] coseKeyTags = { + KMCose.COSE_KEY_KEY_TYPE, keyType, KMType.INVALID_VALUE, + KMCose.COSE_KEY_KEY_ID, keyId, KMType.INVALID_VALUE, + KMCose.COSE_KEY_ALGORITHM, keyAlg, KMType.INVALID_VALUE, + KMCose.COSE_KEY_KEY_OPS, keyOps, KMType.INVALID_VALUE, + KMCose.COSE_KEY_CURVE, curve, KMType.INVALID_VALUE, + KMCose.COSE_KEY_PUBKEY_X, pubX, KMType.INVALID_VALUE, + KMCose.COSE_KEY_PUBKEY_Y, pubY, KMType.INVALID_VALUE, + KMCose.COSE_KEY_PRIV_KEY, priv, KMType.INVALID_VALUE + }; + short arrPtr = handleCosePairTags(coseKeyTags, 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; + } +} \ No newline at end of file diff --git a/Applet/src/com/android/javacard/keymaster/KMCoseCertPayload.java b/Applet/src/com/android/javacard/keymaster/KMCoseCertPayload.java new file mode 100644 index 00000000..76535416 --- /dev/null +++ b/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/Applet/src/com/android/javacard/keymaster/KMCoseHeaders.java b/Applet/src/com/android/javacard/keymaster/KMCoseHeaders.java new file mode 100644 index 00000000..0b82d6cb --- /dev/null +++ b/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 alg, short keyIdPtr) { + short[] headerTags = { + KMCose.COSE_LABEL_ALGORITHM, alg, + KMCose.COSE_LABEL_KEYID, keyIdPtr, + }; + boolean valid = false; + short value; + short ptr; + short tagIndex = 0; + while (tagIndex < headerTags.length) { + value = headerTags[(short) (tagIndex + 1)]; + if (value != KMType.INVALID_VALUE) { + valid = false; + ptr = getValueType(headerTags[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/Applet/src/com/android/javacard/keymaster/KMCoseKey.java b/Applet/src/com/android/javacard/keymaster/KMCoseKey.java new file mode 100644 index 00000000..eafa3006 --- /dev/null +++ b/Applet/src/com/android/javacard/keymaster/KMCoseKey.java @@ -0,0 +1,235 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * KMCoseKey represents COSE_Key section from the Cose standard https://datatracker.ietf.org/doc/html/rfc8152#section-7 + * The supported key types are KMNInteger, KMInteger and the supported value types are KMInteger, KMNInteger, + * KMByteBlob, KMSimpleValue. It corresponds to a CBOR Map type. struct{byte TAG_TYPE; short length; short arrayPtr } + * where arrayPtr is a pointer to array with any KMTag subtype instances. + */ +public class KMCoseKey extends KMCoseMap { + + private static KMCoseKey prototype; + + private KMCoseKey() { + } + + private static KMCoseKey proto(short ptr) { + if (prototype == null) { + prototype = new KMCoseKey(); + } + instanceTable[KM_COSE_KEY_OFFSET] = ptr; + return prototype; + } + + public static short exp() { + short arrPtr = KMArray.instance((short) 4); + KMArray arr = KMArray.cast(arrPtr); + arr.add((short) 0, KMCosePairIntegerTag.exp()); + arr.add((short) 1, KMCosePairNegIntegerTag.exp()); + arr.add((short) 2, KMCosePairByteBlobTag.exp()); + arr.add((short) 3, KMCosePairSimpleValueTag.exp()); + return KMCoseKey.instance(arrPtr); + } + + + public static short instance(short vals) { + short ptr = KMType.instance(COSE_KEY_TYPE, (short) 2); + Util.setShort(heap, (short) (ptr + TLV_HEADER_SIZE), vals); + return ptr; + } + + public static KMCoseKey cast(short ptr) { + if (heap[ptr] != COSE_KEY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + short arrPtr = Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + if (heap[arrPtr] != ARRAY_TYPE) { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return proto(ptr); + } + + @Override + public short getVals() { + return Util.getShort(heap, (short) (instanceTable[KM_COSE_KEY_OFFSET] + TLV_HEADER_SIZE)); + } + + @Override + public short length() { + short arrPtr = getVals(); + return KMArray.cast(arrPtr).length(); + } + + private short getValueType(short key, short significantKey) { + short arr = getVals(); + short length = length(); + short keyPtr; + short valPtr = 0; + short index = 0; + short tagType; + boolean found = false; + while (index < length) { + tagType = KMCosePairTagType.getTagValueType(KMArray.cast(arr).get(index)); + switch (tagType) { + case KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE: + keyPtr = KMCosePairByteBlobTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairByteBlobTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + case KMType.COSE_PAIR_INT_TAG_TYPE: + keyPtr = KMCosePairIntegerTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairIntegerTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + case KMType.COSE_PAIR_NEG_INT_TAG_TYPE: + keyPtr = KMCosePairNegIntegerTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == (byte) KMCosePairTagType.getKeyValueShort(keyPtr)) { + valPtr = KMCosePairNegIntegerTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + case KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE: + keyPtr = KMCosePairSimpleValueTag.cast(KMArray.cast(arr).get(index)).getKeyPtr(); + if (key == KMCosePairTagType.getKeyValueShort(keyPtr) && + significantKey == KMCosePairTagType.getKeyValueSignificantShort(keyPtr)) { + valPtr = KMCosePairSimpleValueTag.cast(KMArray.cast(arr).get(index)).getValuePtr(); + found = true; + } + break; + default: + break; + + } + if (found) + break; + index++; + } + return valPtr; + } + + public short getKeyIdentifier() { + return getValueType(KMCose.COSE_KEY_KEY_ID, KMType.INVALID_VALUE); + } + + public short getEcdsa256PublicKey(byte[] pubKey, short pubKeyOff) { + short baseOffset = pubKeyOff; + pubKey[pubKeyOff] = (byte) 0x04; // uncompressed. + pubKeyOff++; + short ptr = getValueType(KMCose.COSE_KEY_PUBKEY_X, KMType.INVALID_VALUE); + Util.arrayCopy(KMByteBlob.cast(ptr).getBuffer(), KMByteBlob.cast(ptr).getStartOff(), + pubKey, pubKeyOff, KMByteBlob.cast(ptr).length()); + pubKeyOff += KMByteBlob.cast(ptr).length(); + ptr = getValueType(KMCose.COSE_KEY_PUBKEY_Y, KMType.INVALID_VALUE); + Util.arrayCopy(KMByteBlob.cast(ptr).getBuffer(), KMByteBlob.cast(ptr).getStartOff(), + pubKey, pubKeyOff, KMByteBlob.cast(ptr).length()); + pubKeyOff += KMByteBlob.cast(ptr).length(); + return (short) (pubKeyOff - baseOffset); + } + + public short getPrivateKey(byte[] priv, short privOff) { + short ptr = getValueType(KMCose.COSE_KEY_PRIV_KEY, KMType.INVALID_VALUE); + Util.arrayCopy(KMByteBlob.cast(ptr).getBuffer(), KMByteBlob.cast(ptr).getStartOff(), + priv, privOff, KMByteBlob.cast(ptr).length()); + return KMByteBlob.cast(ptr).length(); + } + + public boolean isTestKey() { + short ptr = + getValueType( + Util.getShort(KMCose.COSE_TEST_KEY, (short) 2), // LSB + Util.getShort(KMCose.COSE_TEST_KEY, (short) 0) // MSB (Significant) + ); + boolean isTestKey = false; + if (ptr != 0) + isTestKey = (KMSimpleValue.cast(ptr).getValue() == KMSimpleValue.NULL); + return isTestKey; + } + + /** + * Verifies the KMCoseKey values against the input values. + * + * @param keyType value of the key type + * @param keyIdPtr instance of KMByteBlob containing the key id. + * @param keyAlg value of the algorithm. + * @param keyOps value of the key operations. + * @param curve value of the curve. + * @return true if valid, otherwise false. + */ + public boolean isDataValid(short keyType, short keyIdPtr, short keyAlg, short keyOps, short curve) { + short[] coseKeyTags = { + KMCose.COSE_KEY_KEY_TYPE, keyType, + KMCose.COSE_KEY_KEY_ID, keyIdPtr, + KMCose.COSE_KEY_ALGORITHM, keyAlg, + KMCose.COSE_KEY_KEY_OPS, keyOps, + KMCose.COSE_KEY_CURVE, curve, + }; + boolean valid = false; + short ptr; + short tagIndex = 0; + short value; + while (tagIndex < coseKeyTags.length) { + value = coseKeyTags[(short) (tagIndex + 1)]; + if (value != KMType.INVALID_VALUE) { + valid = false; + ptr = getValueType(coseKeyTags[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/Applet/src/com/android/javacard/keymaster/KMCoseMap.java b/Applet/src/com/android/javacard/keymaster/KMCoseMap.java new file mode 100644 index 00000000..ceb67fb2 --- /dev/null +++ b/Applet/src/com/android/javacard/keymaster/KMCoseMap.java @@ -0,0 +1,165 @@ +/* + * 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; + } + } + + abstract public short getVals(); + + abstract public short length(); + + abstract public void canonicalize(); + + 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); + secondKeyLen = KMKeymasterApplet.encoder.encode(secondKey, scratchpad, firstKeyLen); + 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++; + } + } +} diff --git a/Applet/src/com/android/javacard/keymaster/KMCosePairByteBlobTag.java b/Applet/src/com/android/javacard/keymaster/KMCosePairByteBlobTag.java new file mode 100644 index 00000000..4a03295e --- /dev/null +++ b/Applet/src/com/android/javacard/keymaster/KMCosePairByteBlobTag.java @@ -0,0 +1,130 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * 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 { + + private static KMCosePairByteBlobTag prototype; + + public static Object[] keys; + + 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); + } + + 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)); + } + + 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; + } + +} diff --git a/Applet/src/com/android/javacard/keymaster/KMCosePairCoseKeyTag.java b/Applet/src/com/android/javacard/keymaster/KMCosePairCoseKeyTag.java new file mode 100644 index 00000000..7eb1251e --- /dev/null +++ b/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 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)); + } + + 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; + } + +} diff --git a/Applet/src/com/android/javacard/keymaster/KMCosePairIntegerTag.java b/Applet/src/com/android/javacard/keymaster/KMCosePairIntegerTag.java new file mode 100644 index 00000000..d5fe47b9 --- /dev/null +++ b/Applet/src/com/android/javacard/keymaster/KMCosePairIntegerTag.java @@ -0,0 +1,93 @@ +/* + * 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/Applet/src/com/android/javacard/keymaster/KMCosePairNegIntegerTag.java b/Applet/src/com/android/javacard/keymaster/KMCosePairNegIntegerTag.java new file mode 100644 index 00000000..b2abb95d --- /dev/null +++ b/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/Applet/src/com/android/javacard/keymaster/KMCosePairSimpleValueTag.java b/Applet/src/com/android/javacard/keymaster/KMCosePairSimpleValueTag.java new file mode 100644 index 00000000..6aea5e30 --- /dev/null +++ b/Applet/src/com/android/javacard/keymaster/KMCosePairSimpleValueTag.java @@ -0,0 +1,75 @@ +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/Applet/src/com/android/javacard/keymaster/KMCosePairTagType.java b/Applet/src/com/android/javacard/keymaster/KMCosePairTagType.java new file mode 100644 index 00000000..053fc633 --- /dev/null +++ b/Applet/src/com/android/javacard/keymaster/KMCosePairTagType.java @@ -0,0 +1,233 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +/** + * This class represents the COSE_Key as defined in https://datatracker.ietf.org/doc/html/rfc8152#section-7. + * This is basically a map containing key value pairs. The label for the key can be (uint / int / tstr) and + * the value can be of any type. But this class is confined to support only key and value types which are + * required for remote key provisioning. So keys of type (int / uint) and values of type (int / uint / simple / bstr) + * only are supported. The structure representing all the sub classes of KMCosePairTagType is as follows: + * KM_COSE_PAIR_TAG_TYPE(1byte), Length(2 bytes), COSE_PAIR_*_TAG_TYPE(2 bytes), Key(2 bytes), Value(2 bytes). + * Key can be either KMInteger or KMNInteger and Value can be either KMIntger or KMNinteger or KMSimpleValue + * or KMByteBlob or KMTextString or KMCoseKey. Each subclass of KMCosePairTagType is named after their corresponding + * value type of the Cose pair. + */ +public abstract class KMCosePairTagType extends KMType { + + /** + * Below table represents the allowed values for a key. The maximum length of the key + * can be 4 bytes so each key is represented as 4 bytes. The allowed values are + * placed next to their corresponding key. + */ + public static Object[] allowedKeyPairs; + + private static void createAllowedKeyPairs() { + if (allowedKeyPairs == null) { + allowedKeyPairs = + new Object[]{ + // Key type + (Object) new byte[]{0, 0, 0, KMCose.COSE_KEY_KEY_TYPE}, (Object) new byte[]{KMCose.COSE_KEY_TYPE_EC2, + KMCose.COSE_KEY_TYPE_SYMMETRIC_KEY}, + // Key Algorithm + (Object) new byte[]{0, 0, 0, KMCose.COSE_KEY_ALGORITHM}, + (Object) new byte[]{KMCose.COSE_ALG_AES_GCM_256, KMCose.COSE_ALG_HMAC_256, + KMCose.COSE_ALG_ECDH_ES_HKDF_256, KMCose.COSE_ALG_ES256}, + // Key operations + (Object) new byte[]{0, 0, 0, KMCose.COSE_KEY_KEY_OPS}, (Object) new byte[]{KMCose.COSE_KEY_OP_SIGN, KMCose.COSE_KEY_OP_VERIFY, + KMCose.COSE_KEY_OP_ENCRYPT, KMCose.COSE_KEY_OP_DECRYPT}, + // Key Curve + (Object) new byte[]{0, 0, 0, KMCose.COSE_KEY_CURVE}, (Object) new byte[]{KMCose.COSE_ECCURVE_256}, + // Header Label Algorithm + (Object) new byte[]{0, 0, 0, KMCose.COSE_LABEL_ALGORITHM}, (Object) new byte[]{KMCose.COSE_ALG_AES_GCM_256, + KMCose.COSE_ALG_HMAC_256, KMCose.COSE_ALG_ES256, KMCose.COSE_ALG_ECDH_ES_HKDF_256}, + // Test Key + KMCose.COSE_TEST_KEY, (Object) new byte[]{KMSimpleValue.NULL}, + }; + } + } + + + /** + * Validates the key and the values corresponding to key. + * + * @param key Buffer containing the key. + * @param keyOff Offset in the buffer from where key starts. + * @param keyLen Length of the key buffer. + * @param value Value corresponding to the key. + * @return true if key pair is valid, otherwise false. + */ + public static boolean isKeyPairValid(byte[] key, short keyOff, short keyLen, short value) { + short index = 0; + short valueIdx; + byte[] values; + boolean valid = false; + createAllowedKeyPairs(); + while (index < allowedKeyPairs.length) { + valueIdx = 0; + if (isEqual((byte[]) allowedKeyPairs[index], (short) 0, (short) ((byte[]) allowedKeyPairs[index]).length, + key, keyOff, keyLen)) { + values = (byte[]) allowedKeyPairs[(short) (index + 1)]; + while (valueIdx < values.length) { + if (values[valueIdx] == (byte) value) { + valid = true; + break; + } + valueIdx++; + } + if (valid) + break; + } + index += (short) 2; + } + return valid; + } + + /** + * Compares two key buffers. + * + * @param key1 First buffer containing the key. + * @param offset1 Offset of the first buffer. + * @param length1 Length of the first buffer. + * @param key2 Second buffer containing the key. + * @param offset2 Offset of the second buffer. + * @param length2 Length of the second buffer. + * @return true if both keys are equal, otherwise false. + */ + private static boolean isEqual(byte[] key1, short offset1, short length1, byte[] key2, short offset2, + short length2) { + if (length1 != length2) + return false; + return (0 == KMInteger.unsignedByteArrayCompare(key1, offset1, key2, offset2, length1)); + } + + /** + * Returns the short value of the key. + * + * @param keyPtr Pointer to either KMInteger or KMNInteger + * @return value of the key as short. + */ + public static short getKeyValueShort(short keyPtr) { + short type = KMType.getType(keyPtr); + short value = 0; + if (type == INTEGER_TYPE) { + value = KMInteger.cast(keyPtr).getShort(); + } else if (type == NEG_INTEGER_TYPE) { + value = KMNInteger.cast(keyPtr).getShort(); + } else { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return value; + } + + /** + * Returns the significant short value of the key. + * + * @param keyPtr Pointer to either KMInteger or KMNInteger + * @return value of the key as short. + */ + public static short getKeyValueSignificantShort(short keyPtr) { + short type = KMType.getType(keyPtr); + short value = 0; + if (type == INTEGER_TYPE) { + value = KMInteger.cast(keyPtr).getSignificantShort(); + } else if (type == NEG_INTEGER_TYPE) { + value = KMNInteger.cast(keyPtr).getSignificantShort(); + } else { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return value; + } + + public static void getKeyValue(short keyPtr, byte[] dest, short offset, short len) { + short type = KMType.getType(keyPtr); + if (type == INTEGER_TYPE) { + KMInteger.cast(keyPtr).getValue(dest, offset, len); + } else if (type == NEG_INTEGER_TYPE) { + KMNInteger.cast(keyPtr).getValue(dest, offset, len); + } else { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + } + + /** + * Returns the key offset from the key pointer. + * + * @param keyPtr Pointer to either KMInteger or KMNInteger + * @return offset from where the key starts. + */ + public static short getKeyStartOffset(short keyPtr) { + short type = KMType.getType(keyPtr); + short offset = 0; + if (type == INTEGER_TYPE) { + offset = KMInteger.cast(keyPtr).getStartOff(); + } else if (type == NEG_INTEGER_TYPE) { + offset = KMNInteger.cast(keyPtr).getStartOff(); + } else { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return offset; + } + + /** + * Returns the key length. + * + * @param keyPtr pointer to either KMInteger/KMInteger. + * @return length of the key. + */ + public static short getKeyLength(short keyPtr) { + short type = KMType.getType(keyPtr); + short len = 0; + if (type == INTEGER_TYPE) { + len = KMInteger.cast(keyPtr).length(); + } else if (type == NEG_INTEGER_TYPE) { + len = KMNInteger.cast(keyPtr).length(); + } else { + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); + } + return len; + } + + /** + * This function returns one of COSE_KEY_TAG_*_VALUE_TYPE tag + * information. + * + * @param ptr Pointer to one of the KMCoseKey*Value class. + * @return Tag value type. + */ + public static short getTagValueType(short ptr) { + return Util.getShort(heap, (short) (ptr + TLV_HEADER_SIZE)); + } + + /** + * This function returns the key pointer. + * + * @return key pointer. + */ + public abstract short getKeyPtr(); + + /** + * This function returns the value pointer. + * + * @return value pointer. + */ + public abstract short getValuePtr(); +} diff --git a/Applet/src/com/android/javacard/keymaster/KMCosePairTextStringTag.java b/Applet/src/com/android/javacard/keymaster/KMCosePairTextStringTag.java new file mode 100644 index 00000000..165e3d15 --- /dev/null +++ b/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 { + + private static KMCosePairTextStringTag prototype; + + public static final byte[] keys = { + KMCose.ISSUER, + KMCose.SUBJECT, + }; + + 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 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)); + } + + 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; + } + +} diff --git a/Applet/src/com/android/javacard/keymaster/KMDecoder.java b/Applet/src/com/android/javacard/keymaster/KMDecoder.java index bd07e801..63603f39 100644 --- a/Applet/src/com/android/javacard/keymaster/KMDecoder.java +++ b/Applet/src/com/android/javacard/keymaster/KMDecoder.java @@ -18,15 +18,18 @@ import javacard.framework.ISO7816; import javacard.framework.ISOException; +import javacard.framework.JCSystem; import javacard.framework.Util; - public class KMDecoder { // major types - public static final short UINT_TYPE = 0x00; - public static final short BYTES_TYPE = 0x40; - public static final short ARRAY_TYPE = 0x80; - public static final short MAP_TYPE = 0xA0; + 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; // masks private static final short ADDITIONAL_MASK = 0x1F; @@ -38,29 +41,33 @@ public class KMDecoder { private static final short UINT32_LENGTH = 0x1A; private static final short UINT64_LENGTH = 0x1B; - private byte[] buffer; - private short startOff; - private short length; - private short tagType; - private short tagKey; + private static final short SCRATCH_BUF_SIZE = 6; + private static final short START_OFFSET = 0; + private static final short LEN_OFFSET = 2; + private static final short TAG_KEY_OFFSET = 4; + private Object[] bufferRef; + private short[] scratchBuf; public KMDecoder() { - buffer = null; - startOff = 0; - length = 0; + 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) { - this.buffer = buffer; - this.startOff = startOff; - this.length = (short) (startOff + 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) { - this.buffer = buffer; - this.startOff = startOff; - this.length = (short) (startOff + 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) { @@ -84,10 +91,18 @@ private short decode(short 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.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: @@ -100,6 +115,13 @@ private short decode(short exp) { 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); @@ -141,7 +163,7 @@ private short decodeVerificationToken(short exp) { private short decodeHwAuthToken(short exp) { short vals = decode(KMHardwareAuthToken.cast(exp).getVals()); - return KMHardwareAuthToken.cast(exp).instance(vals); + return KMHardwareAuthToken.instance(vals); } private short decodeHmacSharingParam(short exp) { @@ -154,6 +176,159 @@ private short decodeKeyChar(short exp) { return KMKeyCharacteristics.instance(vals); } + private short decodeCosePairKey(short exp) { + byte[] buffer = (byte[]) bufferRef[0]; + short startOff = scratchBuf[START_OFFSET]; + short keyPtr = (short) 0; + // Cose Key should be always either UINT or Negative int + if ((buffer[startOff] & MAJOR_TYPE_MASK) == UINT_TYPE) { + keyPtr = decodeInteger(exp); + } else if ((buffer[startOff] & MAJOR_TYPE_MASK) == NEG_INT_TYPE) { + keyPtr = decodeNegInteger(exp); + } else { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + return keyPtr; + } + + private short decodeCosePairSimpleValueTag(short exp) { + short keyPtr = decodeCosePairKey((KMCosePairSimpleValueTag.cast(exp).getKeyPtr())); + short valuePtr = decode(KMCosePairSimpleValueTag.cast(exp).getValuePtr()); + return KMCosePairSimpleValueTag.instance(keyPtr, valuePtr); + } + + private short decodeCosePairIntegerValueTag(short exp) { + short keyPtr = decodeCosePairKey((KMCosePairIntegerTag.cast(exp).getKeyPtr())); + short valuePtr = decode(KMCosePairIntegerTag.cast(exp).getValuePtr()); + return KMCosePairIntegerTag.instance(keyPtr, valuePtr); + } + + private short decodeCosePairNegIntegerTag(short exp) { + short keyPtr = decodeCosePairKey((KMCosePairNegIntegerTag.cast(exp).getKeyPtr())); + short valuePtr = decode(KMCosePairNegIntegerTag.cast(exp).getValuePtr()); + return KMCosePairNegIntegerTag.instance(keyPtr, valuePtr); + } + + private short decodeCosePairTxtStringTag(short exp) { + short keyPtr = decodeCosePairKey((KMCosePairTextStringTag.cast(exp).getKeyPtr())); + short valuePtr = decode(KMCosePairTextStringTag.cast(exp).getValuePtr()); + return KMCosePairTextStringTag.instance(keyPtr, valuePtr); + } + + private short decodeCosePairCoseKeyTag(short exp) { + short keyPtr = decodeCosePairKey((KMCosePairCoseKeyTag.cast(exp).getKeyPtr())); + short valuePtr = decode(KMCosePairCoseKeyTag.cast(exp).getValuePtr()); + return KMCosePairCoseKeyTag.instance(keyPtr, valuePtr); + } + + private short decodeCosePairByteBlobTag(short exp) { + short keyPtr = decodeCosePairKey((KMCosePairByteBlobTag.cast(exp).getKeyPtr())); + short valuePtr = decode(KMCosePairByteBlobTag.cast(exp).getValuePtr()); + return KMCosePairByteBlobTag.instance(keyPtr, valuePtr); + } + + private short peekCosePairTagType() { + byte[] buffer = (byte[]) bufferRef[0]; + short startOff = scratchBuf[START_OFFSET]; + // Cose Key should be always either UINT or Negative int + if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE && + (buffer[startOff] & MAJOR_TYPE_MASK) != NEG_INT_TYPE) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + + short additionalMask = (short) (buffer[startOff] & ADDITIONAL_MASK); + short increment = 0; + if (additionalMask < UINT8_LENGTH) { + increment++; + } else if (additionalMask == UINT8_LENGTH) { + increment += 2; + } else if (additionalMask == UINT16_LENGTH) { + increment += 3; + } else if (additionalMask == UINT32_LENGTH) { + increment += 5; + } else { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + short majorType = (short) (buffer[(short) (startOff + increment)] & MAJOR_TYPE_MASK); + short tagValueType = 0; + if (majorType == BYTES_TYPE) { + tagValueType = KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE; + } else if (majorType == UINT_TYPE) { + tagValueType = KMType.COSE_PAIR_INT_TAG_TYPE; + } else if (majorType == NEG_INT_TYPE) { + tagValueType = KMType.COSE_PAIR_NEG_INT_TAG_TYPE; + } else if (majorType == MAP_TYPE) { + tagValueType = KMType.COSE_PAIR_COSE_KEY_TAG_TYPE; + } else if (majorType == SIMPLE_VALUE_TYPE) { + tagValueType = KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE; + } else if (majorType == TSTR_TYPE) { + tagValueType = KMType.COSE_PAIR_TEXT_STR_TAG_TYPE; + }else { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } + return tagValueType; + } + + private short decodeCosePairTag(short tagValueType, short exp) { + switch (tagValueType) { + case KMType.COSE_PAIR_BYTE_BLOB_TAG_TYPE: + return decodeCosePairByteBlobTag(exp); + case KMType.COSE_PAIR_NEG_INT_TAG_TYPE: + return decodeCosePairNegIntegerTag(exp); + case KMType.COSE_PAIR_INT_TAG_TYPE: + return decodeCosePairIntegerValueTag(exp); + case KMType.COSE_PAIR_SIMPLE_VALUE_TAG_TYPE: + return decodeCosePairSimpleValueTag(exp); + case KMType.COSE_PAIR_COSE_KEY_TAG_TYPE: + return decodeCosePairCoseKeyTag(exp); + case KMType.COSE_PAIR_TEXT_STR_TAG_TYPE: + return decodeCosePairTxtStringTag(exp); + default: + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + return 0; + } + } + + private short decodeCoseMap(short exp) { + short payloadLength = readMajorTypeWithPayloadLength(MAP_TYPE); + // get allowed key pairs + short allowedKeyPairs = KMCoseMap.getVals(exp); + short vals = KMArray.instance(payloadLength); + short length = KMArray.cast(allowedKeyPairs).length(); + short index = 0; + boolean tagFound; + short tagInd; + short cosePairTagType; + short tagClass; + short allowedType; + short obj; + + // For each tag in payload ... + while (index < payloadLength) { + tagFound = false; + tagInd = 0; + cosePairTagType = peekCosePairTagType(); + // Check against the allowed tags ... + while (tagInd < length) { + tagClass = KMArray.cast(allowedKeyPairs).get(tagInd); + allowedType = KMCosePairTagType.getTagValueType(tagClass); + if (allowedType == cosePairTagType) { + obj = decode(tagClass); + KMArray.cast(vals).add(index, obj); + tagFound = true; + break; + } + tagInd++; + } + if (!tagFound) { + ISOException.throwIt(ISO7816.SW_DATA_INVALID); + } else { + index++; + } + } + return KMCoseMap.createInstanceFromType(exp, vals); + } + private short decodeKeyParam(short exp) { short payloadLength = readMajorTypeWithPayloadLength(MAP_TYPE); // allowed tags @@ -206,38 +381,56 @@ private short decodeKeyParam(short exp) { private short decodeEnumArrayTag(short exp) { readTagKey(KMEnumArrayTag.cast(exp).getTagType()); - return KMEnumArrayTag.instance(this.tagKey, decode(KMEnumArrayTag.cast(exp).getValues())); + 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(), - this.tagKey, decode(KMIntegerArrayTag.cast(exp).getValues())); + 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(), - this.tagKey, decode(KMIntegerTag.cast(exp).getValue())); + 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(this.tagKey, decode(KMByteTag.cast(exp).getValue())); + 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(this.tagKey, decode(KMBignumTag.cast(exp).getValue())); + 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.cast(exp).instance(payloadLength); + short arrPtr = KMArray.instance(payloadLength); short index = 0; short type; short obj; @@ -267,6 +460,8 @@ private short decodeArray(short exp) { 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); @@ -281,14 +476,19 @@ private short decodeEnumTag(short exp) { 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(tagKey, enumVal); + 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); @@ -297,10 +497,12 @@ private short decodeBoolTag(short exp) { ISOException.throwIt(ISO7816.SW_DATA_INVALID); } incrementStartOff((short) 1); - return KMBoolTag.instance(tagKey); + 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); @@ -315,14 +517,31 @@ private short decodeEnum(short exp) { 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 inst; + 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 decodeInteger(short exp) { short inst; + short startOff = scratchBuf[START_OFFSET]; + byte[] buffer = (byte[]) bufferRef[0]; if ((buffer[startOff] & MAJOR_TYPE_MASK) != UINT_TYPE) { ISOException.throwIt(ISO7816.SW_DATA_INVALID); } @@ -331,6 +550,9 @@ private short decodeInteger(short exp) { 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) { @@ -349,14 +571,78 @@ private short decodeInteger(short exp) { return inst; } + 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(buffer, startOff, payloadLength); + 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); } @@ -370,6 +656,8 @@ private short peekTagType() { } 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); } @@ -377,8 +665,8 @@ private void readTagKey(short expectedTagType) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } incrementStartOff((short) 1); - this.tagType = readShort(); - this.tagKey = readShort(); + short tagType = readShort(); + scratchBuf[TAG_KEY_OFFSET] = readShort(); if (tagType != expectedTagType) { ISOException.throwIt(ISO7816.SW_DATA_INVALID); } @@ -386,7 +674,7 @@ private void readTagKey(short expectedTagType) { // payload length cannot be more then 16 bits. private short readMajorTypeWithPayloadLength(short majorType) { - short payloadLength = 0; + short payloadLength; byte val = readByte(); if ((short) (val & MAJOR_TYPE_MASK) != majorType) { ISOException.throwIt(ISO7816.SW_DATA_INVALID); @@ -406,31 +694,34 @@ private short readMajorTypeWithPayloadLength(short majorType) { } 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() { - byte val = buffer[startOff]; + short startOff = scratchBuf[START_OFFSET]; + byte val = ((byte[]) bufferRef[0])[startOff]; incrementStartOff((short) 1); return val; } private void incrementStartOff(short inc) { - startOff += inc; - if (startOff > this.length) { + scratchBuf[START_OFFSET] += inc; + if (scratchBuf[START_OFFSET] > scratchBuf[LEN_OFFSET]) { ISOException.throwIt(ISO7816.SW_DATA_INVALID); } } public short readCertificateChainLengthAndHeaderLen(byte[] buf, short bufOffset, - short bufLen) { - this.buffer = buf; - this.startOff = bufOffset; - this.length = (short) (bufOffset + bufLen); + short bufLen) { + bufferRef[0] = buf; + scratchBuf[START_OFFSET] = bufOffset; + scratchBuf[LEN_OFFSET] = (short) (bufOffset + bufLen); short totalLen = readMajorTypeWithPayloadLength(BYTES_TYPE); - totalLen += (short) (startOff - bufOffset); + totalLen += (short) (scratchBuf[START_OFFSET] - bufOffset); return totalLen; } } diff --git a/Applet/src/com/android/javacard/keymaster/KMDeviceUniqueKey.java b/Applet/src/com/android/javacard/keymaster/KMDeviceUniqueKey.java new file mode 100644 index 00000000..02b22e57 --- /dev/null +++ b/Applet/src/com/android/javacard/keymaster/KMDeviceUniqueKey.java @@ -0,0 +1,21 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" (short)0IS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.javacard.keymaster; + +public interface KMDeviceUniqueKey { + + short getPublicKey(byte[] buf, short offset); +} diff --git a/Applet/src/com/android/javacard/keymaster/KMEncoder.java b/Applet/src/com/android/javacard/keymaster/KMEncoder.java index 8c96bb3a..fc2a3ecd 100644 --- a/Applet/src/com/android/javacard/keymaster/KMEncoder.java +++ b/Applet/src/com/android/javacard/keymaster/KMEncoder.java @@ -25,9 +25,12 @@ 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; // masks private static final byte ADDITIONAL_MASK = 0x1F; @@ -39,27 +42,34 @@ public class KMEncoder { private static final byte UINT64_LENGTH = (byte) 0x1B; private static final short TINY_PAYLOAD = 0x17; private static final short SHORT_PAYLOAD = 0x100; - private byte[] buffer; - private short startOff; - private short length; - private static short[] stack; - private static byte stackPtr; + private static final short STACK_SIZE = (short) 50; + private static final short SCRATCH_BUF_SIZE = (short) 6; + private static final short START_OFFSET = (short) 0; + private static final short LEN_OFFSET = (short) 2; + private static final short STACK_PTR_OFFSET = (short) 4; + + private Object[] bufferRef; + private short[] scratchBuf; + private short[] stack; public KMEncoder() { - buffer = null; - startOff = 0; - length = 0; - stack = JCSystem.makeTransientShortArray((short) 50, JCSystem.CLEAR_ON_RESET); + 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 static void push(short objPtr) { - stack[stackPtr] = objPtr; - stackPtr++; + private void push(short objPtr) { + stack[scratchBuf[STACK_PTR_OFFSET]] = objPtr; + scratchBuf[STACK_PTR_OFFSET]++; } - private static short pop() { - stackPtr--; - return stack[stackPtr]; + private short pop() { + scratchBuf[STACK_PTR_OFFSET]--; + return stack[scratchBuf[STACK_PTR_OFFSET]]; } private void encode(short obj) { @@ -67,112 +77,106 @@ private void encode(short obj) { } public short encode(short object, byte[] buffer, short startOff) { - stackPtr = 0; - this.buffer = buffer; - this.startOff = startOff; - short len = (short) buffer.length; - if ((len < 0) || (len > KMKeymasterApplet.MAX_LENGTH)) { - this.length = KMKeymasterApplet.MAX_LENGTH; + scratchBuf[STACK_PTR_OFFSET] = 0; + bufferRef[0] = buffer; + scratchBuf[START_OFFSET] = startOff; + short len = (short) (buffer.length - startOff); + if ((len < 0) || len > KMKeymasterApplet.MAX_LENGTH) { + scratchBuf[LEN_OFFSET] = KMKeymasterApplet.MAX_LENGTH; } else { - this.length = (short) buffer.length; + scratchBuf[LEN_OFFSET] = (short) buffer.length; } //this.length = (short)(startOff + length); push(object); encode(); - return (short) (this.startOff - startOff); + return (short) (scratchBuf[START_OFFSET] - startOff); } // array{KMError.OK,Array{KMByteBlobs}} - public void encodeCertChain(byte[] buffer, short offset, short length) { - this.buffer = buffer; - this.startOff = offset; - this.length = (short) (offset + 3); + public void encodeCertChain(byte[] buffer, short offset, short length, short errInt32Ptr) { + bufferRef[0] = buffer; + scratchBuf[START_OFFSET] = offset; + scratchBuf[LEN_OFFSET] = (short) (offset + 1); + //Total length is ArrayHeader + [UIntHeader + length(errInt32Ptr)] + scratchBuf[LEN_OFFSET] += (short) (1 + getEncodedIntegerLength(errInt32Ptr)); writeMajorTypeWithLength(ARRAY_TYPE, (short) 2); // Array of 2 elements - writeByte(UINT_TYPE); // Error.OK + encodeUnsignedInteger(errInt32Ptr); } //array{KMError.OK,Array{KMByteBlobs}} - public short encodeCert(byte[] certBuffer, short bufferStart, short certStart, short certLength) { - this.buffer = certBuffer; - this.startOff = certStart; - this.length = (short) (certStart + 1); + public short encodeCert(byte[] certBuffer, short bufferStart, short certStart, short certLength, short errInt32Ptr) { + bufferRef[0] = certBuffer; + scratchBuf[START_OFFSET] = certStart; + scratchBuf[LEN_OFFSET] = (short) (certStart + 1); //Array header - 2 elements i.e. 1 byte - this.startOff--; - // Error.Ok - 1 byte - this.startOff--; + scratchBuf[START_OFFSET]--; + // errInt32Ptr - PowerResetStatus + ErrorCode - 4 bytes + // Integer header - 1 byte + scratchBuf[START_OFFSET] -= getEncodedIntegerLength(errInt32Ptr); //Array header - 2 elements i.e. 1 byte - this.startOff--; + scratchBuf[START_OFFSET]--; // Cert Byte blob - typically 2 bytes length i.e. 3 bytes header - this.startOff -= 2; + scratchBuf[START_OFFSET] -= 2; if (certLength >= SHORT_PAYLOAD) { - this.startOff--; + scratchBuf[START_OFFSET]--; } - bufferStart = startOff; - if (this.startOff < bufferStart) { + if (scratchBuf[START_OFFSET] < bufferStart) { ISOException.throwIt(ISO7816.SW_WRONG_LENGTH); } + bufferStart = scratchBuf[START_OFFSET]; writeMajorTypeWithLength(ARRAY_TYPE, (short) 2); // Array of 2 elements - writeByte(UINT_TYPE); // Error.OK - writeMajorTypeWithLength(ARRAY_TYPE, (short) 1); // Array of 1 element - writeMajorTypeWithLength(BYTES_TYPE, certLength); // Cert Byte Blob of length - return bufferStart; - } - //Array{KMByteBlobs} - public short encodeCert(byte[] certBuffer, short certStart, short certLength) { - this.buffer = certBuffer; - this.startOff = certStart; - this.length = (short) (certStart + 1); - //Array header - 1 element i.e. 1 byte - this.startOff--; - // Cert Byte blob - typically 2 bytes length i.e. 3 bytes header - this.startOff -= 2; - if (certLength >= SHORT_PAYLOAD) { - this.startOff--; - } - short bufferStart = startOff; + encodeUnsignedInteger(errInt32Ptr); //PowerResetStatus + ErrorCode writeMajorTypeWithLength(ARRAY_TYPE, (short) 1); // Array of 1 element writeMajorTypeWithLength(BYTES_TYPE, certLength); // Cert Byte Blob of length return bufferStart; } - public short encodeError(short err, byte[] buffer, short startOff, short length) { - this.buffer = buffer; - this.startOff = startOff; - this.length = (short) (startOff + length); - // encode the err as UINT with value in err - should not be greater then 5 bytes. - if (err < UINT8_LENGTH) { - writeByte((byte) (UINT_TYPE | err)); - } else if (err < 0x100) { - writeByte((byte) (UINT_TYPE | UINT8_LENGTH)); - writeByte((byte) err); - } else { - writeByte((byte) (UINT_TYPE | UINT16_LENGTH)); - writeShort(err); - } - return (short) (this.startOff - startOff); + public short encodeError(short errInt32Ptr, byte[] buffer, short startOff, short length) { + bufferRef[0] = buffer; + scratchBuf[START_OFFSET] = startOff; + scratchBuf[LEN_OFFSET] = (short) (startOff + length + 1); + encodeUnsignedInteger(errInt32Ptr); + return (short) (scratchBuf[START_OFFSET] - startOff); } private void encode() { - while (stackPtr > 0) { + 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: - encodeInteger(exp); + 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.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; @@ -189,12 +193,83 @@ private void encode() { 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.BYTES_TAG: @@ -223,6 +298,10 @@ private void encodeTag(short tagType, short exp) { } } + private void encodeCoseMap(short obj) { + encodeAsMap(KMCoseMap.getVals(obj)); + } + private void encodeKeyParam(short obj) { encodeAsMap(KMKeyParameters.cast(obj).getVals()); } @@ -247,8 +326,29 @@ 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(KMArray.cast(obj).get(index)); + encode(KMMap.cast(obj).getKeyValue(index)); + encode(KMMap.cast(obj).getKey(index)); index--; } } @@ -299,40 +399,125 @@ private void encodeEnum(short obj) { writeByteValue(KMEnum.cast(obj).getVal()); } - private void encodeInteger(short obj) { - byte[] val = KMInteger.cast(obj).getBuffer(); - short len = KMInteger.cast(obj).length(); - short startOff = KMInteger.cast(obj).getStartOff(); + 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 (val[(short) (startOff + index)] > 0) { + if (buf[(short) (offset + index)] > 0) { break; - } else if (val[(short) (startOff + index)] < 0) { + } 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 - index); + 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) { - writeByte((byte) (UINT_TYPE | 0)); - } else if ((diff == 1) && (val[(short) (startOff + index)] < UINT8_LENGTH) - && (val[(short) (startOff + index)] >= 0)) { - writeByte((byte) (UINT_TYPE | val[(short) (startOff + index)])); + // Fail + ISOException.throwIt(ISO7816.SW_DATA_INVALID); } else if (diff == 1) { - writeByte((byte) (UINT_TYPE | UINT8_LENGTH)); - writeByte(val[(short) (startOff + index)]); + correctedOffset = (short) (offset + 3); + correctedLen = 1; } else if (diff == 2) { - writeByte((byte) (UINT_TYPE | UINT16_LENGTH)); - writeBytes(val, (short) (startOff + index), (short) 2); - } else if (diff <= 4) { - writeByte((byte) (UINT_TYPE | UINT32_LENGTH)); - writeBytes(val, (short) (startOff + len - 4), (short) 4); - } else { - writeByte((byte) (UINT_TYPE | UINT64_LENGTH)); - writeBytes(val, startOff, (short) 8); + 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 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) { @@ -341,12 +526,181 @@ private void encodeByteBlob(short obj) { 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; + } + + private 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((byte) val); + writeByte(val); } } @@ -369,25 +723,28 @@ private void writeMajorTypeWithLength(byte majorType, short len) { } private void writeBytes(byte[] buf, short start, short len) { - Util.arrayCopyNonAtomic(buf, start, buffer, startOff, len); + byte[] buffer = (byte[]) bufferRef[0]; + Util.arrayCopyNonAtomic(buf, start, buffer, scratchBuf[START_OFFSET], len); incrementStartOff(len); } private void writeShort(short val) { - buffer[startOff] = (byte) ((val >> 8) & 0xFF); + byte[] buffer = (byte[]) bufferRef[0]; + buffer[scratchBuf[START_OFFSET]] = (byte) ((val >> 8) & 0xFF); incrementStartOff((short) 1); - buffer[startOff] = (byte) ((val & 0xFF)); + buffer[scratchBuf[START_OFFSET]] = (byte) ((val & 0xFF)); incrementStartOff((short) 1); } private void writeByte(byte val) { - buffer[startOff] = val; + byte[] buffer = (byte[]) bufferRef[0]; + buffer[scratchBuf[START_OFFSET]] = val; incrementStartOff((short) 1); } private void incrementStartOff(short inc) { - startOff += inc; - if (startOff >= this.length) { + scratchBuf[START_OFFSET] += inc; + if (scratchBuf[START_OFFSET] >= scratchBuf[LEN_OFFSET]) { ISOException.throwIt(ISO7816.SW_DATA_INVALID); } } diff --git a/Applet/src/com/android/javacard/keymaster/KMEnum.java b/Applet/src/com/android/javacard/keymaster/KMEnum.java index d02b9f57..1cfe5803 100644 --- a/Applet/src/com/android/javacard/keymaster/KMEnum.java +++ b/Applet/src/com/android/javacard/keymaster/KMEnum.java @@ -111,7 +111,7 @@ private static void create() { 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}, + 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} }; diff --git a/Applet/src/com/android/javacard/keymaster/KMEnumArrayTag.java b/Applet/src/com/android/javacard/keymaster/KMEnumArrayTag.java index c7dee7a0..0f932a9d 100644 --- a/Applet/src/com/android/javacard/keymaster/KMEnumArrayTag.java +++ b/Applet/src/com/android/javacard/keymaster/KMEnumArrayTag.java @@ -127,7 +127,7 @@ public static void create() { // allowed tag values. enums = new Object[]{ - new byte[]{ENCRYPT, DECRYPT, SIGN, VERIFY, WRAP_KEY, ATTEST_KEY}, + 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[]{ diff --git a/Applet/src/com/android/javacard/keymaster/KMError.java b/Applet/src/com/android/javacard/keymaster/KMError.java index 0fc37ec8..0c5db5b5 100644 --- a/Applet/src/com/android/javacard/keymaster/KMError.java +++ b/Applet/src/com/android/javacard/keymaster/KMError.java @@ -65,6 +65,7 @@ public class KMError { 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; @@ -100,6 +101,13 @@ public class KMError { //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) { diff --git a/Applet/src/com/android/javacard/keymaster/KMInteger.java b/Applet/src/com/android/javacard/keymaster/KMInteger.java index 21f6f5ff..73c88203 100644 --- a/Applet/src/com/android/javacard/keymaster/KMInteger.java +++ b/Applet/src/com/android/javacard/keymaster/KMInteger.java @@ -30,7 +30,7 @@ public class KMInteger extends KMType { public static final short UINT_64 = 8; private static KMInteger prototype; - private KMInteger() { + protected KMInteger() { } private static KMInteger proto(short ptr) { @@ -114,7 +114,7 @@ public static short uint_64(byte[] num, short offset) { // Get the length of the integer public short length() { - return Util.getShort(heap, (short) (KMType.instanceTable[KM_INTEGER_OFFSET] + 1)); + return Util.getShort(heap, (short) (getBaseOffset() + 1)); } // Get the buffer pointer in which blob is contained. @@ -124,7 +124,7 @@ public byte[] getBuffer() { // Get the start of value public short getStartOff() { - return (short) (KMType.instanceTable[KM_INTEGER_OFFSET] + TLV_HEADER_SIZE); + return (short) (getBaseOffset() + TLV_HEADER_SIZE); } public void getValue(byte[] dest, short destOff, short length) { @@ -200,4 +200,8 @@ public static byte unsignedByteArrayCompare(byte[] a1, short offset1, byte[] a2, } return 0; } + + protected short getBaseOffset() { + return instanceTable[KM_INTEGER_OFFSET]; + } } diff --git a/Applet/src/com/android/javacard/keymaster/KMKeyParameters.java b/Applet/src/com/android/javacard/keymaster/KMKeyParameters.java index 3acda372..a6222638 100644 --- a/Applet/src/com/android/javacard/keymaster/KMKeyParameters.java +++ b/Applet/src/com/android/javacard/keymaster/KMKeyParameters.java @@ -232,6 +232,63 @@ public static short makeSbEnforced(short keyParamsPtr, byte origin, return createKeyParameters(scratchPad, (short) (arrInd / 2)); } + public static short makeSbEnforced(short keyParamsPtr, byte[] scratchPad) { + final short[] hwEnforcedTagArr = { + // HW Enforced + KMType.ENUM_TAG, KMType.ORIGIN, + KMType.UINT_TAG, KMType.OS_VERSION, + KMType.UINT_TAG, KMType.OS_PATCH_LEVEL, + KMType.UINT_TAG, KMType.VENDOR_PATCH_LEVEL, + KMType.UINT_TAG, KMType.BOOT_PATCH_LEVEL, + KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, + KMType.ENUM_TAG, KMType.ALGORITHM, + KMType.UINT_TAG, KMType.KEYSIZE, + KMType.ULONG_TAG, KMType.RSA_PUBLIC_EXPONENT, + KMType.ENUM_TAG, KMType.BLOB_USAGE_REQ, + KMType.ENUM_ARRAY_TAG, KMType.DIGEST, + KMType.ENUM_ARRAY_TAG, KMType.PADDING, + KMType.ENUM_ARRAY_TAG, KMType.BLOCK_MODE, + KMType.ENUM_ARRAY_TAG, KMType.RSA_OAEP_MGF_DIGEST, + KMType.BOOL_TAG, KMType.NO_AUTH_REQUIRED, + KMType.BOOL_TAG, KMType.CALLER_NONCE, + KMType.UINT_TAG, KMType.MIN_MAC_LENGTH, + KMType.ENUM_TAG, KMType.ECCURVE, + KMType.BOOL_TAG, KMType.INCLUDE_UNIQUE_ID, + KMType.BOOL_TAG, KMType.ROLLBACK_RESISTANCE, + KMType.BOOL_TAG, KMType.UNLOCKED_DEVICE_REQUIRED, + KMType.BOOL_TAG, KMType.RESET_SINCE_ID_ROTATION, + KMType.BOOL_TAG, KMType.EARLY_BOOT_ONLY, + }; + 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++; + } + 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(); diff --git a/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java b/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java index 7e1c045a..32554fb1 100644 --- a/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java +++ b/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java @@ -16,6 +16,7 @@ package com.android.javacard.keymaster; +import com.android.javacard.rkp.RemotelyProvisionedComponentDevice; import javacard.framework.APDU; import javacard.framework.Applet; import javacard.framework.AppletEvent; @@ -37,7 +38,7 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe public static final byte[] F4 = {0x01, 0x00, 0x01}; public static final byte AES_BLOCK_SIZE = 16; public static final byte DES_BLOCK_SIZE = 8; - public static final short MAX_LENGTH = 10000; + public static final short MAX_LENGTH = 15000; public static final short MASTER_KEY_SIZE = 128; public static final short WRAPPING_KEY_SIZE = 32; public static final short MAX_OPERATIONS_COUNT = 4; @@ -94,7 +95,7 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe }; - + public static final short MAX_COSE_BUF_SIZE = (short) 1024; // Top 32 commands are reserved for provisioning. private static final byte KEYMINT_CMD_APDU_START = 0x20; @@ -120,11 +121,20 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe 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; - private static final byte INS_BEGIN_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 24; - private static final byte INS_FINISH_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 25; - private static final byte INS_INIT_STRONGBOX_CMD = KEYMINT_CMD_APDU_START + 26; - private static final byte KEYMINT_CMD_APDU_END = KEYMINT_CMD_APDU_START + 27; + private static final byte INS_UPDATE_AAD_OPERATION_CMD = KEYMINT_CMD_APDU_START + 23; //0x37 + private static final byte INS_BEGIN_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 24; //0x38 + private static final byte INS_FINISH_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 25; //0x39 + private static final byte INS_INIT_STRONGBOX_CMD = KEYMINT_CMD_APDU_START + 26; //0x3A + // RKP + public static final byte INS_GET_RKP_HARDWARE_INFO = KEYMINT_CMD_APDU_START + 27; //0x3B + public static final byte INS_GENERATE_RKP_KEY_CMD = KEYMINT_CMD_APDU_START + 28; //0x3C + 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 + 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 KEYMINT_CMD_APDU_END = KEYMINT_CMD_APDU_START + 35; //0x43 private static final byte INS_END_KM_CMD = 0x7F; @@ -180,12 +190,13 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe public static final byte KEY_BLOB_PARAMS = 3; public static final byte KEY_BLOB_PUB_KEY = 4; // AES GCM constants - private static final byte AES_GCM_AUTH_TAG_LENGTH = 16; - private static final byte AES_GCM_NONCE_LENGTH = 12; + public static final byte AES_GCM_AUTH_TAG_LENGTH = 16; + public static final byte AES_GCM_NONCE_LENGTH = 12; // ComputeHMAC constants private static final short HMAC_SHARED_PARAM_MAX_SIZE = 64; protected static final short MAX_CERT_SIZE = 2048; + protected static RemotelyProvisionedComponentDevice rkp; protected static KMEncoder encoder; protected static KMDecoder decoder; protected static KMRepository repository; @@ -224,6 +235,7 @@ protected KMKeymasterApplet(KMSEProvider seImpl) { // initialize default values initHmacNonceAndSeed(); initSystemBootParams((short)0,(short)0,(short)0,(short)0); + rkp = new RemotelyProvisionedComponentDevice(encoder, decoder, repository, seProvider); } protected void initHmacNonceAndSeed(){ @@ -382,86 +394,96 @@ public void process(APDU apdu) { } // Validate APDU Header. short apduIns = validateApduHeader(apdu); - switch (apduIns) { - case INS_INIT_STRONGBOX_CMD: - processInitStrongBoxCmd(apdu); - sendError(apdu, KMError.OK); - return; - case INS_GENERATE_KEY_CMD: - processGenerateKey(apdu); - 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; - default: - ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); - } + switch (apduIns) { + case INS_INIT_STRONGBOX_CMD: + processInitStrongBoxCmd(apdu); + sendError(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; + default: + ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); + } } catch (KMException exception) { freeOperations(); resetWrappingKey(); sendError(apdu, KMException.reason()); } catch (ISOException exp) { - // sendError(apdu, mapISOErrorToKMError(exp.getReason())); + // sendError(apdu, mapISOErrorToKMError(exp.getReason())); freeOperations(); resetWrappingKey(); sendError(apdu, mapISOErrorToKMError(exp.getReason())); @@ -550,7 +572,7 @@ private short getWrappingKey(){ return KMByteBlob.instance(wrappingKey,(short)1,WRAPPING_KEY_SIZE); } - private void resetData() { + protected void resetData() { short index = 0; while (index < data.length) { data[index] = KMType.INVALID_VALUE; @@ -600,7 +622,7 @@ public static short receiveIncoming(APDU apdu, short reqExp) { index += recvLen; recvLen = apdu.receiveBytes(srcOffset); } - short req = decoder.decode(reqExp,buffer, bufferStartOffset, bufferLength); + short req = decoder.decode(reqExp, buffer, bufferStartOffset, bufferLength); repository.reclaimMemory(bufferLength); return req; } @@ -1167,100 +1189,20 @@ private void processFinishImportWrappedKeyCmd(APDU apdu){ importKey(apdu, keyFmt, 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. - */ - private KMAttestationCert makeCert(byte[] scratchPad) { - byte mode = KMType.FAKE_CERT; - boolean rsaCert = true; - short attestationKey = KMType.INVALID_VALUE; - boolean rsaAttest = true; - // No attestation required for symmetric keys + private KMAttestationCert makeCommonCert(byte[] scratchPad) { short alg = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, data[KEY_PARAMETERS]); - if(KMEnumTag.cast(alg).getValue() != KMType.RSA && - KMEnumTag.cast(alg).getValue() != KMType.EC){ - return null; - } - // Device unique attestation not supported - short deviceUniqueAttest = KMKeyParameters.findTag(KMType.BOOL_TAG, - KMType.DEVICE_UNIQUE_ATTESTATION, data[KEY_PARAMETERS]); - if(deviceUniqueAttest != KMType.INVALID_VALUE){ - KMException.throwIt(KMError.CANNOT_ATTEST_IDS); - } - if(KMEnumTag.cast(alg).getValue() == KMType.EC) { - rsaCert = false; - rsaAttest = false; - } + boolean rsaCert = KMEnumTag.cast(alg).getValue() == KMType.RSA; KMAttestationCert cert = seProvider.getAttestationCert(rsaCert); - // Read attestation challenge if present - short attChallenge = - KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.ATTESTATION_CHALLENGE, data[KEY_PARAMETERS]); - if(attChallenge != KMType.INVALID_VALUE){ - attChallenge = KMByteTag.cast(attChallenge).getValue(); - } + short subject = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.CERTIFICATE_SUBJECT_NAME, data[KEY_PARAMETERS]); + // If no subject name is specified then use the default subject name. if(subject == KMType.INVALID_VALUE || KMByteTag.cast(subject).length() == 0){ subject = KMByteBlob.instance(defaultSubject, (short) 0, (short) defaultSubject.length); }else{ subject = KMByteTag.cast(subject).getValue(); } - // If attestation key is given by the caller - if(data[ATTEST_KEY_BLOB] != KMType.INVALID_VALUE && - KMByteBlob.cast(data[ATTEST_KEY_BLOB]).length() > 0) { - // If no attestation challenge present then it is an error - if (attChallenge == KMType.INVALID_VALUE || KMByteBlob.cast(attChallenge).length() <= 0) { - KMException.throwIt(KMError.INVALID_ARGUMENT); - } - mode = KMType.ATTESTATION_CERT; - attestationKey = data[ATTEST_KEY_BLOB]; - alg = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, data[ATTEST_KEY_PARAMS]); - if(KMEnumTag.cast(alg).getValue() == KMType.EC){ - rsaAttest = false; - } - // If issuer is not present then it is an error - if (KMByteBlob.cast(data[ATTEST_KEY_ISSUER]).length() <= 0) { - KMException.throwIt(KMError.MISSING_ISSUER_SUBJECT_NAME); - } - // TODO KeyMint should parse issuer subject name to validate that it is valid der encoded - // string. - // Currently not done as X509 parsing not yet supported. - - short attKeyPurpose = - KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, data[ATTEST_KEY_PARAMS]); - // If the attest key's purpose is not "attest key" then error. - if (!KMEnumArrayTag.cast(attKeyPurpose).contains(KMType.ATTEST_KEY)) { - KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); - } - }else if (attChallenge != KMType.INVALID_VALUE && KMByteBlob.cast(attChallenge).length() > 0) { - // If the challenge is present and attest key is absent then it is an error. - // Note: The keymint applet does not support factory provisioned attestation keys. - KMException.throwIt(KMError.ATTESTATION_KEYS_NOT_PROVISIONED); - }else { - // Both attestation key and challenge are are absent. - short purpose = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, data[KEY_PARAMETERS]); - if(purpose != KMType.INVALID_VALUE && KMEnumArrayTag.cast(purpose).contains(KMType.SIGN)) { - // If the generated/imported key can sign then mode is self signed - mode = KMType.SELF_SIGNED_CERT; - attestationKey = data[SECRET]; - data[ATTEST_KEY_ISSUER] = subject; - }else{ - // else a fake cert i.e. certificated without signature must be generated - mode = KMType.FAKE_CERT; - attestationKey = KMType.INVALID_VALUE; - data[ATTEST_KEY_ISSUER] = subject; - } - } - cert.attestKey(attestationKey, rsaAttest,mode); - cert.attestationChallenge(attChallenge); - cert.issuer(data[ATTEST_KEY_ISSUER]); cert.subjectName(subject); - // Validity period must be specified short notBefore = KMKeyParameters.findTag(KMType.DATE_TAG, KMType.CERTIFICATE_NOT_BEFORE, data[KEY_PARAMETERS]); if(notBefore == KMType.INVALID_VALUE){ @@ -1299,10 +1241,61 @@ private KMAttestationCert makeCert(byte[] scratchPad) { KMByteBlob.cast(serialNum).add((short)0, (byte)1); } cert.serialNumber(serialNum); + return cert; + } - //Extension - if(mode == KMType.ATTESTATION_CERT){ - // Save attestation application id - must be present. +//TODO remove hwParameters when this is refactored. + private KMAttestationCert makeAttestationCert(short attKeyBlob, short attKeyParam, + short attChallenge, short issuer, short hwParameters, short swParameters, byte[] scratchPad) { + KMAttestationCert cert = makeCommonCert(scratchPad); + + // App Id and App Data, + short appId = KMType.INVALID_VALUE; + short appData = KMType.INVALID_VALUE; + if(attKeyParam != KMType.INVALID_VALUE) { + appId = + KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_ID, attKeyParam); + if (appId != KMTag.INVALID_VALUE) { + appId = KMByteTag.cast(appId).getValue(); + } + appData = + KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_DATA, attKeyParam); + if (appData != KMTag.INVALID_VALUE) { + appData = KMByteTag.cast(appData).getValue(); + } + } + //TODO remove following line + short origBlob = data[KEY_BLOB]; + short pubKey = data[PUB_KEY]; + short keyBlob = parseEncryptedKeyBlob(attKeyBlob, appId, appData, scratchPad); + short attestationKeySecret = KMArray.cast(keyBlob).get(KEY_BLOB_SECRET); + short attestParam = KMArray.cast(keyBlob).get(KEY_BLOB_PARAMS); + attestParam = KMKeyCharacteristics.cast(attestParam).getStrongboxEnforced(); + short attKeyPurpose = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, attestParam); + // If the attest key's purpose is not "attest key" then error. + if (!KMEnumArrayTag.cast(attKeyPurpose).contains(KMType.ATTEST_KEY)) { + KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); + } + // 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 = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, attestParam); + + if(KMEnumTag.cast(alg).getValue() == KMType.RSA) { + short attestationKeyPublic = KMArray.cast(keyBlob).get(KEY_BLOB_PUB_KEY); + cert.rsaAttestKey(attestationKeySecret, attestationKeyPublic, KMType.ATTESTATION_CERT); + }else{ + cert.ecAttestKey(attestationKeySecret, KMType.ATTESTATION_CERT); + } + cert.attestationChallenge(attChallenge); + cert.issuer(issuer); + //TODO remove following line + data[PUB_KEY] =pubKey; + 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) { @@ -1313,22 +1306,255 @@ private KMAttestationCert makeCert(byte[] scratchPad) { setUniqueId(cert, scratchPad); // Add Attestation Ids if present addAttestationIds(cert, scratchPad); - // Add Tags - addTags(data[HW_PARAMETERS], true, cert); - addTags(data[SW_PARAMETERS], false, cert); + + // Add Tags + addTags(hwParameters, true, cert); + addTags(swParameters, false, cert); // Add Device Boot locked status cert.deviceLocked(seProvider.isDeviceBootLocked()); // VB data cert.verifiedBootHash(getVerifiedBootHash(scratchPad)); cert.verifiedBootKey(getBootKey(scratchPad)); cert.verifiedBootState((byte)seProvider.getBootState()); + + //TODO remove the following line + makeKeyCharacteristics(scratchPad); + data[KEY_BLOB] = origBlob; + + return cert; + } + + private KMAttestationCert makeSelfSignedCert(short attPrivKey, short attPubKey, byte[] scratchPad) { + KMAttestationCert cert = makeCommonCert(scratchPad); + short alg = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, data[KEY_PARAMETERS]); + byte mode = KMType.FAKE_CERT; + if(attPrivKey != KMType.INVALID_VALUE){ + mode = KMType.SELF_SIGNED_CERT; + } + short subject = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.CERTIFICATE_SUBJECT_NAME, data[KEY_PARAMETERS]); + // 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(); } - // Public key - cert.publicKey(data[PUB_KEY]); + if(KMEnumTag.cast(alg).getValue() == KMType.RSA) { + cert.rsaAttestKey(attPrivKey, attPubKey, mode); + }else{ + cert.ecAttestKey(attPrivKey, mode); + } +// cert.attestKey(attPrivKey, rsaAttest, KMType.SELF_SIGNED_CERT); +// cert.attestKey(KMType.INVALID_VALUE, rsaAttest, KMType.FAKE_CERT); + cert.issuer(subject); + cert.subjectName(subject); + cert.publicKey(attPubKey); return cert; } + // private KMAttestationCert makeFakeCert(byte[] scratchPad) { + // KMAttestationCert cert = makeCommonCert(scratchPad); + // short alg = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, data[KEY_PARAMETERS]); + // boolean rsaAttest = KMEnumTag.cast(alg).getValue() == KMType.RSA; + // + // // If the generated/imported key can sign then mode is self signed + // short subject = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.CERTIFICATE_SUBJECT_NAME, data[KEY_PARAMETERS]); + // // If no subject name is specified then use the default subject name. + // if(subject == KMType.INVALID_VALUE || KMByteTag.cast(subject).length() == 0){ + // subject = KMByteBlob.instance(defaultSubject, (short) 0, (short) defaultSubject.length); + // }else{ + // subject = KMByteTag.cast(subject).getValue(); + // } + // cert.attestKey(KMType.INVALID_VALUE, rsaAttest,KMType.FAKE_CERT); + // cert.issuer(subject); + // cert.subjectName(subject); + // cert.publicKey(data[PUB_KEY]); + // return cert; + // } + + /** + * 1) If attestation key is present and attestation challenge is absent then it is an error. + * 2) If attestation key is absent and attestation challenge is present then it is an error as + * factory provisioned attestation key is not supported. + * 3) If both are present and issuer is absent or attest key purpose is not ATTEST_KEY then it is an error. + * 4) If the generated/imported keys are RSA or EC then validity period must be specified. + * Device Unique Attestation is not supported. + */ + // private KMAttestationCert makeCert(short attKeyBlob, short attKeyParam, byte[] scratchPad) { + // short appId = KMType.INVALID_VALUE; + // short appData = KMType.INVALID_VALUE; + // if(attKeyParam != KMType.INVALID_VALUE) { + // appId = + // KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_ID, attKeyParam); + // if (appId != KMTag.INVALID_VALUE) { + // appId = KMByteTag.cast(appId).getValue(); + // } + // appData = + // KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_DATA, attKeyParam); + // if (appData != KMTag.INVALID_VALUE) { + // appData = KMByteTag.cast(appData).getValue(); + // } + // } + // byte mode = KMType.FAKE_CERT; + // boolean rsaCert = true; + // short attestationKey = KMType.INVALID_VALUE; + // short attestParam = KMType.INVALID_VALUE; + // boolean rsaAttest = true; + // // No attestation required for symmetric keys + // short alg = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, data[KEY_PARAMETERS]); + // if(KMEnumTag.cast(alg).getValue() != KMType.RSA && + // KMEnumTag.cast(alg).getValue() != KMType.EC){ + // return null; + // } + // // Device unique attestation not supported + // short deviceUniqueAttest = KMKeyParameters.findTag(KMType.BOOL_TAG, + // KMType.DEVICE_UNIQUE_ATTESTATION, data[KEY_PARAMETERS]); + // if(deviceUniqueAttest != KMType.INVALID_VALUE){ + // KMException.throwIt(KMError.CANNOT_ATTEST_IDS); + // } + // if(KMEnumTag.cast(alg).getValue() == KMType.EC) { + // rsaCert = false; + // rsaAttest = false; + // } + // KMAttestationCert cert = seProvider.getAttestationCert(rsaCert); + // // Read attestation challenge if present + // short attChallenge = + // KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.ATTESTATION_CHALLENGE, data[KEY_PARAMETERS]); + // if(attChallenge != KMType.INVALID_VALUE){ + // attChallenge = KMByteTag.cast(attChallenge).getValue(); + // } + // short subject = KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.CERTIFICATE_SUBJECT_NAME, data[KEY_PARAMETERS]); + // // If no subject name is specified then use the default subject name. + // if(subject == KMType.INVALID_VALUE || KMByteTag.cast(subject).length() == 0){ + // subject = KMByteBlob.instance(defaultSubject, (short) 0, (short) defaultSubject.length); + // }else{ + // subject = KMByteTag.cast(subject).getValue(); + // } + // // If attestation key is given by the caller + // if(attKeyBlob != KMType.INVALID_VALUE && KMByteBlob.cast(attKeyBlob).length() > 0) { + // short keyBlob = KMType.INVALID_VALUE; + // keyBlob = parseEncryptedKeyBlob(attKeyBlob, appId, appData, scratchPad); + // + // // If no attestation challenge present then it is an error + // if (attChallenge == KMType.INVALID_VALUE || KMByteBlob.cast(attChallenge).length() <= 0) { + // KMException.throwIt(KMError.INVALID_ARGUMENT); + // } + // mode = KMType.ATTESTATION_CERT; + // attestationKey = KMArray.cast(keyBlob).get(KEY_BLOB_SECRET); + // attestParam = KMArray.cast(keyBlob).get(KEY_BLOB_PARAMS); + // attestParam = KMKeyCharacteristics.cast(attestParam).getStrongboxEnforced(); + // alg = KMKeyParameters.findTag(KMType.ENUM_TAG, KMType.ALGORITHM, attestParam); + // if(KMEnumTag.cast(alg).getValue() == KMType.EC){ + // rsaAttest = false; + // } + // // If issuer is not present then it is an error + // if (KMByteBlob.cast(data[ATTEST_KEY_ISSUER]).length() <= 0) { + // KMException.throwIt(KMError.MISSING_ISSUER_SUBJECT_NAME); + // } + // // TODO KeyMint should parse issuer subject name to validate that it is valid der encoded + // // string. + // // Currently not done as X509 parsing not yet supported. + // + // short attKeyPurpose = + // KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, attestParam); + // // If the attest key's purpose is not "attest key" then error. + // if (!KMEnumArrayTag.cast(attKeyPurpose).contains(KMType.ATTEST_KEY)) { + // KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); + // } + // }else if (attChallenge != KMType.INVALID_VALUE && KMByteBlob.cast(attChallenge).length() > 0) { + // // If the challenge is present and attest key is absent then it is an error. + // // Note: The keymint applet does not support factory provisioned attestation keys. + // KMException.throwIt(KMError.ATTESTATION_KEYS_NOT_PROVISIONED); + // }else { + // // Both attestation key and challenge are are absent. + // short purpose = KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, data[KEY_PARAMETERS]); + // if(purpose != KMType.INVALID_VALUE && KMEnumArrayTag.cast(purpose).contains(KMType.SIGN)) { + // // If the generated/imported key can sign then mode is self signed + // mode = KMType.SELF_SIGNED_CERT; + // attestationKey = data[SECRET]; + // attestParam = data[KEY_PARAMETERS]; + // data[ATTEST_KEY_ISSUER] = subject; + // }else{ + // // else a fake cert i.e. certificated without signature must be generated + // mode = KMType.FAKE_CERT; + // attestationKey = KMType.INVALID_VALUE; + // data[ATTEST_KEY_ISSUER] = subject; + // } + // } + // cert.attestKey(attestationKey, rsaAttest,mode); + // cert.attestationChallenge(attChallenge); + // cert.issuer(data[ATTEST_KEY_ISSUER]); + // cert.subjectName(subject); + // + // // Validity period must be specified + // short notBefore = KMKeyParameters.findTag(KMType.DATE_TAG, KMType.CERTIFICATE_NOT_BEFORE, data[KEY_PARAMETERS]); + // if(notBefore == KMType.INVALID_VALUE){ + // KMException.throwIt(KMError.MISSING_NOT_BEFORE); + // } + // notBefore = KMIntegerTag.cast(notBefore).getValue(); + // short notAfter = KMKeyParameters.findTag(KMType.DATE_TAG, KMType.CERTIFICATE_NOT_AFTER, data[KEY_PARAMETERS]); + // if(notAfter == KMType.INVALID_VALUE ){ + // KMException.throwIt(KMError.MISSING_NOT_AFTER); + // } + // + // notAfter = KMIntegerTag.cast(notAfter).getValue(); + // // VTS sends notBefore == Epoch. + // Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 8, (byte) 0); + // short epoch = KMInteger.instance(scratchPad, (short)0, (short)8); + // short end = KMInteger.instance(dec319999Ms, (short)0, (short)dec319999Ms.length); + // if(KMInteger.compare(notBefore, epoch) == 0){ + // cert.notBefore(KMByteBlob.instance(jan01970, (short)0, (short)jan01970.length), + // true, scratchPad); + // }else { + // cert.notBefore(notBefore, false, scratchPad); + // } + // // VTS sends notAfter == Dec 31st 9999 + // if(KMInteger.compare(notAfter, end) == 0){ + // cert.notAfter(KMByteBlob.instance(dec319999, (short)0, (short)dec319999.length), + // true, scratchPad); + // }else { + // cert.notAfter(notAfter, false, scratchPad); + // } + // // Serial number + // short serialNum = + // KMKeyParameters.findTag(KMType.BIGNUM_TAG, KMType.CERTIFICATE_SERIAL_NUM, data[KEY_PARAMETERS]); + // if (serialNum != KMType.INVALID_VALUE) { + // serialNum = KMBignumTag.cast(serialNum).getValue(); + // }else{ + // serialNum= KMByteBlob.instance((short)1); + // KMByteBlob.cast(serialNum).add((short)0, (byte)1); + // } + // cert.serialNumber(serialNum); + // + // //Extension + // if(mode == KMType.ATTESTATION_CERT){ + // // Save attestation application id - must be present. + // short attAppId = + // KMKeyParameters.findTag( KMType.BYTES_TAG, KMType.ATTESTATION_APPLICATION_ID, data[KEY_PARAMETERS]); + // if (attAppId == KMType.INVALID_VALUE) { + // KMException.throwIt(KMError.ATTESTATION_APPLICATION_ID_MISSING); + // } + // cert.extensionTag(attAppId, false); + // // unique id byte blob - uses application id and temporal month count of creation time. + // setUniqueId(cert, scratchPad); + // // Add Attestation Ids if present + // addAttestationIds(cert, scratchPad); + // // Add Tags + // addTags(data[HW_PARAMETERS], true, cert); + // addTags(data[SW_PARAMETERS], false, cert); + // // Add Device Boot locked status + // cert.deviceLocked(seProvider.isDeviceBootLocked()); + // // VB data + // cert.verifiedBootHash(getVerifiedBootHash(scratchPad)); + // cert.verifiedBootKey(getBootKey(scratchPad)); + // cert.verifiedBootState((byte)seProvider.getBootState()); + // } + // + // // Public key + // cert.publicKey(data[PUB_KEY]); + // return cert; + // } + protected short getBootKey(byte[] scratchPad){ Util.arrayFillNonAtomic(scratchPad, (short)0, VERIFIED_BOOT_KEY_SIZE, (byte)0); short len = seProvider.getVerifiedBootHash(scratchPad,(short)0); @@ -1347,7 +1573,12 @@ protected short getVerifiedBootHash(byte[] scratchPad){ return KMByteBlob.instance(scratchPad,(short)0, VERIFIED_BOOT_HASH_SIZE); } // -------------------------------- - private static void addAttestationIds(KMAttestationCert cert, byte[] scratchPad) { + // 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) { final short[] attTags = new short[]{ KMType.ATTESTATION_ID_BRAND, @@ -1360,17 +1591,28 @@ private static void addAttestationIds(KMAttestationCert cert, byte[] scratchPad) KMType.ATTESTATION_ID_SERIAL }; byte index = 0; - //short attIdTag; + short attIdTag; + short attIdTagValue; + short storedAttIdLen; while (index < (short) attTags.length) { - //attIdTag = repository.getAttId(mapToAttId(attTags[index])); - if(KMKeyParameters.findTag(KMType.BYTES_TAG,attTags[index],data[KEY_PARAMETERS]) == KMType.INVALID_VALUE){ - continue; - } - short attIdTagLen = seProvider.getAttestationId(attTags[index],scratchPad, (short)0); - if (attIdTagLen != 0) { - short blob = KMByteBlob.instance(scratchPad, (short)0, attIdTagLen); - //attIdTag = KMByteTag.instance(attTags[index], blob); - //cert.extensionTag(attIdTag, true); + attIdTag = KMKeyParameters.findTag(KMType.BYTES_TAG, attTags[index], data[KEY_PARAMETERS]); + if (attIdTag != KMType.INVALID_VALUE) { + attIdTagValue = KMByteTag.cast(attIdTag).getValue(); + storedAttIdLen = seProvider.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.INVALID_TAG); + } + short blob = KMByteBlob.instance(scratchPad, (short)0, storedAttIdLen); cert.extensionTag(KMByteTag.instance(attTags[index], blob), true); } index++; @@ -1485,6 +1727,9 @@ private void processFinishOperationCmd(APDU apdu) { 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); @@ -1576,6 +1821,29 @@ private void finishAesDesOperation(KMOperationState op){ data[OUTPUT_DATA] = outData; } + private void finishKeyAgreementOperation(KMOperationState op, byte[] scratchPad) { + try { + KMPKCS8Decoder pkcs8 = KMPKCS8Decoder.instance(); + short blob = pkcs8.decodeEcSubjectPublicKeyInfo(data[INPUT_DATA]); + short len = op.getOperation().finish( + KMByteBlob.cast(blob).getBuffer(), + KMByteBlob.cast(blob).getStartOff(), + KMByteBlob.cast(blob).length(), + scratchPad, + (short) 0 + ); + data[OUTPUT_DATA] = KMByteBlob.instance((short) 32); + Util.arrayCopyNonAtomic( + scratchPad, + (short) 0, + KMByteBlob.cast(data[OUTPUT_DATA]).getBuffer(), + KMByteBlob.cast(data[OUTPUT_DATA]).getStartOff(), + len); + } catch (CryptoException e) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + } + private void finishSigningVerifyingOperation(KMOperationState op, byte[] scratchPad) { Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) 256, (byte) 0); switch (op.getAlgorithm()) { @@ -2030,6 +2298,9 @@ private void processBeginOperationCmd(APDU apdu) { case KMType.DECRYPT: beginCipherOperation(op); break; + case KMType.AGREE_KEY: + beginKeyAgreementOperation(op); + break; default: KMException.throwIt(KMError.UNIMPLEMENTED); break; @@ -2080,16 +2351,26 @@ private void authorizePurpose(KMOperationState op) { switch (op.getAlgorithm()) { case KMType.AES: case KMType.DES: - if (op.getPurpose() == KMType.SIGN || op.getPurpose() == KMType.VERIFY) { + if (op.getPurpose() == KMType.SIGN || op.getPurpose() == KMType.VERIFY || + op.getPurpose() == KMType.AGREE_KEY) { KMException.throwIt(KMError.UNSUPPORTED_PURPOSE); } break; case KMType.EC: - case KMType.HMAC: 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; } @@ -2134,7 +2415,7 @@ private void authorizeDigest(KMOperationState op) { break; case KMType.EC: case KMType.HMAC: - if (param == KMType.INVALID_VALUE) { + if (op.getPurpose() != KMType.AGREE_KEY && param == KMType.INVALID_VALUE) { KMException.throwIt(KMError.UNSUPPORTED_DIGEST); } break; @@ -2203,6 +2484,7 @@ private void authorizePadding(KMOperationState op) { if (mgfDigest != KMType.SHA1 && mgfDigest != KMType.SHA2_256) { KMException.throwIt(KMError.UNSUPPORTED_MGF_DIGEST); } + op.setMgfDigest((byte) mgfDigest); } } op.setPadding((byte) param); @@ -2384,6 +2666,25 @@ private void authorizeAndBeginOperation(KMOperationState op, byte[] scratchPad) } } + 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: @@ -2395,6 +2696,7 @@ private void beginCipherOperation(KMOperationState op) { (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(), @@ -2449,6 +2751,7 @@ private void beginSignVerifyOperation(KMOperationState op) { (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(), @@ -2471,6 +2774,7 @@ private void beginSignVerifyOperation(KMOperationState op) { (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(), @@ -2695,8 +2999,8 @@ private void importKey(APDU apdu, short keyFmt, byte[] scratchPad) { KMException.throwIt(KMError.UNSUPPORTED_ALGORITHM); break; } - makeKeyCharacteristics(scratchPad); - generateAttestation(scratchPad); + makeKeyCharacteristics( scratchPad); + generateAttestation(data[ATTEST_KEY_BLOB], data[ATTEST_KEY_PARAMS],scratchPad); createEncryptedKeyBlob(scratchPad); // prepare the response short resp = KMArray.instance((short) 4); @@ -3100,7 +3404,7 @@ private void processGenerateKey(APDU apdu) { // create key blob and associated attestation. data[ORIGIN] = KMType.GENERATED; makeKeyCharacteristics(scratchPad); - generateAttestation(scratchPad); + generateAttestation(data[ATTEST_KEY_BLOB], data[ATTEST_KEY_PARAMS],scratchPad); createEncryptedKeyBlob(scratchPad); // prepare the response short resp = KMArray.instance((short) 4); @@ -3111,8 +3415,98 @@ private void processGenerateKey(APDU apdu) { sendOutgoing(apdu, resp); } - private void generateAttestation(byte[] scratchPad){ - KMAttestationCert cert = makeCert(scratchPad); + 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.SIGN, data[KEY_PARAMETERS])) { + mode = KMType.SELF_SIGNED_CERT; + }else{ + mode = KMType.FAKE_CERT; + } + } + return mode; + } + + private void generateAttestation(short attKeyBlob, short attKeyParam, byte[] scratchPad){ + // Device unique attestation not supported + 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],data[SB_PARAMETERS], + data[SW_PARAMETERS], scratchPad); + break; + case KMType.SELF_SIGNED_CERT: + //cert = makeCert(attKeyBlob, attKeyParam, scratchPad); + cert = makeSelfSignedCert(data[SECRET], data[PUB_KEY],scratchPad); + break; + case KMType.FAKE_CERT: + //cert = makeCert(attKeyBlob, attKeyParam, scratchPad); + cert = makeSelfSignedCert(KMType.INVALID_VALUE, data[PUB_KEY], scratchPad); + break; + default: + data[CERTIFICATE] = KMArray.instance((short)0); + return; + } + // Allocate memory + short certData = KMByteBlob.instance(MAX_CERT_SIZE); + + cert.buffer(KMByteBlob.cast(certData).getBuffer(), + KMByteBlob.cast(certData).getStartOff(), + KMByteBlob.cast(certData).length()); + + // Build the certificate - this will sign the cert + cert.build(); + // Adjust the start and length of the certificate in the blob + KMByteBlob.cast(certData).setStartOff(cert.getCertStart()); + KMByteBlob.cast(certData).setLength(cert.getCertLength()); + // Initialize the certificate as array of blob + data[CERTIFICATE] = KMArray.instance((short)1); + KMArray.cast(data[CERTIFICATE]).add((short)0, certData); + +/* + boolean rsaCert = KMEnumTag.cast(alg).getValue() == KMType.EC; + short appId = KMType.INVALID_VALUE; + short appData = KMType.INVALID_VALUE; + if(attKeyParam != KMType.INVALID_VALUE) { + appId = + KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_ID, attKeyParam); + if (appId != KMTag.INVALID_VALUE) { + appId = KMByteTag.cast(appId).getValue(); + } + appData = + KMKeyParameters.findTag(KMType.BYTES_TAG, KMType.APPLICATION_DATA, attKeyParam); + if (appData != KMTag.INVALID_VALUE) { + appData = KMByteTag.cast(appData).getValue(); + } + } + + KMAttestationCert cert = makeCert(attKeyBlob, appId, appData, scratchPad); if(cert == null) {// No certificate data[CERTIFICATE] = KMArray.instance((short)0); return; @@ -3132,6 +3526,7 @@ private void generateAttestation(byte[] scratchPad){ // Initialize the certificate as array of blob data[CERTIFICATE] = KMArray.instance((short)1); KMArray.cast(data[CERTIFICATE]).add((short)0, certData); + */ } /** @@ -3324,7 +3719,7 @@ private void checkVersionAndPatchLevel(byte[] scratchPad) { } } } - protected short getBootPatchLevel(byte[] scratchPad){ + protected static short getBootPatchLevel(byte[] scratchPad){ Util.arrayFillNonAtomic(scratchPad,(short)0, BOOT_PATCH_LVL_SIZE, (byte)0); short len = seProvider.getBootPatchLevel(scratchPad,(short)0); if(len != BOOT_PATCH_LVL_SIZE){ @@ -3333,7 +3728,7 @@ protected short getBootPatchLevel(byte[] scratchPad){ return KMInteger.uint_32(scratchPad, (short)0); } - private void makeKeyCharacteristics(byte[] scratchPad) { + private static void makeKeyCharacteristics(byte[] scratchPad) { short osVersion = repository.getOsVersion(); short osPatch = repository.getOsPatch(); short vendorPatch = repository.getVendorPatchLevel(); @@ -3349,7 +3744,7 @@ private void makeKeyCharacteristics(byte[] scratchPad) { KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).setTeeEnforced(data[TEE_PARAMETERS]); } - private void createEncryptedKeyBlob(byte[] scratchPad) { + private static void createEncryptedKeyBlob(byte[] scratchPad) { // make root of trust blob data[ROT] = readROT(scratchPad); if (data[ROT] == KMType.INVALID_VALUE) { @@ -3366,26 +3761,38 @@ private void createEncryptedKeyBlob(byte[] scratchPad) { 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_PARAMS, data[KEY_CHARACTERISTICS]); + + //TODO remove the following temporary creation of keyblob. + 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); + //KMArray.cast(data[KEY_BLOB]).add(KEY_BLOB_PARAMS, data[KEY_CHARACTERISTICS]); + // allocate reclaimable memory. short buffer = repository.alloc((short) 1024); short keyBlob = encoder.encode(data[KEY_BLOB], repository.getHeap(), buffer); data[KEY_BLOB] = KMByteBlob.instance(repository.getHeap(), buffer, keyBlob); } - private void parseEncryptedKeyBlob(short keyBlob, short appId, short appData, byte[] scratchPad) { + private short parseEncryptedKeyBlob(short keyBlob, short appId, short appData, byte[] scratchPad) { + short parsedBlob = KMType.INVALID_VALUE; short rot = readROT(scratchPad); if (rot == KMType.INVALID_VALUE) { KMException.throwIt(KMError.UNKNOWN_ERROR); } try { - short parsedBlob = decoder.decodeArray(keyBlob(), + parsedBlob = decoder.decodeArray(keyBlob(), KMByteBlob.cast(keyBlob).getBuffer(), KMByteBlob.cast(keyBlob).getStartOff(), KMByteBlob.cast(keyBlob).length()); if (KMArray.cast(parsedBlob).length() < 4) { KMException.throwIt(KMError.INVALID_KEY_BLOB); } + // initialize data data[SECRET] = KMArray.cast(parsedBlob).get(KEY_BLOB_SECRET); data[NONCE]= KMArray.cast(parsedBlob).get(KEY_BLOB_NONCE); @@ -3395,22 +3802,27 @@ private void parseEncryptedKeyBlob(short keyBlob, short appId, short appData, by if (KMArray.cast(parsedBlob).length() == 5) { data[PUB_KEY] = KMArray.cast(parsedBlob).get(KEY_BLOB_PUB_KEY); } + data[TEE_PARAMETERS] = KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getTeeEnforced(); data[SB_PARAMETERS] = KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getStrongboxEnforced(); + data[SW_PARAMETERS] = KMKeyCharacteristics.cast(data[KEY_CHARACTERISTICS]).getKeystoreEnforced(); data[HW_PARAMETERS] = KMKeyParameters.makeHwEnforced(data[SB_PARAMETERS], data[TEE_PARAMETERS]); + data[HIDDEN_PARAMETERS] = KMKeyParameters.makeHidden(appId, appData, rot, scratchPad); data[KEY_BLOB] = parsedBlob; // make auth data makeAuthData(scratchPad); // Decrypt Secret and verify auth tag decryptSecret(scratchPad); + KMArray.cast(parsedBlob).add(KEY_BLOB_SECRET, data[SECRET]); } catch (Exception e) { KMException.throwIt(KMError.INVALID_KEY_BLOB); } + return parsedBlob; } // Read RoT - public short readROT(byte[] scratchPad) { + public static short readROT(byte[] scratchPad) { Util.arrayFillNonAtomic(scratchPad,(short)0, (short)256,(byte)0); short len = seProvider.getBootKey(scratchPad, (short)0); len += seProvider.getVerifiedBootHash(scratchPad, (short)len); @@ -3451,7 +3863,7 @@ private void decryptSecret(byte[] scratchPad) { KMByteBlob.instance(scratchPad, (short) 0, KMByteBlob.cast(data[SECRET]).length()); } - private void encryptSecret(byte[] scratchPad) { + private static void encryptSecret(byte[] scratchPad) { // make nonce data[NONCE] = KMByteBlob.instance((short) AES_GCM_NONCE_LENGTH); data[AUTH_TAG] = KMByteBlob.instance(AES_GCM_AUTH_TAG_LENGTH); @@ -3577,7 +3989,7 @@ private static short deriveKey(byte[] scratchPad) { return signLen; } - protected static void sendError(APDU apdu, short err) { + public static void sendError(APDU apdu, short err) { short resp = KMArray.instance((short)1); err = KMError.translate(err); short error = KMInteger.uint_16(err); @@ -3623,4 +4035,117 @@ private void add(byte[] buf, short op1, short op2, short result) { public void powerReset() { //TODO handle power reset signal. } + + 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); + } + 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 offset = repository.allocReclaimableMemory(maxLen); + short len = encoder.encode(object, repository.getHeap(), offset); + 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(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)) { + alg = expLeafCertAlg; + } + if (!KMCoseKey.cast(ptr2).isDataValid(KMCose.COSE_KEY_TYPE_EC2, KMType.INVALID_VALUE, alg, + KMType.INVALID_VALUE, KMCose.COSE_ECCURVE_256)) { + KMException.throwIt(KMError.STATUS_FAILED); + } + if (prevCoseKey == 0) { + prevCoseKey = ptr2; + } + // Get the public key. + keySize = KMCoseKey.cast(prevCoseKey).getEcdsa256PublicKey(scratchPad, (short) 0); + if (keySize != 65) { + KMException.throwIt(KMError.STATUS_FAILED); + } + if (validateEekRoot && (index == 0)) { + boolean found = false; + // In prod mode the first pubkey should match a well-known Google public key. + for (short i = 0; i < (short) authorizedEekRoots.length; i++) { + if (0 == Util.arrayCompare(scratchPad, (short) 0, (byte[]) authorizedEekRoots[i], + (short) 0, (short) ((byte[]) authorizedEekRoots[i]).length)) { + found = true; + break; + } + } + if (!found) { + KMException.throwIt(KMError.STATUS_FAILED); + } + } + // Validate signature. + signStructure = + KMCose.constructCoseSignStructure( + KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_PROTECTED_PARAMS_OFFSET), + KMByteBlob.instance((short) 0), + KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_PAYLOAD_OFFSET)); + encodedLen = KMKeymasterApplet.encodeToApduBuffer(signStructure, scratchPad, + keySize, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + + if (!seProvider.ecVerify256(scratchPad, (short) 0, keySize, scratchPad, keySize, encodedLen, + KMByteBlob.cast(KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_SIGNATURE_OFFSET)).getBuffer(), + KMByteBlob.cast(KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_SIGNATURE_OFFSET)).getStartOff(), + KMByteBlob.cast(KMArray.cast(ptr1).get(KMCose.COSE_SIGN1_SIGNATURE_OFFSET)).length())) { + KMException.throwIt(KMError.STATUS_FAILED); + } + prevCoseKey = ptr2; + } + return prevCoseKey; + } } diff --git a/Applet/src/com/android/javacard/keymaster/KMMap.java b/Applet/src/com/android/javacard/keymaster/KMMap.java new file mode 100644 index 00000000..4b559fa3 --- /dev/null +++ b/Applet/src/com/android/javacard/keymaster/KMMap.java @@ -0,0 +1,153 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +public class KMMap extends KMType { + public static final short ANY_MAP_LENGTH = 0x1000; + private static final short 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, 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/Applet/src/com/android/javacard/keymaster/KMNInteger.java b/Applet/src/com/android/javacard/keymaster/KMNInteger.java new file mode 100644 index 00000000..c9f8e5ab --- /dev/null +++ b/Applet/src/com/android/javacard/keymaster/KMNInteger.java @@ -0,0 +1,124 @@ +/* + * Copyright(C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +public class KMNInteger extends KMInteger { + private static KMNInteger prototype; + public static final byte SIGNED_MASK = (byte) 0x80; + + 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; + } + + @Override + protected short getBaseOffset() { + return instanceTable[KM_NEG_INTEGER_OFFSET]; + } + + public static boolean isSignedInteger(byte[] num, short offset) { + byte val = num[offset]; + return SIGNED_MASK == (val & SIGNED_MASK); + } +} diff --git a/Applet/src/com/android/javacard/keymaster/KMOperation.java b/Applet/src/com/android/javacard/keymaster/KMOperation.java index 3132e4b3..f21977db 100644 --- a/Applet/src/com/android/javacard/keymaster/KMOperation.java +++ b/Applet/src/com/android/javacard/keymaster/KMOperation.java @@ -29,7 +29,7 @@ short update(byte[] inputDataBuf, short inputDataStart, short inputDataLength, // Used for signature operations short update(byte[] inputDataBuf, short inputDataStart, short inputDataLength); - // Used for finishing cipher operations. + // Used for finishing cipher operations or ecdh keyAgreement. short finish(byte[] inputDataBuf, short inputDataStart, short inputDataLength, byte[] outputDataBuf, short outputDataStart); diff --git a/Applet/src/com/android/javacard/keymaster/KMOperationState.java b/Applet/src/com/android/javacard/keymaster/KMOperationState.java index 203fa937..5bd532c8 100644 --- a/Applet/src/com/android/javacard/keymaster/KMOperationState.java +++ b/Applet/src/com/android/javacard/keymaster/KMOperationState.java @@ -40,8 +40,9 @@ public class KMOperationState { 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; public static final byte OPERATION_HANDLE_SIZE = 8; - public static final byte DATA_SIZE = 8; + public static final byte DATA_SIZE = 9; public static final byte AUTH_TIME_SIZE = 8; // short type @@ -142,6 +143,10 @@ public void reset() { } Util.arrayFillNonAtomic(opHandle, (short) 0, OPERATION_HANDLE_SIZE, (byte) 0); Util.arrayFillNonAtomic(authTime, (short) 0, AUTH_TIME_SIZE, (byte) 0); + + if(null != operation[0]) + ((KMOperation)operation[0]).abort(); + operation[0] = null; /* dFlag = false; @@ -290,11 +295,19 @@ public short getDigest() { return data[DIGEST]; } + public short getMgfDigest() { + return data[MGF_DIGEST]; + } + public void setDigest(byte digest) { data[DIGEST] = digest; // dataUpdated(); } + public void setMgfDigest(byte mgfDigest) { + data[MGF_DIGEST] = mgfDigest; + } + public boolean isAesGcmUpdateAllowed() { return (data[FLAGS] & AES_GCM_UPDATE_ALLOWED) != 0; } diff --git a/Applet/src/com/android/javacard/keymaster/KMPKCS8Decoder.java b/Applet/src/com/android/javacard/keymaster/KMPKCS8Decoder.java index a88ebac2..efc7d66d 100644 --- a/Applet/src/com/android/javacard/keymaster/KMPKCS8Decoder.java +++ b/Applet/src/com/android/javacard/keymaster/KMPKCS8Decoder.java @@ -45,6 +45,29 @@ public short decodeEc(short blob){ return decodeEcPrivateKey((short)1); } + public short decodeEcSubjectPublicKeyInfo(short blob) { + init(blob); + header(ASN1_SEQUENCE); + short len = header(ASN1_SEQUENCE); + short ecPublicInfo = KMByteBlob.instance(len); + getBytes(ecPublicInfo); + if(Util.arrayCompare( + KMByteBlob.cast(ecPublicInfo).getBuffer(), + KMByteBlob.cast(ecPublicInfo).getStartOff(), + EC_ALGORITHM, + (short)0,KMByteBlob.cast(ecPublicInfo).length()) !=0){ + KMException.throwIt(KMError.UNKNOWN_ERROR); + } + len = header(ASN1_BIT_STRING); + if(len < 1) KMException.throwIt(KMError.UNKNOWN_ERROR); + // TODO need to handle if unused bits are not zero + byte unusedBits = getByte(); + if(unusedBits != 0) KMException.throwIt(KMError.UNIMPLEMENTED); + short pubKey = KMByteBlob.instance((short)(len -1)); + getBytes(pubKey); + return pubKey; + } + //Seq[Int,Int,Int,Int,] public short decodeRsaPrivateKey(short version){ short resp = KMArray.instance((short)3); @@ -57,6 +80,7 @@ public short decodeRsaPrivateKey(short version){ len = header(ASN1_INTEGER); short modulus = KMByteBlob.instance(len); getBytes(modulus); + updateModulus(modulus); len = header(ASN1_INTEGER); short pubKey = KMByteBlob.instance(len); getBytes(pubKey); @@ -68,6 +92,16 @@ public short decodeRsaPrivateKey(short version){ KMArray.cast(resp).add((short)2, privKey); return resp; } + + private void updateModulus(short blob) { + byte[] buffer = KMByteBlob.cast(blob).getBuffer(); + short startOff = KMByteBlob.cast(blob).getStartOff(); + short len = KMByteBlob.cast(blob).length(); + if(0 == buffer[startOff] && len > 256) { + KMByteBlob.cast(blob).setStartOff(++startOff); + KMByteBlob.cast(blob).setLength(--len); + } + } // Seq [Int, Blob] public void decodeCommon(short version, byte[] alg){ diff --git a/Applet/src/com/android/javacard/keymaster/KMRepository.java b/Applet/src/com/android/javacard/keymaster/KMRepository.java index ced3ec48..1be6c37d 100644 --- a/Applet/src/com/android/javacard/keymaster/KMRepository.java +++ b/Applet/src/com/android/javacard/keymaster/KMRepository.java @@ -29,7 +29,7 @@ */ public class KMRepository implements KMUpgradable { - public static final short HEAP_SIZE = 10000; + public static final short HEAP_SIZE = 15000; // Data table configuration public static final short DATA_INDEX_SIZE = 12; @@ -60,7 +60,7 @@ public class KMRepository implements KMUpgradable { // Class Attributes private byte[] heap; - private short heapIndex; + private short[] heapIndex; private byte[] dataTable; private short dataIndex; private short reclaimIndex; @@ -75,7 +75,7 @@ public static KMRepository instance() { public KMRepository(boolean isUpgrading) { newDataTable(isUpgrading); heap = JCSystem.makeTransientByteArray(HEAP_SIZE, JCSystem.CLEAR_ON_RESET); - heapIndex = 0; + heapIndex = JCSystem.makeTransientShortArray((short)1, JCSystem.CLEAR_ON_RESET); reclaimIndex = HEAP_SIZE; //Initialize the device locked status @@ -117,8 +117,8 @@ public void onProcess() { } public void clean() { - Util.arrayFillNonAtomic(heap, (short) 0, heapIndex, (byte) 0); - heapIndex = 0; + Util.arrayFillNonAtomic(heap, (short) 0, heapIndex[0], (byte) 0); + heapIndex[0] = 0; reclaimIndex = HEAP_SIZE; } @@ -132,7 +132,7 @@ public void onSelect() { // 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 - length)) <= heapIndex) + if ((((short) (reclaimIndex - length)) <= heapIndex[0]) || (length >= HEAP_SIZE / 2)) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } @@ -142,28 +142,28 @@ public short allocReclaimableMemory(short length) { // Reclaims the memory back. public void reclaimMemory(short length) { - if (reclaimIndex < heapIndex) { + if (reclaimIndex < heapIndex[0]) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } reclaimIndex += length; } public short allocAvailableMemory() { - if (heapIndex >= heap.length) { + if (heapIndex[0] >= heap.length) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } - short index = heapIndex; - heapIndex = (short) heap.length; + short index = heapIndex[0]; + heapIndex[0] = (short) heap.length; return index; } public short alloc(short length) { - if ((((short) (heapIndex + length)) > heap.length) || - (((short) (heapIndex + length)) > reclaimIndex)) { + if ((((short) (heapIndex[0] + length)) > heap.length) || + (((short) (heapIndex[0] + length)) > reclaimIndex)) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } - heapIndex += length; - return (short) (heapIndex - length); + heapIndex[0] += length; + return (short) (heapIndex[0] - length); } private short dataAlloc(short length) { diff --git a/Applet/src/com/android/javacard/keymaster/KMSEProvider.java b/Applet/src/com/android/javacard/keymaster/KMSEProvider.java index 95027d0e..9dbc0413 100644 --- a/Applet/src/com/android/javacard/keymaster/KMSEProvider.java +++ b/Applet/src/com/android/javacard/keymaster/KMSEProvider.java @@ -361,6 +361,104 @@ short ecSign256( byte[] outputDataBuf, short outputDataStart); + /** + * Implementation of HKDF as per RFC5869 https://datatracker.ietf.org/doc/html/rfc5869#section-2 + * + * @param ikm is the buffer containing input key material. + * @param ikmOff is the start of the input key. + * @param ikmLen is the length of the input key. + * @param salt is the buffer containing the salt. + * @param saltOff is the start of the salt buffer. + * @param saltLen is the length of the salt buffer. + * @param info is the buffer containing the application specific information + * @param infoOff is the start of the info buffer. + * @param infoLen is the length of the info buffer. + * @param out is the output buffer. + * @param outOff is the start of the output buffer. + * @param outLen is the length of the expected out buffer. + * @return Length of the out buffer which is outLen. + */ + short hkdf( + byte[] ikm, + short ikmOff, + short ikmLen, + byte[] salt, + short saltOff, + short saltLen, + byte[] info, + short infoOff, + short infoLen, + byte[] out, + short outOff, + short outLen); + + /** + * This function performs ECDH key agreement and generates a secret. + * + * @param privKey is the buffer containing the private key from first party. + * @param privKeyOff is the offset of the private key buffer. + * @param privKeyLen is the length of the private key buffer. + * @param publicKey is the buffer containing the public key from second party. + * @param publicKeyOff is the offset of the public key buffer. + * @param publicKeyLen is the length of the public key buffer. + * @param secret is the output buffer. + * @param secretOff is the offset of the output buffer. + * @return The length of the secret. + */ + short ecdhKeyAgreement( + byte[] privKey, + short privKeyOff, + short privKeyLen, + byte[] publicKey, + short publicKeyOff, + short publicKeyLen, + byte[] secret, + short secretOff); + + /** + * This is a oneshort operation that verifies the data using EC public key + * + * @param pubKey is the public key buffer. + * @param pubKeyOffset is the start of the public key buffer. + * @param pubKeyLen is the length of the public key. + * @param inputDataBuf is the buffer of the input data. + * @param inputDataStart is the start of the input data buffer. + * @param inputDataLength is the length of the input data buffer in bytes. + * @param signatureDataBuf is the buffer the signature input data. + * @param signatureDataStart is the start of the signature input data. + * @param signatureDataLen is the length of the signature input data. + * @return true if verification is successful, otherwise false. + */ + boolean ecVerify256( + byte[] pubKey, + short pubKeyOffset, + short pubKeyLen, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] signatureDataBuf, + short signatureDataStart, + short signatureDataLen); + + /** + * This is a oneshot operation that signs the data using device unique key. + * + * @param ecPrivKey instance of KMECDeviceUniqueKey to sign the input data. + * @param inputDataBuf is the buffer of the input data. + * @param inputDataStart is the start of the input data buffer. + * @param inputDataLength is the length of the input data buffer in bytes. + * @param outputDataBuf is the output buffer that contains the signature. + * @param outputDataStart is the start of the output data buffer. + * @return length of the decrypted data. + */ + short ecSign256( + KMDeviceUniqueKey ecPrivKey, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] outputDataBuf, + short outputDataStart); + short ecSign256(byte[] secret, short secretStart, short secretLength, byte[] inputDataBuf, short inputDataStart, short inputDataLength, byte[] outputDataBuf, short outputDataStart); @@ -428,6 +526,7 @@ KMOperation initSymmetricOperation( * @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. @@ -441,6 +540,7 @@ KMOperation initAsymmetricOperation( byte alg, byte padding, byte digest, + byte mgfDigest, byte[] privKeyBuf, short privKeyStart, short privKeyLength, @@ -503,6 +603,7 @@ KMOperation initAsymmetricOperation( /** * Returns the value of the attestation id. + * * @param tag - attestation id tag key as defined KMType. * @param buffer - memorey buffer in which value of the id must be copied * @param start - start offset in the buffer @@ -531,7 +632,8 @@ KMOperation initAsymmetricOperation( short getBootState(); /** - * Returns true if device bootloader is locked. Part of RoT. Part of data sent by the aosp bootloader. + * Returns true if device bootloader is locked. Part of RoT. Part of data sent by the aosp + * bootloader. */ boolean isDeviceBootLocked(); @@ -540,4 +642,68 @@ KMOperation initAsymmetricOperation( */ short getBootPatchLevel(byte[] buffer, short start); + /** + * Creates an ECKey instance and sets the public and private keys to it. + * + * @param testMode to indicate if current execution is for test or production. + * @param pubKey buffer containing the public key. + * @param pubKeyOff public key buffer start offset. + * @param pubKeyLen public key buffer length. + * @param privKey buffer containing the private key. + * @param privKeyOff private key buffer start offset. + * @param privKeyLen private key buffer length. + * @return instance of KMDeviceUniqueKey. + */ + KMDeviceUniqueKey createDeviceUniqueKey(boolean testMode, + byte[] pubKey, short pubKeyOff, short pubKeyLen, + byte[] privKey, short privKeyOff, short privKeyLen); + + /** + * Returns the instance KMDeviceUnique if it is created. + * + * @param testMode Indicates if current execution is for test or production. + * @return instance of KMDeviceUniqueKey if present; null otherwise. + */ + KMDeviceUniqueKey getDeviceUniqueKey(boolean testMode); + + /** + * Persists the additional certificate chain in persistent memory. + * + * @param buf buffer containing the cbor encoded additional certificate chain. + * @param offset start offset of the buffer. + * @param len length of the buffer. + */ + void persistAdditionalCertChain(byte[] buf, short offset, short len); + + /** + * Returns the additional certificate chain length. + * + * @return length of the encoded additional certificate chain. + */ + short getAdditionalCertChainLength(); + + /** + * Returns the additional certificate chain. + * + * @return additional cert chain. + */ + byte[] getAdditionalCertChain(); + + /** + * Generate boot certificate chain. + * + * @param testMode to indicate if current execution is for test or production. + * @param scratchPad buffer to store temporary results. + * @return instance of the boot certificate chin. + */ + short generateBcc(boolean testMode, byte[] scratchPad); + + /** + * Returns the boot certificate chain. + * + * @return boot certificate chain. + */ + byte[] getBootCertificateChain(); + + } diff --git a/Applet/src/com/android/javacard/keymaster/KMSimpleValue.java b/Applet/src/com/android/javacard/keymaster/KMSimpleValue.java new file mode 100644 index 00000000..794f3f92 --- /dev/null +++ b/Applet/src/com/android/javacard/keymaster/KMSimpleValue.java @@ -0,0 +1,69 @@ +package com.android.javacard.keymaster; + +import javacard.framework.ISO7816; +import javacard.framework.ISOException; +import javacard.framework.Util; + +public class KMSimpleValue extends KMType { + private static KMSimpleValue prototype; + + public static final byte FALSE = (byte) 20; + public static final byte TRUE = (byte) 21; + public static final byte NULL = (byte) 22; + + + 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 short length() { + return Util.getShort(heap, (short) (instanceTable[KM_SIMPLE_VALUE_OFFSET] + 1)); + } + + 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; + } + + public byte getValue() { + return heap[(short) (instanceTable[KM_SIMPLE_VALUE_OFFSET] + 3)]; + } + + private static boolean isSimpleValueValid(byte value) { + switch (value) { + case TRUE: + case FALSE: + case NULL: + break; + default: + return false; + } + return true; + } +} diff --git a/Applet/src/com/android/javacard/keymaster/KMTextString.java b/Applet/src/com/android/javacard/keymaster/KMTextString.java new file mode 100644 index 00000000..310cfa01 --- /dev/null +++ b/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 short 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/Applet/src/com/android/javacard/keymaster/KMType.java b/Applet/src/com/android/javacard/keymaster/KMType.java index 4f6008c7..a1352c78 100644 --- a/Applet/src/com/android/javacard/keymaster/KMType.java +++ b/Applet/src/com/android/javacard/keymaster/KMType.java @@ -44,6 +44,20 @@ public abstract class KMType { public static final byte VERIFICATION_TOKEN_TYPE = 0x09; public static final byte HMAC_SHARING_PARAM_TYPE = 0x0A; public static final byte X509_CERT = 0x0B; + public static final byte NEG_INTEGER_TYPE = 0x0C; + public static final byte TEXT_STRING_TYPE = 0x0D; + public static final byte MAP_TYPE = 0x0E; + public static final byte COSE_KEY_TYPE = 0x0F; + public static final byte COSE_PAIR_TAG_TYPE = 0x10; + public static final byte COSE_PAIR_INT_TAG_TYPE = 0x20; + public static final byte COSE_PAIR_NEG_INT_TAG_TYPE = 0x30; + public static final byte COSE_PAIR_BYTE_BLOB_TAG_TYPE = 0x40; + public static final byte COSE_PAIR_COSE_KEY_TAG_TYPE = 0x60; + public static final byte COSE_PAIR_SIMPLE_VALUE_TAG_TYPE = 0x70; + public static final byte COSE_PAIR_TEXT_STR_TAG_TYPE = (byte) 0x80; + public static final byte SIMPLE_VALUE_TYPE = (byte) 0x90; + public static final byte COSE_HEADERS_TYPE = (byte) 0xA0; + public static final byte COSE_CERT_PAYLOAD_TYPE = (byte) 0xB0; // Tag Types public static final short INVALID_TAG = 0x0000; public static final short ENUM_TAG = 0x1000; @@ -292,6 +306,8 @@ public abstract class KMType { 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; @@ -309,7 +325,20 @@ public abstract class KMType { 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_BIGNUM_TAG_OFFSET = KM_TYPE_BASE_OFFSET + 15; + public static final byte KM_NEG_INTEGER_OFFSET = KM_TYPE_BASE_OFFSET + 15; + public static final byte KM_TEXT_STRING_OFFSET = KM_TYPE_BASE_OFFSET + 16; + public static final byte KM_MAP_OFFSET = KM_TYPE_BASE_OFFSET + 17; + public static final byte KM_COSE_KEY_OFFSET = KM_TYPE_BASE_OFFSET + 18; + public static final byte KM_COSE_KEY_INT_VAL_OFFSET = KM_TYPE_BASE_OFFSET + 19; + public static final byte KM_COSE_KEY_NINT_VAL_OFFSET = KM_TYPE_BASE_OFFSET + 20; + public static final byte KM_COSE_KEY_BYTE_BLOB_VAL_OFFSET = KM_TYPE_BASE_OFFSET + 21; + public static final byte KM_COSE_KEY_COSE_KEY_VAL_OFFSET = KM_TYPE_BASE_OFFSET + 22; + public static final byte KM_COSE_KEY_SIMPLE_VAL_OFFSET = KM_TYPE_BASE_OFFSET + 23; + public static final byte KM_SIMPLE_VALUE_OFFSET = KM_TYPE_BASE_OFFSET + 24; + public static final byte KM_COSE_HEADERS_OFFSET = KM_TYPE_BASE_OFFSET + 25; + public static final byte KM_COSE_KEY_TXT_STR_VAL_OFFSET = KM_TYPE_BASE_OFFSET + 26; + public static final byte KM_COSE_CERT_PAYLOAD_OFFSET = KM_TYPE_BASE_OFFSET + 27; + public static final byte KM_BIGNUM_TAG_OFFSET = KM_TYPE_BASE_OFFSET + 28; // Attestation types public static final byte NO_CERT = 0; @@ -325,7 +354,7 @@ public abstract class KMType { protected static KMRepository repository; protected static byte[] heap; // Instance table - public static final byte INSTANCE_TABLE_SIZE = 16; + public static final byte INSTANCE_TABLE_SIZE = 29; protected static short[] instanceTable; public static void initialize() { diff --git a/Applet/src/com/android/javacard/rkp/RemotelyProvisionedComponentDevice.java b/Applet/src/com/android/javacard/rkp/RemotelyProvisionedComponentDevice.java new file mode 100644 index 00000000..3b2efb90 --- /dev/null +++ b/Applet/src/com/android/javacard/rkp/RemotelyProvisionedComponentDevice.java @@ -0,0 +1,1408 @@ +/* + * 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.rkp; + +import com.android.javacard.keymaster.*; +import javacard.framework.*; + +/* + * This class handles the remote key provisioning. Generates an RKP key and generates a certificate signing + * request(CSR). The generation of CSR is divided amoung multiple functions to the save the memory inside + * the Applet. The set of functions to be called sequentially in the order to complete the process of + * generating the CSR are processBeginSendData, processUpdateKey, processUpdateEekChain, + * processUpdateChallenge, processFinishSendData and getResponse. ProcessUpdateKey is called N times, where + * N is the number of keys. Similarly getResponse is called is multiple times till the client receives the + * response completely. + */ +public class RemotelyProvisionedComponentDevice { + + private static final byte TRUE = 0x01; + private static final byte FALSE = 0x00; + // RKP Version + private static final short RKP_VERSION = (short) 0x01; + // Boot params + private static final byte OS_VERSION_ID = 0x00; + private static final byte SYSTEM_PATCH_LEVEL_ID = 0x01; + private static final byte BOOT_PATCH_LEVEL_ID = 0x02; + private static final byte VENDOR_PATCH_LEVEL_ID = 0x03; + // Device Info labels + public static final byte[] BRAND = {0x62, 0x72, 0x61, 0x6E, 0x64}; + public static final byte[] MANUFACTURER = {0x6D, 0x61, 0x6E, 0x75, 0x66, 0x61, 0x63, 0x74, 0x75, + 0x72, 0x65, 0x72}; + public static final byte[] PRODUCT = {0x70, 0x72, 0x6F, 0x64, 0x75, 0x63, 0x74}; + public static final byte[] MODEL = {0x6D, 0x6F, 0x64, 0x65, 0x6C}; + public static final byte[] BOARD = {0x62, 0x6F, 0x61, 0x72, 0x64}; + public static final byte[] VB_STATE = {0x76, 0x62, 0x5F, 0x73, 0x74, 0x61, 0x74, 0x65}; + public static final byte[] BOOTLOADER_STATE = + {0x62, 0x6F, 0x6F, 0x74, 0x6C, 0x6F, 0x61, 0x64, 0x65, 0x72, 0x5F, 0x73, 0x74, 0x61, 0x74, + 0x65}; + public static final byte[] VB_META_DIGEST = + {0X76, 0X62, 0X6D, 0X65, 0X74, 0X61, 0X5F, 0X64, 0X69, 0X67, 0X65, 0X73, 0X74}; + public static final byte[] OS_VERSION = {0x6F, 0x73, 0x5F, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, + 0x6E}; + public static final byte[] SYSTEM_PATCH_LEVEL = + {0x73, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x5F, 0x70, 0x61, 0x74, 0x63, 0x68, 0x5F, 0x6C, 0x65, + 0x76, 0x65, 0x6C}; + public static final byte[] BOOT_PATCH_LEVEL = + {0x62, 0x6F, 0x6F, 0x74, 0x5F, 0x70, 0x61, 0x74, 0x63, 0x68, 0x5F, 0x6C, 0x65, 0x76, 0x65, + 0x6C}; + public static final byte[] VENDOR_PATCH_LEVEL = + {0x76, 0x65, 0x6E, 0x64, 0x6F, 0x72, 0x5F, 0x70, 0x61, 0x74, 0x63, 0x68, 0x5F, 0x6C, 0x65, + 0x76, 0x65, 0x6C}; + public static final byte[] DEVICE_INFO_VERSION = + {0x76, 0x65, 0x72, 0x73, 0x69, 0x6F, 0x6E}; + public static final byte[] SECURITY_LEVEL = + {0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x5F, 0x6C, 0x65, 0x76, 0x65, 0x6C}; + // Verified boot state values + public static final byte[] VB_STATE_GREEN = {0x67, 0x72, 0x65, 0x65, 0x6E}; + public static final byte[] VB_STATE_YELLOW = {0x79, 0x65, 0x6C, 0x6C, 0x6F, 0x77}; + public static final byte[] VB_STATE_ORANGE = {0x6F, 0x72, 0x61, 0x6E, 0x67, 0x65}; + public static final byte[] VB_STATE_RED = {0x72, 0x65, 0x64}; + // Boot loader state values + public static final byte[] UNLOCKED = {0x75, 0x6E, 0x6C, 0x6F, 0x63, 0x6B, 0x65, 0x64}; + public static final byte[] LOCKED = {0x6C, 0x6F, 0x63, 0x6B, 0x65, 0x64}; + // Device info CDDL schema version + public static final byte DI_SCHEMA_VERSION = 1; + public static final byte[] DI_SECURITY_LEVEL = {0x73, 0x74, 0x72, 0x6F, 0x6E, 0x67, 0x62, 0x6F, + 0x78}; + private static final short MAX_SEND_DATA = 1024; + // more data or no data + private static final byte MORE_DATA = 0x01; // flag to denote more data to retrieve + private static final byte NO_DATA = 0x00; + // Response processing states + private static final byte START_PROCESSING = 0x00; + private static final byte PROCESSING_BCC_IN_PROGRESS = 0x02; + private static final byte PROCESSING_BCC_COMPLETE = 0x04; + private static final byte PROCESSING_ACC_IN_PROGRESS = 0x08; // Additional certificate chain. + private static final byte PROCESSING_ACC_COMPLETE = 0x0A; + // data table + private static final short DATA_SIZE = 512; + private static final short DATA_INDEX_SIZE = 11; + public static final short DATA_INDEX_ENTRY_SIZE = 4; + public static final short DATA_INDEX_ENTRY_LENGTH = 0; + public static final short DATA_INDEX_ENTRY_OFFSET = 2; + // data offsets + private static final short EPHEMERAL_MAC_KEY = 0; + private static final short TOTAL_KEYS_TO_SIGN = 1; + private static final short KEYS_TO_SIGN_COUNT = 2; + private static final short TEST_MODE = 3; + private static final short EEK_KEY = 4; + private static final short EEK_KEY_ID = 5; + private static final short CHALLENGE = 6; + private static final short GENERATE_CSR_PHASE = 7; + private static final short EPHEMERAL_PUB_KEY = 8; + private static final short RESPONSE_PROCESSING_STATE = 9; + private static final short ACC_PROCESSED_LENGTH = 10; + + // data item sizes + private static final short MAC_KEY_SIZE = 32; + private static final short SHORT_SIZE = 2; + private static final short BYTE_SIZE = 1; + private static final short TEST_MODE_SIZE = 1; + // generate csr states + private static final byte BEGIN = 0x01; + private static final byte UPDATE = 0x02; + private static final byte FINISH = 0x04; + private static final byte GET_RESPONSE = 0x06; + // variables + private byte[] data; + private KMEncoder encoder; + private KMDecoder decoder; + private KMRepository repository; + private KMSEProvider seProvider; + private Object[] operation; + private short[] dataIndex; + public static Object[] authorizedEekRoots; + + public RemotelyProvisionedComponentDevice(KMEncoder encoder, KMDecoder decoder, + KMRepository repository, KMSEProvider seProvider) { + this.encoder = encoder; + this.decoder = decoder; + this.repository = repository; + this.seProvider = seProvider; + data = JCSystem.makeTransientByteArray(DATA_SIZE, JCSystem.CLEAR_ON_RESET); + operation = JCSystem.makeTransientObjectArray((short) 1, JCSystem.CLEAR_ON_RESET); + dataIndex = JCSystem.makeTransientShortArray((short) 1, JCSystem.CLEAR_ON_RESET); + operation[0] = null; + createAuthorizedEEKRoot(); + } + + private void createAuthorizedEEKRoot() { + if (authorizedEekRoots == null) { + authorizedEekRoots = + new Object[] + { + new 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() { + if (dataIndex[0] != 0) { + KMException.throwIt(KMError.INVALID_STATE); + } + 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. + final byte[] google = {0x47, 0x6F, 0x6F, 0x67, 0x6C, 0x65}; + short respPtr = KMArray.instance((short) 3); + KMArray resp = KMArray.cast(respPtr); + resp.add((short) 0, KMInteger.uint_16(RKP_VERSION)); + resp.add((short) 1, KMByteBlob.instance(google, (short) 0, (short) google.length)); + resp.add((short) 2, KMInteger.uint_8(KMType.RKP_CURVE_P256)); + KMKeymasterApplet.sendOutgoing(apdu, respPtr); + } + + /** + * This function generates an EC key pair with attest key as purpose and creates an encrypted key + * blob. It then generates a COSEMac message which includes the ECDSA public key. + */ + public void processGenerateRkpKey(APDU apdu) { + short arr = KMArray.instance((short) 1); + KMArray.cast(arr).add((short) 0, KMSimpleValue.exp()); + arr = KMKeymasterApplet.receiveIncoming(apdu, arr); + // Re-purpose the apdu buffer as scratch pad. + byte[] scratchPad = apdu.getBuffer(); + // test mode flag. + boolean testMode = + (KMSimpleValue.TRUE == KMSimpleValue.cast(KMArray.cast(arr).get((short) 0)).getValue()); + KMKeymasterApplet.generateRkpKey(scratchPad, getEcAttestKeyParameters()); + short pubKey = KMKeymasterApplet.getPubKey(); + short coseMac0 = constructCoseMacForRkpKey(testMode, scratchPad, pubKey); + // Encode the COSE_MAC0 object + arr = KMArray.instance((short) 3); + KMArray.cast(arr).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(arr).add((short) 1, coseMac0); + KMArray.cast(arr).add((short) 2, KMKeymasterApplet.getPivateKey()); + KMKeymasterApplet.sendOutgoing(apdu, arr); + } + + public void processBeginSendData(APDU apdu) { + try { + initializeDataTable(); + short arr = KMArray.instance((short) 3); + KMArray.cast(arr).add((short) 0, KMInteger.exp()); // Array length + KMArray.cast(arr).add((short) 1, KMInteger.exp()); // Total length of the encoded CoseKeys. + KMArray.cast(arr).add((short) 2, KMSimpleValue.exp()); + arr = KMKeymasterApplet.receiveIncoming(apdu, arr); + // Re-purpose the apdu buffer as scratch pad. + byte[] scratchPad = apdu.getBuffer(); + // Generate ephemeral mac key. + short dataEntryIndex = createEntry(EPHEMERAL_MAC_KEY, MAC_KEY_SIZE); + seProvider.newRandomNumber(data, dataEntryIndex, MAC_KEY_SIZE); + // Initialize hmac operation. + initHmacOperation(); + // Partially encode CoseMac structure with partial payload. + constructPartialPubKeysToSignMac(scratchPad, + KMInteger.cast(KMArray.cast(arr).get((short) 0)).getShort(), + KMInteger.cast(KMArray.cast(arr).get((short) 1)).getShort()); + // Store the total keys in data table. + dataEntryIndex = createEntry(TOTAL_KEYS_TO_SIGN, SHORT_SIZE); + Util.setShort(data, dataEntryIndex, + KMInteger.cast(KMArray.cast(arr).get((short) 0)).getShort()); + // Store the test mode value in data table. + dataEntryIndex = createEntry(TEST_MODE, TEST_MODE_SIZE); + data[dataEntryIndex] = + (KMSimpleValue.TRUE == KMSimpleValue.cast(KMArray.cast(arr).get((short) 2)).getValue()) ? + TRUE : FALSE; + // Store the current csr status, which is BEGIN. + createEntry(GENERATE_CSR_PHASE, BYTE_SIZE); + updateState(BEGIN); + // Send response. + KMKeymasterApplet.sendError(apdu, KMError.OK); + } catch (Exception e) { + clearDataTable(); + releaseOperation(); + throw e; + } + } + + public void processUpdateKey(APDU apdu) { + 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.sendError(apdu, KMError.OK); + } catch (Exception e) { + clearDataTable(); + releaseOperation(); + throw e; + } + } + + public void processUpdateEekChain(APDU apdu) { + 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.sendError(apdu, KMError.OK); + } catch (Exception e) { + clearDataTable(); + releaseOperation(); + throw e; + } + } + + public void processUpdateChallenge(APDU apdu) { + 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 dataEntryIndex = createEntry(CHALLENGE, KMByteBlob.cast(challenge).length()); + Util.arrayCopyNonAtomic( + KMByteBlob.cast(challenge).getBuffer(), + KMByteBlob.cast(challenge).getStartOff(), + data, + dataEntryIndex, + KMByteBlob.cast(challenge).length() + ); + // Update the state + updateState(UPDATE); + KMKeymasterApplet.sendError(apdu, KMError.OK); + } catch (Exception e) { + clearDataTable(); + releaseOperation(); + throw e; + } + } + + // This function returns pubKeysToSignMac, deviceInfo and partially constructed protected data + // wrapped inside byte blob. The partial protected data contains Headers and encrypted signedMac. + public void processFinishSendData(APDU apdu) { + 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 len = + ((KMOperation) operation[0]).sign(null, (short) 0, + (short) 0, scratchPad, (short) 0); + // release operation + releaseOperation(); + short pubKeysToSignMac = KMByteBlob.instance(scratchPad, (short) 0, len); + // Create DeviceInfo + short deviceInfo = createDeviceInfo(scratchPad); + // Generate Nonce for AES-GCM + seProvider.newRandomNumber(scratchPad, (short) 0, + KMKeymasterApplet.AES_GCM_NONCE_LENGTH); + short nonce = KMByteBlob.instance(scratchPad, (short) 0, + KMKeymasterApplet.AES_GCM_NONCE_LENGTH); + // Initializes cipher instance. + initAesGcmOperation(scratchPad, nonce); + // Encode Enc_Structure as additional data for AES-GCM. + processAesGcmUpdateAad(scratchPad); + short partialPayloadLen = processSignedMac(scratchPad, pubKeysToSignMac, deviceInfo); + short partialCipherText = KMByteBlob.instance(scratchPad, (short) 0, partialPayloadLen); + short coseEncryptProtectedHeader = getCoseEncryptProtectedHeader(scratchPad); + short coseEncryptUnProtectedHeader = getCoseEncryptUnprotectedHeader(scratchPad, nonce); + len = KMKeymasterApplet.encodeToApduBuffer(deviceInfo, scratchPad, + (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + short encodedDeviceInfo = KMByteBlob.instance(scratchPad, (short) 0, len); + updateState(FINISH); + short arr = KMArray.instance((short) 7); + KMArray.cast(arr).add((short) 0, KMInteger.uint_16(KMError.OK)); + KMArray.cast(arr).add((short) 1, pubKeysToSignMac); + KMArray.cast(arr).add((short) 2, encodedDeviceInfo); + KMArray.cast(arr).add((short) 3, coseEncryptProtectedHeader); + KMArray.cast(arr).add((short) 4, coseEncryptUnProtectedHeader); + KMArray.cast(arr).add((short) 5, partialCipherText); + KMArray.cast(arr).add((short) 6, KMInteger.uint_8(MORE_DATA)); + KMKeymasterApplet.sendOutgoing(apdu, arr); + } catch (Exception e) { + clearDataTable(); + releaseOperation(); + throw e; + } + } + + public void processGetResponse(APDU apdu) { + 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) { + 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() { + return (seProvider.getAdditionalCertChainLength() == 0 ? false : true); + } + + private short processFinalData(byte[] scratchPad) { + // Call finish on AES GCM Cipher + byte[] empty = {}; + short len = + ((KMOperation) operation[0]).finish(empty, (short) 0, (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; + } + + + private short getHmacKey(boolean testMode, byte[] scratchPad) { + short macKey = KMByteBlob.instance(MAC_KEY_SIZE); + Util.arrayFillNonAtomic(KMByteBlob.cast(macKey).getBuffer(), + KMByteBlob.cast(macKey).getStartOff(), MAC_KEY_SIZE, (byte) 0); + if (!testMode) { + Util.arrayFillNonAtomic(scratchPad, (short) 0, (short) (2 * MAC_KEY_SIZE), (byte) 0); + short len = + seProvider.hkdf( + scratchPad, //ikm + (short) 0, // ikm offset + MAC_KEY_SIZE, // ikm size + scratchPad, // salt + MAC_KEY_SIZE, // salt offset + MAC_KEY_SIZE, // salt length + KMCose.MAC_DERIVE_KEY_CTX, + (short) 0, + (short) KMCose.MAC_DERIVE_KEY_CTX.length, + KMByteBlob.cast(macKey).getBuffer(), + KMByteBlob.cast(macKey).getStartOff(), + MAC_KEY_SIZE + ); + if (len != MAC_KEY_SIZE) { + KMException.throwIt(KMError.INVALID_MAC_LENGTH); + } + } + return macKey; + } + + /** + * 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(); + // Get the mackey. + short macKey = getHmacKey(testMode, scratchPad); + + // validate protected Headers + short ptr = KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_PROTECTED_PARAMS_OFFSET); + ptr = decoder.decode(coseHeadersExp, KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), KMByteBlob.cast(ptr).length()); + + if (!KMCoseHeaders.cast(ptr).isDataValid(KMCose.COSE_ALG_HMAC_256, KMType.INVALID_VALUE)) { + KMException.throwIt(KMError.STATUS_FAILED); + } + + // Validate payload. + ptr = KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_PAYLOAD_OFFSET); + ptr = decoder.decode(coseKeyExp, KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), KMByteBlob.cast(ptr).length()); + + if (!KMCoseKey.cast(ptr).isDataValid(KMCose.COSE_KEY_TYPE_EC2, KMType.INVALID_VALUE, + KMCose.COSE_ALG_ES256, KMType.INVALID_VALUE, KMCose.COSE_ECCURVE_256)) { + KMException.throwIt(KMError.STATUS_FAILED); + } + + boolean isTestKey = KMCoseKey.cast(ptr).isTestKey(); + if (isTestKey && !testMode) { + KMException.throwIt(KMError.STATUS_TEST_KEY_IN_PRODUCTION_REQUEST); + } else if (!isTestKey && testMode) { + KMException.throwIt(KMError.STATUS_PRODUCTION_KEY_IN_TEST_REQUEST); + } + + // Compute CoseMac Structure and compare the macs. + short macStructure = + KMCose.constructCoseMacStructure(KMArray.cast(coseMacPtr).get( + KMCose.COSE_MAC0_PROTECTED_PARAMS_OFFSET), + KMByteBlob.instance((short) 0), + KMArray.cast(coseMacPtr).get(KMCose.COSE_MAC0_PAYLOAD_OFFSET)); + short encodedLen = KMKeymasterApplet.encodeToApduBuffer(macStructure, scratchPad, (short) 0, + KMKeymasterApplet.MAX_COSE_BUF_SIZE); + + short hmacLen = seProvider.hmacSign(KMByteBlob.cast(macKey).getBuffer(), + KMByteBlob.cast(macKey).getStartOff(), + (short) 32, 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( + 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)); + Util.arrayCopyNonAtomic(repository.getHeap(), bufIndex, scratchPad, len, partialPayloadLen); + ((KMOperation) operation[0]).update(scratchPad, (short) 0, (short) (len + partialPayloadLen)); + } + + private static short getAttestKeyParameters() { + short tagIndex = 0; + short arrPtr = KMArray.instance((short) 6); + // Key size - 256 + short keySize = KMIntegerTag + .instance(KMType.UINT_TAG, KMType.KEYSIZE, KMInteger.uint_16((short) 256)); + // Digest - SHA256 + short byteBlob = KMByteBlob.instance((short) 1); + KMByteBlob.cast(byteBlob).add((short) 0, KMType.SHA2_256); + short digest = KMEnumArrayTag.instance(KMType.DIGEST, byteBlob); + // Purpose - Attest + byteBlob = KMByteBlob.instance((short) 1); + KMByteBlob.cast(byteBlob).add((short) 0, KMType.ATTEST_KEY); + short purpose = KMEnumArrayTag.instance(KMType.PURPOSE, byteBlob); + + KMArray.cast(arrPtr).add(tagIndex++, purpose); + // Algorithm - EC + KMArray.cast(arrPtr).add(tagIndex++, KMEnumTag.instance(KMType.ALGORITHM, KMType.EC)); + KMArray.cast(arrPtr).add(tagIndex++, keySize); + KMArray.cast(arrPtr).add(tagIndex++, digest); + // Curve - P256 + KMArray.cast(arrPtr).add(tagIndex++, KMEnumTag.instance(KMType.ECCURVE, KMType.P_256)); + // No Authentication is required to use this key. + KMArray.cast(arrPtr).add(tagIndex, KMBoolTag.instance(KMType.NO_AUTH_REQUIRED)); + return KMKeyParameters.instance(arrPtr); + } + + private short createSignedMac(KMDeviceUniqueKey deviceUniqueKey, byte[] scratchPad, + short deviceMapPtr, short pubKeysToSign) { + // Challenge + 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( + KMNInteger.uint_8(KMCose.COSE_ALG_ES256), + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE); + protectedHeaders = KMKeymasterApplet.encodeToApduBuffer(protectedHeaders, scratchPad, + (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + protectedHeaders = KMByteBlob.instance(scratchPad, (short) 0, protectedHeaders); + + /* construct cose sign structure */ + short signStructure = + KMCose.constructCoseSignStructure(protectedHeaders, aad, ephmeralMacKey); + signStructure = KMKeymasterApplet.encodeToApduBuffer(signStructure, scratchPad, + (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + short len = + seProvider.ecSign256( + deviceUniqueKey, + scratchPad, + (short) 0, + signStructure, + scratchPad, + signStructure + ); + signStructure = KMByteBlob.instance(scratchPad, signStructure, len); + + /* Construct unprotected headers */ + short unprotectedHeader = KMArray.instance((short) 0); + unprotectedHeader = KMCoseHeaders.instance(unprotectedHeader); + + /* construct Cose_Sign1 */ + return KMCose.constructCoseSign1(protectedHeaders, unprotectedHeader, + ephmeralMacKey, signStructure); + } + + + private KMDeviceUniqueKey createDeviceUniqueKey(boolean testMode, byte[] scratchPad) { + KMDeviceUniqueKey deviceUniqueKey; + short[] lengths = {0, 0}; + if (testMode) { + seProvider.createAsymmetricKey( + KMType.EC, + scratchPad, + (short) 0, + (short) 128, + scratchPad, + (short) 128, + (short) 128, + lengths); + deviceUniqueKey = + seProvider.createDeviceUniqueKey(true, scratchPad, (short) 128, lengths[1], + scratchPad, (short) 0, lengths[0]); + } else { + deviceUniqueKey = seProvider.getDeviceUniqueKey(false); + } + return deviceUniqueKey; + } + + /** + * DeviceInfo is a CBOR Map structure described by the following CDDL. + *

+ * DeviceInfo = { ? "brand" : tstr, ? "manufacturer" : tstr, ? "product" : tstr, ? "model" : tstr, + * ? "board" : 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" : 1, // The + * CDDL schema version. "security_level" : "tee" / "strongbox" } + */ + private short createDeviceInfo(byte[] scratchpad) { + // Device Info Key Value pairs. + short[] deviceIds = { + KMType.INVALID_VALUE, KMType.INVALID_VALUE, + KMType.INVALID_VALUE, KMType.INVALID_VALUE, + KMType.INVALID_VALUE, KMType.INVALID_VALUE, + KMType.INVALID_VALUE, KMType.INVALID_VALUE, + KMType.INVALID_VALUE, KMType.INVALID_VALUE, + KMType.INVALID_VALUE, KMType.INVALID_VALUE, + KMType.INVALID_VALUE, KMType.INVALID_VALUE, + KMType.INVALID_VALUE, KMType.INVALID_VALUE, + KMType.INVALID_VALUE, KMType.INVALID_VALUE, + KMType.INVALID_VALUE, KMType.INVALID_VALUE, + KMType.INVALID_VALUE, KMType.INVALID_VALUE, + KMType.INVALID_VALUE, KMType.INVALID_VALUE, + KMType.INVALID_VALUE, KMType.INVALID_VALUE, + KMType.INVALID_VALUE, KMType.INVALID_VALUE, + }; + short[] out = {0/* index */, 0 /* length */}; + updateItem(deviceIds, out, BRAND, getAttestationId(KMType.ATTESTATION_ID_BRAND, scratchpad)); + updateItem(deviceIds, out, MANUFACTURER, + getAttestationId(KMType.ATTESTATION_ID_MANUFACTURER, scratchpad)); + updateItem(deviceIds, out, PRODUCT, + getAttestationId(KMType.ATTESTATION_ID_PRODUCT, scratchpad)); + updateItem(deviceIds, out, MODEL, getAttestationId(KMType.ATTESTATION_ID_MODEL, scratchpad)); + updateItem(deviceIds, out, VB_STATE, getVbState()); + updateItem(deviceIds, out, BOOTLOADER_STATE, getBootloaderState()); + updateItem(deviceIds, out, VB_META_DIGEST, getVerifiedBootHash(scratchpad)); + updateItem(deviceIds, out, OS_VERSION, getBootParams(OS_VERSION_ID, scratchpad)); + updateItem(deviceIds, out, SYSTEM_PATCH_LEVEL, + getBootParams(SYSTEM_PATCH_LEVEL_ID, scratchpad)); + updateItem(deviceIds, out, BOOT_PATCH_LEVEL, getBootParams(BOOT_PATCH_LEVEL_ID, scratchpad)); + updateItem(deviceIds, out, VENDOR_PATCH_LEVEL, + getBootParams(VENDOR_PATCH_LEVEL_ID, scratchpad)); + updateItem(deviceIds, out, DEVICE_INFO_VERSION, KMInteger.uint_8(DI_SCHEMA_VERSION)); + updateItem(deviceIds, out, SECURITY_LEVEL, + KMTextString.instance(DI_SECURITY_LEVEL, (short) 0, (short) DI_SECURITY_LEVEL.length)); + // Create device info map. + short map = KMMap.instance(out[1]); + short mapIndex = 0; + short index = 0; + while (index < (short) deviceIds.length) { + if (deviceIds[index] != KMType.INVALID_VALUE) { + KMMap.cast(map).add(mapIndex++, deviceIds[index], deviceIds[(short) (index + 1)]); + } + 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 meta 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[] meta, byte[] item, short value) { + if (KMType.INVALID_VALUE != value) { + deviceIds[meta[0]++] = + KMTextString.instance(item, (short) 0, (short) item.length); + deviceIds[meta[0]++] = value; + meta[1]++; + } + } + + private short getAttestationId(short attestId, byte[] scratchpad) { + short attIdTagLen = seProvider.getAttestationId(attestId, scratchpad, (short) 0); + if (attIdTagLen != 0) { + return KMTextString.instance(scratchpad, (short) 0, attIdTagLen); + } + return KMType.INVALID_VALUE; + } + + private short getVerifiedBootHash(byte[] scratchPad) { + short len = seProvider.getVerifiedBootHash(scratchPad, (short) 0); + if (len != 0) { + return KMByteBlob.instance(scratchPad, (short) 0, len); + } + return KMType.INVALID_VALUE; + } + + private short getBootloaderState() { + short bootloaderState; + if (seProvider.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 = seProvider.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 getBootParams(byte bootParam, byte[] scratchPad) { + short value = KMType.INVALID_VALUE; + switch (bootParam) { + case OS_VERSION_ID: + value = repository.getOsVersion(); + break; + case SYSTEM_PATCH_LEVEL_ID: + value = repository.getOsPatch(); + break; + case BOOT_PATCH_LEVEL_ID: + short len = seProvider.getBootPatchLevel(scratchPad, (short) 0); + value = KMByteBlob.instance(scratchPad, (short) 0, len); + break; + case VENDOR_PATCH_LEVEL_ID: + value = repository.getVendorPatchLevel(); + break; + default: + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // Convert Integer to Text String for OS_VERSION. + if (bootParam == OS_VERSION_ID) { + value = + KMTextString + .instance(KMInteger.cast(value).getBuffer(), KMInteger.cast(value).getStartOff(), + KMInteger.cast(value).length()); + } + 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); + + 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. + short[] lengths = {0/* Private key Length*/, 0 /* Public key length*/}; + seProvider.createAsymmetricKey( + KMType.EC, + scratchPad, + (short) 0, + (short) 128, + scratchPad, + (short) 128, + (short) 128, + lengths); + // Copy the ephemeral private key from scratch pad + short ptr = KMByteBlob.instance(lengths[0]); + Util.arrayCopyNonAtomic( + scratchPad, + (short) 0, + KMByteBlob.cast(ptr).getBuffer(), + KMByteBlob.cast(ptr).getStartOff(), + lengths[0]); + //Store ephemeral public key in data table for later usage. + short dataEntryIndex = createEntry(EPHEMERAL_PUB_KEY, lengths[1]); + Util.arrayCopyNonAtomic(scratchPad, (short) 128, data, dataEntryIndex, lengths[1]); + return ptr; + } + + private void initHmacOperation() { + short dataEntryIndex = getEntry(EPHEMERAL_MAC_KEY); + operation[0] = + seProvider.initSymmetricOperation( + 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.initSymmetricOperation( + 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( + 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( + KMInteger.uint_8(KMCose.COSE_KEY_TYPE_EC2), + KMType.INVALID_VALUE, + KMNInteger.uint_8(KMCose.COSE_ALG_ES256), + KMType.INVALID_VALUE, + KMInteger.uint_8(KMCose.COSE_ECCURVE_256), + data, + pubKeyIndex, + getEntryLength(EPHEMERAL_PUB_KEY), + KMType.INVALID_VALUE, + false + ); + short keyIdentifierPtr = KMByteBlob + .instance(data, getEntry(EEK_KEY_ID), getEntryLength(EEK_KEY_ID)); + short unprotectedHeaderRecipient = + KMCose.constructHeaders(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 = seProvider.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 = seProvider.generateBcc(true, scratchPad); + len = KMKeymasterApplet + .encodeToApduBuffer(bcc, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + } else { + //len = seProvider.getBootCertificateChainLength(); + byte[] bcc = seProvider.getBootCertificateChain(); + //len = seProvider.readBootCertificateChain(scratchPad, (short) 0); + 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( + 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 + KMDeviceUniqueKey deviceUniqueKey = + createDeviceUniqueKey((TRUE == data[getEntry(TEST_MODE)]) ? true : false, scratchPad); + // Create signedMac + short signedMac = createSignedMac(deviceUniqueKey, 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( + 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(KMType.INVALID_VALUE, KMType.INVALID_VALUE, nonce, KMType.INVALID_VALUE); + } + + private short constructCoseEncryptHeaders(byte[] scratchPad, short nonce) { + // CoseEncrypt protected headers. + short protectedHeader = KMCose.constructHeaders( + KMInteger.uint_8(KMCose.COSE_ALG_AES_GCM_256), + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE); + // Encode the protected header as byte blob. + protectedHeader = KMKeymasterApplet.encodeToApduBuffer(protectedHeader, scratchPad, (short) 0, + KMKeymasterApplet.MAX_COSE_BUF_SIZE); + protectedHeader = KMByteBlob.instance(scratchPad, (short) 0, protectedHeader); + /* CoseEncrypt unprotected headers */ + short unprotectedHeader = + KMCose.constructHeaders(KMType.INVALID_VALUE, KMType.INVALID_VALUE, nonce, + KMType.INVALID_VALUE); + // Construct partial CoseEncrypt + return KMCose.constructCoseEncrypt(protectedHeader, unprotectedHeader, KMType.INVALID_VALUE, + KMType.INVALID_VALUE); + } + + + private short constructCoseMacForRkpKey(boolean testMode, byte[] scratchPad, short pubKey) { + // prepare cosekey + short coseKey = + KMCose.constructCoseKey( + KMInteger.uint_8(KMCose.COSE_KEY_TYPE_EC2), + KMType.INVALID_VALUE, + KMNInteger.uint_8(KMCose.COSE_ALG_ES256), + KMType.INVALID_VALUE, + KMInteger.uint_8(KMCose.COSE_ECCURVE_256), + KMByteBlob.cast(pubKey).getBuffer(), + KMByteBlob.cast(pubKey).getStartOff(), + KMByteBlob.cast(pubKey).length(), + KMType.INVALID_VALUE, + testMode); + // Encode the cose key and make it as payload. + short len = KMKeymasterApplet + .encodeToApduBuffer(coseKey, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + short payload = KMByteBlob.instance(scratchPad, (short) 0, len); + // Get the mackey. + short macKey = getHmacKey(testMode, scratchPad); + // Prepare protected header, which is required to construct the COSE_MAC0 + short headerPtr = KMCose.constructHeaders( + KMInteger.uint_8(KMCose.COSE_ALG_HMAC_256), + KMType.INVALID_VALUE, + KMType.INVALID_VALUE, + KMType.INVALID_VALUE); + // Encode the protected header as byte blob. + len = KMKeymasterApplet + .encodeToApduBuffer(headerPtr, scratchPad, (short) 0, KMKeymasterApplet.MAX_COSE_BUF_SIZE); + 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 = seProvider + .hmacSign(KMByteBlob.cast(macKey).getBuffer(), KMByteBlob.cast(macKey).getStartOff(), + (short) 32, 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); + } +} diff --git a/HAL/Android.bp b/HAL/Android.bp index 33384a9e..bf842f8b 100644 --- a/HAL/Android.bp +++ b/HAL/Android.bp @@ -22,6 +22,7 @@ cc_library { "CborConverter.cpp", "JavacardKeyMintDevice.cpp", "JavacardKeyMintOperation.cpp", + "JavacardRemotelyProvisionedComponentDevice.cpp", "JavacardSecureElement.cpp", "JavacardSharedSecret.cpp", "keymint_utils.cpp", @@ -95,7 +96,7 @@ cc_binary { "service.cpp", ], required: [ -// "RemoteProvisioner", + "RemoteProvisioner", "android.hardware.strongbox_keystore.xml", ], } diff --git a/HAL/CborConverter.cpp b/HAL/CborConverter.cpp index 82e3fef5..a97d12ad 100644 --- a/HAL/CborConverter.cpp +++ b/HAL/CborConverter.cpp @@ -378,6 +378,24 @@ bool CborConverter::getTimeStampToken(const unique_ptr& item, const uint32 return true; } +bool CborConverter::getArrayItem(const std::unique_ptr& item, const uint32_t pos, + Array& array) { + unique_ptr arrayItem(nullptr); + getItemAtPos(item, pos, arrayItem); + if ((arrayItem == nullptr) || (MajorType::ARRAY != getType(arrayItem))) return false; + array = std::move(*arrayItem.get()->asArray()); + return true; +} + +bool CborConverter::getMapItem(const std::unique_ptr& item, const uint32_t pos, + Map& map) { + unique_ptr mapItem(nullptr); + getItemAtPos(item, pos, mapItem); + if ((mapItem == nullptr) || (MajorType::MAP != getType(mapItem))) return false; + map = std::move(*mapItem.get()->asMap()); + return true; +} + bool CborConverter::getKeyParameters(const unique_ptr& item, const uint32_t pos, vector& keyParams) { bool ret = false; diff --git a/HAL/CborConverter.h b/HAL/CborConverter.h index a75db90d..ca44533f 100644 --- a/HAL/CborConverter.h +++ b/HAL/CborConverter.h @@ -82,6 +82,13 @@ class CborConverter { vector>& data); bool addTimeStampToken(Array& array, const TimeStampToken& token); + + bool getMapItem(const std::unique_ptr& item, const uint32_t pos, + Map& map); + + bool getArrayItem(const std::unique_ptr& item, const uint32_t pos, + Array& array); + inline bool getErrorCode(const std::unique_ptr& item, const uint32_t pos, keymaster_error_t& errorCode) { uint64_t errorVal; diff --git a/HAL/JavacardKeyMintOperation.cpp b/HAL/JavacardKeyMintOperation.cpp index 03e6e73d..20e02297 100644 --- a/HAL/JavacardKeyMintOperation.cpp +++ b/HAL/JavacardKeyMintOperation.cpp @@ -42,7 +42,6 @@ ScopedAStatus JavacardKeyMintOperation::updateAad(const vector& input, cbor_.addTimeStampToken(request, timestampToken.value_or(TimeStampToken())); auto [item, err] = card_->sendRequest(Instruction::INS_UPDATE_AAD_OPERATION_CMD, request); if (err != KM_ERROR_OK) { - opHandle_ = 0; return km_utils::kmError2ScopedAStatus(err); } return ScopedAStatus::ok(); @@ -217,7 +216,6 @@ keymaster_error_t JavacardKeyMintOperation::sendUpdate(const vector& in cbor_.addTimeStampToken(request, timestampToken); auto [item, error] = card_->sendRequest(Instruction::INS_UPDATE_OPERATION_CMD, request); if (error != KM_ERROR_OK) { - opHandle_ = 0; return error; } vector respData; @@ -240,7 +238,6 @@ keymaster_error_t JavacardKeyMintOperation::sendFinish(const vector& da cbor_.addHardwareAuthToken(request, authToken); cbor_.addTimeStampToken(request, timestampToken); auto [item, err] = card_->sendRequest(Instruction::INS_FINISH_OPERATION_CMD, request); - opHandle_ = 0; if (err != KM_ERROR_OK) { return err; } @@ -248,6 +245,7 @@ keymaster_error_t JavacardKeyMintOperation::sendFinish(const vector& da if (!cbor_.getBinaryArray(item, 1, respData)) { return KM_ERROR_UNKNOWN_ERROR; } + opHandle_ = 0; output.insert(output.end(), respData.begin(), respData.end()); return KM_ERROR_OK; } diff --git a/HAL/JavacardRemotelyProvisionedComponentDevice.cpp b/HAL/JavacardRemotelyProvisionedComponentDevice.cpp new file mode 100644 index 00000000..5657f9e2 --- /dev/null +++ b/HAL/JavacardRemotelyProvisionedComponentDevice.cpp @@ -0,0 +1,264 @@ +/* + * 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 +#include +#include +#include +#include +#include + +namespace aidl::android::hardware::security::keymint { +using namespace cppcose; +using namespace keymaster; +using namespace cppbor; +// RKP error codes defined in keymint applet. +constexpr keymaster_error_t kStatusFailed = static_cast(32000); +constexpr keymaster_error_t kStatusInvalidMac = static_cast(32001); +constexpr keymaster_error_t kStatusProductionKeyInTestRequest = static_cast(32002); +constexpr keymaster_error_t kStatusTestKeyInProductionRequest = static_cast(32003); +constexpr keymaster_error_t kStatusInvalidEek = static_cast(32004); +constexpr keymaster_error_t kStatusInvalidState = static_cast(32005); + +namespace { + +keymaster_error_t translateRkpErrorCode(keymaster_error_t error) { + switch(static_cast(-error)) { + case kStatusFailed: + case kStatusInvalidState: + return static_cast(BnRemotelyProvisionedComponent::STATUS_FAILED); + case kStatusInvalidMac: + return static_cast(BnRemotelyProvisionedComponent::STATUS_INVALID_MAC); + case kStatusProductionKeyInTestRequest: + return static_cast(BnRemotelyProvisionedComponent::STATUS_PRODUCTION_KEY_IN_TEST_REQUEST); + case kStatusTestKeyInProductionRequest: + return static_cast(BnRemotelyProvisionedComponent::STATUS_TEST_KEY_IN_PRODUCTION_REQUEST); + case kStatusInvalidEek: + return static_cast(BnRemotelyProvisionedComponent::STATUS_INVALID_EEK); + } + return error; +} + +ScopedAStatus defaultHwInfo(RpcHardwareInfo* info) { + info->versionNumber = 1; + info->rpcAuthorName = "Google"; + info->supportedEekCurve = RpcHardwareInfo::CURVE_P256; + return ScopedAStatus::ok(); +} + +uint32_t coseKeyEncodedSize(const std::vector& keysToSign) { + uint32_t size = 0; + for(auto& macKey : keysToSign) { + auto [macedKeyItem, _, coseMacErrMsg] = + cppbor::parse(macKey.macedKey); + if (!macedKeyItem || !macedKeyItem->asArray() || + macedKeyItem->asArray()->size() != kCoseMac0EntryCount) { + LOG(ERROR) << "Invalid COSE_Mac0 structure"; + return 0; + } + auto payload = macedKeyItem->asArray()->get(kCoseMac0Payload)->asBstr(); + if (!payload) return 0; + size += payload->value().size(); + } + return size; +} + +} // namespace + +ScopedAStatus +JavacardRemotelyProvisionedComponentDevice::getHardwareInfo(RpcHardwareInfo* info) { + auto [item, err] = card_->sendRequest(Instruction::INS_GET_RKP_HARDWARE_INFO); + uint32_t versionNumber; + uint32_t supportedEekCurve; + if (err != KM_ERROR_OK || + !cbor_.getUint64(item, 1, versionNumber) || + !cbor_.getBinaryArray(item, 2, info->rpcAuthorName ) || + !cbor_.getUint64(item, 3, supportedEekCurve)) { + LOG(ERROR) << "Error in response of getHardwareInfo."; + LOG(INFO) << "Returning defaultHwInfo in getHardwareInfo."; + return defaultHwInfo(info); + } + info->versionNumber = static_cast(versionNumber); + info->supportedEekCurve = static_cast(supportedEekCurve); + return ScopedAStatus::ok(); +} + +ScopedAStatus +JavacardRemotelyProvisionedComponentDevice::generateEcdsaP256KeyPair(bool testMode, + MacedPublicKey* macedPublicKey, + std::vector* privateKeyHandle) { + cppbor::Array array; + array.add(testMode); + auto [item, err] = card_->sendRequest(Instruction::INS_GENERATE_RKP_KEY_CMD, array); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in sending generateEcdsaP256KeyPair."; + return km_utils::kmError2ScopedAStatus(translateRkpErrorCode(err)); + } + if (!cbor_.getBinaryArray(item, 1, macedPublicKey->macedKey) || + !cbor_.getBinaryArray(item, 2, *privateKeyHandle)) { + LOG(ERROR) << "Error in decoding og response in generateEcdsaP256KeyPair."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus +JavacardRemotelyProvisionedComponentDevice::beginSendData( + bool testMode, const std::vector& keysToSign) { + uint32_t totalEncodedSize = coseKeyEncodedSize(keysToSign); + cppbor::Array array; + array.add(keysToSign.size()); + array.add(totalEncodedSize); + array.add(testMode); + auto [_, err] = card_->sendRequest(Instruction::INS_BEGIN_SEND_DATA_CMD, array); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in beginSendData."; + return km_utils::kmError2ScopedAStatus(translateRkpErrorCode(err)); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus +JavacardRemotelyProvisionedComponentDevice::updateMacedKey( + const std::vector& keysToSign) { + for(auto& macedPublicKey : keysToSign) { + cppbor::Array array; + array.add(EncodedItem(macedPublicKey.macedKey)); + auto [_, err] = card_->sendRequest(Instruction::INS_UPDATE_KEY_CMD, array); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in updateMacedKey."; + return km_utils::kmError2ScopedAStatus(translateRkpErrorCode(err)); + } + } + return ScopedAStatus::ok(); +} + +ScopedAStatus +JavacardRemotelyProvisionedComponentDevice::updateChallenge( + const std::vector& challenge) { + Array array; + array.add(challenge); + auto [_, err] = card_->sendRequest(Instruction::INS_UPDATE_CHALLENGE_CMD, array); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in updateChallenge."; + return km_utils::kmError2ScopedAStatus(translateRkpErrorCode(err)); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus +JavacardRemotelyProvisionedComponentDevice::updateEEK( + const std::vector& endpointEncCertChain) { + std::vector eekChain = endpointEncCertChain; + auto [_, err] = card_->sendRequest(Instruction::INS_UPDATE_EEK_CHAIN_CMD, eekChain); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in updateEEK."; + return km_utils::kmError2ScopedAStatus(translateRkpErrorCode(err)); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus +JavacardRemotelyProvisionedComponentDevice::finishSendData( + std::vector* keysToSignMac, DeviceInfo* deviceInfo, + std::vector& coseEncryptProtectedHeader, cppbor::Map& coseEncryptUnProtectedHeader, + std::vector& partialCipheredData, uint32_t& respFlag) { + + std::vector decodedKeysToSignMac; + std::vector decodedDeviceInfo; + 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)); + } + if (!cbor_.getBinaryArray(item, 1, decodedKeysToSignMac) || + !cbor_.getBinaryArray(item, 2, decodedDeviceInfo) || + !cbor_.getBinaryArray(item, 3, coseEncryptProtectedHeader) || + !cbor_.getMapItem(item, 4, coseEncryptUnProtectedHeader) || + !cbor_.getBinaryArray(item, 5, partialCipheredData) || + !cbor_.getUint64(item, 6, respFlag)) { + LOG(ERROR) << "Error in decoding og response in finishSendData."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + *keysToSignMac = decodedKeysToSignMac; + deviceInfo->deviceInfo = decodedDeviceInfo; + return ScopedAStatus::ok(); +} + +ScopedAStatus +JavacardRemotelyProvisionedComponentDevice::getResponse( + std::vector& partialCipheredData, cppbor::Array& recepientStructure, + uint32_t& respFlag) { + auto [item, err] = card_->sendRequest(Instruction::INS_GET_RESPONSE_CMD); + if (err != KM_ERROR_OK) { + LOG(ERROR) << "Error in getResponse."; + return km_utils::kmError2ScopedAStatus(translateRkpErrorCode(err)); + } + if (!cbor_.getBinaryArray(item, 1, partialCipheredData) || + !cbor_.getArrayItem(item, 2, recepientStructure) || + !cbor_.getUint64(item, 3, respFlag)) { + LOG(ERROR) << "Error in decoding og response in getResponse."; + return km_utils::kmError2ScopedAStatus(KM_ERROR_UNKNOWN_ERROR); + } + return ScopedAStatus::ok(); +} + +ScopedAStatus +JavacardRemotelyProvisionedComponentDevice::generateCertificateRequest(bool testMode, + const std::vector& keysToSign, + const std::vector& endpointEncCertChain, + const std::vector& challenge, + DeviceInfo* deviceInfo, ProtectedData* protectedData, + std::vector* keysToSignMac) { + std::vector coseEncryptProtectedHeader; + cppbor::Map coseEncryptUnProtectedHeader; + cppbor::Array recipients; + std::vector cipheredData; + uint32_t respFlag; + auto ret = beginSendData(testMode, keysToSign); + if (!ret.isOk()) return ret; + + ret = updateMacedKey(keysToSign); + if (!ret.isOk()) return ret; + + ret = updateChallenge(challenge); + if (!ret.isOk()) return ret; + + ret = updateEEK(endpointEncCertChain); + if (!ret.isOk()) return ret; + + ret = finishSendData(keysToSignMac, deviceInfo, coseEncryptProtectedHeader, + coseEncryptUnProtectedHeader, cipheredData, + respFlag); + if (!ret.isOk()) return ret; + + while (respFlag != 0) { // more data is pending to receive + ret = getResponse(cipheredData, recipients, respFlag); + if (!ret.isOk()) return ret; + } + // Create ConseEncrypt structure. + protectedData->protectedData = + cppbor::Array() + .add(coseEncryptProtectedHeader) // Protected + .add(std::move(coseEncryptUnProtectedHeader)) // Unprotected + .add(cipheredData) // Payload + .add(std::move(recipients)) + .encode(); + return ScopedAStatus::ok(); +} + +} // namespace aidl::android::hardware::security::keymint diff --git a/HAL/JavacardRemotelyProvisionedComponentDevice.h b/HAL/JavacardRemotelyProvisionedComponentDevice.h new file mode 100644 index 00000000..c9dd85c5 --- /dev/null +++ b/HAL/JavacardRemotelyProvisionedComponentDevice.h @@ -0,0 +1,79 @@ +/* + * Copyright 2021, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "CborConverter.h" +#include "JavacardSecureElement.h" + +namespace aidl::android::hardware::security::keymint { +using namespace ::keymint::javacard; +using ndk::ScopedAStatus; + +class JavacardRemotelyProvisionedComponentDevice + : public BnRemotelyProvisionedComponent { + public: + explicit JavacardRemotelyProvisionedComponentDevice( + shared_ptr card) + : card_(card) {} + + virtual ~JavacardRemotelyProvisionedComponentDevice() = default; + + ScopedAStatus getHardwareInfo(RpcHardwareInfo* info) override; + + ScopedAStatus generateEcdsaP256KeyPair( + bool testMode, MacedPublicKey* macedPublicKey, + std::vector* privateKeyHandle) override; + + ScopedAStatus generateCertificateRequest( + bool testMode, const std::vector& keysToSign, + const std::vector& endpointEncCertChain, + const std::vector& challenge, DeviceInfo* deviceInfo, + ProtectedData* protectedData, + std::vector* keysToSignMac) override; + + private: + ScopedAStatus beginSendData(bool testMode, + const std::vector& keysToSign); + + ScopedAStatus updateMacedKey(const std::vector& keysToSign); + + ScopedAStatus updateChallenge(const std::vector& challenge); + + ScopedAStatus updateEEK(const std::vector& endpointEncCertChain); + + ScopedAStatus finishSendData(std::vector* keysToSignMac, + DeviceInfo* deviceInfo, + std::vector& coseEncryptProtectedHeader, + cppbor::Map& coseEncryptUnProtectedHeader, + std::vector& partialCipheredData, + uint32_t& respFlag); + + ScopedAStatus getResponse(std::vector& partialCipheredData, + cppbor::Array& recepientStructure, + uint32_t& respFlag); + std::shared_ptr card_; + CborConverter cbor_; +}; + +} // namespace aidl::android::hardware::security::keymint diff --git a/HAL/JavacardSecureElement.cpp b/HAL/JavacardSecureElement.cpp index 447f1d80..57ec6181 100644 --- a/HAL/JavacardSecureElement.cpp +++ b/HAL/JavacardSecureElement.cpp @@ -114,6 +114,17 @@ JavacardSecureElement::sendRequest(Instruction ins, Array& request) { return cbor_.decodeData(response); } +std::tuple, keymaster_error_t> +JavacardSecureElement::sendRequest(Instruction ins, std::vector& command) { + vector response; + auto sendError = sendData(ins, command, response); + if (sendError != KM_ERROR_OK) { + return {unique_ptr(nullptr), sendError}; + } + // decode the response and send that back + return cbor_.decodeData(response); +} + std::tuple, keymaster_error_t> JavacardSecureElement::sendRequest(Instruction ins) { vector response; diff --git a/HAL/JavacardSecureElement.h b/HAL/JavacardSecureElement.h index 88277f86..160d3d4d 100644 --- a/HAL/JavacardSecureElement.h +++ b/HAL/JavacardSecureElement.h @@ -60,6 +60,15 @@ enum class Instruction { INS_BEGIN_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 24, INS_FINISH_IMPORT_WRAPPED_KEY_CMD = KEYMINT_CMD_APDU_START + 25, INS_SET_BOOT_PARAMS_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, }; class JavacardSecureElement { @@ -75,6 +84,7 @@ class JavacardSecureElement { std::tuple, keymaster_error_t> sendRequest(Instruction ins, Array& request); std::tuple, keymaster_error_t> sendRequest(Instruction ins); + std::tuple, keymaster_error_t> sendRequest(Instruction ins, std::vector& command); keymaster_error_t sendData(Instruction ins, std::vector& inData, std::vector& response); diff --git a/HAL/SocketTransport.cpp b/HAL/SocketTransport.cpp index bd30e4c2..10ceab35 100644 --- a/HAL/SocketTransport.cpp +++ b/HAL/SocketTransport.cpp @@ -60,7 +60,6 @@ bool SocketTransport::openConnection() { bool SocketTransport::sendData(const vector& inData, vector& output) { uint8_t buffer[MAX_RECV_BUFFER_SIZE]; int count = 1; - closeConnection(); while (!socketStatus && count++ < 5) { sleep(1); LOG(ERROR) << "Trying to open socket connection... count: " << count; diff --git a/HAL/android.hardware.security.keymint-service.strongbox.xml b/HAL/android.hardware.security.keymint-service.strongbox.xml index ba52aadd..0631f129 100644 --- a/HAL/android.hardware.security.keymint-service.strongbox.xml +++ b/HAL/android.hardware.security.keymint-service.strongbox.xml @@ -3,4 +3,8 @@ android.hardware.security.keymint IKeyMintDevice/strongbox + + android.hardware.security.keymint + IRemotelyProvisionedComponent/strongbox + diff --git a/HAL/android.hardware.strongbox_keystore.xml b/HAL/android.hardware.strongbox_keystore.xml index 65201830..d92d6059 100644 --- a/HAL/android.hardware.strongbox_keystore.xml +++ b/HAL/android.hardware.strongbox_keystore.xml @@ -12,5 +12,6 @@ --> - + + diff --git a/HAL/service.cpp b/HAL/service.cpp index a8f92bd1..3d518770 100644 --- a/HAL/service.cpp +++ b/HAL/service.cpp @@ -26,7 +26,7 @@ #include "JavacardSecureElement.h" #include "JavacardSharedSecret.h" #include "keymint_utils.h" -//#include "JavacardRemotelyProvisionedComponentDevice.h" +#include "JavacardRemotelyProvisionedComponentDevice.h" #include using aidl::android::hardware::security::keymint::JavacardKeyMintDevice; @@ -55,7 +55,7 @@ int main() { // Add Shared Secret Service addService(card); // Add Remotely Provisioned Component Service - // addService(keyMint); + addService(card); ABinderProcess_joinThreadPool(); return EXIT_FAILURE; // should not reach diff --git a/ProvisioningTool/Makefile b/ProvisioningTool/Makefile new file mode 100644 index 00000000..7b992f47 --- /dev/null +++ b/ProvisioningTool/Makefile @@ -0,0 +1,58 @@ +CC = g++ +SRC_DIR = src + +CONSTRUCT_APDUS_SRC = $(SRC_DIR)/construct_apdus.cpp \ + $(SRC_DIR)/cppbor/cppbor.cpp \ + $(SRC_DIR)/cppbor/cppbor_parse.cpp \ + $(SRC_DIR)/utils.cpp \ + $(SRC_DIR)/cppcose/cppcose.cpp + +CONSTRUCT_APDUS_OBJFILES = $(CONSTRUCT_APDUS_SRC:.cpp=.o) +CONSTRUCT_APDUS_BIN = construct_keymint_apdus + +# source files for provision +PROVISION_SRC = $(SRC_DIR)/provision.cpp \ + $(SRC_DIR)/socket.cpp \ + $(SRC_DIR)/cppbor/cppbor.cpp \ + $(SRC_DIR)/cppbor/cppbor_parse.cpp \ + $(SRC_DIR)/utils.cpp \ + +#object files for keymint provision +PROVISION_OBJFILES = $(PROVISION_SRC:.cpp=.o) +PROVISION_BIN = provision_keymint + +ifeq ($(OS),Windows_NT) + uname_S := Windows +else + uname_S := $(shell uname -s) +endif + +ifeq ($(uname_S), Windows) + PLATFORM = -D__WIN32__ +endif +ifeq ($(uname_S), Linux) + PLATFORM = -D__LINUX__ +endif + +DEBUG = -g +CXXFLAGS = $(DEBUG) $(PLATFORM) -Wall -std=c++2a +CFLAGS = $(CXXFLAGS) -Iinclude +LDFLAGS = -Llib/ +LIB_JSON = -ljsoncpp +LIB_CRYPTO = -lcrypto +LDLIBS = $(LIB_JSON) $(LIB_CRYPTO) + +all: $(CONSTRUCT_APDUS_BIN) $(PROVISION_BIN) + +$(CONSTRUCT_APDUS_BIN): $(CONSTRUCT_APDUS_OBJFILES) + $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS) + +$(PROVISION_BIN): $(PROVISION_OBJFILES) + $(CC) $(LDFLAGS) -o $@ $^ $(LDLIBS) + +%.o: %.cpp + $(CC) $(CFLAGS) -c -o $@ $^ + +.PHONY: clean +clean: + rm -f $(CONSTRUCT_APDUS_OBJFILES) $(CONSTRUCT_APDUS_BIN) $(PROVISION_OBJFILES) $(PROVISION_BIN) diff --git a/ProvisioningTool/README.md b/ProvisioningTool/README.md new file mode 100644 index 00000000..21fd0034 --- /dev/null +++ b/ProvisioningTool/README.md @@ -0,0 +1,41 @@ +# Provisioning tool +This directory contains two tools. One which constructs the apdus and dumps them to a json file, Other which gets the apuds from the json file and provision them into a secure element simulator. Both the tools can be compiled and executed from a Linux machine. + +#### Build instruction +The default target generates both the executables. One construct_apdus and the other provision. +$ make +Individual targets can also be selected as shown below +$ make construct_apdus +$ make provision +Make clean will remove all the object files and binaries +$ make clean + +#### Environment setup +Before executing the binaries make sure LD_LIBRARY_PATH is set +export LD_LIBRARY_PATH=./lib:$LD_LIBRARY_PATH + +#### Sample resources for quick testing +one sample json files is located in this directory with name +[sample_json_keymint_cf.txt](sample_json_keymint_cf.txt) +for your reference. Use sample_json_keymint_cf.txt for keymint +cuttlefish target. Also the required certificates and keys can be found in +[test_resources](test_resources) directory for your reference. + +#### Usage for construct_apdus +

+Usage: construct_keymint_apdus options
+Valid options are:
+-h, --help                        show the help message and exit.
+-i, --input  jsonFile 	 Input json file 
+-o, --output jsonFile 	 Output json file
+
+ +#### Usage for provision +
+Usage: provision_keymint options
+Valid options are:
+-h, --help                      show the help message and exit.
+-i, --input  jsonFile 	  Input json file 
+-s, --provision_stautus   Prints the current provision status.
+-l, --lock_provision      Locks the provision state.
+
diff --git a/ProvisioningTool/include/UniquePtr.h b/ProvisioningTool/include/UniquePtr.h new file mode 100644 index 00000000..da74780b --- /dev/null +++ b/ProvisioningTool/include/UniquePtr.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/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 // for size_t + +#include + +// Default deleter for pointer types. +template struct DefaultDelete { + enum { type_must_be_complete = sizeof(T) }; + DefaultDelete() {} + void operator()(T* p) const { delete p; } +}; + +// Default deleter for array types. +template struct DefaultDelete { + enum { type_must_be_complete = sizeof(T) }; + void operator()(T* p) const { delete[] p; } +}; + +template > +using UniquePtr = std::unique_ptr; + + diff --git a/ProvisioningTool/include/constants.h b/ProvisioningTool/include/constants.h new file mode 100644 index 00000000..89854703 --- /dev/null +++ b/ProvisioningTool/include/constants.h @@ -0,0 +1,103 @@ +/* + ** + ** Copyright 2021, The Android Open Source Project + ** + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** + ** http://www.apache.org/licenses/LICENSE-2.0 + ** + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + */ +#pragma once + +#include +#include +#include +#include +#include +#include "UniquePtr.h" + +#define SUCCESS 0 +#define FAILURE 1 +#define P1_40 0x40 +#define P1_50 0x50 +#define APDU_CLS 0x80 +#define APDU_P1 0x50 +#define APDU_P2 0x00 +#define INS_BEGIN_KM_CMD 0x00 +#define APDU_RESP_STATUS_OK 0x9000 + + + +template +struct OpenSslObjectDeleter { + void operator()(T* p) { FreeFunc(p); } +}; + +#define DEFINE_OPENSSL_OBJECT_POINTER(name) \ + typedef OpenSslObjectDeleter name##_Delete; \ + typedef UniquePtr name##_Ptr; + +DEFINE_OPENSSL_OBJECT_POINTER(EC_KEY) +DEFINE_OPENSSL_OBJECT_POINTER(EVP_PKEY) +DEFINE_OPENSSL_OBJECT_POINTER(X509) +DEFINE_OPENSSL_OBJECT_POINTER(EC_POINT) +DEFINE_OPENSSL_OBJECT_POINTER(EC_GROUP) +DEFINE_OPENSSL_OBJECT_POINTER(BN_CTX) +DEFINE_OPENSSL_OBJECT_POINTER(EVP_MD_CTX) + +typedef OpenSslObjectDeleter BIGNUM_Delete; +typedef UniquePtr BIGNUM_Ptr; + +// EC Affine point length for Nist P256. +constexpr uint32_t kAffinePointLength = 32; + +// Tags +constexpr uint64_t kTagAlgorithm = 268435458u; +constexpr uint64_t kTagDigest = 536870917u; +constexpr uint64_t kTagCurve = 268435466u; +constexpr uint64_t kTagPurpose = 536870913u; +constexpr uint64_t kTagAttestationIdBrand = 2415919814u; +constexpr uint64_t kTagAttestationIdDevice = 2415919815u; +constexpr uint64_t kTagAttestationIdProduct = 2415919816u; +constexpr uint64_t kTagAttestationIdSerial = 2415919817u; +constexpr uint64_t kTagAttestationIdImei = 2415919818u; +constexpr uint64_t kTagAttestationIdMeid = 2415919819u; +constexpr uint64_t kTagAttestationIdManufacturer = 2415919820u; +constexpr uint64_t kTagAttestationIdModel = 2415919821u; + +// Values +constexpr uint64_t kCurveP256 = 1; +constexpr uint64_t kAlgorithmEc = 3; +constexpr uint64_t kDigestSha256 = 4; +constexpr uint64_t kPurposeAttest = 0x7F; +constexpr uint64_t kKeyFormatRaw = 3; + +// json keys +constexpr char kAttestKey[] = "attest_key"; +constexpr char kAttestCertChain[] = "attest_cert_chain"; +constexpr char kAttestCertParams[] = "attest_cert_params"; +constexpr char kSharedSecret[] = "shared_secret"; +constexpr char kBootParams[] = "boot_params"; +constexpr char kAttestationIds[] = "attestation_ids"; +constexpr char kDeviceUniqueKey[] = "device_unique_key"; +constexpr char kAdditionalCertChain[] = "additional_cert_chain"; +constexpr char kSignerInfo[] = "signer_info"; +constexpr char kProvisionStatus[] = "provision_status"; +constexpr char kLockProvision[] = "lock_provision"; + +// Instruction constatnts +// TODO Modify according to keymint +constexpr int kAttestationIdsCmd = INS_BEGIN_KM_CMD + 1; +constexpr int kPresharedSecretCmd = INS_BEGIN_KM_CMD + 2; +constexpr int kLockProvisionCmd = INS_BEGIN_KM_CMD + 3; +constexpr int kGetProvisionStatusCmd = INS_BEGIN_KM_CMD + 4; +constexpr int kBootParamsCmd = INS_BEGIN_KM_CMD + 5; +constexpr int kDeviceUniqueKeyCmd = INS_BEGIN_KM_CMD + 6; +constexpr int kAdditionalCertChainCmd = INS_BEGIN_KM_CMD + 7; diff --git a/ProvisioningTool/include/cppbor/cppbor.h b/ProvisioningTool/include/cppbor/cppbor.h new file mode 100644 index 00000000..45ae67cf --- /dev/null +++ b/ProvisioningTool/include/cppbor/cppbor.h @@ -0,0 +1,1113 @@ +/* + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace cppbor { + +enum MajorType : uint8_t { + UINT = 0 << 5, + NINT = 1 << 5, + BSTR = 2 << 5, + TSTR = 3 << 5, + ARRAY = 4 << 5, + MAP = 5 << 5, + SEMANTIC = 6 << 5, + SIMPLE = 7 << 5, +}; + +enum SimpleType { + BOOLEAN, + NULL_T, // Only two supported, as yet. +}; + +enum SpecialAddlInfoValues : uint8_t { + FALSE = 20, + TRUE = 21, + NULL_V = 22, + ONE_BYTE_LENGTH = 24, + TWO_BYTE_LENGTH = 25, + FOUR_BYTE_LENGTH = 26, + EIGHT_BYTE_LENGTH = 27, +}; + +class Item; +class Uint; +class Nint; +class Int; +class Tstr; +class Bstr; +class Simple; +class Bool; +class Array; +class Map; +class Null; +class SemanticTag; +class EncodedItem; +class ViewTstr; +class ViewBstr; + +/** + * Returns the size of a CBOR header that contains the additional info value addlInfo. + */ +size_t headerSize(uint64_t addlInfo); + +/** + * Encodes a CBOR header with the specified type and additional info into the range [pos, end). + * Returns a pointer to one past the last byte written, or nullptr if there isn't sufficient space + * to write the header. + */ +uint8_t* encodeHeader(MajorType type, uint64_t addlInfo, uint8_t* pos, const uint8_t* end); + +using EncodeCallback = std::function; + +/** + * Encodes a CBOR header with the specified type and additional info, passing each byte in turn to + * encodeCallback. + */ +void encodeHeader(MajorType type, uint64_t addlInfo, EncodeCallback encodeCallback); + +/** + * Encodes a CBOR header witht he specified type and additional info, writing each byte to the + * provided OutputIterator. + */ +template ::iterator_category>>> +void encodeHeader(MajorType type, uint64_t addlInfo, OutputIterator iter) { + return encodeHeader(type, addlInfo, [&](uint8_t v) { *iter++ = v; }); +} + +/** + * Item represents a CBOR-encodeable data item. Item is an abstract interface with a set of virtual + * methods that allow encoding of the item or conversion to the appropriate derived type. + */ +class Item { + public: + virtual ~Item() {} + + /** + * Returns the CBOR type of the item. + */ + virtual MajorType type() const = 0; + + // These methods safely downcast an Item to the appropriate subclass. + virtual Int* asInt() { return nullptr; } + const Int* asInt() const { return const_cast(this)->asInt(); } + virtual Uint* asUint() { return nullptr; } + const Uint* asUint() const { return const_cast(this)->asUint(); } + virtual Nint* asNint() { return nullptr; } + const Nint* asNint() const { return const_cast(this)->asNint(); } + virtual Tstr* asTstr() { return nullptr; } + const Tstr* asTstr() const { return const_cast(this)->asTstr(); } + virtual Bstr* asBstr() { return nullptr; } + const Bstr* asBstr() const { return const_cast(this)->asBstr(); } + virtual Simple* asSimple() { return nullptr; } + const Simple* asSimple() const { return const_cast(this)->asSimple(); } + virtual Map* asMap() { return nullptr; } + const Map* asMap() const { return const_cast(this)->asMap(); } + virtual Array* asArray() { return nullptr; } + const Array* asArray() const { return const_cast(this)->asArray(); } + + virtual ViewTstr* asViewTstr() { return nullptr; } + const ViewTstr* asViewTstr() const { return const_cast(this)->asViewTstr(); } + virtual ViewBstr* asViewBstr() { return nullptr; } + const ViewBstr* asViewBstr() const { return const_cast(this)->asViewBstr(); } + + // Like those above, these methods safely downcast an Item when it's actually a SemanticTag. + // However, if you think you want to use these methods, you probably don't. Typically, the way + // you should handle tagged Items is by calling the appropriate method above (e.g. asInt()) + // which will return a pointer to the tagged Item, rather than the tag itself. If you want to + // find out if the Item* you're holding is to something with one or more tags applied, see + // semanticTagCount() and semanticTag() below. + virtual SemanticTag* asSemanticTag() { return nullptr; } + const SemanticTag* asSemanticTag() const { return const_cast(this)->asSemanticTag(); } + + /** + * Returns the number of semantic tags prefixed to this Item. + */ + virtual size_t semanticTagCount() const { return 0; } + + /** + * Returns the semantic tag at the specified nesting level `nesting`, iff `nesting` is less than + * the value returned by semanticTagCount(). + * + * CBOR tags are "nested" by applying them in sequence. The "rightmost" tag is the "inner" tag. + * That is, given: + * + * 4(5(6("AES"))) which encodes as C1 C2 C3 63 414553 + * + * The tstr "AES" is tagged with 6. The combined entity ("AES" tagged with 6) is tagged with 5, + * etc. So in this example, semanticTagCount() would return 3, and semanticTag(0) would return + * 5 semanticTag(1) would return 5 and semanticTag(2) would return 4. For values of n > 2, + * semanticTag(n) will return 0, but this is a meaningless value. + * + * If this layering is confusing, you probably don't have to worry about it. Nested tagging does + * not appear to be common, so semanticTag(0) is the only one you'll use. + */ + virtual uint64_t semanticTag(size_t /* nesting */ = 0) const { return 0; } + + /** + * Returns true if this is a "compound" item, i.e. one that contains one or more other items. + */ + virtual bool isCompound() const { return false; } + + bool operator==(const Item& other) const&; + bool operator!=(const Item& other) const& { return !(*this == other); } + + /** + * Returns the number of bytes required to encode this Item into CBOR. Note that if this is a + * complex Item, calling this method will require walking the whole tree. + */ + virtual size_t encodedSize() const = 0; + + /** + * Encodes the Item into buffer referenced by range [*pos, end). Returns a pointer to one past + * the last position written. Returns nullptr if there isn't enough space to encode. + */ + virtual uint8_t* encode(uint8_t* pos, const uint8_t* end) const = 0; + + /** + * Encodes the Item by passing each encoded byte to encodeCallback. + */ + virtual void encode(EncodeCallback encodeCallback) const = 0; + + /** + * Clones the Item + */ + virtual std::unique_ptr clone() const = 0; + + /** + * Encodes the Item into the provided OutputIterator. + */ + template ::iterator_category> + void encode(OutputIterator i) const { + return encode([&](uint8_t v) { *i++ = v; }); + } + + /** + * Encodes the Item into a new std::vector. + */ + std::vector encode() const { + std::vector retval; + retval.reserve(encodedSize()); + encode(std::back_inserter(retval)); + return retval; + } + + /** + * Encodes the Item into a new std::string. + */ + std::string toString() const { + std::string retval; + retval.reserve(encodedSize()); + encode([&](uint8_t v) { retval.push_back(v); }); + return retval; + } + + /** + * Encodes only the header of the Item. + */ + inline uint8_t* encodeHeader(uint64_t addlInfo, uint8_t* pos, const uint8_t* end) const { + return ::cppbor::encodeHeader(type(), addlInfo, pos, end); + } + + /** + * Encodes only the header of the Item. + */ + inline void encodeHeader(uint64_t addlInfo, EncodeCallback encodeCallback) const { + ::cppbor::encodeHeader(type(), addlInfo, encodeCallback); + } +}; + +/** + * EncodedItem represents a bit of already-encoded CBOR. Caveat emptor: It does no checking to + * ensure that the provided data is a valid encoding, cannot be meaninfully-compared with other + * kinds of items and you cannot use the as*() methods to find out what's inside it. + */ +class EncodedItem : public Item { + public: + explicit EncodedItem(std::vector value) : mValue(std::move(value)) {} + + bool operator==(const EncodedItem& other) const& { return mValue == other.mValue; } + + // Type can't be meaningfully-obtained. We could extract the type from the first byte and return + // it, but you can't do any of the normal things with an EncodedItem so there's no point. + MajorType type() const override { + assert(false); + return static_cast(-1); + } + size_t encodedSize() const override { return mValue.size(); } + uint8_t* encode(uint8_t* pos, const uint8_t* end) const override { + if (end - pos < static_cast(mValue.size())) return nullptr; + return std::copy(mValue.begin(), mValue.end(), pos); + } + void encode(EncodeCallback encodeCallback) const override { + std::for_each(mValue.begin(), mValue.end(), encodeCallback); + } + std::unique_ptr clone() const override { return std::make_unique(mValue); } + + private: + std::vector mValue; +}; + +/** + * Int is an abstraction that allows Uint and Nint objects to be manipulated without caring about + * the sign. + */ +class Int : public Item { + public: + bool operator==(const Int& other) const& { return value() == other.value(); } + + virtual int64_t value() const = 0; + using Item::asInt; + Int* asInt() override { return this; } +}; + +/** + * Uint is a concrete Item that implements CBOR major type 0. + */ +class Uint : public Int { + public: + static constexpr MajorType kMajorType = UINT; + + explicit Uint(uint64_t v) : mValue(v) {} + + bool operator==(const Uint& other) const& { return mValue == other.mValue; } + + MajorType type() const override { return kMajorType; } + using Item::asUint; + Uint* asUint() override { return this; } + + size_t encodedSize() const override { return headerSize(mValue); } + + int64_t value() const override { return mValue; } + uint64_t unsignedValue() const { return mValue; } + + using Item::encode; + uint8_t* encode(uint8_t* pos, const uint8_t* end) const override { + return encodeHeader(mValue, pos, end); + } + void encode(EncodeCallback encodeCallback) const override { + encodeHeader(mValue, encodeCallback); + } + + std::unique_ptr clone() const override { return std::make_unique(mValue); } + + private: + uint64_t mValue; +}; + +/** + * Nint is a concrete Item that implements CBOR major type 1. + + * Note that it is incapable of expressing the full range of major type 1 values, becaue it can only + * express values that fall into the range [std::numeric_limits::min(), -1]. It cannot + * express values in the range [std::numeric_limits::min() - 1, + * -std::numeric_limits::max()]. + */ +class Nint : public Int { + public: + static constexpr MajorType kMajorType = NINT; + + explicit Nint(int64_t v); + + bool operator==(const Nint& other) const& { return mValue == other.mValue; } + + MajorType type() const override { return kMajorType; } + using Item::asNint; + Nint* asNint() override { return this; } + size_t encodedSize() const override { return headerSize(addlInfo()); } + + int64_t value() const override { return mValue; } + + using Item::encode; + uint8_t* encode(uint8_t* pos, const uint8_t* end) const override { + return encodeHeader(addlInfo(), pos, end); + } + void encode(EncodeCallback encodeCallback) const override { + encodeHeader(addlInfo(), encodeCallback); + } + + std::unique_ptr clone() const override { return std::make_unique(mValue); } + + private: + uint64_t addlInfo() const { return -1ll - mValue; } + + int64_t mValue; +}; + +/** + * Bstr is a concrete Item that implements major type 2. + */ +class Bstr : public Item { + public: + static constexpr MajorType kMajorType = BSTR; + + // Construct an empty Bstr + explicit Bstr() {} + + // Construct from a vector + explicit Bstr(std::vector v) : mValue(std::move(v)) {} + + // Construct from a string + explicit Bstr(const std::string& v) + : mValue(reinterpret_cast(v.data()), + reinterpret_cast(v.data()) + v.size()) {} + + // Construct from a pointer/size pair + explicit Bstr(const std::pair& buf) + : mValue(buf.first, buf.first + buf.second) {} + + // Construct from a pair of iterators + template ::iterator_category, + typename = typename std::iterator_traits::iterator_category> + explicit Bstr(const std::pair& pair) : mValue(pair.first, pair.second) {} + + // Construct from an iterator range. + template ::iterator_category, + typename = typename std::iterator_traits::iterator_category> + Bstr(I1 begin, I2 end) : mValue(begin, end) {} + + bool operator==(const Bstr& other) const& { return mValue == other.mValue; } + + MajorType type() const override { return kMajorType; } + using Item::asBstr; + Bstr* asBstr() override { return this; } + size_t encodedSize() const override { return headerSize(mValue.size()) + mValue.size(); } + using Item::encode; + uint8_t* encode(uint8_t* pos, const uint8_t* end) const override; + void encode(EncodeCallback encodeCallback) const override { + encodeHeader(mValue.size(), encodeCallback); + encodeValue(encodeCallback); + } + + const std::vector& value() const { return mValue; } + std::vector&& moveValue() { return std::move(mValue); } + + std::unique_ptr clone() const override { return std::make_unique(mValue); } + + private: + void encodeValue(EncodeCallback encodeCallback) const; + + std::vector mValue; +}; + +/** + * ViewBstr is a read-only version of Bstr backed by std::string_view + */ +class ViewBstr : public Item { + public: + static constexpr MajorType kMajorType = BSTR; + + // Construct an empty ViewBstr + explicit ViewBstr() {} + + // Construct from a string_view of uint8_t values + explicit ViewBstr(std::basic_string_view v) : mView(std::move(v)) {} + + // Construct from a string_view + explicit ViewBstr(std::string_view v) + : mView(reinterpret_cast(v.data()), v.size()) {} + + // Construct from an iterator range + template ::iterator_category, + typename = typename std::iterator_traits::iterator_category> + ViewBstr(I1 begin, I2 end) : mView(begin, end) {} + + // Construct from a uint8_t pointer pair + ViewBstr(const uint8_t* begin, const uint8_t* end) + : mView(begin, std::distance(begin, end)) {} + + bool operator==(const ViewBstr& other) const& { return mView == other.mView; } + + MajorType type() const override { return kMajorType; } + using Item::asViewBstr; + ViewBstr* asViewBstr() override { return this; } + size_t encodedSize() const override { return headerSize(mView.size()) + mView.size(); } + using Item::encode; + uint8_t* encode(uint8_t* pos, const uint8_t* end) const override; + void encode(EncodeCallback encodeCallback) const override { + encodeHeader(mView.size(), encodeCallback); + encodeValue(encodeCallback); + } + + const std::basic_string_view& view() const { return mView; } + + std::unique_ptr clone() const override { return std::make_unique(mView); } + + private: + void encodeValue(EncodeCallback encodeCallback) const; + + std::basic_string_view mView; +}; + +/** + * Tstr is a concrete Item that implements major type 3. + */ +class Tstr : public Item { + public: + static constexpr MajorType kMajorType = TSTR; + + // Construct from a string + explicit Tstr(std::string v) : mValue(std::move(v)) {} + + // Construct from a string_view + explicit Tstr(const std::string_view& v) : mValue(v) {} + + // Construct from a C string + explicit Tstr(const char* v) : mValue(std::string(v)) {} + + // Construct from a pair of iterators + template ::iterator_category, + typename = typename std::iterator_traits::iterator_category> + explicit Tstr(const std::pair& pair) : mValue(pair.first, pair.second) {} + + // Construct from an iterator range + template ::iterator_category, + typename = typename std::iterator_traits::iterator_category> + Tstr(I1 begin, I2 end) : mValue(begin, end) {} + + bool operator==(const Tstr& other) const& { return mValue == other.mValue; } + + MajorType type() const override { return kMajorType; } + using Item::asTstr; + Tstr* asTstr() override { return this; } + size_t encodedSize() const override { return headerSize(mValue.size()) + mValue.size(); } + using Item::encode; + uint8_t* encode(uint8_t* pos, const uint8_t* end) const override; + void encode(EncodeCallback encodeCallback) const override { + encodeHeader(mValue.size(), encodeCallback); + encodeValue(encodeCallback); + } + + const std::string& value() const { return mValue; } + std::string&& moveValue() { return std::move(mValue); } + + std::unique_ptr clone() const override { return std::make_unique(mValue); } + + private: + void encodeValue(EncodeCallback encodeCallback) const; + + std::string mValue; +}; + +/** + * ViewTstr is a read-only version of Tstr backed by std::string_view + */ +class ViewTstr : public Item { + public: + static constexpr MajorType kMajorType = TSTR; + + // Construct an empty ViewTstr + explicit ViewTstr() {} + + // Construct from a string_view + explicit ViewTstr(std::string_view v) : mView(std::move(v)) {} + + // Construct from an iterator range + template ::iterator_category, + typename = typename std::iterator_traits::iterator_category> + ViewTstr(I1 begin, I2 end) : mView(begin, end) {} + + // Construct from a uint8_t pointer pair + ViewTstr(const uint8_t* begin, const uint8_t* end) + : mView(reinterpret_cast(begin), + std::distance(begin, end)) {} + + bool operator==(const ViewTstr& other) const& { return mView == other.mView; } + + MajorType type() const override { return kMajorType; } + using Item::asViewTstr; + ViewTstr* asViewTstr() override { return this; } + size_t encodedSize() const override { return headerSize(mView.size()) + mView.size(); } + using Item::encode; + uint8_t* encode(uint8_t* pos, const uint8_t* end) const override; + void encode(EncodeCallback encodeCallback) const override { + encodeHeader(mView.size(), encodeCallback); + encodeValue(encodeCallback); + } + + const std::string_view& view() const { return mView; } + + std::unique_ptr clone() const override { return std::make_unique(mView); } + + private: + void encodeValue(EncodeCallback encodeCallback) const; + + std::string_view mView; +}; + +/* + * Array is a concrete Item that implements CBOR major type 4. + * + * Note that Arrays are not copyable. This is because copying them is expensive and making them + * move-only ensures that they're never copied accidentally. If you actually want to copy an Array, + * use the clone() method. + */ +class Array : public Item { + public: + static constexpr MajorType kMajorType = ARRAY; + + Array() = default; + Array(const Array& other) = delete; + Array(Array&&) = default; + Array& operator=(const Array&) = delete; + Array& operator=(Array&&) = default; + + bool operator==(const Array& other) const&; + + /** + * Construct an Array from a variable number of arguments of different types. See + * details::makeItem below for details on what types may be provided. In general, this accepts + * all of the types you'd expect and doest the things you'd expect (integral values are addes as + * Uint or Nint, std::string and char* are added as Tstr, bools are added as Bool, etc.). + */ + template + Array(Args&&... args); + + /** + * Append a single element to the Array, of any compatible type. + */ + template + Array& add(T&& v) &; + template + Array&& add(T&& v) &&; + + bool isCompound() const override { return true; } + + virtual size_t size() const { return mEntries.size(); } + + size_t encodedSize() const override { + return std::accumulate(mEntries.begin(), mEntries.end(), headerSize(size()), + [](size_t sum, auto& entry) { return sum + entry->encodedSize(); }); + } + + using Item::encode; // Make base versions visible. + uint8_t* encode(uint8_t* pos, const uint8_t* end) const override; + void encode(EncodeCallback encodeCallback) const override; + + const std::unique_ptr& operator[](size_t index) const { return get(index); } + std::unique_ptr& operator[](size_t index) { return get(index); } + + const std::unique_ptr& get(size_t index) const { return mEntries[index]; } + std::unique_ptr& get(size_t index) { return mEntries[index]; } + + MajorType type() const override { return kMajorType; } + using Item::asArray; + Array* asArray() override { return this; } + + std::unique_ptr clone() const override; + + auto begin() { return mEntries.begin(); } + auto begin() const { return mEntries.begin(); } + auto end() { return mEntries.end(); } + auto end() const { return mEntries.end(); } + + protected: + std::vector> mEntries; +}; + +/* + * Map is a concrete Item that implements CBOR major type 5. + * + * Note that Maps are not copyable. This is because copying them is expensive and making them + * move-only ensures that they're never copied accidentally. If you actually want to copy a + * Map, use the clone() method. + */ +class Map : public Item { + public: + static constexpr MajorType kMajorType = MAP; + + using entry_type = std::pair, std::unique_ptr>; + + Map() = default; + Map(const Map& other) = delete; + Map(Map&&) = default; + Map& operator=(const Map& other) = delete; + Map& operator=(Map&&) = default; + + bool operator==(const Map& other) const&; + + /** + * Construct a Map from a variable number of arguments of different types. An even number of + * arguments must be provided (this is verified statically). See details::makeItem below for + * details on what types may be provided. In general, this accepts all of the types you'd + * expect and doest the things you'd expect (integral values are addes as Uint or Nint, + * std::string and char* are added as Tstr, bools are added as Bool, etc.). + */ + template + Map(Args&&... args); + + /** + * Append a key/value pair to the Map, of any compatible types. + */ + template + Map& add(Key&& key, Value&& value) &; + template + Map&& add(Key&& key, Value&& value) &&; + + bool isCompound() const override { return true; } + + virtual size_t size() const { return mEntries.size(); } + + size_t encodedSize() const override { + return std::accumulate( + mEntries.begin(), mEntries.end(), headerSize(size()), [](size_t sum, auto& entry) { + return sum + entry.first->encodedSize() + entry.second->encodedSize(); + }); + } + + using Item::encode; // Make base versions visible. + uint8_t* encode(uint8_t* pos, const uint8_t* end) const override; + void encode(EncodeCallback encodeCallback) const override; + + /** + * Find and return the value associated with `key`, if any. + * + * If the searched-for `key` is not present, returns `nullptr`. + * + * Note that if the map is canonicalized (sorted), Map::get() peforms a binary search. If your + * map is large and you're searching in it many times, it may be worthwhile to canonicalize it + * to make Map::get() faster. Any use of a method that might modify the map disables the + * speedup. + */ + template + const std::unique_ptr& get(Key key) const; + + // Note that use of non-const operator[] marks the map as not canonicalized. + auto& operator[](size_t index) { + mCanonicalized = false; + return mEntries[index]; + } + const auto& operator[](size_t index) const { return mEntries[index]; } + + MajorType type() const override { return kMajorType; } + using Item::asMap; + Map* asMap() override { return this; } + + /** + * Sorts the map in canonical order, as defined in RFC 7049. Use this before encoding if you + * want canonicalization; cppbor does not canonicalize by default, though the integer encodings + * are always canonical and cppbor does not support indefinite-length encodings, so map order + * canonicalization is the only thing that needs to be done. + * + * @param recurse If set to true, canonicalize() will also walk the contents of the map and + * canonicalize any contained maps as well. + */ + Map& canonicalize(bool recurse = false) &; + Map&& canonicalize(bool recurse = false) && { + canonicalize(recurse); + return std::move(*this); + } + + bool isCanonical() { return mCanonicalized; } + + std::unique_ptr clone() const override; + + auto begin() { + mCanonicalized = false; + return mEntries.begin(); + } + auto begin() const { return mEntries.begin(); } + auto end() { + mCanonicalized = false; + return mEntries.end(); + } + auto end() const { return mEntries.end(); } + + // Returns true if a < b, per CBOR map key canonicalization rules. + static bool keyLess(const Item* a, const Item* b); + + protected: + std::vector mEntries; + + private: + bool mCanonicalized = false; +}; + +class SemanticTag : public Item { + public: + static constexpr MajorType kMajorType = SEMANTIC; + + template + SemanticTag(uint64_t tagValue, T&& taggedItem); + SemanticTag(const SemanticTag& other) = delete; + SemanticTag(SemanticTag&&) = default; + SemanticTag& operator=(const SemanticTag& other) = delete; + SemanticTag& operator=(SemanticTag&&) = default; + + bool operator==(const SemanticTag& other) const& { + return mValue == other.mValue && *mTaggedItem == *other.mTaggedItem; + } + + bool isCompound() const override { return true; } + + virtual size_t size() const { return 1; } + + // Encoding returns the tag + enclosed Item. + size_t encodedSize() const override { return headerSize(mValue) + mTaggedItem->encodedSize(); } + + using Item::encode; // Make base versions visible. + uint8_t* encode(uint8_t* pos, const uint8_t* end) const override; + void encode(EncodeCallback encodeCallback) const override; + + // type() is a bit special. In normal usage it should return the wrapped type, but during + // parsing when we haven't yet parsed the tagged item, it needs to return SEMANTIC. + MajorType type() const override { return mTaggedItem ? mTaggedItem->type() : SEMANTIC; } + using Item::asSemanticTag; + SemanticTag* asSemanticTag() override { return this; } + + // Type information reflects the enclosed Item. Note that if the immediately-enclosed Item is + // another tag, these methods will recurse down to the non-tag Item. + using Item::asInt; + Int* asInt() override { return mTaggedItem->asInt(); } + using Item::asUint; + Uint* asUint() override { return mTaggedItem->asUint(); } + using Item::asNint; + Nint* asNint() override { return mTaggedItem->asNint(); } + using Item::asTstr; + Tstr* asTstr() override { return mTaggedItem->asTstr(); } + using Item::asBstr; + Bstr* asBstr() override { return mTaggedItem->asBstr(); } + using Item::asSimple; + Simple* asSimple() override { return mTaggedItem->asSimple(); } + using Item::asMap; + Map* asMap() override { return mTaggedItem->asMap(); } + using Item::asArray; + Array* asArray() override { return mTaggedItem->asArray(); } + using Item::asViewTstr; + ViewTstr* asViewTstr() override { return mTaggedItem->asViewTstr(); } + using Item::asViewBstr; + ViewBstr* asViewBstr() override { return mTaggedItem->asViewBstr(); } + + std::unique_ptr clone() const override; + + size_t semanticTagCount() const override; + uint64_t semanticTag(size_t nesting = 0) const override; + + protected: + SemanticTag() = default; + SemanticTag(uint64_t value) : mValue(value) {} + uint64_t mValue; + std::unique_ptr mTaggedItem; +}; + +/** + * Simple is abstract Item that implements CBOR major type 7. It is intended to be subclassed to + * create concrete Simple types. At present only Bool is provided. + */ +class Simple : public Item { + public: + static constexpr MajorType kMajorType = SIMPLE; + + bool operator==(const Simple& other) const&; + + virtual SimpleType simpleType() const = 0; + MajorType type() const override { return kMajorType; } + + Simple* asSimple() override { return this; } + + virtual const Bool* asBool() const { return nullptr; }; + virtual const Null* asNull() const { return nullptr; }; +}; + +/** + * Bool is a concrete type that implements CBOR major type 7, with additional item values for TRUE + * and FALSE. + */ +class Bool : public Simple { + public: + static constexpr SimpleType kSimpleType = BOOLEAN; + + explicit Bool(bool v) : mValue(v) {} + + bool operator==(const Bool& other) const& { return mValue == other.mValue; } + + SimpleType simpleType() const override { return kSimpleType; } + const Bool* asBool() const override { return this; } + + size_t encodedSize() const override { return 1; } + + using Item::encode; + uint8_t* encode(uint8_t* pos, const uint8_t* end) const override { + return encodeHeader(mValue ? TRUE : FALSE, pos, end); + } + void encode(EncodeCallback encodeCallback) const override { + encodeHeader(mValue ? TRUE : FALSE, encodeCallback); + } + + bool value() const { return mValue; } + + std::unique_ptr clone() const override { return std::make_unique(mValue); } + + private: + bool mValue; +}; + +/** + * Null is a concrete type that implements CBOR major type 7, with additional item value for NULL + */ +class Null : public Simple { + public: + static constexpr SimpleType kSimpleType = NULL_T; + + explicit Null() {} + + SimpleType simpleType() const override { return kSimpleType; } + const Null* asNull() const override { return this; } + + size_t encodedSize() const override { return 1; } + + using Item::encode; + uint8_t* encode(uint8_t* pos, const uint8_t* end) const override { + return encodeHeader(NULL_V, pos, end); + } + void encode(EncodeCallback encodeCallback) const override { + encodeHeader(NULL_V, encodeCallback); + } + + std::unique_ptr clone() const override { return std::make_unique(); } +}; + +/** + * Returns pretty-printed CBOR for |item| + * + * If a byte-string is larger than |maxBStrSize| its contents will not be printed, instead the value + * of the form "" will be + * printed. Pass zero for |maxBStrSize| to disable this. + * + * The |mapKeysToNotPrint| parameter specifies the name of map values to not print. This is useful + * for unit tests. + */ +std::string prettyPrint(const Item* item, size_t maxBStrSize = 32, + const std::vector& mapKeysNotToPrint = {}); + +/** + * Returns pretty-printed CBOR for |value|. + * + * Only valid CBOR should be passed to this function. + * + * If a byte-string is larger than |maxBStrSize| its contents will not be printed, instead the value + * of the form "" will be + * printed. Pass zero for |maxBStrSize| to disable this. + * + * The |mapKeysToNotPrint| parameter specifies the name of map values to not print. This is useful + * for unit tests. + */ +std::string prettyPrint(const std::vector& encodedCbor, size_t maxBStrSize = 32, + const std::vector& mapKeysNotToPrint = {}); + +/** + * Details. Mostly you shouldn't have to look below, except perhaps at the docstring for makeItem. + */ +namespace details { + +template +struct is_iterator_pair_over : public std::false_type {}; + +template +struct is_iterator_pair_over< + std::pair, V, + typename std::enable_if_t::value_type>>> + : public std::true_type {}; + +template +struct is_unique_ptr_of_subclass_of_v : public std::false_type {}; + +template +struct is_unique_ptr_of_subclass_of_v, + typename std::enable_if_t>> + : public std::true_type {}; + +/* check if type is one of std::string (1), std::string_view (2), null-terminated char* (3) or pair + * of iterators (4)*/ +template +struct is_text_type_v : public std::false_type {}; + +template +struct is_text_type_v< + T, typename std::enable_if_t< + /* case 1 */ // + std::is_same_v>, std::string> + /* case 2 */ // + || std::is_same_v>, std::string_view> + /* case 3 */ // + || std::is_same_v>, char*> // + || std::is_same_v>, const char*> + /* case 4 */ + || details::is_iterator_pair_over::value>> : public std::true_type {}; + +/** + * Construct a unique_ptr from many argument types. Accepts: + * + * (a) booleans; + * (b) integers, all sizes and signs; + * (c) text strings, as defined by is_text_type_v above; + * (d) byte strings, as std::vector(d1), pair of iterators (d2) or pair + * (d3); and + * (e) Item subclass instances, including Array and Map. Items may be provided by naked pointer + * (e1), unique_ptr (e2), reference (e3) or value (e3). If provided by reference or value, will + * be moved if possible. If provided by pointer, ownership is taken. + * (f) null pointer; + * (g) enums, using the underlying integer value. + */ +template +std::unique_ptr makeItem(T v) { + Item* p = nullptr; + if constexpr (/* case a */ std::is_same_v) { + p = new Bool(v); + } else if constexpr (/* case b */ std::is_integral_v) { // b + if (v < 0) { + p = new Nint(v); + } else { + p = new Uint(static_cast(v)); + } + } else if constexpr (/* case c */ // + details::is_text_type_v::value) { + p = new Tstr(v); + } else if constexpr (/* case d1 */ // + std::is_same_v>, + std::vector> + /* case d2 */ // + || details::is_iterator_pair_over::value + /* case d3 */ // + || std::is_same_v>, + std::pair>) { + p = new Bstr(v); + } else if constexpr (/* case e1 */ // + std::is_pointer_v && + std::is_base_of_v>) { + p = v; + } else if constexpr (/* case e2 */ // + details::is_unique_ptr_of_subclass_of_v::value) { + p = v.release(); + } else if constexpr (/* case e3 */ // + std::is_base_of_v) { + p = new T(std::move(v)); + } else if constexpr (/* case f */ std::is_null_pointer_v) { + p = new Null(); + } else if constexpr (/* case g */ std::is_enum_v) { + return makeItem(static_cast>(v)); + } else { + // It's odd that this can't be static_assert(false), since it shouldn't be evaluated if one + // of the above ifs matches. But static_assert(false) always triggers. + static_assert(std::is_same_v, "makeItem called with unsupported type"); + } + return std::unique_ptr(p); +} + +inline void map_helper(Map& /* map */) {} + +template +inline void map_helper(Map& map, Key&& key, Value&& value, Rest&&... rest) { + map.add(std::forward(key), std::forward(value)); + map_helper(map, std::forward(rest)...); +} + +} // namespace details + +template >> || ...)>> +Array::Array(Args&&... args) { + mEntries.reserve(sizeof...(args)); + (mEntries.push_back(details::makeItem(std::forward(args))), ...); +} + +template +Array& Array::add(T&& v) & { + mEntries.push_back(details::makeItem(std::forward(v))); + return *this; +} + +template +Array&& Array::add(T&& v) && { + mEntries.push_back(details::makeItem(std::forward(v))); + return std::move(*this); +} + +template > +Map::Map(Args&&... args) { + static_assert((sizeof...(Args)) % 2 == 0, "Map must have an even number of entries"); + mEntries.reserve(sizeof...(args) / 2); + details::map_helper(*this, std::forward(args)...); +} + +template +Map& Map::add(Key&& key, Value&& value) & { + mEntries.push_back({details::makeItem(std::forward(key)), + details::makeItem(std::forward(value))}); + mCanonicalized = false; + return *this; +} + +template +Map&& Map::add(Key&& key, Value&& value) && { + this->add(std::forward(key), std::forward(value)); + return std::move(*this); +} + +static const std::unique_ptr kEmptyItemPtr; + +template || std::is_enum_v || + details::is_text_type_v::value>> +const std::unique_ptr& Map::get(Key key) const { + auto keyItem = details::makeItem(key); + + if (mCanonicalized) { + // It's sorted, so binary-search it. + auto found = std::lower_bound(begin(), end(), keyItem.get(), + [](const entry_type& entry, const Item* key) { + return keyLess(entry.first.get(), key); + }); + return (found == end() || *found->first != *keyItem) ? kEmptyItemPtr : found->second; + } else { + // Unsorted, do a linear search. + auto found = std::find_if( + begin(), end(), [&](const entry_type& entry) { return *entry.first == *keyItem; }); + return found == end() ? kEmptyItemPtr : found->second; + } +} + +template +SemanticTag::SemanticTag(uint64_t value, T&& taggedItem) + : mValue(value), mTaggedItem(details::makeItem(std::forward(taggedItem))) {} + +} // namespace cppbor diff --git a/ProvisioningTool/include/cppbor/cppbor_parse.h b/ProvisioningTool/include/cppbor/cppbor_parse.h new file mode 100644 index 00000000..22cd18d0 --- /dev/null +++ b/ProvisioningTool/include/cppbor/cppbor_parse.h @@ -0,0 +1,195 @@ +/* + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/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" + +namespace cppbor { + +using ParseResult = std::tuple /* result */, const uint8_t* /* newPos */, + std::string /* errMsg */>; + +/** + * Parse the first CBOR data item (possibly compound) from the range [begin, end). + * + * Returns a tuple of Item pointer, buffer pointer and error message. If parsing is successful, the + * Item pointer is non-null, the buffer pointer points to the first byte after the + * successfully-parsed item and the error message string is empty. If parsing fails, the Item + * pointer is null, the buffer pointer points to the first byte that was unparseable (the first byte + * of a data item header that is malformed in some way, e.g. an invalid value, or a length that is + * too large for the remaining buffer, etc.) and the string contains an error message describing the + * problem encountered. + */ +ParseResult parse(const uint8_t* begin, const uint8_t* end); + +/** + * Parse the first CBOR data item (possibly compound) from the range [begin, end). + * + * Returns a tuple of Item pointer, buffer pointer and error message. If parsing is successful, the + * Item pointer is non-null, the buffer pointer points to the first byte after the + * successfully-parsed item and the error message string is empty. If parsing fails, the Item + * pointer is null, the buffer pointer points to the first byte that was unparseable (the first byte + * of a data item header that is malformed in some way, e.g. an invalid value, or a length that is + * too large for the remaining buffer, etc.) and the string contains an error message describing the + * problem encountered. + * + * The returned CBOR data item will contain View* items backed by + * std::string_view types over the input range. + * WARNING! If the input range changes underneath, the corresponding views will + * carry the same change. + */ +ParseResult parseWithViews(const uint8_t* begin, const uint8_t* end); + +/** + * Parse the first CBOR data item (possibly compound) from the byte vector. + * + * Returns a tuple of Item pointer, buffer pointer and error message. If parsing is successful, the + * Item pointer is non-null, the buffer pointer points to the first byte after the + * successfully-parsed item and the error message string is empty. If parsing fails, the Item + * pointer is null, the buffer pointer points to the first byte that was unparseable (the first byte + * of a data item header that is malformed in some way, e.g. an invalid value, or a length that is + * too large for the remaining buffer, etc.) and the string contains an error message describing the + * problem encountered. + */ +inline ParseResult parse(const std::vector& encoding) { + return parse(encoding.data(), encoding.data() + encoding.size()); +} + +/** + * Parse the first CBOR data item (possibly compound) from the range [begin, begin + size). + * + * Returns a tuple of Item pointer, buffer pointer and error message. If parsing is successful, the + * Item pointer is non-null, the buffer pointer points to the first byte after the + * successfully-parsed item and the error message string is empty. If parsing fails, the Item + * pointer is null, the buffer pointer points to the first byte that was unparseable (the first byte + * of a data item header that is malformed in some way, e.g. an invalid value, or a length that is + * too large for the remaining buffer, etc.) and the string contains an error message describing the + * problem encountered. + */ +inline ParseResult parse(const uint8_t* begin, size_t size) { + return parse(begin, begin + size); +} + +/** + * Parse the first CBOR data item (possibly compound) from the range [begin, begin + size). + * + * Returns a tuple of Item pointer, buffer pointer and error message. If parsing is successful, the + * Item pointer is non-null, the buffer pointer points to the first byte after the + * successfully-parsed item and the error message string is empty. If parsing fails, the Item + * pointer is null, the buffer pointer points to the first byte that was unparseable (the first byte + * of a data item header that is malformed in some way, e.g. an invalid value, or a length that is + * too large for the remaining buffer, etc.) and the string contains an error message describing the + * problem encountered. + * + * The returned CBOR data item will contain View* items backed by + * std::string_view types over the input range. + * WARNING! If the input range changes underneath, the corresponding views will + * carry the same change. + */ +inline ParseResult parseWithViews(const uint8_t* begin, size_t size) { + return parseWithViews(begin, begin + size); +} + +/** + * Parse the first CBOR data item (possibly compound) from the value contained in a Bstr. + * + * Returns a tuple of Item pointer, buffer pointer and error message. If parsing is successful, the + * Item pointer is non-null, the buffer pointer points to the first byte after the + * successfully-parsed item and the error message string is empty. If parsing fails, the Item + * pointer is null, the buffer pointer points to the first byte that was unparseable (the first byte + * of a data item header that is malformed in some way, e.g. an invalid value, or a length that is + * too large for the remaining buffer, etc.) and the string contains an error message describing the + * problem encountered. + */ +inline ParseResult parse(const Bstr* bstr) { + if (!bstr) + return ParseResult(nullptr, nullptr, "Null Bstr pointer"); + return parse(bstr->value()); +} + +class ParseClient; + +/** + * Parse the CBOR data in the range [begin, end) in streaming fashion, calling methods on the + * provided ParseClient when elements are found. + */ +void parse(const uint8_t* begin, const uint8_t* end, ParseClient* parseClient); + +/** + * Parse the CBOR data in the range [begin, end) in streaming fashion, calling methods on the + * provided ParseClient when elements are found. Uses the View* item types + * instead of the copying ones. + */ +void parseWithViews(const uint8_t* begin, const uint8_t* end, ParseClient* parseClient); + +/** + * Parse the CBOR data in the vector in streaming fashion, calling methods on the + * provided ParseClient when elements are found. + */ +inline void parse(const std::vector& encoding, ParseClient* parseClient) { + return parse(encoding.data(), encoding.data() + encoding.size(), parseClient); +} + +/** + * A pure interface that callers of the streaming parse functions must implement. + */ +class ParseClient { + public: + virtual ~ParseClient() {} + + /** + * Called when an item is found. The Item pointer points to the found item; use type() and + * the appropriate as*() method to examine the value. hdrBegin points to the first byte of the + * header, valueBegin points to the first byte of the value and end points one past the end of + * the item. In the case of header-only items, such as integers, and compound items (ARRAY, + * MAP or SEMANTIC) whose end has not yet been found, valueBegin and end are equal and point to + * the byte past the header. + * + * Note that for compound types (ARRAY, MAP, and SEMANTIC), the Item will have no content. For + * Map and Array items, the size() method will return a correct value, but the index operators + * are unsafe, and the object cannot be safely compared with another Array/Map. + * + * The method returns a ParseClient*. In most cases "return this;" will be the right answer, + * but a different ParseClient may be returned, which the parser will begin using. If the method + * returns nullptr, parsing will be aborted immediately. + */ + virtual ParseClient* item(std::unique_ptr& item, const uint8_t* hdrBegin, + const uint8_t* valueBegin, const uint8_t* end) = 0; + + /** + * Called when the end of a compound item (MAP or ARRAY) is found. The item argument will be + * the same one passed to the item() call -- and may be empty if item() moved its value out. + * hdrBegin, valueBegin and end point to the beginning of the item header, the beginning of the + * first contained value, and one past the end of the last contained value, respectively. + * + * Note that the Item will have no content. + * + * As with item(), itemEnd() can change the ParseClient by returning a different one, or end the + * parsing by returning nullptr; + */ + virtual ParseClient* itemEnd(std::unique_ptr& item, const uint8_t* hdrBegin, + const uint8_t* valueBegin, const uint8_t* end) = 0; + + /** + * Called when parsing encounters an error. position is set to the first unparsed byte (one + * past the last successfully-parsed byte) and errorMessage contains an message explaining what + * sort of error occurred. + */ + virtual void error(const uint8_t* position, const std::string& errorMessage) = 0; +}; + +} // namespace cppbor diff --git a/ProvisioningTool/include/cppcose/cppcose.h b/ProvisioningTool/include/cppcose/cppcose.h new file mode 100644 index 00000000..09a2d76f --- /dev/null +++ b/ProvisioningTool/include/cppcose/cppcose.h @@ -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. + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include + +namespace cppcose { + +template class ErrMsgOr; +using bytevec = std::vector; + +constexpr int kCoseSign1EntryCount = 4; +constexpr int kCoseSign1ProtectedParams = 0; +constexpr int kCoseSign1UnprotectedParams = 1; +constexpr int kCoseSign1Payload = 2; +constexpr int kCoseSign1Signature = 3; + +constexpr int kCoseMac0EntryCount = 4; +constexpr int kCoseMac0ProtectedParams = 0; +constexpr int kCoseMac0UnprotectedParams = 1; +constexpr int kCoseMac0Payload = 2; +constexpr int kCoseMac0Tag = 3; + +constexpr int kCoseEncryptEntryCount = 4; +constexpr int kCoseEncryptProtectedParams = 0; +constexpr int kCoseEncryptUnprotectedParams = 1; +constexpr int kCoseEncryptPayload = 2; +constexpr int kCoseEncryptRecipients = 3; + +enum Label : int { + ALGORITHM = 1, + KEY_ID = 4, + IV = 5, + COSE_KEY = -1, +}; + +enum CoseKeyAlgorithm : int { + AES_GCM_256 = 3, + HMAC_256 = 5, + ES256 = -7, // ECDSA with SHA-256 + EDDSA = -8, + ECDH_ES_HKDF_256 = -25, +}; + +enum CoseKeyCurve : int { P256 = 1, X25519 = 4, ED25519 = 6 }; +enum CoseKeyType : int { OCTET_KEY_PAIR = 1, EC2 = 2, SYMMETRIC_KEY = 4 }; +enum CoseKeyOps : int { SIGN = 1, VERIFY = 2, ENCRYPT = 3, DECRYPT = 4 }; + +constexpr int kAesGcmNonceLength = 12; +constexpr int kAesGcmTagSize = 16; +constexpr int kAesGcmKeySize = 32; +constexpr int kAesGcmKeySizeBits = 256; + +template class ErrMsgOr { + public: + ErrMsgOr(std::string errMsg) // NOLINT(google-explicit-constructor) + : errMsg_(std::move(errMsg)) {} + ErrMsgOr(const char* errMsg) // NOLINT(google-explicit-constructor) + : errMsg_(errMsg) {} + ErrMsgOr(T val) // NOLINT(google-explicit-constructor) + : value_(std::move(val)) {} + + explicit operator bool() const { return value_.has_value(); } + + T* operator->() & { + assert(value_); + return &value_.value(); + } + T& operator*() & { + assert(value_); + return value_.value(); + }; + T&& operator*() && { + assert(value_); + return std::move(value_).value(); + }; + + const std::string& message() { return errMsg_; } + std::string moveMessage() { return std::move(errMsg_); } + + T moveValue() { + assert(value_); + return std::move(value_).value(); + } + + private: + std::string errMsg_; + std::optional value_; +}; + +class CoseKey { + public: + CoseKey() {} + CoseKey(const CoseKey&) = delete; + CoseKey(CoseKey&&) = default; + + enum Label : int { + KEY_TYPE = 1, + KEY_ID = 2, + ALGORITHM = 3, + KEY_OPS = 4, + CURVE = -1, + PUBKEY_X = -2, + PUBKEY_Y = -3, + PRIVATE_KEY = -4, + TEST_KEY = -70000 // Application-defined + }; + + static ErrMsgOr parse(const bytevec& coseKey) { + auto [parsedKey, _, errMsg] = cppbor::parse(coseKey); + if (!parsedKey) return errMsg + " when parsing key"; + if (!parsedKey->asMap()) return "CoseKey must be a map"; + return CoseKey(static_cast(parsedKey.release())); + } + + static ErrMsgOr parse(const bytevec& coseKey, CoseKeyType expectedKeyType, + CoseKeyAlgorithm expectedAlgorithm, CoseKeyCurve expectedCurve) { + auto key = parse(coseKey); + if (!key) return key; + + if (!key->checkIntValue(CoseKey::KEY_TYPE, expectedKeyType) || + !key->checkIntValue(CoseKey::ALGORITHM, expectedAlgorithm) || + !key->checkIntValue(CoseKey::CURVE, expectedCurve)) { + return "Unexpected key type:"; + } + + return key; + } + static ErrMsgOr parseP256(const bytevec& coseKey) { + auto key = parse(coseKey, EC2, ES256, P256); + if (!key) return key; + + auto& pubkey_x = key->getMap().get(PUBKEY_X); + auto& pubkey_y = key->getMap().get(PUBKEY_Y); + if (!pubkey_x || !pubkey_y || !pubkey_x->asBstr() || !pubkey_y->asBstr() || + pubkey_x->asBstr()->value().size() != 32 || pubkey_y->asBstr()->value().size() != 32) { + return "Invalid P256 public key"; + } + + return key; + } + + std::optional getIntValue(Label label) { + const auto& value = key_->get(label); + if (!value || !value->asInt()) return {}; + return value->asInt()->value(); + } + + std::optional getBstrValue(Label label) { + const auto& value = key_->get(label); + if (!value || !value->asBstr()) return {}; + return value->asBstr()->value(); + } + + const cppbor::Map& getMap() const { return *key_; } + cppbor::Map&& moveMap() { return std::move(*key_); } + + bool checkIntValue(Label label, int expectedValue) { + const auto& value = key_->get(label); + return value && value->asInt() && value->asInt()->value() == expectedValue; + } + + void add(Label label, int value) { key_->add(label, value); } + void add(Label label, bytevec value) { key_->add(label, std::move(value)); } + + bytevec encode() { return key_->canonicalize().encode(); } + + private: + explicit CoseKey(cppbor::Map* parsedKey) : key_(parsedKey) {} + + // This is the full parsed key structure. + std::unique_ptr key_; +}; + +ErrMsgOr createCoseSign1Signature(const bytevec& key, const bytevec& protectedParams, + const bytevec& payload, const bytevec& aad); +ErrMsgOr constructCoseSign1(const bytevec& key, const bytevec& payload, + const bytevec& aad); +ErrMsgOr constructCoseSign1(const bytevec& key, cppbor::Map extraProtectedFields, + const bytevec& payload, const bytevec& aad); +/** + * Verify and parse a COSE_Sign1 message, returning the payload. + * + * @param ignoreSignature indicates whether signature verification should be skipped. If true, no + * verification of the signature will be done. + * + * @param coseSign1 is the COSE_Sign1 to verify and parse. + * + * @param signingCoseKey is a CBOR-encoded COSE_Key to use to verify the signature. The bytevec may + * be empty, in which case the function assumes that coseSign1's payload is the COSE_Key to + * use, i.e. that coseSign1 is a self-signed "certificate". + */ +ErrMsgOr verifyAndParseCoseSign1(bool ignoreSignature, + const cppbor::Array* coseSign1, + const bytevec& signingCoseKey, + const bytevec& aad); +} // namespace cppcose diff --git a/ProvisioningTool/include/json/assertions.h b/ProvisioningTool/include/json/assertions.h new file mode 100644 index 00000000..fbec7ae0 --- /dev/null +++ b/ProvisioningTool/include/json/assertions.h @@ -0,0 +1,54 @@ +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef CPPTL_JSON_ASSERTIONS_H_INCLUDED +#define CPPTL_JSON_ASSERTIONS_H_INCLUDED + +#include +#include + +#if !defined(JSON_IS_AMALGAMATION) +#include "config.h" +#endif // if !defined(JSON_IS_AMALGAMATION) + +/** It should not be possible for a maliciously designed file to + * cause an abort() or seg-fault, so these macros are used only + * for pre-condition violations and internal logic errors. + */ +#if JSON_USE_EXCEPTION + +// @todo <= add detail about condition in exception +# define JSON_ASSERT(condition) \ + {if (!(condition)) {Json::throwLogicError( "assert json failed" );}} + +# define JSON_FAIL_MESSAGE(message) \ + { \ + std::ostringstream oss; oss << message; \ + Json::throwLogicError(oss.str()); \ + abort(); \ + } + +#else // JSON_USE_EXCEPTION + +# define JSON_ASSERT(condition) assert(condition) + +// The call to assert() will show the failure message in debug builds. In +// release builds we abort, for a core-dump or debugger. +# define JSON_FAIL_MESSAGE(message) \ + { \ + std::ostringstream oss; oss << message; \ + assert(false && oss.str().c_str()); \ + abort(); \ + } + + +#endif + +#define JSON_ASSERT_MESSAGE(condition, message) \ + if (!(condition)) { \ + JSON_FAIL_MESSAGE(message); \ + } + +#endif // CPPTL_JSON_ASSERTIONS_H_INCLUDED diff --git a/ProvisioningTool/include/json/autolink.h b/ProvisioningTool/include/json/autolink.h new file mode 100644 index 00000000..6fcc8afa --- /dev/null +++ b/ProvisioningTool/include/json/autolink.h @@ -0,0 +1,25 @@ +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_AUTOLINK_H_INCLUDED +#define JSON_AUTOLINK_H_INCLUDED + +#include "config.h" + +#ifdef JSON_IN_CPPTL +#include +#endif + +#if !defined(JSON_NO_AUTOLINK) && !defined(JSON_DLL_BUILD) && \ + !defined(JSON_IN_CPPTL) +#define CPPTL_AUTOLINK_NAME "json" +#undef CPPTL_AUTOLINK_DLL +#ifdef JSON_DLL +#define CPPTL_AUTOLINK_DLL +#endif +#include "autolink.h" +#endif + +#endif // JSON_AUTOLINK_H_INCLUDED diff --git a/ProvisioningTool/include/json/config.h b/ProvisioningTool/include/json/config.h new file mode 100644 index 00000000..5ca32281 --- /dev/null +++ b/ProvisioningTool/include/json/config.h @@ -0,0 +1,119 @@ +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_CONFIG_H_INCLUDED +#define JSON_CONFIG_H_INCLUDED + +/// If defined, indicates that json library is embedded in CppTL library. +//# define JSON_IN_CPPTL 1 + +/// If defined, indicates that json may leverage CppTL library +//# define JSON_USE_CPPTL 1 +/// If defined, indicates that cpptl vector based map should be used instead of +/// std::map +/// as Value container. +//# define JSON_USE_CPPTL_SMALLMAP 1 + +// If non-zero, the library uses exceptions to report bad input instead of C +// assertion macros. The default is to use exceptions. +#ifndef JSON_USE_EXCEPTION +#define JSON_USE_EXCEPTION 1 +#endif + +/// If defined, indicates that the source file is amalgated +/// to prevent private header inclusion. +/// Remarks: it is automatically defined in the generated amalgated header. +// #define JSON_IS_AMALGAMATION + +#ifdef JSON_IN_CPPTL +#include +#ifndef JSON_USE_CPPTL +#define JSON_USE_CPPTL 1 +#endif +#endif + +#ifdef JSON_IN_CPPTL +#define JSON_API CPPTL_API +#elif defined(JSON_DLL_BUILD) +#if defined(_MSC_VER) +#define JSON_API __declspec(dllexport) +#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING +#endif // if defined(_MSC_VER) +#elif defined(JSON_DLL) +#if defined(_MSC_VER) +#define JSON_API __declspec(dllimport) +#define JSONCPP_DISABLE_DLL_INTERFACE_WARNING +#endif // if defined(_MSC_VER) +#endif // ifdef JSON_IN_CPPTL +#if !defined(JSON_API) +#define JSON_API +#endif + +#if !defined(JSON_HAS_UNIQUE_PTR) +#if __cplusplus >= 201103L +#define JSON_HAS_UNIQUE_PTR (1) +#elif _MSC_VER >= 1600 +#define JSON_HAS_UNIQUE_PTR (1) +#else +#define JSON_HAS_UNIQUE_PTR (0) +#endif +#endif + +// If JSON_NO_INT64 is defined, then Json only support C++ "int" type for +// integer +// Storages, and 64 bits integer support is disabled. +// #define JSON_NO_INT64 1 + +#if defined(_MSC_VER) && _MSC_VER <= 1200 // MSVC 6 +// Microsoft Visual Studio 6 only support conversion from __int64 to double +// (no conversion from unsigned __int64). +#define JSON_USE_INT64_DOUBLE_CONVERSION 1 +// Disable warning 4786 for VS6 caused by STL (identifier was truncated to '255' +// characters in the debug information) +// All projects I've ever seen with VS6 were using this globally (not bothering +// with pragma push/pop). +#pragma warning(disable : 4786) +#endif // if defined(_MSC_VER) && _MSC_VER < 1200 // MSVC 6 + +#if defined(_MSC_VER) && _MSC_VER >= 1500 // MSVC 2008 +/// Indicates that the following function is deprecated. +#define JSONCPP_DEPRECATED(message) __declspec(deprecated(message)) +#elif defined(__clang__) && defined(__has_feature) +#if __has_feature(attribute_deprecated_with_message) +#define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message))) +#endif +#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)) +#define JSONCPP_DEPRECATED(message) __attribute__ ((deprecated(message))) +#elif defined(__GNUC__) && (__GNUC__ > 3 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 1)) +#define JSONCPP_DEPRECATED(message) __attribute__((__deprecated__)) +#endif + +#if !defined(JSONCPP_DEPRECATED) +#define JSONCPP_DEPRECATED(message) +#endif // if !defined(JSONCPP_DEPRECATED) + +namespace Json { +typedef int Int; +typedef unsigned int UInt; +#if defined(JSON_NO_INT64) +typedef int LargestInt; +typedef unsigned int LargestUInt; +#undef JSON_HAS_INT64 +#else // if defined(JSON_NO_INT64) +// For Microsoft Visual use specific types as long long is not supported +#if defined(_MSC_VER) // Microsoft Visual Studio +typedef __int64 Int64; +typedef unsigned __int64 UInt64; +#else // if defined(_MSC_VER) // Other platforms, use long long +typedef long long int Int64; +typedef unsigned long long int UInt64; +#endif // if defined(_MSC_VER) +typedef Int64 LargestInt; +typedef UInt64 LargestUInt; +#define JSON_HAS_INT64 +#endif // if defined(JSON_NO_INT64) +} // end namespace Json + +#endif // JSON_CONFIG_H_INCLUDED diff --git a/ProvisioningTool/include/json/features.h b/ProvisioningTool/include/json/features.h new file mode 100644 index 00000000..78135478 --- /dev/null +++ b/ProvisioningTool/include/json/features.h @@ -0,0 +1,51 @@ +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef CPPTL_JSON_FEATURES_H_INCLUDED +#define CPPTL_JSON_FEATURES_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include "forwards.h" +#endif // if !defined(JSON_IS_AMALGAMATION) + +namespace Json { + +/** \brief Configuration passed to reader and writer. + * This configuration object can be used to force the Reader or Writer + * to behave in a standard conforming way. + */ +class JSON_API Features { +public: + /** \brief A configuration that allows all features and assumes all strings + * are UTF-8. + * - C & C++ comments are allowed + * - Root object can be any JSON value + * - Assumes Value strings are encoded in UTF-8 + */ + static Features all(); + + /** \brief A configuration that is strictly compatible with the JSON + * specification. + * - Comments are forbidden. + * - Root object must be either an array or an object value. + * - Assumes Value strings are encoded in UTF-8 + */ + static Features strictMode(); + + /** \brief Initialize the configuration like JsonConfig::allFeatures; + */ + Features(); + + /// \c true if comments are allowed. Default: \c true. + bool allowComments_; + + /// \c true if root must be either an array or an object value. Default: \c + /// false. + bool strictRoot_; +}; + +} // namespace Json + +#endif // CPPTL_JSON_FEATURES_H_INCLUDED diff --git a/ProvisioningTool/include/json/forwards.h b/ProvisioningTool/include/json/forwards.h new file mode 100644 index 00000000..ccfe09ab --- /dev/null +++ b/ProvisioningTool/include/json/forwards.h @@ -0,0 +1,37 @@ +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_FORWARDS_H_INCLUDED +#define JSON_FORWARDS_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include "config.h" +#endif // if !defined(JSON_IS_AMALGAMATION) + +namespace Json { + +// writer.h +class FastWriter; +class StyledWriter; + +// reader.h +class Reader; + +// features.h +class Features; + +// value.h +typedef unsigned int ArrayIndex; +class StaticString; +class Path; +class PathArgument; +class Value; +class ValueIteratorBase; +class ValueIterator; +class ValueConstIterator; + +} // namespace Json + +#endif // JSON_FORWARDS_H_INCLUDED diff --git a/ProvisioningTool/include/json/json.h b/ProvisioningTool/include/json/json.h new file mode 100644 index 00000000..8f10ac2b --- /dev/null +++ b/ProvisioningTool/include/json/json.h @@ -0,0 +1,15 @@ +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_JSON_H_INCLUDED +#define JSON_JSON_H_INCLUDED + +#include "autolink.h" +#include "value.h" +#include "reader.h" +#include "writer.h" +#include "features.h" + +#endif // JSON_JSON_H_INCLUDED diff --git a/ProvisioningTool/include/json/reader.h b/ProvisioningTool/include/json/reader.h new file mode 100644 index 00000000..9c9923a5 --- /dev/null +++ b/ProvisioningTool/include/json/reader.h @@ -0,0 +1,360 @@ +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef CPPTL_JSON_READER_H_INCLUDED +#define CPPTL_JSON_READER_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include "features.h" +#include "value.h" +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include +#include +#include + +// Disable warning C4251: : needs to have dll-interface to +// be used by... +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(push) +#pragma warning(disable : 4251) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +namespace Json { + +/** \brief Unserialize a JSON document into a + *Value. + * + * \deprecated Use CharReader and CharReaderBuilder. + */ +class JSON_API Reader { +public: + typedef char Char; + typedef const Char* Location; + + /** \brief Constructs a Reader allowing all features + * for parsing. + */ + Reader(); + + /** \brief Constructs a Reader allowing the specified feature set + * for parsing. + */ + Reader(const Features& features); + + /** \brief Read a Value from a JSON + * document. + * \param document UTF-8 encoded string containing the document to read. + * \param root [out] Contains the root value of the document if it was + * successfully parsed. + * \param collectComments \c true to collect comment and allow writing them + * back during + * serialization, \c false to discard comments. + * This parameter is ignored if + * Features::allowComments_ + * is \c false. + * \return \c true if the document was successfully parsed, \c false if an + * error occurred. + */ + bool + parse(const std::string& document, Value& root, bool collectComments = true); + + /** \brief Read a Value from a JSON + document. + * \param beginDoc Pointer on the beginning of the UTF-8 encoded string of the + document to read. + * \param endDoc Pointer on the end of the UTF-8 encoded string of the + document to read. + * Must be >= beginDoc. + * \param root [out] Contains the root value of the document if it was + * successfully parsed. + * \param collectComments \c true to collect comment and allow writing them + back during + * serialization, \c false to discard comments. + * This parameter is ignored if + Features::allowComments_ + * is \c false. + * \return \c true if the document was successfully parsed, \c false if an + error occurred. + */ + bool parse(const char* beginDoc, + const char* endDoc, + Value& root, + bool collectComments = true); + + /// \brief Parse from input stream. + /// \see Json::operator>>(std::istream&, Json::Value&). + bool parse(std::istream& is, Value& root, bool collectComments = true); + + /** \brief Returns a user friendly string that list errors in the parsed + * document. + * \return Formatted error message with the list of errors with their location + * in + * the parsed document. An empty string is returned if no error + * occurred + * during parsing. + * \deprecated Use getFormattedErrorMessages() instead (typo fix). + */ + JSONCPP_DEPRECATED("Use getFormattedErrorMessages() instead.") + std::string getFormatedErrorMessages() const; + + /** \brief Returns a user friendly string that list errors in the parsed + * document. + * \return Formatted error message with the list of errors with their location + * in + * the parsed document. An empty string is returned if no error + * occurred + * during parsing. + */ + std::string getFormattedErrorMessages() const; + +private: + enum TokenType { + tokenEndOfStream = 0, + tokenObjectBegin, + tokenObjectEnd, + tokenArrayBegin, + tokenArrayEnd, + tokenString, + tokenNumber, + tokenTrue, + tokenFalse, + tokenNull, + tokenArraySeparator, + tokenMemberSeparator, + tokenComment, + tokenError + }; + + class Token { + public: + TokenType type_; + Location start_; + Location end_; + }; + + class ErrorInfo { + public: + Token token_; + std::string message_; + Location extra_; + }; + + typedef std::deque Errors; + + bool readToken(Token& token); + void skipSpaces(); + bool match(Location pattern, int patternLength); + bool readComment(); + bool readCStyleComment(); + bool readCppStyleComment(); + bool readString(); + void readNumber(); + bool readValue(); + bool readObject(Token& token); + bool readArray(Token& token); + bool decodeNumber(Token& token); + bool decodeNumber(Token& token, Value& decoded); + bool decodeString(Token& token); + bool decodeString(Token& token, std::string& decoded); + bool decodeDouble(Token& token); + bool decodeDouble(Token& token, Value& decoded); + bool decodeUnicodeCodePoint(Token& token, + Location& current, + Location end, + unsigned int& unicode); + bool decodeUnicodeEscapeSequence(Token& token, + Location& current, + Location end, + unsigned int& unicode); + bool addError(const std::string& message, Token& token, Location extra = 0); + bool recoverFromError(TokenType skipUntilToken); + bool addErrorAndRecover(const std::string& message, + Token& token, + TokenType skipUntilToken); + void skipUntilSpace(); + Value& currentValue(); + Char getNextChar(); + void + getLocationLineAndColumn(Location location, int& line, int& column) const; + std::string getLocationLineAndColumn(Location location) const; + void addComment(Location begin, Location end, CommentPlacement placement); + void skipCommentTokens(Token& token); + + typedef std::stack Nodes; + Nodes nodes_; + Errors errors_; + std::string document_; + Location begin_; + Location end_; + Location current_; + Location lastValueEnd_; + Value* lastValue_; + std::string commentsBefore_; + Features features_; + bool collectComments_; +}; // Reader + +/** Interface for reading JSON from a char array. + */ +class JSON_API CharReader { +public: + virtual ~CharReader() {} + /** \brief Read a Value from a JSON + document. + * The document must be a UTF-8 encoded string containing the document to read. + * + * \param beginDoc Pointer on the beginning of the UTF-8 encoded string of the + document to read. + * \param endDoc Pointer on the end of the UTF-8 encoded string of the + document to read. + * Must be >= beginDoc. + * \param root [out] Contains the root value of the document if it was + * successfully parsed. + * \param errs [out] Formatted error messages (if not NULL) + * a user friendly string that lists errors in the parsed + * document. + * \return \c true if the document was successfully parsed, \c false if an + error occurred. + */ + virtual bool parse( + char const* beginDoc, char const* endDoc, + Value* root, std::string* errs) = 0; + + class Factory { + public: + virtual ~Factory() {} + /** \brief Allocate a CharReader via operator new(). + * \throw std::exception if something goes wrong (e.g. invalid settings) + */ + virtual CharReader* newCharReader() const = 0; + }; // Factory +}; // CharReader + +/** \brief Build a CharReader implementation. + +Usage: +\code + using namespace Json; + CharReaderBuilder builder; + builder["collectComments"] = false; + Value value; + std::string errs; + bool ok = parseFromStream(builder, std::cin, &value, &errs); +\endcode +*/ +class JSON_API CharReaderBuilder : public CharReader::Factory { +public: + // Note: We use a Json::Value so that we can add data-members to this class + // without a major version bump. + /** Configuration of this builder. + These are case-sensitive. + Available settings (case-sensitive): + - `"collectComments": false or true` + - true to collect comment and allow writing them + back during serialization, false to discard comments. + This parameter is ignored if allowComments is false. + - `"allowComments": false or true` + - true if comments are allowed. + - `"strictRoot": false or true` + - true if root must be either an array or an object value + - `"allowDroppedNullPlaceholders": false or true` + - true if dropped null placeholders are allowed. (See StreamWriterBuilder.) + - `"allowNumericKeys": false or true` + - true if numeric object keys are allowed. + - `"allowSingleQuotes": false or true` + - true if '' are allowed for strings (both keys and values) + - `"stackLimit": integer` + - Exceeding stackLimit (recursive depth of `readValue()`) will + cause an exception. + - This is a security issue (seg-faults caused by deeply nested JSON), + so the default is low. + - `"failIfExtra": false or true` + - If true, `parse()` returns false when extra non-whitespace trails + the JSON value in the input string. + - `"rejectDupKeys": false or true` + - If true, `parse()` returns false when a key is duplicated within an object. + - `"allowSpecialFloats": false or true` + - If true, special float values (NaNs and infinities) are allowed + and their values are lossfree restorable. + + You can examine 'settings_` yourself + to see the defaults. You can also write and read them just like any + JSON Value. + \sa setDefaults() + */ + Json::Value settings_; + + CharReaderBuilder(); + virtual ~CharReaderBuilder(); + + virtual CharReader* newCharReader() const; + + /** \return true if 'settings' are legal and consistent; + * otherwise, indicate bad settings via 'invalid'. + */ + bool validate(Json::Value* invalid) const; + + /** A simple way to update a specific setting. + */ + Value& operator[](std::string key); + + /** Called by ctor, but you can use this to reset settings_. + * \pre 'settings' != NULL (but Json::null is fine) + * \remark Defaults: + * \snippet src/lib_json/json_reader.cpp CharReaderBuilderDefaults + */ + static void setDefaults(Json::Value* settings); + /** Same as old Features::strictMode(). + * \pre 'settings' != NULL (but Json::null is fine) + * \remark Defaults: + * \snippet src/lib_json/json_reader.cpp CharReaderBuilderStrictMode + */ + static void strictMode(Json::Value* settings); +}; + +/** Consume entire stream and use its begin/end. + * Someday we might have a real StreamReader, but for now this + * is convenient. + */ +bool JSON_API parseFromStream( + CharReader::Factory const&, + std::istream&, + Value* root, std::string* errs); + +/** \brief Read from 'sin' into 'root'. + + Always keep comments from the input JSON. + + This can be used to read a file into a particular sub-object. + For example: + \code + Json::Value root; + cin >> root["dir"]["file"]; + cout << root; + \endcode + Result: + \verbatim + { + "dir": { + "file": { + // The input stream JSON would be nested here. + } + } + } + \endverbatim + \throw std::exception on parse error. + \see Json::operator<<() +*/ +JSON_API std::istream& operator>>(std::istream&, Value&); + +} // namespace Json + +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(pop) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +#endif // CPPTL_JSON_READER_H_INCLUDED diff --git a/ProvisioningTool/include/json/value.h b/ProvisioningTool/include/json/value.h new file mode 100644 index 00000000..66433f88 --- /dev/null +++ b/ProvisioningTool/include/json/value.h @@ -0,0 +1,850 @@ +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef CPPTL_JSON_H_INCLUDED +#define CPPTL_JSON_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include "forwards.h" +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include + +#ifndef JSON_USE_CPPTL_SMALLMAP +#include +#else +#include +#endif +#ifdef JSON_USE_CPPTL +#include +#endif + +// Disable warning C4251: : needs to have dll-interface to +// be used by... +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(push) +#pragma warning(disable : 4251) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +//Conditional NORETURN attribute on the throw functions would: +// a) suppress false positives from static code analysis +// b) possibly improve optimization opportunities. +#if !defined(JSONCPP_NORETURN) +# if defined(_MSC_VER) +# define JSONCPP_NORETURN __declspec(noreturn) +# elif defined(__GNUC__) +# define JSONCPP_NORETURN __attribute__ ((__noreturn__)) +# else +# define JSONCPP_NORETURN +# endif +#endif + +/** \brief JSON (JavaScript Object Notation). + */ +namespace Json { + +/** Base class for all exceptions we throw. + * + * We use nothing but these internally. Of course, STL can throw others. + */ +class JSON_API Exception : public std::exception { +public: + Exception(std::string const& msg); + virtual ~Exception() throw(); + virtual char const* what() const throw(); +protected: + std::string const msg_; +}; + +/** Exceptions which the user cannot easily avoid. + * + * E.g. out-of-memory (when we use malloc), stack-overflow, malicious input + * + * \remark derived from Json::Exception + */ +class JSON_API RuntimeError : public Exception { +public: + RuntimeError(std::string const& msg); +}; + +/** Exceptions thrown by JSON_ASSERT/JSON_FAIL macros. + * + * These are precondition-violations (user bugs) and internal errors (our bugs). + * + * \remark derived from Json::Exception + */ +class JSON_API LogicError : public Exception { +public: + LogicError(std::string const& msg); +}; + +/// used internally +JSONCPP_NORETURN void throwRuntimeError(std::string const& msg); +/// used internally +JSONCPP_NORETURN void throwLogicError(std::string const& msg); + +/** \brief Type of the value held by a Value object. + */ +enum ValueType { + nullValue = 0, ///< 'null' value + intValue, ///< signed integer value + uintValue, ///< unsigned integer value + realValue, ///< double value + stringValue, ///< UTF-8 string value + booleanValue, ///< bool value + arrayValue, ///< array value (ordered list) + objectValue ///< object value (collection of name/value pairs). +}; + +enum CommentPlacement { + commentBefore = 0, ///< a comment placed on the line before a value + commentAfterOnSameLine, ///< a comment just after a value on the same line + commentAfter, ///< a comment on the line after a value (only make sense for + /// root value) + numberOfCommentPlacement +}; + +//# ifdef JSON_USE_CPPTL +// typedef CppTL::AnyEnumerator EnumMemberNames; +// typedef CppTL::AnyEnumerator EnumValues; +//# endif + +/** \brief Lightweight wrapper to tag static string. + * + * Value constructor and objectValue member assignement takes advantage of the + * StaticString and avoid the cost of string duplication when storing the + * string or the member name. + * + * Example of usage: + * \code + * Json::Value aValue( StaticString("some text") ); + * Json::Value object; + * static const StaticString code("code"); + * object[code] = 1234; + * \endcode + */ +class JSON_API StaticString { +public: + explicit StaticString(const char* czstring) : c_str_(czstring) {} + + operator const char*() const { return c_str_; } + + const char* c_str() const { return c_str_; } + +private: + const char* c_str_; +}; + +/** \brief Represents a JSON value. + * + * This class is a discriminated union wrapper that can represents a: + * - signed integer [range: Value::minInt - Value::maxInt] + * - unsigned integer (range: 0 - Value::maxUInt) + * - double + * - UTF-8 string + * - boolean + * - 'null' + * - an ordered list of Value + * - collection of name/value pairs (javascript object) + * + * The type of the held value is represented by a #ValueType and + * can be obtained using type(). + * + * Values of an #objectValue or #arrayValue can be accessed using operator[]() + * methods. + * Non-const methods will automatically create the a #nullValue element + * if it does not exist. + * The sequence of an #arrayValue will be automatically resized and initialized + * with #nullValue. resize() can be used to enlarge or truncate an #arrayValue. + * + * The get() methods can be used to obtain default value in the case the + * required element does not exist. + * + * It is possible to iterate over the list of a #objectValue values using + * the getMemberNames() method. + * + * \note #Value string-length fit in size_t, but keys must be < 2^30. + * (The reason is an implementation detail.) A #CharReader will raise an + * exception if a bound is exceeded to avoid security holes in your app, + * but the Value API does *not* check bounds. That is the responsibility + * of the caller. + */ +class JSON_API Value { + friend class ValueIteratorBase; +public: + typedef std::vector Members; + typedef ValueIterator iterator; + typedef ValueConstIterator const_iterator; + typedef Json::UInt UInt; + typedef Json::Int Int; +#if defined(JSON_HAS_INT64) + typedef Json::UInt64 UInt64; + typedef Json::Int64 Int64; +#endif // defined(JSON_HAS_INT64) + typedef Json::LargestInt LargestInt; + typedef Json::LargestUInt LargestUInt; + typedef Json::ArrayIndex ArrayIndex; + + static const Value& nullRef; +#if !defined(__ARMEL__) + /// \deprecated This exists for binary compatibility only. Use nullRef. + static const Value null; +#endif + /// Minimum signed integer value that can be stored in a Json::Value. + static const LargestInt minLargestInt; + /// Maximum signed integer value that can be stored in a Json::Value. + static const LargestInt maxLargestInt; + /// Maximum unsigned integer value that can be stored in a Json::Value. + static const LargestUInt maxLargestUInt; + + /// Minimum signed int value that can be stored in a Json::Value. + static const Int minInt; + /// Maximum signed int value that can be stored in a Json::Value. + static const Int maxInt; + /// Maximum unsigned int value that can be stored in a Json::Value. + static const UInt maxUInt; + +#if defined(JSON_HAS_INT64) + /// Minimum signed 64 bits int value that can be stored in a Json::Value. + static const Int64 minInt64; + /// Maximum signed 64 bits int value that can be stored in a Json::Value. + static const Int64 maxInt64; + /// Maximum unsigned 64 bits int value that can be stored in a Json::Value. + static const UInt64 maxUInt64; +#endif // defined(JSON_HAS_INT64) + +//MW: workaround for bug in NVIDIAs CUDA 7.5 nvcc compiler +#ifdef __NVCC__ +public: +#else +private: +#endif //__NVCC__ +#ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION + class CZString { + public: + enum DuplicationPolicy { + noDuplication = 0, + duplicate, + duplicateOnCopy + }; + CZString(ArrayIndex index); + CZString(char const* str, unsigned length, DuplicationPolicy allocate); + CZString(CZString const& other); + ~CZString(); + CZString& operator=(CZString other); + bool operator<(CZString const& other) const; + bool operator==(CZString const& other) const; + ArrayIndex index() const; + //const char* c_str() const; ///< \deprecated + char const* data() const; + unsigned length() const; + bool isStaticString() const; + + private: + void swap(CZString& other); + + struct StringStorage { + unsigned policy_: 2; + unsigned length_: 30; // 1GB max + }; + + char const* cstr_; // actually, a prefixed string, unless policy is noDup + union { + ArrayIndex index_; + StringStorage storage_; + }; + }; + +public: +#ifndef JSON_USE_CPPTL_SMALLMAP + typedef std::map ObjectValues; +#else + typedef CppTL::SmallMap ObjectValues; +#endif // ifndef JSON_USE_CPPTL_SMALLMAP +#endif // ifndef JSONCPP_DOC_EXCLUDE_IMPLEMENTATION + +public: + /** \brief Create a default Value of the given type. + + This is a very useful constructor. + To create an empty array, pass arrayValue. + To create an empty object, pass objectValue. + Another Value can then be set to this one by assignment. +This is useful since clear() and resize() will not alter types. + + Examples: +\code +Json::Value null_value; // null +Json::Value arr_value(Json::arrayValue); // [] +Json::Value obj_value(Json::objectValue); // {} +\endcode + */ + Value(ValueType type = nullValue); + Value(Int value); + Value(UInt value); +#if defined(JSON_HAS_INT64) + Value(Int64 value); + Value(UInt64 value); +#endif // if defined(JSON_HAS_INT64) + Value(double value); + Value(const char* value); ///< Copy til first 0. (NULL causes to seg-fault.) + Value(const char* begin, const char* end); ///< Copy all, incl zeroes. + /** \brief Constructs a value from a static string. + + * Like other value string constructor but do not duplicate the string for + * internal storage. The given string must remain alive after the call to this + * constructor. + * \note This works only for null-terminated strings. (We cannot change the + * size of this class, so we have nowhere to store the length, + * which might be computed later for various operations.) + * + * Example of usage: + * \code + * static StaticString foo("some text"); + * Json::Value aValue(foo); + * \endcode + */ + Value(const StaticString& value); + Value(const std::string& value); ///< Copy data() til size(). Embedded zeroes too. +#ifdef JSON_USE_CPPTL + Value(const CppTL::ConstString& value); +#endif + Value(bool value); + /// Deep copy. + Value(const Value& other); + ~Value(); + + /// Deep copy, then swap(other). + /// \note Over-write existing comments. To preserve comments, use #swapPayload(). + Value &operator=(const Value &other); + /// Swap everything. + void swap(Value& other); + /// Swap values but leave comments and source offsets in place. + void swapPayload(Value& other); + + ValueType type() const; + + /// Compare payload only, not comments etc. + bool operator<(const Value& other) const; + bool operator<=(const Value& other) const; + bool operator>=(const Value& other) const; + bool operator>(const Value& other) const; + bool operator==(const Value& other) const; + bool operator!=(const Value& other) const; + int compare(const Value& other) const; + + const char* asCString() const; ///< Embedded zeroes could cause you trouble! + std::string asString() const; ///< Embedded zeroes are possible. + /** Get raw char* of string-value. + * \return false if !string. (Seg-fault if str or end are NULL.) + */ + bool getString( + char const** begin, char const** end) const; +#ifdef JSON_USE_CPPTL + CppTL::ConstString asConstString() const; +#endif + Int asInt() const; + UInt asUInt() const; +#if defined(JSON_HAS_INT64) + Int64 asInt64() const; + UInt64 asUInt64() const; +#endif // if defined(JSON_HAS_INT64) + LargestInt asLargestInt() const; + LargestUInt asLargestUInt() const; + float asFloat() const; + double asDouble() const; + bool asBool() const; + + bool isNull() const; + bool isBool() const; + bool isInt() const; + bool isInt64() const; + bool isUInt() const; + bool isUInt64() const; + bool isIntegral() const; + bool isDouble() const; + bool isNumeric() const; + bool isString() const; + bool isArray() const; + bool isObject() const; + + bool isConvertibleTo(ValueType other) const; + + /// Number of values in array or object + ArrayIndex size() const; + + /// \brief Return true if empty array, empty object, or null; + /// otherwise, false. + bool empty() const; + + /// Return isNull() + bool operator!() const; + + /// Remove all object members and array elements. + /// \pre type() is arrayValue, objectValue, or nullValue + /// \post type() is unchanged + void clear(); + + /// Resize the array to size elements. + /// New elements are initialized to null. + /// May only be called on nullValue or arrayValue. + /// \pre type() is arrayValue or nullValue + /// \post type() is arrayValue + void resize(ArrayIndex size); + + /// Access an array element (zero based index ). + /// If the array contains less than index element, then null value are + /// inserted + /// in the array so that its size is index+1. + /// (You may need to say 'value[0u]' to get your compiler to distinguish + /// this from the operator[] which takes a string.) + Value& operator[](ArrayIndex index); + + /// Access an array element (zero based index ). + /// If the array contains less than index element, then null value are + /// inserted + /// in the array so that its size is index+1. + /// (You may need to say 'value[0u]' to get your compiler to distinguish + /// this from the operator[] which takes a string.) + Value& operator[](int index); + + /// Access an array element (zero based index ) + /// (You may need to say 'value[0u]' to get your compiler to distinguish + /// this from the operator[] which takes a string.) + const Value& operator[](ArrayIndex index) const; + + /// Access an array element (zero based index ) + /// (You may need to say 'value[0u]' to get your compiler to distinguish + /// this from the operator[] which takes a string.) + const Value& operator[](int index) const; + + /// If the array contains at least index+1 elements, returns the element + /// value, + /// otherwise returns defaultValue. + Value get(ArrayIndex index, const Value& defaultValue) const; + /// Return true if index < size(). + bool isValidIndex(ArrayIndex index) const; + /// \brief Append value to array at the end. + /// + /// Equivalent to jsonvalue[jsonvalue.size()] = value; + Value& append(const Value& value); + + /// Access an object value by name, create a null member if it does not exist. + /// \note Because of our implementation, keys are limited to 2^30 -1 chars. + /// Exceeding that will cause an exception. + Value& operator[](const char* key); + /// Access an object value by name, returns null if there is no member with + /// that name. + const Value& operator[](const char* key) const; + /// Access an object value by name, create a null member if it does not exist. + /// \param key may contain embedded nulls. + Value& operator[](const std::string& key); + /// Access an object value by name, returns null if there is no member with + /// that name. + /// \param key may contain embedded nulls. + const Value& operator[](const std::string& key) const; + /** \brief Access an object value by name, create a null member if it does not + exist. + + * If the object has no entry for that name, then the member name used to store + * the new entry is not duplicated. + * Example of use: + * \code + * Json::Value object; + * static const StaticString code("code"); + * object[code] = 1234; + * \endcode + */ + Value& operator[](const StaticString& key); +#ifdef JSON_USE_CPPTL + /// Access an object value by name, create a null member if it does not exist. + Value& operator[](const CppTL::ConstString& key); + /// Access an object value by name, returns null if there is no member with + /// that name. + const Value& operator[](const CppTL::ConstString& key) const; +#endif + /// Return the member named key if it exist, defaultValue otherwise. + /// \note deep copy + Value get(const char* key, const Value& defaultValue) const; + /// Return the member named key if it exist, defaultValue otherwise. + /// \note deep copy + /// \note key may contain embedded nulls. + Value get(const char* begin, const char* end, const Value& defaultValue) const; + /// Return the member named key if it exist, defaultValue otherwise. + /// \note deep copy + /// \param key may contain embedded nulls. + Value get(const std::string& key, const Value& defaultValue) const; +#ifdef JSON_USE_CPPTL + /// Return the member named key if it exist, defaultValue otherwise. + /// \note deep copy + Value get(const CppTL::ConstString& key, const Value& defaultValue) const; +#endif + /// Most general and efficient version of isMember()const, get()const, + /// and operator[]const + /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30 + Value const* find(char const* begin, char const* end) const; + /// Most general and efficient version of object-mutators. + /// \note As stated elsewhere, behavior is undefined if (end-begin) >= 2^30 + /// \return non-zero, but JSON_ASSERT if this is neither object nor nullValue. + Value const* demand(char const* begin, char const* end); + /// \brief Remove and return the named member. + /// + /// Do nothing if it did not exist. + /// \return the removed Value, or null. + /// \pre type() is objectValue or nullValue + /// \post type() is unchanged + /// \deprecated + Value removeMember(const char* key); + /// Same as removeMember(const char*) + /// \param key may contain embedded nulls. + /// \deprecated + Value removeMember(const std::string& key); + /// Same as removeMember(const char* begin, const char* end, Value* removed), + /// but 'key' is null-terminated. + bool removeMember(const char* key, Value* removed); + /** \brief Remove the named map member. + + Update 'removed' iff removed. + \param key may contain embedded nulls. + \return true iff removed (no exceptions) + */ + bool removeMember(std::string const& key, Value* removed); + /// Same as removeMember(std::string const& key, Value* removed) + bool removeMember(const char* begin, const char* end, Value* removed); + /** \brief Remove the indexed array element. + + O(n) expensive operations. + Update 'removed' iff removed. + \return true iff removed (no exceptions) + */ + bool removeIndex(ArrayIndex i, Value* removed); + + /// Return true if the object has a member named key. + /// \note 'key' must be null-terminated. + bool isMember(const char* key) const; + /// Return true if the object has a member named key. + /// \param key may contain embedded nulls. + bool isMember(const std::string& key) const; + /// Same as isMember(std::string const& key)const + bool isMember(const char* begin, const char* end) const; +#ifdef JSON_USE_CPPTL + /// Return true if the object has a member named key. + bool isMember(const CppTL::ConstString& key) const; +#endif + + /// \brief Return a list of the member names. + /// + /// If null, return an empty list. + /// \pre type() is objectValue or nullValue + /// \post if type() was nullValue, it remains nullValue + Members getMemberNames() const; + + //# ifdef JSON_USE_CPPTL + // EnumMemberNames enumMemberNames() const; + // EnumValues enumValues() const; + //# endif + + /// \deprecated Always pass len. + JSONCPP_DEPRECATED("Use setComment(std::string const&) instead.") + void setComment(const char* comment, CommentPlacement placement); + /// Comments must be //... or /* ... */ + void setComment(const char* comment, size_t len, CommentPlacement placement); + /// Comments must be //... or /* ... */ + void setComment(const std::string& comment, CommentPlacement placement); + bool hasComment(CommentPlacement placement) const; + /// Include delimiters and embedded newlines. + std::string getComment(CommentPlacement placement) const; + + std::string toStyledString() const; + + const_iterator begin() const; + const_iterator end() const; + + iterator begin(); + iterator end(); + +private: + void initBasic(ValueType type, bool allocated = false); + + Value& resolveReference(const char* key); + Value& resolveReference(const char* key, const char* end); + + struct CommentInfo { + CommentInfo(); + ~CommentInfo(); + + void setComment(const char* text, size_t len); + + char* comment_; + }; + + // struct MemberNamesTransform + //{ + // typedef const char *result_type; + // const char *operator()( const CZString &name ) const + // { + // return name.c_str(); + // } + //}; + + union ValueHolder { + LargestInt int_; + LargestUInt uint_; + double real_; + bool bool_; + char* string_; // actually ptr to unsigned, followed by str, unless !allocated_ + ObjectValues* map_; + } value_; + ValueType type_ : 8; + unsigned int allocated_ : 1; // Notes: if declared as bool, bitfield is useless. + // If not allocated_, string_ must be null-terminated. + CommentInfo* comments_; +}; + +/** \brief Experimental and untested: represents an element of the "path" to + * access a node. + */ +class JSON_API PathArgument { +public: + friend class Path; + + PathArgument(); + PathArgument(ArrayIndex index); + PathArgument(const char* key); + PathArgument(const std::string& key); + +private: + enum Kind { + kindNone = 0, + kindIndex, + kindKey + }; + std::string key_; + ArrayIndex index_; + Kind kind_; +}; + +/** \brief Experimental and untested: represents a "path" to access a node. + * + * Syntax: + * - "." => root node + * - ".[n]" => elements at index 'n' of root node (an array value) + * - ".name" => member named 'name' of root node (an object value) + * - ".name1.name2.name3" + * - ".[0][1][2].name1[3]" + * - ".%" => member name is provided as parameter + * - ".[%]" => index is provied as parameter + */ +class JSON_API Path { +public: + Path(const std::string& path, + const PathArgument& a1 = PathArgument(), + const PathArgument& a2 = PathArgument(), + const PathArgument& a3 = PathArgument(), + const PathArgument& a4 = PathArgument(), + const PathArgument& a5 = PathArgument()); + + const Value& resolve(const Value& root) const; + Value resolve(const Value& root, const Value& defaultValue) const; + /// Creates the "path" to access the specified node and returns a reference on + /// the node. + Value& make(Value& root) const; + +private: + typedef std::vector InArgs; + typedef std::vector Args; + + void makePath(const std::string& path, const InArgs& in); + void addPathInArg(const std::string& path, + const InArgs& in, + InArgs::const_iterator& itInArg, + PathArgument::Kind kind); + void invalidPath(const std::string& path, int location); + + Args args_; +}; + +/** \brief base class for Value iterators. + * + */ +class JSON_API ValueIteratorBase { +public: + typedef std::bidirectional_iterator_tag iterator_category; + typedef unsigned int size_t; + typedef int difference_type; + typedef ValueIteratorBase SelfType; + + bool operator==(const SelfType& other) const { return isEqual(other); } + + bool operator!=(const SelfType& other) const { return !isEqual(other); } + + difference_type operator-(const SelfType& other) const { + return other.computeDistance(*this); + } + + /// Return either the index or the member name of the referenced value as a + /// Value. + Value key() const; + + /// Return the index of the referenced Value, or -1 if it is not an arrayValue. + UInt index() const; + + /// Return the member name of the referenced Value, or "" if it is not an + /// objectValue. + /// \note Avoid `c_str()` on result, as embedded zeroes are possible. + std::string name() const; + + /// Return the member name of the referenced Value. "" if it is not an + /// objectValue. + /// \deprecated This cannot be used for UTF-8 strings, since there can be embedded nulls. + JSONCPP_DEPRECATED("Use `key = name();` instead.") + char const* memberName() const; + /// Return the member name of the referenced Value, or NULL if it is not an + /// objectValue. + /// \note Better version than memberName(). Allows embedded nulls. + char const* memberName(char const** end) const; + +protected: + Value& deref() const; + + void increment(); + + void decrement(); + + difference_type computeDistance(const SelfType& other) const; + + bool isEqual(const SelfType& other) const; + + void copy(const SelfType& other); + +private: + Value::ObjectValues::iterator current_; + // Indicates that iterator is for a null value. + bool isNull_; + +public: + // For some reason, BORLAND needs these at the end, rather + // than earlier. No idea why. + ValueIteratorBase(); + explicit ValueIteratorBase(const Value::ObjectValues::iterator& current); +}; + +/** \brief const iterator for object and array value. + * + */ +class JSON_API ValueConstIterator : public ValueIteratorBase { + friend class Value; + +public: + typedef const Value value_type; + //typedef unsigned int size_t; + //typedef int difference_type; + typedef const Value& reference; + typedef const Value* pointer; + typedef ValueConstIterator SelfType; + + ValueConstIterator(); + +private: +/*! \internal Use by Value to create an iterator. + */ + explicit ValueConstIterator(const Value::ObjectValues::iterator& current); +public: + SelfType& operator=(const ValueIteratorBase& other); + + SelfType operator++(int) { + SelfType temp(*this); + ++*this; + return temp; + } + + SelfType operator--(int) { + SelfType temp(*this); + --*this; + return temp; + } + + SelfType& operator--() { + decrement(); + return *this; + } + + SelfType& operator++() { + increment(); + return *this; + } + + reference operator*() const { return deref(); } + + pointer operator->() const { return &deref(); } +}; + +/** \brief Iterator for object and array value. + */ +class JSON_API ValueIterator : public ValueIteratorBase { + friend class Value; + +public: + typedef Value value_type; + typedef unsigned int size_t; + typedef int difference_type; + typedef Value& reference; + typedef Value* pointer; + typedef ValueIterator SelfType; + + ValueIterator(); + ValueIterator(const ValueConstIterator& other); + ValueIterator(const ValueIterator& other); + +private: +/*! \internal Use by Value to create an iterator. + */ + explicit ValueIterator(const Value::ObjectValues::iterator& current); +public: + SelfType& operator=(const SelfType& other); + + SelfType operator++(int) { + SelfType temp(*this); + ++*this; + return temp; + } + + SelfType operator--(int) { + SelfType temp(*this); + --*this; + return temp; + } + + SelfType& operator--() { + decrement(); + return *this; + } + + SelfType& operator++() { + increment(); + return *this; + } + + reference operator*() const { return deref(); } + + pointer operator->() const { return &deref(); } +}; + +} // namespace Json + + +namespace std { +/// Specialize std::swap() for Json::Value. +template<> +inline void swap(Json::Value& a, Json::Value& b) { a.swap(b); } +} + + +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(pop) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +#endif // CPPTL_JSON_H_INCLUDED diff --git a/ProvisioningTool/include/json/version.h b/ProvisioningTool/include/json/version.h new file mode 100644 index 00000000..d0f3dcb0 --- /dev/null +++ b/ProvisioningTool/include/json/version.h @@ -0,0 +1,13 @@ +// DO NOT EDIT. This file (and "version") is generated by CMake. +// Run CMake configure step to update it. +#ifndef JSON_VERSION_H_INCLUDED +# define JSON_VERSION_H_INCLUDED + +# define JSONCPP_VERSION_STRING "0.10.7" +# define JSONCPP_VERSION_MAJOR 0 +# define JSONCPP_VERSION_MINOR 10 +# define JSONCPP_VERSION_PATCH 7 +# define JSONCPP_VERSION_QUALIFIER +# define JSONCPP_VERSION_HEXA ((JSONCPP_VERSION_MAJOR << 24) | (JSONCPP_VERSION_MINOR << 16) | (JSONCPP_VERSION_PATCH << 8)) + +#endif // JSON_VERSION_H_INCLUDED diff --git a/ProvisioningTool/include/json/writer.h b/ProvisioningTool/include/json/writer.h new file mode 100644 index 00000000..a7fd11d2 --- /dev/null +++ b/ProvisioningTool/include/json/writer.h @@ -0,0 +1,320 @@ +// Copyright 2007-2010 Baptiste Lepilleur +// Distributed under MIT license, or public domain if desired and +// recognized in your jurisdiction. +// See file LICENSE for detail or copy at http://jsoncpp.sourceforge.net/LICENSE + +#ifndef JSON_WRITER_H_INCLUDED +#define JSON_WRITER_H_INCLUDED + +#if !defined(JSON_IS_AMALGAMATION) +#include "value.h" +#endif // if !defined(JSON_IS_AMALGAMATION) +#include +#include +#include + +// Disable warning C4251: : needs to have dll-interface to +// be used by... +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(push) +#pragma warning(disable : 4251) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +namespace Json { + +class Value; + +/** + +Usage: +\code + using namespace Json; + void writeToStdout(StreamWriter::Factory const& factory, Value const& value) { + std::unique_ptr const writer( + factory.newStreamWriter()); + writer->write(value, &std::cout); + std::cout << std::endl; // add lf and flush + } +\endcode +*/ +class JSON_API StreamWriter { +protected: + std::ostream* sout_; // not owned; will not delete +public: + StreamWriter(); + virtual ~StreamWriter(); + /** Write Value into document as configured in sub-class. + Do not take ownership of sout, but maintain a reference during function. + \pre sout != NULL + \return zero on success (For now, we always return zero, so check the stream instead.) + \throw std::exception possibly, depending on configuration + */ + virtual int write(Value const& root, std::ostream* sout) = 0; + + /** \brief A simple abstract factory. + */ + class JSON_API Factory { + public: + virtual ~Factory(); + /** \brief Allocate a CharReader via operator new(). + * \throw std::exception if something goes wrong (e.g. invalid settings) + */ + virtual StreamWriter* newStreamWriter() const = 0; + }; // Factory +}; // StreamWriter + +/** \brief Write into stringstream, then return string, for convenience. + * A StreamWriter will be created from the factory, used, and then deleted. + */ +std::string JSON_API writeString(StreamWriter::Factory const& factory, Value const& root); + + +/** \brief Build a StreamWriter implementation. + +Usage: +\code + using namespace Json; + Value value = ...; + StreamWriterBuilder builder; + builder["commentStyle"] = "None"; + builder["indentation"] = " "; // or whatever you like + std::unique_ptr writer( + builder.newStreamWriter()); + writer->write(value, &std::cout); + std::cout << std::endl; // add lf and flush +\endcode +*/ +class JSON_API StreamWriterBuilder : public StreamWriter::Factory { +public: + // Note: We use a Json::Value so that we can add data-members to this class + // without a major version bump. + /** Configuration of this builder. + Available settings (case-sensitive): + - "commentStyle": "None" or "All" + - "indentation": "" + - "enableYAMLCompatibility": false or true + - slightly change the whitespace around colons + - "dropNullPlaceholders": false or true + - Drop the "null" string from the writer's output for nullValues. + Strictly speaking, this is not valid JSON. But when the output is being + fed to a browser's Javascript, it makes for smaller output and the + browser can handle the output just fine. + - "useSpecialFloats": false or true + - If true, outputs non-finite floating point values in the following way: + NaN values as "NaN", positive infinity as "Infinity", and negative infinity + as "-Infinity". + + You can examine 'settings_` yourself + to see the defaults. You can also write and read them just like any + JSON Value. + \sa setDefaults() + */ + Json::Value settings_; + + StreamWriterBuilder(); + virtual ~StreamWriterBuilder(); + + /** + * \throw std::exception if something goes wrong (e.g. invalid settings) + */ + virtual StreamWriter* newStreamWriter() const; + + /** \return true if 'settings' are legal and consistent; + * otherwise, indicate bad settings via 'invalid'. + */ + bool validate(Json::Value* invalid) const; + /** A simple way to update a specific setting. + */ + Value& operator[](std::string key); + + /** Called by ctor, but you can use this to reset settings_. + * \pre 'settings' != NULL (but Json::null is fine) + * \remark Defaults: + * \snippet src/lib_json/json_writer.cpp StreamWriterBuilderDefaults + */ + static void setDefaults(Json::Value* settings); +}; + +/** \brief Abstract class for writers. + * \deprecated Use StreamWriter. (And really, this is an implementation detail.) + */ +class JSON_API Writer { +public: + virtual ~Writer(); + + virtual std::string write(const Value& root) = 0; +}; + +/** \brief Outputs a Value in JSON format + *without formatting (not human friendly). + * + * The JSON document is written in a single line. It is not intended for 'human' + *consumption, + * but may be usefull to support feature such as RPC where bandwith is limited. + * \sa Reader, Value + * \deprecated Use StreamWriterBuilder. + */ +class JSON_API FastWriter : public Writer { + +public: + FastWriter(); + virtual ~FastWriter() {} + + void enableYAMLCompatibility(); + +public: // overridden from Writer + virtual std::string write(const Value& root); + +private: + void writeValue(const Value& value); + + std::string document_; + bool yamlCompatiblityEnabled_; +}; + +/** \brief Writes a Value in JSON format in a + *human friendly way. + * + * The rules for line break and indent are as follow: + * - Object value: + * - if empty then print {} without indent and line break + * - if not empty the print '{', line break & indent, print one value per + *line + * and then unindent and line break and print '}'. + * - Array value: + * - if empty then print [] without indent and line break + * - if the array contains no object value, empty array or some other value + *types, + * and all the values fit on one lines, then print the array on a single + *line. + * - otherwise, it the values do not fit on one line, or the array contains + * object or non empty array, then print one value per line. + * + * If the Value have comments then they are outputed according to their + *#CommentPlacement. + * + * \sa Reader, Value, Value::setComment() + * \deprecated Use StreamWriterBuilder. + */ +class JSON_API StyledWriter : public Writer { +public: + StyledWriter(); + virtual ~StyledWriter() {} + +public: // overridden from Writer + /** \brief Serialize a Value in JSON format. + * \param root Value to serialize. + * \return String containing the JSON document that represents the root value. + */ + virtual std::string write(const Value& root); + +private: + void writeValue(const Value& value); + void writeArrayValue(const Value& value); + bool isMultineArray(const Value& value); + void pushValue(const std::string& value); + void writeIndent(); + void writeWithIndent(const std::string& value); + void indent(); + void unindent(); + void writeCommentBeforeValue(const Value& root); + void writeCommentAfterValueOnSameLine(const Value& root); + bool hasCommentForValue(const Value& value); + static std::string normalizeEOL(const std::string& text); + + typedef std::vector ChildValues; + + ChildValues childValues_; + std::string document_; + std::string indentString_; + int rightMargin_; + int indentSize_; + bool addChildValues_; +}; + +/** \brief Writes a Value in JSON format in a + human friendly way, + to a stream rather than to a string. + * + * The rules for line break and indent are as follow: + * - Object value: + * - if empty then print {} without indent and line break + * - if not empty the print '{', line break & indent, print one value per + line + * and then unindent and line break and print '}'. + * - Array value: + * - if empty then print [] without indent and line break + * - if the array contains no object value, empty array or some other value + types, + * and all the values fit on one lines, then print the array on a single + line. + * - otherwise, it the values do not fit on one line, or the array contains + * object or non empty array, then print one value per line. + * + * If the Value have comments then they are outputed according to their + #CommentPlacement. + * + * \param indentation Each level will be indented by this amount extra. + * \sa Reader, Value, Value::setComment() + * \deprecated Use StreamWriterBuilder. + */ +class JSON_API StyledStreamWriter { +public: + StyledStreamWriter(std::string indentation = "\t"); + ~StyledStreamWriter() {} + +public: + /** \brief Serialize a Value in JSON format. + * \param out Stream to write to. (Can be ostringstream, e.g.) + * \param root Value to serialize. + * \note There is no point in deriving from Writer, since write() should not + * return a value. + */ + void write(std::ostream& out, const Value& root); + +private: + void writeValue(const Value& value); + void writeArrayValue(const Value& value); + bool isMultineArray(const Value& value); + void pushValue(const std::string& value); + void writeIndent(); + void writeWithIndent(const std::string& value); + void indent(); + void unindent(); + void writeCommentBeforeValue(const Value& root); + void writeCommentAfterValueOnSameLine(const Value& root); + bool hasCommentForValue(const Value& value); + static std::string normalizeEOL(const std::string& text); + + typedef std::vector ChildValues; + + ChildValues childValues_; + std::ostream* document_; + std::string indentString_; + int rightMargin_; + std::string indentation_; + bool addChildValues_ : 1; + bool indented_ : 1; +}; + +#if defined(JSON_HAS_INT64) +std::string JSON_API valueToString(Int value); +std::string JSON_API valueToString(UInt value); +#endif // if defined(JSON_HAS_INT64) +std::string JSON_API valueToString(LargestInt value); +std::string JSON_API valueToString(LargestUInt value); +std::string JSON_API valueToString(double value); +std::string JSON_API valueToString(bool value); +std::string JSON_API valueToQuotedString(const char* value); + +/// \brief Output using the StyledStreamWriter. +/// \see Json::operator>>() +JSON_API std::ostream& operator<<(std::ostream&, const Value& root); + +} // namespace Json + +#if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) +#pragma warning(pop) +#endif // if defined(JSONCPP_DISABLE_DLL_INTERFACE_WARNING) + +#endif // JSON_WRITER_H_INCLUDED diff --git a/ProvisioningTool/include/socket.h b/ProvisioningTool/include/socket.h new file mode 100644 index 00000000..8f47325a --- /dev/null +++ b/ProvisioningTool/include/socket.h @@ -0,0 +1,53 @@ +/* + ** + ** 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 + +class SocketTransport +{ +public: + static inline std::shared_ptr getInstance() { + static std::shared_ptr socket = std::shared_ptr(new SocketTransport()); + return socket; + } + + ~SocketTransport(); + /** + * Creates a socket instance and connects to the provided server IP and port. + */ + bool openConnection(); + /** + * Sends data over socket and receives data back. + */ + bool sendData(const std::vector &inData, std::vector &output); + /** + * Closes the connection. + */ + bool closeConnection(); + /** + * Returns the state of the connection status. Returns true if the connection is active, + * false if connection is broken. + */ + bool isConnected(); + +private: + SocketTransport() : mSocket(-1), socketStatus(false) {} + /** + * Socket instance. + */ + int mSocket; + bool socketStatus; +}; \ No newline at end of file diff --git a/ProvisioningTool/include/utils.h b/ProvisioningTool/include/utils.h new file mode 100644 index 00000000..9eb991bd --- /dev/null +++ b/ProvisioningTool/include/utils.h @@ -0,0 +1,30 @@ +/* + ** + ** Copyright 2021, The Android Open Source Project + ** + ** Licensed under the Apache License, Version 2.0 (the "License"); + ** you may not use this file except in compliance with the License. + ** You may obtain a copy of the License at + ** + ** http://www.apache.org/licenses/LICENSE-2.0 + ** + ** Unless required by applicable law or agreed to in writing, software + ** distributed under the License is distributed on an "AS IS" BASIS, + ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ** See the License for the specific language governing permissions and + ** limitations under the License. + */ +#pragma once +#include +#include +#include +#include +#include + +std::string getHexString(std::vector& input); + +std::string hex2str(std::string a); + +int readJsonFile(Json::Value& root, std::string& inputFileName); + +int writeJsonFile(Json::Value& writerRoot, std::string& outputFileName); \ No newline at end of file diff --git a/ProvisioningTool/lib/README.md b/ProvisioningTool/lib/README.md new file mode 100644 index 00000000..d9ac780e --- /dev/null +++ b/ProvisioningTool/lib/README.md @@ -0,0 +1,25 @@ +# Instructions to build jsoncpp +Download the code from below opensource link: +https://github.com/open-source-parsers/jsoncpp/tree/0.y.z + +#### Unzip it +
+unzip jsoncpp-0.y.z.zip
+cd jsoncpp-0.y.z
+
+ +#### Build +
+$ mkdir -p build/debug
+$ cd build/debug
+$ cmake -DCMAKE_BUILD_TYPE=debug -DBUILD_STATIC_LIBS=ON -DBUILD_SHARED_LIBS=ON -DARCHIVE_INSTALL_DIR=. -G "Unix Makefiles" ../..
+$ make
+
+ +#### Check the generated static and dynamic link library +
+$ find . -name *.a
+./src/lib_json/libjsoncpp.a
+$ find . -name *.so
+./src/lib_json/libjsoncpp.so
+
diff --git a/ProvisioningTool/lib/libjsoncpp.a b/ProvisioningTool/lib/libjsoncpp.a new file mode 100644 index 00000000..601854f4 Binary files /dev/null and b/ProvisioningTool/lib/libjsoncpp.a differ diff --git a/ProvisioningTool/lib/libjsoncpp.so b/ProvisioningTool/lib/libjsoncpp.so new file mode 100755 index 00000000..a23ae279 Binary files /dev/null and b/ProvisioningTool/lib/libjsoncpp.so differ diff --git a/ProvisioningTool/lib/libjsoncpp.so.0 b/ProvisioningTool/lib/libjsoncpp.so.0 new file mode 100755 index 00000000..a23ae279 Binary files /dev/null and b/ProvisioningTool/lib/libjsoncpp.so.0 differ diff --git a/ProvisioningTool/lib/libjsoncpp.so.0.10.7 b/ProvisioningTool/lib/libjsoncpp.so.0.10.7 new file mode 100755 index 00000000..a23ae279 Binary files /dev/null and b/ProvisioningTool/lib/libjsoncpp.so.0.10.7 differ diff --git a/ProvisioningTool/sample_json_keymint_cf.txt b/ProvisioningTool/sample_json_keymint_cf.txt new file mode 100644 index 00000000..70f9c71e --- /dev/null +++ b/ProvisioningTool/sample_json_keymint_cf.txt @@ -0,0 +1,28 @@ +{ + "attest_ids": { + "brand": "generic", + "device": "vsoc_x86_64", + "product": "aosp_cf_x86_64_phone", + "serial": "", + "imei": "000000000000000", + "meid": "000000000000000", + "manufacturer": "Google", + "model": "Cuttlefish x86_64 phone" + }, + "shared_secret": "0000000000000000000000000000000000000000000000000000000000000000", + "set_boot_params": { + "boot_patch_level": 0, + "verified_boot_key": "0000000000000000000000000000000000000000000000000000000000000000", + "verified_boot_key_hash": "0000000000000000000000000000000000000000000000000000000000000000", + "boot_state": 2, + "device_locked": 1 + }, + "device_unique_key": "test_resources/batch_key.der", + "signer_info": { + "signer_name": "Google", + "signing_keys": [ + "test_resources/ca_key.der", + "test_resources/intermediate_key.der" + ] + } +} diff --git a/ProvisioningTool/src/construct_apdus.cpp b/ProvisioningTool/src/construct_apdus.cpp new file mode 100644 index 00000000..190e3a78 --- /dev/null +++ b/ProvisioningTool/src/construct_apdus.cpp @@ -0,0 +1,612 @@ +/* + ** + ** 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cppbor/cppbor.h" +#include "cppcose/cppcose.h" + +// static globals. +static std::string inputFileName; +static std::string outputFileName; +Json::Value root; +Json::Value writerRoot; + +using namespace std; +using cppbor::Array; +using cppbor::Map; +using cppbor::Bstr; +using cppcose::CoseKey; +using cppcose::EC2; +using cppcose::ES256; +using cppcose::P256; +using cppcose::SIGN; +using cppcose::bytevec; + + +// static function declarations +static int processInputFile(); +static int ecRawKeyFromPKCS8(const std::vector& pkcs8Blob, std::vector& secret, + std::vector& pub_x, std::vector& pub_y); +static int processAttestationIds(); +static int processSharedSecret(); +static int processSetBootParameters(); +static int readDataFromFile(const char *fileName, std::vector& data); +static int addApduHeader(const int ins, std::vector& inputData); +static int getIntValue(Json::Value& Obj, const char* key, uint32_t *value); +static int getBlobValue(Json::Value& Obj, const char* key, std::vector& blob); +static int getStringValue(Json::Value& Obj, const char* key, std::string& str); +static int processDeviceUniqueKey(); +static int processAdditionalCertificateChain(); +static int getDeviceUniqueKey(bytevec& privKey, bytevec& x, bytevec& y); + + +// Print usage. +void usage() { + printf("Usage: Please give json files with values as input to generate the apdus command. Please refer to sample_json files available in the folder for reference. Sample json files are written using hardcode parameters to be used for testing setup on cuttlefilsh emulator and goldfish emulators\n"); + printf("construct_keymint_apdus [options]\n"); + printf("Valid options are:\n"); + printf("-h, --help show this help message and exit.\n"); + printf("-i, --input jsonFile \t Input json file \n"); + printf("-o, --output jsonFile \t Output json file \n"); +} + + +int ecRawKeyFromPKCS8(const std::vector& pkcs8Blob, std::vector& secret, + std::vector& pub_x, std::vector& pub_y) { + const uint8_t *data = pkcs8Blob.data(); + EVP_PKEY *evpkey = d2i_PrivateKey(EVP_PKEY_EC, nullptr, &data, pkcs8Blob.size()); + if(!evpkey) { + printf("\n Failed to decode private key from PKCS8, Error: %ld", ERR_peek_last_error()); + return FAILURE; + } + EVP_PKEY_Ptr pkey(evpkey); + + EC_KEY_Ptr ec_key(EVP_PKEY_get1_EC_KEY(pkey.get())); + if(!ec_key.get()) { + printf("\n Failed to create EC_KEY, Error: %ld", ERR_peek_last_error()); + return FAILURE; + } + + //Get EC Group + const EC_GROUP *group = EC_KEY_get0_group(ec_key.get()); + if(group == NULL) { + printf("\n Failed to get the EC_GROUP from ec_key."); + return FAILURE; + } + + //Extract private key. + const BIGNUM *privBn = EC_KEY_get0_private_key(ec_key.get()); + int privKeyLen = BN_num_bytes(privBn); + std::unique_ptr privKey(new uint8_t[privKeyLen]); + BN_bn2bin(privBn, privKey.get()); + secret.insert(secret.begin(), privKey.get(), privKey.get()+privKeyLen); + + //Extract public key. + BIGNUM_Ptr x(BN_new()); + BIGNUM_Ptr y(BN_new()); + std::vector dataX(kAffinePointLength); + std::vector dataY(kAffinePointLength); + BN_CTX_Ptr ctx(BN_CTX_new()); + if (ctx == nullptr) { + printf("\nFailed to get BN_CTX \n"); + return FAILURE; + } + const EC_POINT *point = EC_KEY_get0_public_key(ec_key.get()); + + if (!EC_POINT_get_affine_coordinates_GFp(EC_KEY_get0_group(ec_key.get()), point, x.get(), + y.get(), ctx.get())) { + printf("\nFailed to get affine coordinates\n"); + return FAILURE; + } + if (BN_bn2binpad(x.get(), dataX.data(), kAffinePointLength) != kAffinePointLength) { + printf("\nFailed to get x coordinate\n"); + return FAILURE; + } + if (BN_bn2binpad(y.get(), dataY.data(), kAffinePointLength) != kAffinePointLength) { + printf("\nFailed to get y coordinate\n"); + return FAILURE; + } + pub_x = dataX; + pub_y = dataY; + return SUCCESS; +} + +int getIntValue(Json::Value& bootParamsObj, const char* key, uint32_t *value) { + Json::Value val = bootParamsObj[key]; + if(val.empty()) + return FAILURE; + + if(!val.isInt()) + return FAILURE; + + *value = (uint32_t)val.asInt(); + + return SUCCESS; +} + +int getStringValue(Json::Value& Obj, const char* key, std::string& str) { + Json::Value val = Obj[key]; + if(val.empty()) + return FAILURE; + + if(!val.isString()) + return FAILURE; + + str = val.asString(); + + return SUCCESS; + +} + +int getBlobValue(Json::Value& bootParamsObj, const char* key, std::vector& blob) { + Json::Value val = bootParamsObj[key]; + if(val.empty()) + return FAILURE; + + if(!val.isString()) + return FAILURE; + + std::string blobStr = hex2str(val.asString()); + + for(char ch : blobStr) { + blob.push_back((uint8_t)ch); + } + + return SUCCESS; +} + + +// Parses the input json file. Prepares the apdu for each entry in the json +// file and dump all the apdus into the output json file. +int processInputFile() { + + // Parse Json file + if (0 != readJsonFile(root, inputFileName)) { + return FAILURE; + } + if (0 != processDeviceUniqueKey() || + 0 != processAdditionalCertificateChain() || + 0 != processAttestationIds() || + 0 != processSharedSecret() || + 0 != processSetBootParameters()) { + return FAILURE; + } + if (SUCCESS != writeJsonFile(writerRoot, outputFileName)) { + return FAILURE; + } + printf("\n Successfully written json to outfile: %s\n ", outputFileName.c_str()); + return SUCCESS; +} + +int processAdditionalCertificateChain() { + Json::Value signerInfo = root.get(kSignerInfo, Json::Value::nullRef); + if (!signerInfo.isNull()) { + std::string signerName; + std::string signingKeyFile; + std::vector previousKey; + Array array; + + if (SUCCESS != getStringValue(signerInfo, "signer_name", signerName)) { + printf("\n Improper value for signer_name in json file \n"); + return FAILURE; + } + + Json::Value keys = signerInfo.get("signing_keys", Json::Value::nullRef); + if (!keys.isNull()) { + if (!keys.isArray()) { + printf("\n Improper value for signing_keys in json file \n"); + return FAILURE; + } + for(uint32_t i = 0; i < keys.size(); i++) { + std::vector data; + std::vector privateKey; + std::vector x_coord; + std::vector y_coord; + + if (!keys[i].isString()) { + printf("\n Improper value for signing_keys in json file \n"); + return FAILURE; + } + + if(SUCCESS != readDataFromFile(keys[i].asString().data(), data)) { + printf("\n Failed to read the attestation key from the file.\n"); + return FAILURE; + } + if (SUCCESS != ecRawKeyFromPKCS8(data, privateKey, x_coord, y_coord)) { + return FAILURE; + } + + if (i == 0) { + // self-signed. + previousKey = privateKey; + } + + auto rootCoseSign = + cppcose::constructCoseSign1(previousKey, /* Signing key */ + cppbor::Map() /* Payload CoseKey */ + .add(CoseKey::KEY_TYPE, EC2) + .add(CoseKey::ALGORITHM, ES256) + .add(CoseKey::CURVE, P256) + .add(CoseKey::KEY_OPS, SIGN) + .add(CoseKey::PUBKEY_X, x_coord) + .add(CoseKey::PUBKEY_Y, y_coord) + .canonicalize() + .encode(), + {} /* AAD */); + if (!rootCoseSign) { + printf("\n Failed to construct CoseSign1 %s\n", rootCoseSign.moveMessage().c_str()); + return FAILURE; + } + + // Add to cbor array + array.add(rootCoseSign.moveValue()); + previousKey = privateKey; + } + } + + std::vector dk_priv; + std::vector dk_pub_x; + std::vector dk_pub_y; + if (SUCCESS == getDeviceUniqueKey(dk_priv, dk_pub_x, dk_pub_y)) { + auto dkCoseSign = + cppcose::constructCoseSign1(previousKey, /* Signing key */ + cppbor::Map() /* Payload CoseKey */ + .add(CoseKey::KEY_TYPE, EC2) + .add(CoseKey::ALGORITHM, ES256) + .add(CoseKey::CURVE, P256) + .add(CoseKey::KEY_OPS, SIGN) + .add(CoseKey::PUBKEY_X, dk_pub_x) + .add(CoseKey::PUBKEY_Y, dk_pub_y) + .canonicalize() + .encode(), + {} /* AAD */); + if (!dkCoseSign) { + printf("\n Failed to construct CoseSign1 %s\n", dkCoseSign.moveMessage().c_str()); + return FAILURE; + } + array.add(dkCoseSign.moveValue()); + std::vector cborData = Map().add(signerName, std::move(array)).encode(); + if(SUCCESS != addApduHeader(kAdditionalCertChainCmd, cborData)) { + return FAILURE; + } + // Write to json. + writerRoot[kAdditionalCertChain] = getHexString(cborData); + } else { + return FAILURE; + } + + } else { + printf("\n Improper value for signer_info in json file \n"); + return FAILURE; + } + printf("\n Constructed additional cert chain APDU successfully. \n"); + return SUCCESS; +} + +int getDeviceUniqueKey(bytevec& privKey, bytevec& x, bytevec& y) { + Json::Value keyFile = root.get(kDeviceUniqueKey, Json::Value::nullRef); + if (!keyFile.isNull()) { + std::vector data; + + std::string keyFileName = keyFile.asString(); + if(SUCCESS != readDataFromFile(keyFileName.data(), data)) { + printf("\n Failed to read the attestation key from the file.\n"); + return FAILURE; + } + if (SUCCESS != ecRawKeyFromPKCS8(data, privKey, x, y)) { + return FAILURE; + } + } else { + printf("\n Improper value for device_unique_key in json file \n"); + return FAILURE; + } + return SUCCESS; +} + +int processDeviceUniqueKey() { + std::vector privateKey; + std::vector x_coord; + std::vector y_coord; + if (SUCCESS == getDeviceUniqueKey(privateKey, x_coord, y_coord)) { + // Construct COSE_Key + cppbor::Map cose_public_key_map = cppbor::Map() + .add(CoseKey::KEY_TYPE, EC2) + .add(CoseKey::ALGORITHM, ES256) + .add(CoseKey::CURVE, P256) + .add(CoseKey::KEY_OPS, SIGN) + .add(CoseKey::PUBKEY_X, x_coord) + .add(CoseKey::PUBKEY_Y, y_coord) + .add(CoseKey::PRIVATE_KEY, privateKey); + + Array array; + array.add(std::move(cose_public_key_map.canonicalize())); + std::vector cborData = array.encode(); + + if(SUCCESS != addApduHeader(kDeviceUniqueKeyCmd, cborData)) { + return FAILURE; + } + // Write to json. + writerRoot[kDeviceUniqueKey] = getHexString(cborData); + + } else { + return FAILURE; + } + printf("\n Constructed device unique key APDU successfully. \n"); + return SUCCESS; +} + + +int processAttestationIds() { + //AttestIDParams params; + Json::Value attestIds = root.get("attest_ids", Json::Value::nullRef); + if (!attestIds.isNull()) { + Json::Value value; + Map map; + Json::Value::Members keys = attestIds.getMemberNames(); + for(std::string key : keys) { + value = attestIds[key]; + if(value.empty()) { + continue; + } + if (!value.isString()) { + printf("\n Fail: Value for each attest ids key should be a string in the json file \n"); + return FAILURE; + } + std::string idVal = value.asString(); + if (0 == key.compare("brand")) { + map.add(kTagAttestationIdBrand, std::vector(idVal.begin(), idVal.end())); + } else if(0 == key.compare("device")) { + map.add(kTagAttestationIdDevice, std::vector(idVal.begin(), idVal.end())); + } else if(0 == key.compare("product")) { + map.add(kTagAttestationIdProduct, std::vector(idVal.begin(), idVal.end())); + } else if(0 == key.compare("serial")) { + map.add(kTagAttestationIdSerial, std::vector(idVal.begin(), idVal.end())); + } else if(0 == key.compare("imei")) { + map.add(kTagAttestationIdImei, std::vector(idVal.begin(), idVal.end())); + } else if(0 == key.compare("meid")) { + map.add(kTagAttestationIdMeid, std::vector(idVal.begin(), idVal.end())); + } else if(0 == key.compare("manufacturer")) { + map.add(kTagAttestationIdManufacturer, std::vector(idVal.begin(), idVal.end())); + } else if(0 == key.compare("model")) { + map.add(kTagAttestationIdModel, std::vector(idVal.begin(), idVal.end())); + } else { + printf("\n unknown attestation id key:%s \n", key.c_str()); + return FAILURE; + } + } + + //------------------------- + // construct cbor input. + Array array; + array.add(std::move(map)); + std::vector cborData = array.encode(); + if (SUCCESS != addApduHeader(kAttestationIdsCmd, cborData)) { + return FAILURE; + } + // Write to json. + writerRoot[kAttestationIds] = getHexString(cborData); + //------------------------- + } else { + printf("\n Fail: Improper value found for attest_ids key inside the json file \n"); + return FAILURE; + } + printf("\n Constructed attestation ids APDU successfully \n"); + return SUCCESS; +} + +int processSharedSecret() { + Json::Value sharedSecret = root.get("shared_secret", Json::Value::nullRef); + if (!sharedSecret.isNull()) { + + if (!sharedSecret.isString()) { + printf("\n Fail: Value for shared secret key should be string inside the json file\n"); + return FAILURE; + } + std::string secret = hex2str(sharedSecret.asString()); + std::vector data(secret.begin(), secret.end()); + // -------------------------- + // Construct apdu. + Array array; + array.add(data); + std::vector cborData = array.encode(); + if (SUCCESS != addApduHeader(kPresharedSecretCmd, cborData)) { + return FAILURE; + } + // Write to json. + writerRoot[kSharedSecret] = getHexString(cborData); + // -------------------------- + } else { + printf("\n Fail: Improper value for shared_secret key inside the json file\n"); + return FAILURE; + } + printf("\n Constructed shared secret APDU successfully \n"); + return SUCCESS; +} + +int processSetBootParameters() { + uint32_t bootPatchLevel; + std::vector verifiedBootKey; + std::vector verifiedBootKeyHash; + uint32_t verifiedBootState; + uint32_t deviceLocked; + Json::Value bootParamsObj = root.get("set_boot_params", Json::Value::nullRef); + if (!bootParamsObj.isNull()) { + + if(SUCCESS != getIntValue(bootParamsObj, "boot_patch_level", &bootPatchLevel)) { + printf("\n Invalid value for boot_patch_level or boot_patch_level tag missing\n"); + return FAILURE; + } + if(SUCCESS != getBlobValue(bootParamsObj, "verified_boot_key", verifiedBootKey)) { + printf("\n Invalid value for verified_boot_key or verified_boot_key tag missing\n"); + return FAILURE; + } + if(SUCCESS != getBlobValue(bootParamsObj, "verified_boot_key_hash", verifiedBootKeyHash)) { + printf("\n Invalid value for verified_boot_key_hash or verified_boot_key_hash tag missing\n"); + return FAILURE; + } + if(SUCCESS != getIntValue(bootParamsObj, "boot_state", &verifiedBootState)) { + printf("\n Invalid value for boot_state or boot_state tag missing\n"); + return FAILURE; + } + if(SUCCESS != getIntValue(bootParamsObj, "device_locked", &deviceLocked)) { + printf("\n Invalid value for device_locked or device_locked tag missing\n"); + return FAILURE; + } + + } else { + printf("\n Fail: Improper value found for set_boot_params key inside the json file\n"); + return FAILURE; + } + //--------------------------------- + // prepare cbor data. + Array array; + array.add(bootPatchLevel). + add(verifiedBootKey). /* Verified Boot Key */ + add(verifiedBootKeyHash). /* Verified Boot Hash */ + add(verifiedBootState). /* boot state */ + add(deviceLocked); /* device locked */ + + std::vector cborData = array.encode(); + if (SUCCESS != addApduHeader(kBootParamsCmd, cborData)) { + return FAILURE; + } + // Write to json. + writerRoot[kBootParams] = getHexString(cborData); + + //--------------------------------- + printf("\n Constructed boot paramters APDU successfully \n"); + return SUCCESS; +} + + + +int addApduHeader(const int ins, std::vector& inputData) { + 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 + if (inputData.size() > 0) { + inputData.insert(inputData.begin(), static_cast(inputData.size() & 0xFF)); // LSB + inputData.insert(inputData.begin(), static_cast(inputData.size() >> 8)); // MSB + } + inputData.insert(inputData.begin(), static_cast(0x00)); + //Expected length of output. + //Accepting complete length of output every time. + inputData.push_back(static_cast(0x00)); + inputData.push_back(static_cast(0x00)); + } else { + printf("\n Failed to construct apdu. input data larger than USHORT_MAX.\n"); + return FAILURE; + } + + inputData.insert(inputData.begin(), static_cast(APDU_P2));//P2 + inputData.insert(inputData.begin(), static_cast(APDU_P1));//P1 + inputData.insert(inputData.begin(), static_cast(ins));//INS + inputData.insert(inputData.begin(), static_cast(APDU_CLS));//CLS + return SUCCESS; +} + +int readDataFromFile(const char *filename, std::vector& data) { + FILE *fp; + int ret = SUCCESS; + fp = fopen(filename, "rb"); + if(fp == NULL) { + printf("\nFailed to open file: \n"); + return FAILURE; + } + fseek(fp, 0L, SEEK_END); + long int filesize = ftell(fp); + rewind(fp); + std::unique_ptr buf(new uint8_t[filesize]); + if( 0 == fread(buf.get(), filesize, 1, fp)) { + printf("\n No content in the file \n"); + ret = FAILURE; + goto exit; + } + data.insert(data.end(), buf.get(), buf.get() + filesize); +exit: + fclose(fp); + return ret; +} + +int main(int argc, char* argv[]) { + int c; + struct option longOpts[] = { + {"input", required_argument, NULL, 'i'}, + {"output", required_argument, NULL, 'o'}, + {"help", no_argument, NULL, 'h'}, + {0,0,0,0} + }; + + if (argc <= 1) { + printf("\n Invalid command \n"); + usage(); + return FAILURE; + } + + /* getopt_long stores the option index here. */ + while ((c = getopt_long(argc, argv, ":hi:o:", longOpts, NULL)) != -1) { + switch(c) { + case 'i': + // input file + inputFileName = std::string(optarg); + std::cout << "input file: " << inputFileName << std::endl; + break; + case 'o': + // output file + outputFileName = std::string(optarg); + std::cout << "output file: " << outputFileName << std::endl; + break; + case 'h': + // help + usage(); + return SUCCESS; + case ':': + printf("\n missing argument\n"); + usage(); + return FAILURE; + case '?': + default: + printf("\n Invalid option\n"); + usage(); + return FAILURE; + } + } + if (inputFileName.empty() || outputFileName.empty() || optind < argc) { + printf("\n Missing mandatory arguments \n"); + usage(); + return FAILURE; + } + // Process input file; construct apuds and store in output json file. + processInputFile(); + return SUCCESS; +} diff --git a/ProvisioningTool/src/cppbor/cppbor.cpp b/ProvisioningTool/src/cppbor/cppbor.cpp new file mode 100644 index 00000000..3414b8e5 --- /dev/null +++ b/ProvisioningTool/src/cppbor/cppbor.cpp @@ -0,0 +1,626 @@ +/* + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/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 + +#include +#include + +#include + +using std::string; +using std::vector; + + +#if !defined(__TRUSTY__) && !defined(__LINUX__) +#include +#define LOG_TAG "CppBor" +#else +#define CHECK(x) (void)(x) +#endif + +#ifdef __LINUX__ +#define ERROR "ERROR: " +#define LOG(x) std::cout << x +#endif + +namespace cppbor { + +namespace { + +template ::value>> +Iterator writeBigEndian(T value, Iterator pos) { + for (unsigned i = 0; i < sizeof(value); ++i) { + *pos++ = static_cast(value >> (8 * (sizeof(value) - 1))); + value = static_cast(value << 8); + } + return pos; +} + +template ::value>> +void writeBigEndian(T value, std::function& cb) { + for (unsigned i = 0; i < sizeof(value); ++i) { + cb(static_cast(value >> (8 * (sizeof(value) - 1)))); + value = static_cast(value << 8); + } +} + +bool cborAreAllElementsNonCompound(const Item* compoundItem) { + if (compoundItem->type() == ARRAY) { + const Array* array = compoundItem->asArray(); + for (size_t n = 0; n < array->size(); n++) { + const Item* entry = (*array)[n].get(); + switch (entry->type()) { + case ARRAY: + case MAP: + return false; + default: + break; + } + } + } else { + const Map* map = compoundItem->asMap(); + for (auto& [keyEntry, valueEntry] : *map) { + switch (keyEntry->type()) { + case ARRAY: + case MAP: + return false; + default: + break; + } + switch (valueEntry->type()) { + case ARRAY: + case MAP: + return false; + default: + break; + } + } + } + return true; +} + +bool prettyPrintInternal(const Item* item, string& out, size_t indent, size_t maxBStrSize, + const vector& mapKeysToNotPrint) { + if (!item) { + out.append(""); + return false; + } + + char buf[80]; + + string indentString(indent, ' '); + + size_t tagCount = item->semanticTagCount(); + while (tagCount > 0) { + --tagCount; + snprintf(buf, sizeof(buf), "tag %" PRIu64 " ", item->semanticTag(tagCount)); + out.append(buf); + } + + switch (item->type()) { + case SEMANTIC: + // Handled above. + break; + + case UINT: + snprintf(buf, sizeof(buf), "%" PRIu64, item->asUint()->unsignedValue()); + out.append(buf); + break; + + case NINT: + snprintf(buf, sizeof(buf), "%" PRId64, item->asNint()->value()); + out.append(buf); + break; + + case BSTR: { + const uint8_t* valueData; + size_t valueSize; + const Bstr* bstr = item->asBstr(); + if (bstr != nullptr) { + const vector& value = bstr->value(); + valueData = value.data(); + valueSize = value.size(); + } else { + const ViewBstr* viewBstr = item->asViewBstr(); + assert(viewBstr != nullptr); + + std::basic_string_view view = viewBstr->view(); + valueData = view.data(); + valueSize = view.size(); + } + + if (valueSize > maxBStrSize) { + unsigned char digest[SHA_DIGEST_LENGTH]; + SHA_CTX ctx; + SHA1_Init(&ctx); + SHA1_Update(&ctx, valueData, valueSize); + SHA1_Final(digest, &ctx); + char buf2[SHA_DIGEST_LENGTH * 2 + 1]; + for (size_t n = 0; n < SHA_DIGEST_LENGTH; n++) { + snprintf(buf2 + n * 2, 3, "%02x", digest[n]); + } + snprintf(buf, sizeof(buf), "", valueSize, buf2); + out.append(buf); + } else { + out.append("{"); + for (size_t n = 0; n < valueSize; n++) { + if (n > 0) { + out.append(", "); + } + snprintf(buf, sizeof(buf), "0x%02x", valueData[n]); + out.append(buf); + } + out.append("}"); + } + } break; + + case TSTR: + out.append("'"); + { + // TODO: escape "'" characters + if (item->asTstr() != nullptr) { + out.append(item->asTstr()->value().c_str()); + } else { + const ViewTstr* viewTstr = item->asViewTstr(); + assert(viewTstr != nullptr); + out.append(viewTstr->view()); + } + } + out.append("'"); + break; + + case ARRAY: { + const Array* array = item->asArray(); + if (array->size() == 0) { + out.append("[]"); + } else if (cborAreAllElementsNonCompound(array)) { + out.append("["); + for (size_t n = 0; n < array->size(); n++) { + if (!prettyPrintInternal((*array)[n].get(), out, indent + 2, maxBStrSize, + mapKeysToNotPrint)) { + return false; + } + out.append(", "); + } + out.append("]"); + } else { + out.append("[\n" + indentString); + for (size_t n = 0; n < array->size(); n++) { + out.append(" "); + if (!prettyPrintInternal((*array)[n].get(), out, indent + 2, maxBStrSize, + mapKeysToNotPrint)) { + return false; + } + out.append(",\n" + indentString); + } + out.append("]"); + } + } break; + + case MAP: { + const Map* map = item->asMap(); + + if (map->size() == 0) { + out.append("{}"); + } else { + out.append("{\n" + indentString); + for (auto& [map_key, map_value] : *map) { + out.append(" "); + + if (!prettyPrintInternal(map_key.get(), out, indent + 2, maxBStrSize, + mapKeysToNotPrint)) { + return false; + } + out.append(" : "); + if (map_key->type() == TSTR && + std::find(mapKeysToNotPrint.begin(), mapKeysToNotPrint.end(), + map_key->asTstr()->value()) != mapKeysToNotPrint.end()) { + out.append(""); + } else { + if (!prettyPrintInternal(map_value.get(), out, indent + 2, maxBStrSize, + mapKeysToNotPrint)) { + return false; + } + } + out.append(",\n" + indentString); + } + out.append("}"); + } + } break; + + case SIMPLE: + const Bool* asBool = item->asSimple()->asBool(); + const Null* asNull = item->asSimple()->asNull(); + if (asBool != nullptr) { + out.append(asBool->value() ? "true" : "false"); + } else if (asNull != nullptr) { + out.append("null"); + } else { +#ifndef __TRUSTY__ + LOG(ERROR) << "Only boolean/null is implemented for SIMPLE"; +#endif // __TRUSTY__ + return false; + } + break; + } + + return true; +} + +} // namespace + +size_t headerSize(uint64_t addlInfo) { + if (addlInfo < ONE_BYTE_LENGTH) return 1; + if (addlInfo <= std::numeric_limits::max()) return 2; + if (addlInfo <= std::numeric_limits::max()) return 3; + if (addlInfo <= std::numeric_limits::max()) return 5; + return 9; +} + +uint8_t* encodeHeader(MajorType type, uint64_t addlInfo, uint8_t* pos, const uint8_t* end) { + size_t sz = headerSize(addlInfo); + if (end - pos < static_cast(sz)) return nullptr; + switch (sz) { + case 1: + *pos++ = type | static_cast(addlInfo); + return pos; + case 2: + *pos++ = type | ONE_BYTE_LENGTH; + *pos++ = static_cast(addlInfo); + return pos; + case 3: + *pos++ = type | TWO_BYTE_LENGTH; + return writeBigEndian(static_cast(addlInfo), pos); + case 5: + *pos++ = type | FOUR_BYTE_LENGTH; + return writeBigEndian(static_cast(addlInfo), pos); + case 9: + *pos++ = type | EIGHT_BYTE_LENGTH; + return writeBigEndian(addlInfo, pos); + default: + CHECK(false); // Impossible to get here. + return nullptr; + } +} + +void encodeHeader(MajorType type, uint64_t addlInfo, EncodeCallback encodeCallback) { + size_t sz = headerSize(addlInfo); + switch (sz) { + case 1: + encodeCallback(type | static_cast(addlInfo)); + break; + case 2: + encodeCallback(type | ONE_BYTE_LENGTH); + encodeCallback(static_cast(addlInfo)); + break; + case 3: + encodeCallback(type | TWO_BYTE_LENGTH); + writeBigEndian(static_cast(addlInfo), encodeCallback); + break; + case 5: + encodeCallback(type | FOUR_BYTE_LENGTH); + writeBigEndian(static_cast(addlInfo), encodeCallback); + break; + case 9: + encodeCallback(type | EIGHT_BYTE_LENGTH); + writeBigEndian(addlInfo, encodeCallback); + break; + default: + CHECK(false); // Impossible to get here. + } +} + +bool Item::operator==(const Item& other) const& { + if (type() != other.type()) return false; + switch (type()) { + case UINT: + return *asUint() == *(other.asUint()); + case NINT: + return *asNint() == *(other.asNint()); + case BSTR: + if (asBstr() != nullptr && other.asBstr() != nullptr) { + return *asBstr() == *(other.asBstr()); + } + if (asViewBstr() != nullptr && other.asViewBstr() != nullptr) { + return *asViewBstr() == *(other.asViewBstr()); + } + // Interesting corner case: comparing a Bstr and ViewBstr with + // identical contents. The function currently returns false for + // this case. + // TODO: if it should return true, this needs a deep comparison + return false; + case TSTR: + if (asTstr() != nullptr && other.asTstr() != nullptr) { + return *asTstr() == *(other.asTstr()); + } + if (asViewTstr() != nullptr && other.asViewTstr() != nullptr) { + return *asViewTstr() == *(other.asViewTstr()); + } + // Same corner case as Bstr + return false; + case ARRAY: + return *asArray() == *(other.asArray()); + case MAP: + return *asMap() == *(other.asMap()); + case SIMPLE: + return *asSimple() == *(other.asSimple()); + case SEMANTIC: + return *asSemanticTag() == *(other.asSemanticTag()); + default: + CHECK(false); // Impossible to get here. + return false; + } +} + +Nint::Nint(int64_t v) : mValue(v) { + CHECK(v < 0); +} + +bool Simple::operator==(const Simple& other) const& { + if (simpleType() != other.simpleType()) return false; + + switch (simpleType()) { + case BOOLEAN: + return *asBool() == *(other.asBool()); + case NULL_T: + return true; + default: + CHECK(false); // Impossible to get here. + return false; + } +} + +uint8_t* Bstr::encode(uint8_t* pos, const uint8_t* end) const { + pos = encodeHeader(mValue.size(), pos, end); + if (!pos || end - pos < static_cast(mValue.size())) return nullptr; + return std::copy(mValue.begin(), mValue.end(), pos); +} + +void Bstr::encodeValue(EncodeCallback encodeCallback) const { + for (auto c : mValue) { + encodeCallback(c); + } +} + +uint8_t* ViewBstr::encode(uint8_t* pos, const uint8_t* end) const { + pos = encodeHeader(mView.size(), pos, end); + if (!pos || end - pos < static_cast(mView.size())) return nullptr; + return std::copy(mView.begin(), mView.end(), pos); +} + +void ViewBstr::encodeValue(EncodeCallback encodeCallback) const { + for (auto c : mView) { + encodeCallback(static_cast(c)); + } +} + +uint8_t* Tstr::encode(uint8_t* pos, const uint8_t* end) const { + pos = encodeHeader(mValue.size(), pos, end); + if (!pos || end - pos < static_cast(mValue.size())) return nullptr; + return std::copy(mValue.begin(), mValue.end(), pos); +} + +void Tstr::encodeValue(EncodeCallback encodeCallback) const { + for (auto c : mValue) { + encodeCallback(static_cast(c)); + } +} + +uint8_t* ViewTstr::encode(uint8_t* pos, const uint8_t* end) const { + pos = encodeHeader(mView.size(), pos, end); + if (!pos || end - pos < static_cast(mView.size())) return nullptr; + return std::copy(mView.begin(), mView.end(), pos); +} + +void ViewTstr::encodeValue(EncodeCallback encodeCallback) const { + for (auto c : mView) { + encodeCallback(static_cast(c)); + } +} + +bool Array::operator==(const Array& other) const& { + return size() == other.size() + // Can't use vector::operator== because the contents are pointers. std::equal lets us + // provide a predicate that does the dereferencing. + && std::equal(mEntries.begin(), mEntries.end(), other.mEntries.begin(), + [](auto& a, auto& b) -> bool { return *a == *b; }); +} + +uint8_t* Array::encode(uint8_t* pos, const uint8_t* end) const { + pos = encodeHeader(size(), pos, end); + if (!pos) return nullptr; + for (auto& entry : mEntries) { + pos = entry->encode(pos, end); + if (!pos) return nullptr; + } + return pos; +} + +void Array::encode(EncodeCallback encodeCallback) const { + encodeHeader(size(), encodeCallback); + for (auto& entry : mEntries) { + entry->encode(encodeCallback); + } +} + +std::unique_ptr Array::clone() const { + auto res = std::make_unique(); + for (size_t i = 0; i < mEntries.size(); i++) { + res->add(mEntries[i]->clone()); + } + return res; +} + +bool Map::operator==(const Map& other) const& { + return size() == other.size() + // Can't use vector::operator== because the contents are pairs of pointers. std::equal + // lets us provide a predicate that does the dereferencing. + && std::equal(begin(), end(), other.begin(), [](auto& a, auto& b) { + return *a.first == *b.first && *a.second == *b.second; + }); +} + +uint8_t* Map::encode(uint8_t* pos, const uint8_t* end) const { + pos = encodeHeader(size(), pos, end); + if (!pos) return nullptr; + for (auto& entry : mEntries) { + pos = entry.first->encode(pos, end); + if (!pos) return nullptr; + pos = entry.second->encode(pos, end); + if (!pos) return nullptr; + } + return pos; +} + +void Map::encode(EncodeCallback encodeCallback) const { + encodeHeader(size(), encodeCallback); + for (auto& entry : mEntries) { + entry.first->encode(encodeCallback); + entry.second->encode(encodeCallback); + } +} + +bool Map::keyLess(const Item* a, const Item* b) { + // CBOR map canonicalization rules are: + + // 1. If two keys have different lengths, the shorter one sorts earlier. + if (a->encodedSize() < b->encodedSize()) return true; + if (a->encodedSize() > b->encodedSize()) return false; + + // 2. If two keys have the same length, the one with the lower value in (byte-wise) lexical + // order sorts earlier. This requires encoding both items. + auto encodedA = a->encode(); + auto encodedB = b->encode(); + + return std::lexicographical_compare(encodedA.begin(), encodedA.end(), // + encodedB.begin(), encodedB.end()); +} + +void recursivelyCanonicalize(std::unique_ptr& item) { + switch (item->type()) { + case UINT: + case NINT: + case BSTR: + case TSTR: + case SIMPLE: + return; + + case ARRAY: + std::for_each(item->asArray()->begin(), item->asArray()->end(), + recursivelyCanonicalize); + return; + + case MAP: + item->asMap()->canonicalize(true /* recurse */); + return; + + case SEMANTIC: + // This can't happen. SemanticTags delegate their type() method to the contained Item's + // type. + assert(false); + return; + } +} + +Map& Map::canonicalize(bool recurse) & { + if (recurse) { + for (auto& entry : mEntries) { + recursivelyCanonicalize(entry.first); + recursivelyCanonicalize(entry.second); + } + } + + if (size() < 2 || mCanonicalized) { + // Trivially or already canonical; do nothing. + return *this; + } + + std::sort(begin(), end(), + [](auto& a, auto& b) { return keyLess(a.first.get(), b.first.get()); }); + mCanonicalized = true; + return *this; +} + +std::unique_ptr Map::clone() const { + auto res = std::make_unique(); + for (auto& [key, value] : *this) { + res->add(key->clone(), value->clone()); + } + res->mCanonicalized = mCanonicalized; + return res; +} + +std::unique_ptr SemanticTag::clone() const { + return std::make_unique(mValue, mTaggedItem->clone()); +} + +uint8_t* SemanticTag::encode(uint8_t* pos, const uint8_t* end) const { + // Can't use the encodeHeader() method that calls type() to get the major type, since that will + // return the tagged Item's type. + pos = ::cppbor::encodeHeader(kMajorType, mValue, pos, end); + if (!pos) return nullptr; + return mTaggedItem->encode(pos, end); +} + +void SemanticTag::encode(EncodeCallback encodeCallback) const { + // Can't use the encodeHeader() method that calls type() to get the major type, since that will + // return the tagged Item's type. + ::cppbor::encodeHeader(kMajorType, mValue, encodeCallback); + mTaggedItem->encode(encodeCallback); +} + +size_t SemanticTag::semanticTagCount() const { + size_t levelCount = 1; // Count this level. + const SemanticTag* cur = this; + while (cur->mTaggedItem && (cur = cur->mTaggedItem->asSemanticTag()) != nullptr) ++levelCount; + return levelCount; +} + +uint64_t SemanticTag::semanticTag(size_t nesting) const { + // Getting the value of a specific nested tag is a bit tricky, because we start with the outer + // tag and don't know how many are inside. We count the number of nesting levels to find out + // how many there are in total, then to get the one we want we have to walk down levelCount - + // nesting steps. + size_t levelCount = semanticTagCount(); + if (nesting >= levelCount) return 0; + + levelCount -= nesting; + const SemanticTag* cur = this; + while (--levelCount > 0) cur = cur->mTaggedItem->asSemanticTag(); + + return cur->mValue; +} + +string prettyPrint(const Item* item, size_t maxBStrSize, const vector& mapKeysToNotPrint) { + string out; + prettyPrintInternal(item, out, 0, maxBStrSize, mapKeysToNotPrint); + return out; +} +string prettyPrint(const vector& encodedCbor, size_t maxBStrSize, + const vector& mapKeysToNotPrint) { + auto [item, _, message] = parse(encodedCbor); + if (item == nullptr) { +#ifndef __TRUSTY__ + LOG(ERROR) << "Data to pretty print is not valid CBOR: " << message; +#endif // __TRUSTY__ + return ""; + } + + return prettyPrint(item.get(), maxBStrSize, mapKeysToNotPrint); +} + +} // namespace cppbor diff --git a/ProvisioningTool/src/cppbor/cppbor_parse.cpp b/ProvisioningTool/src/cppbor/cppbor_parse.cpp new file mode 100644 index 00000000..b1803310 --- /dev/null +++ b/ProvisioningTool/src/cppbor/cppbor_parse.cpp @@ -0,0 +1,389 @@ +/* + * Copyright 2019 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/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 "cppbor/cppbor_parse.h" + +#include + +#if !defined( __TRUSTY__) && !defined(__LINUX__) +#include +#define LOG_TAG "CppBor" +#else +#define CHECK(x) (void)(x) +#endif + +namespace cppbor { + +namespace { + +std::string insufficientLengthString(size_t bytesNeeded, size_t bytesAvail, + const std::string& type) { + char buf[1024]; + snprintf(buf, sizeof(buf), "Need %zu byte(s) for %s, have %zu.", bytesNeeded, type.c_str(), + bytesAvail); + return std::string(buf); +} + +template >> +std::tuple parseLength(const uint8_t* pos, const uint8_t* end, + ParseClient* parseClient) { + if (pos + sizeof(T) > end) { + parseClient->error(pos - 1, insufficientLengthString(sizeof(T), end - pos, "length field")); + return {false, 0, pos}; + } + + const uint8_t* intEnd = pos + sizeof(T); + T result = 0; + do { + result = static_cast((result << 8) | *pos++); + } while (pos < intEnd); + return {true, result, pos}; +} + +std::tuple parseRecursively(const uint8_t* begin, const uint8_t* end, + bool emitViews, ParseClient* parseClient); + +std::tuple handleUint(uint64_t value, const uint8_t* hdrBegin, + const uint8_t* hdrEnd, + ParseClient* parseClient) { + std::unique_ptr item = std::make_unique(value); + return {hdrEnd, + parseClient->item(item, hdrBegin, hdrEnd /* valueBegin */, hdrEnd /* itemEnd */)}; +} + +std::tuple handleNint(uint64_t value, const uint8_t* hdrBegin, + const uint8_t* hdrEnd, + ParseClient* parseClient) { + if (value > std::numeric_limits::max()) { + parseClient->error(hdrBegin, "NINT values that don't fit in int64_t are not supported."); + return {hdrBegin, nullptr /* end parsing */}; + } + std::unique_ptr item = std::make_unique(-1 - static_cast(value)); + return {hdrEnd, + parseClient->item(item, hdrBegin, hdrEnd /* valueBegin */, hdrEnd /* itemEnd */)}; +} + +std::tuple handleBool(uint64_t value, const uint8_t* hdrBegin, + const uint8_t* hdrEnd, + ParseClient* parseClient) { + std::unique_ptr item = std::make_unique(value == TRUE); + return {hdrEnd, + parseClient->item(item, hdrBegin, hdrEnd /* valueBegin */, hdrEnd /* itemEnd */)}; +} + +std::tuple handleNull(const uint8_t* hdrBegin, const uint8_t* hdrEnd, + ParseClient* parseClient) { + std::unique_ptr item = std::make_unique(); + return {hdrEnd, + parseClient->item(item, hdrBegin, hdrEnd /* valueBegin */, hdrEnd /* itemEnd */)}; +} + +template +std::tuple handleString(uint64_t length, const uint8_t* hdrBegin, + const uint8_t* valueBegin, const uint8_t* end, + const std::string& errLabel, + ParseClient* parseClient) { + if (end - valueBegin < static_cast(length)) { + parseClient->error(hdrBegin, insufficientLengthString(length, end - valueBegin, errLabel)); + return {hdrBegin, nullptr /* end parsing */}; + } + + std::unique_ptr item = std::make_unique(valueBegin, valueBegin + length); + return {valueBegin + length, + parseClient->item(item, hdrBegin, valueBegin, valueBegin + length)}; +} + +class IncompleteItem { + public: + virtual ~IncompleteItem() {} + virtual void add(std::unique_ptr item) = 0; +}; + +class IncompleteArray : public Array, public IncompleteItem { + public: + explicit IncompleteArray(size_t size) : mSize(size) {} + + // We return the "complete" size, rather than the actual size. + size_t size() const override { return mSize; } + + void add(std::unique_ptr item) override { + mEntries.reserve(mSize); + mEntries.push_back(std::move(item)); + } + + private: + size_t mSize; +}; + +class IncompleteMap : public Map, public IncompleteItem { + public: + explicit IncompleteMap(size_t size) : mSize(size) {} + + // We return the "complete" size, rather than the actual size. + size_t size() const override { return mSize; } + + void add(std::unique_ptr item) override { + if (mKeyHeldForAdding) { + mEntries.reserve(mSize); + mEntries.push_back({std::move(mKeyHeldForAdding), std::move(item)}); + } else { + mKeyHeldForAdding = std::move(item); + } + } + + private: + std::unique_ptr mKeyHeldForAdding; + size_t mSize; +}; + +class IncompleteSemanticTag : public SemanticTag, public IncompleteItem { + public: + explicit IncompleteSemanticTag(uint64_t value) : SemanticTag(value) {} + + // We return the "complete" size, rather than the actual size. + size_t size() const override { return 1; } + + void add(std::unique_ptr item) override { mTaggedItem = std::move(item); } +}; + +std::tuple handleEntries(size_t entryCount, const uint8_t* hdrBegin, + const uint8_t* pos, const uint8_t* end, + const std::string& typeName, + bool emitViews, + ParseClient* parseClient) { + while (entryCount > 0) { + --entryCount; + if (pos == end) { + parseClient->error(hdrBegin, "Not enough entries for " + typeName + "."); + return {hdrBegin, nullptr /* end parsing */}; + } + std::tie(pos, parseClient) = parseRecursively(pos, end, emitViews, parseClient); + if (!parseClient) return {hdrBegin, nullptr}; + } + return {pos, parseClient}; +} + +std::tuple handleCompound( + std::unique_ptr item, uint64_t entryCount, const uint8_t* hdrBegin, + const uint8_t* valueBegin, const uint8_t* end, const std::string& typeName, + bool emitViews, ParseClient* parseClient) { + parseClient = + parseClient->item(item, hdrBegin, valueBegin, valueBegin /* don't know the end yet */); + if (!parseClient) return {hdrBegin, nullptr}; + + const uint8_t* pos; + std::tie(pos, parseClient) = + handleEntries(entryCount, hdrBegin, valueBegin, end, typeName, emitViews, parseClient); + if (!parseClient) return {hdrBegin, nullptr}; + + return {pos, parseClient->itemEnd(item, hdrBegin, valueBegin, pos)}; +} + +std::tuple parseRecursively(const uint8_t* begin, const uint8_t* end, + bool emitViews, ParseClient* parseClient) { + const uint8_t* pos = begin; + + MajorType type = static_cast(*pos & 0xE0); + uint8_t tagInt = *pos & 0x1F; + ++pos; + + bool success = true; + uint64_t addlData; + if (tagInt < ONE_BYTE_LENGTH) { + addlData = tagInt; + } else if (tagInt > EIGHT_BYTE_LENGTH) { + parseClient->error( + begin, + "Reserved additional information value or unsupported indefinite length item."); + return {begin, nullptr}; + } else { + switch (tagInt) { + case ONE_BYTE_LENGTH: + std::tie(success, addlData, pos) = parseLength(pos, end, parseClient); + break; + + case TWO_BYTE_LENGTH: + std::tie(success, addlData, pos) = parseLength(pos, end, parseClient); + break; + + case FOUR_BYTE_LENGTH: + std::tie(success, addlData, pos) = parseLength(pos, end, parseClient); + break; + + case EIGHT_BYTE_LENGTH: + std::tie(success, addlData, pos) = parseLength(pos, end, parseClient); + break; + + default: + CHECK(false); // It's impossible to get here + break; + } + } + + if (!success) return {begin, nullptr}; + + switch (type) { + case UINT: + return handleUint(addlData, begin, pos, parseClient); + + case NINT: + return handleNint(addlData, begin, pos, parseClient); + + case BSTR: + if (emitViews) { + return handleString(addlData, begin, pos, end, "byte string", parseClient); + } else { + return handleString(addlData, begin, pos, end, "byte string", parseClient); + } + + case TSTR: + if (emitViews) { + return handleString(addlData, begin, pos, end, "text string", parseClient); + } else { + return handleString(addlData, begin, pos, end, "text string", parseClient); + } + + case ARRAY: + return handleCompound(std::make_unique(addlData), addlData, begin, pos, + end, "array", emitViews, parseClient); + + case MAP: + return handleCompound(std::make_unique(addlData), addlData * 2, begin, + pos, end, "map", emitViews, parseClient); + + case SEMANTIC: + return handleCompound(std::make_unique(addlData), 1, begin, pos, + end, "semantic", emitViews, parseClient); + + case SIMPLE: + switch (addlData) { + case TRUE: + case FALSE: + return handleBool(addlData, begin, pos, parseClient); + case NULL_V: + return handleNull(begin, pos, parseClient); + default: + parseClient->error(begin, "Unsupported floating-point or simple value."); + return {begin, nullptr}; + } + } + CHECK(false); // Impossible to get here. + return {}; +} + +class FullParseClient : public ParseClient { + public: + virtual ParseClient* item(std::unique_ptr& item, const uint8_t*, const uint8_t*, + const uint8_t* end) override { + if (mParentStack.empty() && !item->isCompound()) { + // This is the first and only item. + mTheItem = std::move(item); + mPosition = end; + return nullptr; // We're done. + } + + if (item->isCompound()) { + // Starting a new compound data item, i.e. a new parent. Save it on the parent stack. + // It's safe to save a raw pointer because the unique_ptr is guaranteed to stay in + // existence until the corresponding itemEnd() call. + mParentStack.push(item.get()); + return this; + } else { + appendToLastParent(std::move(item)); + return this; + } + } + + virtual ParseClient* itemEnd(std::unique_ptr& item, const uint8_t*, const uint8_t*, + const uint8_t* end) override { + CHECK(item->isCompound() && item.get() == mParentStack.top()); + mParentStack.pop(); + + if (mParentStack.empty()) { + mTheItem = std::move(item); + mPosition = end; + return nullptr; // We're done + } else { + appendToLastParent(std::move(item)); + return this; + } + } + + virtual void error(const uint8_t* position, const std::string& errorMessage) override { + mPosition = position; + mErrorMessage = errorMessage; + } + + std::tuple /* result */, const uint8_t* /* newPos */, + std::string /* errMsg */> + parseResult() { + std::unique_ptr p = std::move(mTheItem); + return {std::move(p), mPosition, std::move(mErrorMessage)}; + } + + private: + void appendToLastParent(std::unique_ptr item) { + auto parent = mParentStack.top(); +//#if __has_feature(cxx_rtti) + assert(dynamic_cast(parent)); +//#endif + + IncompleteItem* parentItem{}; + if (parent->type() == ARRAY) { + parentItem = static_cast(parent); + } else if (parent->type() == MAP) { + parentItem = static_cast(parent); + } else if (parent->asSemanticTag()) { + parentItem = static_cast(parent); + } else { + CHECK(false); // Impossible to get here. + } + parentItem->add(std::move(item)); + } + + std::unique_ptr mTheItem; + std::stack mParentStack; + const uint8_t* mPosition = nullptr; + std::string mErrorMessage; +}; + +} // anonymous namespace + +void parse(const uint8_t* begin, const uint8_t* end, ParseClient* parseClient) { + parseRecursively(begin, end, false, parseClient); +} + +std::tuple /* result */, const uint8_t* /* newPos */, + std::string /* errMsg */> +parse(const uint8_t* begin, const uint8_t* end) { + FullParseClient parseClient; + parse(begin, end, &parseClient); + return parseClient.parseResult(); +} + +void parseWithViews(const uint8_t* begin, const uint8_t* end, ParseClient* parseClient) { + parseRecursively(begin, end, true, parseClient); +} + +std::tuple /* result */, const uint8_t* /* newPos */, + std::string /* errMsg */> +parseWithViews(const uint8_t* begin, const uint8_t* end) { + FullParseClient parseClient; + parseWithViews(begin, end, &parseClient); + return parseClient.parseResult(); +} + +} // namespace cppbor diff --git a/ProvisioningTool/src/cppcose/cppcose.cpp b/ProvisioningTool/src/cppcose/cppcose.cpp new file mode 100644 index 00000000..e0c5aaab --- /dev/null +++ b/ProvisioningTool/src/cppcose/cppcose.cpp @@ -0,0 +1,210 @@ +/* + * 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 + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cppcose { + +ErrMsgOr ECDSA_sign(const bytevec& key, bytevec& input) { + EVP_PKEY_CTX* pkeyCtx = NULL; + EVP_MD_CTX_Ptr digestCtx(EVP_MD_CTX_new()); + auto bn = BIGNUM_Ptr(BN_bin2bn(key.data(), key.size(), nullptr)); + if (bn.get() == nullptr) { + return "Error creating BIGNUM for private key"; + } + auto privEcKey = EC_KEY_Ptr(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); + if (EC_KEY_set_private_key(privEcKey.get(), bn.get()) != 1) { + return "Error setting private key from BIGNUM"; + } + auto privPkey = EVP_PKEY_Ptr(EVP_PKEY_new()); + if (EVP_PKEY_set1_EC_KEY(privPkey.get(), privEcKey.get()) != 1) { + return "Error setting private key"; + } + + if (EVP_DigestSignInit(digestCtx.get(), &pkeyCtx, EVP_sha256(), nullptr /* engine */, privPkey.get()) != + 1) { + return "Failed to do digest sign init."; + } + size_t outlen = EVP_PKEY_size(privPkey.get()); + bytevec signature(outlen); + if (!EVP_DigestSign(digestCtx.get(), signature.data(), &outlen, input.data(), input.size())) { + return "Ecdsa sign failed."; + } + return signature; +} + +bool ECDSA_verify(const bytevec& input, const bytevec& signature, const bytevec& key) { + EVP_PKEY_CTX* pkeyCtx = NULL; + EVP_MD_CTX_Ptr digestCtx(EVP_MD_CTX_new()); + auto ecGroup = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); + auto ecKey = EC_KEY_Ptr(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); + if (ecGroup.get() == nullptr) { + return "Failed to get EC_GROUP from curve name"; + } + auto ecPoint = EC_POINT_Ptr(EC_POINT_new(ecGroup.get())); + if (ecPoint.get() == nullptr) { + return "Failed to get EC_POINT from EC_GROUP"; + } + if (EC_POINT_oct2point(ecGroup.get(), ecPoint.get(), key.data(), key.size(), nullptr) != + 1) { + return 0; + } + // set public key + if (EC_KEY_set_public_key(ecKey.get(), ecPoint.get()) != 1) { + return 0; + } + auto pkey = EVP_PKEY_Ptr(EVP_PKEY_new()); + if (EVP_PKEY_set1_EC_KEY(pkey.get(), ecKey.get()) != 1) { + return 0; + } + if (EVP_DigestVerifyInit(digestCtx.get(), &pkeyCtx, EVP_sha256(), nullptr /* engine */, pkey.get()) != + 1) { + return 0; + } + return EVP_DigestVerify(digestCtx.get(), signature.data(), signature.size(), input.data(), input.size()); +} + +ErrMsgOr getEcPointFromAffineCoordinates(const bytevec& pubx, const bytevec& puby) { + auto ecGroup = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); + if (ecGroup.get() == nullptr) { + return "Failed to get EC_GROUP from curve name"; + } + auto ecPoint = EC_POINT_Ptr(EC_POINT_new(ecGroup.get())); + if (ecPoint.get() == nullptr) { + return "Failed to get EC_POINT from EC_GROUP"; + } + auto bn_x = BIGNUM_Ptr(BN_bin2bn(pubx.data(), pubx.size(), nullptr)); + if (bn_x.get() == nullptr) { + return "Error creating BIGNUM for peer public key X coordinate"; + } + auto bn_y = BIGNUM_Ptr(BN_bin2bn(puby.data(), puby.size(), nullptr)); + if (bn_y.get() == nullptr) { + return "Error creating BIGNUM for peer public key Y coordinate"; + } + if (!EC_POINT_set_affine_coordinates(ecGroup.get(), ecPoint.get(), bn_x.get(), bn_y.get(), + nullptr)) { + return "Failed to set affine coordinates"; + } + size_t pubKeyLen; + pubKeyLen = EC_POINT_point2oct(ecGroup.get(), ecPoint.get(), POINT_CONVERSION_UNCOMPRESSED, + nullptr, 0, nullptr); + if (pubKeyLen == 0) { + return "Failed to convert EC_POINT to buffer."; + } + bytevec pubkey(pubKeyLen); + EC_POINT_point2oct(ecGroup.get(), ecPoint.get(), POINT_CONVERSION_UNCOMPRESSED, pubkey.data(), + pubKeyLen, nullptr); + return pubkey; +} + +ErrMsgOr createCoseSign1Signature(const bytevec& key, const bytevec& protectedParams, + const bytevec& payload, const bytevec& aad) { + bytevec signatureInput = cppbor::Array() + .add("Signature1") // + .add(protectedParams) + .add(aad) + .add(payload) + .encode(); + auto signature = ECDSA_sign(key, signatureInput); + if (!signature) return "Signing failed"; + return signature; +} + +ErrMsgOr constructCoseSign1(const bytevec& key, cppbor::Map protectedParams, + const bytevec& payload, const bytevec& aad) { + bytevec protParms = protectedParams.add(ALGORITHM, ES256).canonicalize().encode(); + auto signature = createCoseSign1Signature(key, protParms, payload, aad); + if (!signature) return signature.moveMessage(); + + return cppbor::Array() + .add(std::move(protParms)) + .add(cppbor::Map() /* unprotected parameters */) + .add(std::move(payload)) + .add(std::move(*signature)); +} + +ErrMsgOr constructCoseSign1(const bytevec& key, const bytevec& payload, + const bytevec& aad) { + return constructCoseSign1(key, {} /* protectedParams */, payload, aad); +} + +ErrMsgOr verifyAndParseCoseSign1(bool ignoreSignature, const cppbor::Array* coseSign1, + const bytevec& signingCoseKey, const bytevec& aad) { + if (!coseSign1 || coseSign1->size() != kCoseSign1EntryCount) { + return "Invalid COSE_Sign1"; + } + + const cppbor::Bstr* protectedParams = coseSign1->get(kCoseSign1ProtectedParams)->asBstr(); + const cppbor::Map* unprotectedParams = coseSign1->get(kCoseSign1UnprotectedParams)->asMap(); + const cppbor::Bstr* payload = coseSign1->get(kCoseSign1Payload)->asBstr(); + + if (!protectedParams || !unprotectedParams || !payload) { + return "Missing input parameters"; + } + + auto [parsedProtParams, _, errMsg] = cppbor::parse(protectedParams); + if (!parsedProtParams) { + return errMsg + " when parsing protected params."; + } + if (!parsedProtParams->asMap()) { + return "Protected params must be a map"; + } + + auto& algorithm = parsedProtParams->asMap()->get(ALGORITHM); + if (!algorithm || !algorithm->asInt() || algorithm->asInt()->value() != EDDSA) { + return "Unsupported signature algorithm"; + } + + if (!ignoreSignature) { + const cppbor::Bstr* signature = coseSign1->get(kCoseSign1Signature)->asBstr(); + if (!signature || signature->value().empty()) { + return "Missing signature input"; + } + + bool selfSigned = signingCoseKey.empty(); + + bytevec signatureInput = + cppbor::Array().add("Signature1").add(*protectedParams).add(aad).add(*payload).encode(); + + auto key = + CoseKey::parseP256(selfSigned ? payload->value() : signingCoseKey); + if (!key) return "Bad signing key: " + key.moveMessage(); + + + auto pubkey = getEcPointFromAffineCoordinates( + *key->getBstrValue(CoseKey::PUBKEY_X), *key->getBstrValue(CoseKey::PUBKEY_Y)); + if (!pubkey) return pubkey.moveMessage(); + + if (!ECDSA_verify(signatureInput, signature->value(), *pubkey)) { + return "Signature verification failed"; + } + } + + return payload->value(); +} +} // namespace cppcose diff --git a/ProvisioningTool/src/provision.cpp b/ProvisioningTool/src/provision.cpp new file mode 100644 index 00000000..e70e5ce8 --- /dev/null +++ b/ProvisioningTool/src/provision.cpp @@ -0,0 +1,329 @@ +/* + ** + ** 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. + */ +#include +#include +#include +#include +#include "socket.h" +#include +#include +#include +#include +#include +#include + +#define SE_POWER_RESET_STATUS_FLAG (1 << 30) +// TODO keymint provision status +enum ProvisionStatus { + NOT_PROVISIONED = 0x00, + PROVISION_STATUS_ATTESTATION_KEY = 0x01, + PROVISION_STATUS_ATTESTATION_CERT_CHAIN = 0x02, + PROVISION_STATUS_ATTESTATION_CERT_PARAMS = 0x04, + PROVISION_STATUS_ATTEST_IDS = 0x08, + PROVISION_STATUS_PRESHARED_SECRET = 0x10, + PROVISION_STATUS_BOOT_PARAM = 0x20, + PROVISION_STATUS_PROVISIONING_LOCKED = 0x40, +}; + +// TODO keymint provision status and lock +std::string provisionStatusApdu = hex2str("80084000000000"); +std::string lockProvisionApdu = hex2str("80074000000000"); + +Json::Value root; +static std::string inputFileName; +using cppbor::Item; +using cppbor::Array; +using cppbor::Uint; +using cppbor::MajorType; + +// static function declarations +static uint16_t getApduStatus(std::vector& inputData); +static int sendData(std::shared_ptr& pSocket, std::string input, std::vector& response); +static int provisionData(std::shared_ptr& pSocket, std::string apdu, std::vector& response); +static int provisionData(std::shared_ptr& pSocket, const char* jsonKey); +static int getUint64(const std::unique_ptr &item, const uint32_t pos, uint64_t *value); + + +// Print usage. +void usage() { + printf("Usage: Please consturcture the apdu(s) with help of construct apdu tool and pass the output file to this utility.\n"); + printf("provision_keymint [options]\n"); + printf("Valid options are:\n"); + printf("-h, --help show this help message and exit.\n"); + printf("-i, --input jsonFile \t Input json file \n"); + printf("-s, --provision_status jsonFile \t Gets the provision status of applet. \n"); + printf("-l, --lock_provision jsonFile \t Gets the provision status of applet. \n"); + +} + +static uint16_t getApduStatus(std::vector& inputData) { + // Last two bytes are the status SW0SW1 + uint8_t SW0 = inputData.at(inputData.size() - 2); + uint8_t SW1 = inputData.at(inputData.size() - 1); + return (SW0 << 8 | SW1); +} + +static int sendData(std::shared_ptr& pSocket, std::string input, std::vector& response) { + + std::vector apdu(input.begin(), input.end()); + + if(!pSocket->sendData(apdu, response)) { + std::cout << "Failed to provision attestation key" << std::endl; + return FAILURE; + } + + // 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)) { + printf("\n Received error response with error: %d\n", getApduStatus(response)); + return FAILURE; + } + // remove the status bytes + response.pop_back(); + response.pop_back(); + return SUCCESS; +} + +int getUint64(const std::unique_ptr &item, const uint32_t pos, uint64_t* value) { + Array *arr = nullptr; + + if (MajorType::ARRAY != item.get()->type()) { + return FAILURE; + } + arr = const_cast(item.get()->asArray()); + if (arr->size() < (pos + 1)) { + return FAILURE; + } + *value = arr->get(pos)->asUint()->value(); + return SUCCESS; +} + + +uint64_t unmaskPowerResetFlag(uint64_t errorCode) { + bool isSeResetOccurred = (0 != (errorCode & SE_POWER_RESET_STATUS_FLAG)); + + if (isSeResetOccurred) { + printf("\n Secure element reset happened\n"); + errorCode &= ~SE_POWER_RESET_STATUS_FLAG; + } + return errorCode; +} + +int provisionData(std::shared_ptr& pSocket, std::string apdu, std::vector& response) { + if (SUCCESS != sendData(pSocket, apdu, response)) { + return FAILURE; + } + auto [item, pos, message] = cppbor::parse(response); + if(item != nullptr) { + uint64_t err; + if(MajorType::ARRAY == item.get()->type()) { + if(SUCCESS != getUint64(item, 0, &err)) { + printf("\n Failed to parse the error code \n"); + return FAILURE; + } + } else if (MajorType::UINT == item.get()->type()) { + const Uint* uintVal = item.get()->asUint(); + err = uintVal->value(); + } + err = unmaskPowerResetFlag(err); + if (err != 0) { + printf("\n Failed with error:%ld", err); + return FAILURE; + } + } else { + printf("\n Failed to parse the response\n"); + return FAILURE; + } + return SUCCESS; +} + +int provisionData(std::shared_ptr& pSocket, const char* jsonKey) { + Json::Value val = root.get(jsonKey, Json::Value::nullRef); + if (!val.isNull()) { + if (val.isString()) { + std::vector response; + if (SUCCESS != provisionData(pSocket, hex2str(val.asString()), response)) { + printf("\n Error while provisioning %s \n", jsonKey); + return FAILURE; + } + } else { + printf("\n Fail: Expected (%s) tag value is string. \n", jsonKey); + return FAILURE; + } + } + printf("\n Successfully provisioned %s \n", jsonKey); + return SUCCESS; +} + +int openConnection(std::shared_ptr& pSocket) { + if (!pSocket->isConnected()) { + if (!pSocket->openConnection()) + return FAILURE; + } else { + printf("\n Socket already opened.\n"); + } + return SUCCESS; +} + +// Parses the input json file. Sends the apdus to JCServer. +int processInputFile() { + // Parse Json file + if (0 != readJsonFile(root, inputFileName)) { + return FAILURE; + } + std::shared_ptr pSocket = SocketTransport::getInstance(); + if (SUCCESS != openConnection(pSocket)) { + printf("\n Failed to open connection \n"); + return FAILURE; + } + if (0 != provisionData(pSocket, kDeviceUniqueKey) || + 0 != provisionData(pSocket, kAdditionalCertChain) || + 0 != provisionData(pSocket, kAttestationIds) || + 0 != provisionData(pSocket, kSharedSecret) || + 0 != provisionData(pSocket, kBootParams)) { + return FAILURE; + } + return SUCCESS; +} + +int lockProvision() { + std::vector response; + std::shared_ptr pSocket = SocketTransport::getInstance(); + if (SUCCESS != openConnection(pSocket)) { + printf("\n Failed to open connection \n"); + return FAILURE; + } + if (SUCCESS != provisionData(pSocket, lockProvisionApdu, response)) { + printf("\n Failed to lock provision.\n"); + return FAILURE; + } + printf("\n Provision lock is successfull.\n"); + return SUCCESS; +} + +int getProvisionStatus() { + std::vector response; + std::shared_ptr pSocket = SocketTransport::getInstance(); + if (SUCCESS != openConnection(pSocket)) { + printf("\n Failed to open connection \n"); + return FAILURE; + } + + if (SUCCESS != provisionData(pSocket, provisionStatusApdu, response)) { + printf("\n Failed to get provision status \n"); + return FAILURE; + } + auto [item, pos, message] = cppbor::parse(response); + if(item != nullptr) { + uint64_t status; + if(SUCCESS != getUint64(item, 1, &status)) { + printf("\n Failed to get the provision status.\n"); + return FAILURE; + } + // TODO Handle Keymint Provision status once added. + if ( (0 != (status & ProvisionStatus::PROVISION_STATUS_ATTESTATION_KEY)) && + (0 != (status & ProvisionStatus::PROVISION_STATUS_ATTESTATION_CERT_CHAIN)) && + (0 != (status & ProvisionStatus::PROVISION_STATUS_ATTESTATION_CERT_PARAMS)) && + (0 != (status & ProvisionStatus::PROVISION_STATUS_PRESHARED_SECRET)) && + (0 != (status & ProvisionStatus::PROVISION_STATUS_BOOT_PARAM))) { + printf("\n SE is provisioned \n"); + } else { + if (0 == (status & ProvisionStatus::PROVISION_STATUS_ATTESTATION_KEY)) { + printf("\n Attestation key is not provisioned \n"); + } + if (0 == (status & ProvisionStatus::PROVISION_STATUS_ATTESTATION_CERT_CHAIN)) { + printf("\n Attestation certificate chain is not provisioned \n"); + } + if (0 == (status & ProvisionStatus::PROVISION_STATUS_ATTESTATION_CERT_PARAMS)) { + printf("\n Attestation certificate params are not provisioned \n"); + } + if (0 == (status & ProvisionStatus::PROVISION_STATUS_PRESHARED_SECRET)) { + printf("\n Shared secret is not provisioned \n"); + } + if (0 == (status & ProvisionStatus::PROVISION_STATUS_BOOT_PARAM)) { + printf("\n Boot params are not provisioned \n"); + } + } + } else { + printf("\n Fail to parse the response \n"); + return FAILURE; + } + return SUCCESS; +} + +int main(int argc, char* argv[]) { + int c; + bool provisionStatusSet = false; + bool lockProvisionSet = false; + + struct option longOpts[] = { + {"input", required_argument, NULL, 'i'}, + {"provision_status", no_argument, NULL, 's'}, + {"lock_provision", no_argument, NULL, 'l'}, + {"help", no_argument, NULL, 'h'}, + {0,0,0,0} + }; + + if (argc <= 1) { + printf("\n Invalid command \n"); + usage(); + return FAILURE; + } + + /* getopt_long stores the option index here. */ + while ((c = getopt_long(argc, argv, ":hls:i:", longOpts, NULL)) != -1) { + switch(c) { + case 'i': + // input file + inputFileName = std::string(optarg); + std::cout << "input file: " << inputFileName << std::endl; + break; + case 's': + provisionStatusSet = true; + break; + case 'l': + lockProvisionSet = true; + break; + case 'h': + // help + usage(); + return SUCCESS; + case ':': + printf("\n Required arguments missing.\n"); + usage(); + return FAILURE; + case '?': + default: + printf("\n Invalid option\n"); + usage(); + return FAILURE; + } + } + // Process input file; send apuds to JCServer over socket. + if (argc >= 3) { + if (SUCCESS != processInputFile()) { + return FAILURE; + } + } + if (provisionStatusSet) + getProvisionStatus(); + if (lockProvisionSet) + lockProvision(); + return SUCCESS; +} + + diff --git a/ProvisioningTool/src/socket.cpp b/ProvisioningTool/src/socket.cpp new file mode 100644 index 00000000..137cab3b --- /dev/null +++ b/ProvisioningTool/src/socket.cpp @@ -0,0 +1,109 @@ +/* + ** + ** 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. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "socket.h" + +#define PORT 8080 +#define IPADDR "127.0.0.1" +#define MAX_RECV_BUFFER_SIZE 2500 + +using namespace std; + +SocketTransport::~SocketTransport() { + if (closeConnection()) + std::cout << "Socket is closed"; +} + +bool SocketTransport::openConnection() { + struct sockaddr_in serv_addr; + if ((mSocket = socket(AF_INET, SOCK_STREAM, 0)) < 0) { + perror("Socket "); + return false; + } + + 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) { + std::cout << "Invalid address/ Address not supported."; + return false; + } + + if (connect(mSocket, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) { + close(mSocket); + perror("Socket "); + return false; + } + socketStatus = true; + return true; +} + +bool SocketTransport::sendData(const std::vector& inData, std::vector& output) { + uint8_t buffer[MAX_RECV_BUFFER_SIZE]; + int count = 1; + while (!socketStatus && count++ < 5) { + sleep(1); + std::cout << "Trying to open socket connection... count: " << count; + openConnection(); + } + + if (count >= 5) { + std::cout << "Failed to open socket connection"; + return false; + } + + if (0 > send(mSocket, inData.data(), inData.size(), 0)) { + static int connectionResetCnt = 0; /* To avoid loop */ + if (ECONNRESET == errno && connectionResetCnt == 0) { + // Connection reset. Try open socket and then sendData. + socketStatus = false; + connectionResetCnt++; + return sendData(inData, output); + } + std::cout << "Failed to send data over socket err: " << errno; + connectionResetCnt = 0; + return false; + } + + ssize_t valRead = read(mSocket, buffer, MAX_RECV_BUFFER_SIZE); + if (0 > valRead) { + std::cout << "Failed to read data from socket."; + } + for (ssize_t i = 0; i < valRead; i++) { + output.push_back(buffer[i]); + } + return true; +} + +bool SocketTransport::closeConnection() { + close(mSocket); + socketStatus = false; + return true; +} + +bool SocketTransport::isConnected() { + return socketStatus; +} + diff --git a/ProvisioningTool/src/utils.cpp b/ProvisioningTool/src/utils.cpp new file mode 100644 index 00000000..41ad8a6c --- /dev/null +++ b/ProvisioningTool/src/utils.cpp @@ -0,0 +1,96 @@ +/* + ** + ** 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. + */ +#include +#include +#include +#include + + +constexpr char hex_value[256] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, // '0'..'9' + 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 'A'..'F' + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 'a'..'f' + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +std::string getHexString(std::vector& input) { + std::stringstream ss; + for (auto b : input) { + ss << std::setw(2) << std::setfill('0') << std::hex << (int) (b & 0xFF); + } + return ss.str(); +} + + +std::string hex2str(std::string a) { + std::string b; + size_t num = a.size() / 2; + b.resize(num); + for (size_t i = 0; i < num; i++) { + b[i] = (hex_value[a[i * 2] & 0xFF] << 4) + (hex_value[a[i * 2 + 1] & 0xFF]); + } + return b; +} + + +// Parses the json file and returns 0 if success; otherwise 1. +int readJsonFile(Json::Value& root, std::string& inputFileName) { + Json::CharReaderBuilder builder; + std::string errorMessage; + + if(!root.empty()) { + printf("\n Already parsed \n"); + return 1; + } + std::ifstream stream(inputFileName); + if (Json::parseFromStream(builder, stream, &root, &errorMessage)) { + printf("\n Parsed json file successfully.\n"); + return 0; + } else { + printf("\n Failed to parse json file error:%s\n", errorMessage.c_str()); + return 1; + } +} + +// Write the json data to the output file. +int writeJsonFile(Json::Value& writerRoot, std::string& outputFileName) { + + std::ofstream ofs; + // Delete file if already exists. + std::remove(outputFileName.data()); + ofs.open(outputFileName, std::ofstream::out | std::ios_base::app); + if (ofs.fail()) { + printf("\n Fail to open the output file:%s", outputFileName.c_str()); + return FAILURE; + } + + Json::StyledWriter styledWriter; + ofs << styledWriter.write(writerRoot); + + ofs.close(); + return SUCCESS; +} \ No newline at end of file diff --git a/ProvisioningTool/test_resources/batch_cert.der b/ProvisioningTool/test_resources/batch_cert.der new file mode 100644 index 00000000..355bc984 Binary files /dev/null and b/ProvisioningTool/test_resources/batch_cert.der differ diff --git a/ProvisioningTool/test_resources/batch_key.der b/ProvisioningTool/test_resources/batch_key.der new file mode 100644 index 00000000..f4902073 Binary files /dev/null and b/ProvisioningTool/test_resources/batch_key.der differ diff --git a/ProvisioningTool/test_resources/ca_cert.der b/ProvisioningTool/test_resources/ca_cert.der new file mode 100644 index 00000000..f574a4c5 Binary files /dev/null and b/ProvisioningTool/test_resources/ca_cert.der differ diff --git a/ProvisioningTool/test_resources/ca_key.der b/ProvisioningTool/test_resources/ca_key.der new file mode 100644 index 00000000..e687b07f Binary files /dev/null and b/ProvisioningTool/test_resources/ca_key.der differ diff --git a/ProvisioningTool/test_resources/intermediate_cert.der b/ProvisioningTool/test_resources/intermediate_cert.der new file mode 100644 index 00000000..615f423e Binary files /dev/null and b/ProvisioningTool/test_resources/intermediate_cert.der differ diff --git a/ProvisioningTool/test_resources/intermediate_key.der b/ProvisioningTool/test_resources/intermediate_key.der new file mode 100644 index 00000000..888e4f38 Binary files /dev/null and b/ProvisioningTool/test_resources/intermediate_key.der differ diff --git a/TestingTools/JCProxy/.project b/TestingTools/JCProxy/.project new file mode 100644 index 00000000..dbfe8daa --- /dev/null +++ b/TestingTools/JCProxy/.project @@ -0,0 +1,17 @@ + + + JCProxy + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/TestingTools/JCProxy/lib/apduio-RELEASE71.jar b/TestingTools/JCProxy/lib/apduio-RELEASE71.jar new file mode 100644 index 00000000..6560f6e8 Binary files /dev/null and b/TestingTools/JCProxy/lib/apduio-RELEASE71.jar differ diff --git a/TestingTools/JCProxy/lib/jcardsim-3.0.5-SNAPSHOT.jar b/TestingTools/JCProxy/lib/jcardsim-3.0.5-SNAPSHOT.jar new file mode 100644 index 00000000..d756d67b Binary files /dev/null and b/TestingTools/JCProxy/lib/jcardsim-3.0.5-SNAPSHOT.jar differ diff --git a/TestingTools/JCProxy/src/com/android/javacard/jcproxy/JCProxyMain.java b/TestingTools/JCProxy/src/com/android/javacard/jcproxy/JCProxyMain.java new file mode 100644 index 00000000..90832b3f --- /dev/null +++ b/TestingTools/JCProxy/src/com/android/javacard/jcproxy/JCProxyMain.java @@ -0,0 +1,107 @@ +package com.android.javacard.jcproxy; + +import java.io.*; +import java.net.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; + +import com.sun.javacard.apduio.CadTransportException; + +/** + * This program demonstrates a simple TCP/IP socket server. + * + * @author www.codejava.net + */ +public class JCProxyMain { + + public static void main(String[] args) { + if (args.length < 1) { + System.out.println("Port no is expected as argument."); + return; + } + + int port = Integer.parseInt(args[0]); + Simulator simulator = new JCardSimulator(); + + try (ServerSocket serverSocket = new ServerSocket(port)) { + simulator.initaliseSimulator(); + if (!simulator.setupKeymasterOnSimulator()) { + System.out.println("Failed to setup Java card keymaster simulator."); + System.exit(-1); + } + byte[] outData; + + while (true) { + try { + Socket socket = serverSocket.accept(); + System.out.println("\n"); + System.out.println("------------------------New client connected on " + + socket.getPort() + "--------------------"); + OutputStream output = null; + InputStream isReader = null; + try { + socket.setReceiveBufferSize(1024 * 5); + output = socket.getOutputStream(); + isReader = socket.getInputStream(); + + byte[] inBytes = new byte[65536]; + int readLen = 0, index = 0; + System.out.println("Socket input buffer size: " + + socket.getReceiveBufferSize()); + while ((readLen = isReader.read(inBytes, index, 1024 * 5)) > 0) { + if (readLen > 0) { + System.out.println("Bytes read from index (" + index + + ") socket: " + readLen + " Estimate read: " + + isReader.available()); + byte[] outBytes; + + try { + outBytes = simulator.executeApdu( + Arrays.copyOfRange(inBytes, 0, index + readLen)); + outData = simulator.decodeDataOut(); + System.out.println( + "Return Data " + Utils.byteArrayToHexString(outData)); + byte[] finalOutData = new byte[outData.length + + outBytes.length]; + System.arraycopy(outData, 0, finalOutData, 0, outData.length); + System.arraycopy(outBytes, 0, finalOutData, outData.length, + outBytes.length); + output.write(finalOutData); + output.flush(); + index = 0; + } catch (IllegalArgumentException e) { + e.printStackTrace(); + index = readLen; + } + } + } + } catch (IOException e) { + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (output != null) + output.close(); + if (isReader != null) + isReader.close(); + socket.close(); + } + } catch (IOException e) { + break; + } catch (Exception e) { + break; + } + System.out.println("Client disconnected."); + } + simulator.disconnectSimulator(); + } catch (IOException ex) { + System.out.println("Server exception: " + ex.getMessage()); + ex.printStackTrace(); + } catch (CadTransportException e1) { + e1.printStackTrace(); + } catch (Exception e1) { + e1.printStackTrace(); + } + } +} diff --git a/TestingTools/JCProxy/src/com/android/javacard/jcproxy/JCardSimulator.java b/TestingTools/JCProxy/src/com/android/javacard/jcproxy/JCardSimulator.java new file mode 100644 index 00000000..7af495f3 --- /dev/null +++ b/TestingTools/JCProxy/src/com/android/javacard/jcproxy/JCardSimulator.java @@ -0,0 +1,61 @@ +package com.android.javacard.jcproxy; + +import javax.smartcardio.CommandAPDU; +import javax.smartcardio.ResponseAPDU; + +import com.android.javacard.keymaster.KMJCardSimApplet; +import com.licel.jcardsim.smartcardio.CardSimulator; +import com.licel.jcardsim.utils.AIDUtil; + +import javacard.framework.AID; + +public class JCardSimulator implements Simulator { + + private CardSimulator simulator; + ResponseAPDU response; + + public JCardSimulator() { + simulator = new CardSimulator(); + } + + @Override + public void initaliseSimulator() throws Exception { + } + + @Override + public void disconnectSimulator() throws Exception { + AID appletAID1 = AIDUtil.create("A000000062"); + // Delete i.e. uninstall applet + simulator.deleteApplet(appletAID1); + } + + @Override + public boolean setupKeymasterOnSimulator() throws Exception { + AID appletAID1 = AIDUtil.create("A000000062"); + simulator.installApplet(appletAID1, KMJCardSimApplet.class); + // Select applet + simulator.selectApplet(appletAID1); + return true; + } + + private final byte[] intToByteArray(int value) { + return new byte[] { + (byte) (value >>> 8), (byte) value }; + } + + @Override + public byte[] executeApdu(byte[] apdu) throws Exception { + System.out.println("Executing APDU = " + Utils.byteArrayToHexString(apdu)); + CommandAPDU apduCmd = new CommandAPDU(apdu); + response = simulator.transmitCommand(apduCmd); + System.out.println("Status = " + + Utils.byteArrayToHexString(intToByteArray(response.getSW()))); + return intToByteArray(response.getSW()); + } + + @Override + public byte[] decodeDataOut() { + return response.getData(); + } + +} diff --git a/TestingTools/JCProxy/src/com/android/javacard/jcproxy/Simulator.java b/TestingTools/JCProxy/src/com/android/javacard/jcproxy/Simulator.java new file mode 100644 index 00000000..6c4f9bbc --- /dev/null +++ b/TestingTools/JCProxy/src/com/android/javacard/jcproxy/Simulator.java @@ -0,0 +1,15 @@ +package com.android.javacard.jcproxy; + +public interface Simulator { + byte[] STATUS_OK = Utils.hexStringToByteArray("9000"); + + void initaliseSimulator() throws Exception; + + void disconnectSimulator() throws Exception; + + public boolean setupKeymasterOnSimulator() throws Exception; + + byte[] executeApdu(byte[] apdu) throws Exception; + + byte[] decodeDataOut(); +} diff --git a/TestingTools/JCProxy/src/com/android/javacard/jcproxy/Utils.java b/TestingTools/JCProxy/src/com/android/javacard/jcproxy/Utils.java new file mode 100644 index 00000000..50470f6b --- /dev/null +++ b/TestingTools/JCProxy/src/com/android/javacard/jcproxy/Utils.java @@ -0,0 +1,28 @@ +package com.android.javacard.jcproxy; + +public class Utils { + + public static byte[] hexStringToByteArray(String s) { + int len = s.length(); + if (len % 2 != 0) + throw new IllegalArgumentException("Expecting each byte of 2 char."); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + + Character.digit(s.charAt(i + 1), 16)); + } + return data; + } + + private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); + + public static String byteArrayToHexString(byte[] bytes) { + char[] hexChars = new char[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = HEX_ARRAY[v >>> 4]; + hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; + } + return new String(hexChars); + } +} diff --git a/TestingTools/README.md b/TestingTools/README.md new file mode 100644 index 00000000..adc3da27 --- /dev/null +++ b/TestingTools/README.md @@ -0,0 +1,11 @@ +# TestingTools +[JCProxy](JCProxy) is a testing tool, which provides a way to communicate with +JCardSimulator from android emulator/device. +It basically opens a socket connection on the port (port mentioned in program arguments) +and listens for the incomming data on this port. This tool uses apduio and JCarsim jars +to validate and transmit the APDUs to the Keymaster Applet. + +###Build +Import JCProxy server application either in Eclipse or IntelliJ. Add the provided jars inside +[lib](JCProxy/lib) directory to the project and also add [Keymaster Applet](../Applet) as +dependent project. Add port number (Ex: 8080) as program arguments. diff --git a/patches/git_cuttlefish.patch b/aosp_integration_patches/device_google_cuttlefish.patch similarity index 89% rename from patches/git_cuttlefish.patch rename to aosp_integration_patches/device_google_cuttlefish.patch index 40500a2c..9fe013e1 100644 --- a/patches/git_cuttlefish.patch +++ b/aosp_integration_patches/device_google_cuttlefish.patch @@ -1,8 +1,8 @@ diff --git a/shared/device.mk b/shared/device.mk -index 1ae572408..240e8de5f 100644 +index 6cebe8ac9..a92183296 100644 --- a/shared/device.mk +++ b/shared/device.mk -@@ -514,6 +514,11 @@ endif +@@ -530,6 +530,11 @@ endif PRODUCT_PACKAGES += \ $(LOCAL_KEYMINT_PRODUCT_PACKAGE) @@ -14,7 +14,7 @@ index 1ae572408..240e8de5f 100644 # Keymint configuration PRODUCT_COPY_FILES += \ frameworks/native/data/etc/android.software.device_id_attestation.xml:$(TARGET_COPY_OUT_VENDOR)/etc/permissions/android.software.device_id_attestation.xml -@@ -623,6 +628,7 @@ PRODUCT_PACKAGES += setup_wifi +@@ -631,6 +636,7 @@ PRODUCT_PACKAGES += setup_wifi PRODUCT_VENDOR_PROPERTIES += ro.vendor.wifi_impl=virt_wifi endif @@ -23,10 +23,10 @@ index 1ae572408..240e8de5f 100644 PRODUCT_HOST_PACKAGES += socket_vsock_proxy diff --git a/shared/sepolicy/vendor/file_contexts b/shared/sepolicy/vendor/file_contexts -index 76983557c..17f61a3b6 100644 +index 72362dc1f..62e3ef768 100644 --- a/shared/sepolicy/vendor/file_contexts +++ b/shared/sepolicy/vendor/file_contexts -@@ -95,6 +95,7 @@ +@@ -89,6 +89,7 @@ /vendor/bin/hw/android\.hardware\.input\.classifier@1\.0-service.default u:object_r:hal_input_classifier_default_exec:s0 /vendor/bin/hw/android\.hardware\.thermal@2\.0-service\.mock u:object_r:hal_thermal_default_exec:s0 /vendor/bin/hw/android\.hardware\.security\.keymint-service\.remote u:object_r:hal_keymint_remote_exec:s0 @@ -55,15 +55,16 @@ index 000000000..839fd1a6b +allow hal_keymint_strongbox port:tcp_socket { name_connect }; +allow hal_keymint_strongbox vendor_data_file:file { open read getattr }; diff --git a/shared/sepolicy/vendor/service_contexts b/shared/sepolicy/vendor/service_contexts -index d20d026cf..dfdacecfb 100644 +index d20d026cf..8531d9e55 100644 --- a/shared/sepolicy/vendor/service_contexts +++ b/shared/sepolicy/vendor/service_contexts -@@ -4,6 +4,7 @@ android.hardware.neuralnetworks.IDevice/nnapi-sample_float_slow u:object_r:hal_n +@@ -4,6 +4,8 @@ android.hardware.neuralnetworks.IDevice/nnapi-sample_float_slow u:object_r:hal_n android.hardware.neuralnetworks.IDevice/nnapi-sample_minimal u:object_r:hal_neuralnetworks_service:s0 android.hardware.neuralnetworks.IDevice/nnapi-sample_quant u:object_r:hal_neuralnetworks_service:s0 android.hardware.neuralnetworks.IDevice/nnapi-sample_sl_shim u:object_r:hal_neuralnetworks_service:s0 - +android.hardware.security.keymint.IKeyMintDevice/strongbox u:object_r:hal_keymint_service:s0 +android.hardware.security.sharedsecret.ISharedSecret/strongbox u:object_r:hal_sharedsecret_service:s0 ++android.hardware.security.keymint.IRemotelyProvisionedComponent/strongbox u:object_r:hal_keymint_service:s0 # Binder service mappings gce u:object_r:gce_service:s0 diff --git a/aosp_integration_patches/hardware_interfaces.patch b/aosp_integration_patches/hardware_interfaces.patch new file mode 100644 index 00000000..e76fa74c --- /dev/null +++ b/aosp_integration_patches/hardware_interfaces.patch @@ -0,0 +1,1176 @@ +diff --git a/compatibility_matrices/compatibility_matrix.current.xml b/compatibility_matrices/compatibility_matrix.current.xml +index 0b779eee4..33bd0d97d 100644 +--- a/compatibility_matrices/compatibility_matrix.current.xml ++++ b/compatibility_matrices/compatibility_matrix.current.xml +@@ -330,6 +330,13 @@ + default + + ++ ++ android.hardware.security.keymint ++ ++ IRemotelyProvisionedComponent ++ strongbox ++ ++ + + android.hardware.light + 1 +@@ -491,6 +498,14 @@ + default + + ++ ++ android.hardware.security.sharedsecret ++ 1 ++ ++ ISharedSecret ++ strongbox ++ ++ + + android.hardware.sensors + 1.0 +diff --git a/security/keymint/aidl/vts/functional/AttestKeyTest.cpp b/security/keymint/aidl/vts/functional/AttestKeyTest.cpp +index 26ed34427..2d5bc9575 100644 +--- a/security/keymint/aidl/vts/functional/AttestKeyTest.cpp ++++ b/security/keymint/aidl/vts/functional/AttestKeyTest.cpp +@@ -198,7 +198,7 @@ TEST_P(AttestKeyTest, RsaAttestedAttestKeys) { + AttestationKey attest_key; + vector attest_key_characteristics; + vector attest_key_cert_chain; +- ASSERT_EQ(ErrorCode::OK, ++ auto result = + GenerateKey(AuthorizationSetBuilder() + .RsaSigningKey(2048, 65537) + .AttestKey() +@@ -209,7 +209,13 @@ TEST_P(AttestKeyTest, RsaAttestedAttestKeys) { + .Authorization(TAG_NO_AUTH_REQUIRED) + .SetDefaultValidity(), + {} /* attestation signing key */, &attest_key.keyBlob, +- &attest_key_characteristics, &attest_key_cert_chain)); ++ &attest_key_characteristics, &attest_key_cert_chain); ++ //Strongbox does not support Factory provisioned attestation key. ++ if (SecLevel() == SecurityLevel::STRONGBOX) { ++ ASSERT_EQ(ErrorCode::ATTESTATION_KEYS_NOT_PROVISIONED, result); ++ return; ++ } ++ ASSERT_EQ(ErrorCode::OK, result); + + EXPECT_GT(attest_key_cert_chain.size(), 1); + verify_subject_and_serial(attest_key_cert_chain[0], serial_int, subject, false); +@@ -297,7 +303,7 @@ TEST_P(AttestKeyTest, RsaAttestKeyChaining) { + attest_key_opt = attest_key; + } + +- EXPECT_EQ(ErrorCode::OK, ++ auto result = + GenerateKey(AuthorizationSetBuilder() + .RsaSigningKey(2048, 65537) + .AttestKey() +@@ -308,8 +314,13 @@ TEST_P(AttestKeyTest, RsaAttestKeyChaining) { + .Authorization(TAG_CERTIFICATE_SUBJECT, subject_der) + .SetDefaultValidity(), + attest_key_opt, &key_blob_list[i], &attested_key_characteristics, +- &cert_chain_list[i])); +- ++ &cert_chain_list[i]); ++ // Strongbox does not support Factory provisioned attestation key. ++ if (SecLevel() == SecurityLevel::STRONGBOX) { ++ ASSERT_EQ(ErrorCode::ATTESTATION_KEYS_NOT_PROVISIONED, result); ++ return; ++ } ++ ASSERT_EQ(ErrorCode::OK, result); + AuthorizationSet hw_enforced = HwEnforcedAuthorizations(attested_key_characteristics); + AuthorizationSet sw_enforced = SwEnforcedAuthorizations(attested_key_characteristics); + ASSERT_GT(cert_chain_list[i].size(), 0); +@@ -369,7 +380,7 @@ TEST_P(AttestKeyTest, EcAttestKeyChaining) { + attest_key_opt = attest_key; + } + +- EXPECT_EQ(ErrorCode::OK, ++ auto result = + GenerateKey(AuthorizationSetBuilder() + .EcdsaSigningKey(EcCurve::P_256) + .AttestKey() +@@ -380,8 +391,13 @@ TEST_P(AttestKeyTest, EcAttestKeyChaining) { + .Authorization(TAG_NO_AUTH_REQUIRED) + .SetDefaultValidity(), + attest_key_opt, &key_blob_list[i], &attested_key_characteristics, +- &cert_chain_list[i])); +- ++ &cert_chain_list[i]); ++ // Strongbox does not support Factory provisioned attestation key. ++ if (SecLevel() == SecurityLevel::STRONGBOX) { ++ ASSERT_EQ(ErrorCode::ATTESTATION_KEYS_NOT_PROVISIONED, result); ++ return; ++ } ++ ASSERT_EQ(ErrorCode::OK, result); + AuthorizationSet hw_enforced = HwEnforcedAuthorizations(attested_key_characteristics); + AuthorizationSet sw_enforced = SwEnforcedAuthorizations(attested_key_characteristics); + ASSERT_GT(cert_chain_list[i].size(), 0); +@@ -442,35 +458,40 @@ TEST_P(AttestKeyTest, AlternateAttestKeyChaining) { + attest_key.keyBlob = key_blob_list[i - 1]; + attest_key_opt = attest_key; + } +- ++ ErrorCode result; + if ((i & 0x1) == 1) { +- EXPECT_EQ(ErrorCode::OK, +- GenerateKey(AuthorizationSetBuilder() +- .EcdsaSigningKey(EcCurve::P_256) +- .AttestKey() +- .AttestationChallenge("foo") +- .AttestationApplicationId("bar") +- .Authorization(TAG_CERTIFICATE_SERIAL, serial_blob) +- .Authorization(TAG_CERTIFICATE_SUBJECT, subject_der) +- .Authorization(TAG_NO_AUTH_REQUIRED) +- .SetDefaultValidity(), +- attest_key_opt, &key_blob_list[i], &attested_key_characteristics, +- &cert_chain_list[i])); ++ result = ++ GenerateKey(AuthorizationSetBuilder() ++ .EcdsaSigningKey(EcCurve::P_256) ++ .AttestKey() ++ .AttestationChallenge("foo") ++ .AttestationApplicationId("bar") ++ .Authorization(TAG_CERTIFICATE_SERIAL, serial_blob) ++ .Authorization(TAG_CERTIFICATE_SUBJECT, subject_der) ++ .Authorization(TAG_NO_AUTH_REQUIRED) ++ .SetDefaultValidity(), ++ attest_key_opt, &key_blob_list[i], &attested_key_characteristics, ++ &cert_chain_list[i]); + } else { +- EXPECT_EQ(ErrorCode::OK, +- GenerateKey(AuthorizationSetBuilder() +- .RsaSigningKey(2048, 65537) +- .AttestKey() +- .AttestationChallenge("foo") +- .AttestationApplicationId("bar") +- .Authorization(TAG_CERTIFICATE_SERIAL, serial_blob) +- .Authorization(TAG_CERTIFICATE_SUBJECT, subject_der) +- .Authorization(TAG_NO_AUTH_REQUIRED) +- .SetDefaultValidity(), +- attest_key_opt, &key_blob_list[i], &attested_key_characteristics, +- &cert_chain_list[i])); ++ result = ++ GenerateKey(AuthorizationSetBuilder() ++ .RsaSigningKey(2048, 65537) ++ .AttestKey() ++ .AttestationChallenge("foo") ++ .AttestationApplicationId("bar") ++ .Authorization(TAG_CERTIFICATE_SERIAL, serial_blob) ++ .Authorization(TAG_CERTIFICATE_SUBJECT, subject_der) ++ .Authorization(TAG_NO_AUTH_REQUIRED) ++ .SetDefaultValidity(), ++ attest_key_opt, &key_blob_list[i], &attested_key_characteristics, ++ &cert_chain_list[i]); + } +- ++ // Strongbox does not support Factory provisioned attestation key. ++ if (SecLevel() == SecurityLevel::STRONGBOX) { ++ ASSERT_EQ(ErrorCode::ATTESTATION_KEYS_NOT_PROVISIONED, result); ++ return; ++ } ++ ASSERT_EQ(ErrorCode::OK, result); + AuthorizationSet hw_enforced = HwEnforcedAuthorizations(attested_key_characteristics); + AuthorizationSet sw_enforced = SwEnforcedAuthorizations(attested_key_characteristics); + ASSERT_GT(cert_chain_list[i].size(), 0); +diff --git a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp +index 20324117b..741bcf8f6 100644 +--- a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp ++++ b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.cpp +@@ -1145,6 +1145,15 @@ vector KeyMintAidlTestBase::InvalidCurves() { + } + } + ++vector KeyMintAidlTestBase::ValidExponents() { ++ if (SecLevel() == SecurityLevel::STRONGBOX) { ++ return {65537}; ++ } else { ++ return {3, 65537}; ++ } ++} ++ ++ + vector KeyMintAidlTestBase::ValidDigests(bool withNone, bool withMD5) { + switch (SecLevel()) { + case SecurityLevel::SOFTWARE: +diff --git a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h +index ec3fcf6a3..0561a9b94 100644 +--- a/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h ++++ b/security/keymint/aidl/vts/functional/KeyMintAidlTestBase.h +@@ -250,7 +250,9 @@ class KeyMintAidlTestBase : public ::testing::TestWithParam { + .SetDefaultValidity(); + tagModifier(&rsaBuilder); + errorCode = GenerateKey(rsaBuilder, &rsaKeyData.blob, &rsaKeyData.characteristics); +- EXPECT_EQ(expectedReturn, errorCode); ++ if (!(SecLevel() == SecurityLevel::STRONGBOX && ErrorCode::ATTESTATION_KEYS_NOT_PROVISIONED == errorCode)) { ++ EXPECT_EQ(expectedReturn, errorCode); ++ } + + /* ECDSA */ + KeyData ecdsaKeyData; +@@ -262,7 +264,10 @@ class KeyMintAidlTestBase : public ::testing::TestWithParam { + .SetDefaultValidity(); + tagModifier(&ecdsaBuilder); + errorCode = GenerateKey(ecdsaBuilder, &ecdsaKeyData.blob, &ecdsaKeyData.characteristics); +- EXPECT_EQ(expectedReturn, errorCode); ++ if (!(SecLevel() == SecurityLevel::STRONGBOX && ErrorCode::ATTESTATION_KEYS_NOT_PROVISIONED == errorCode)) { ++ EXPECT_EQ(expectedReturn, errorCode); ++ } ++ + return {aesKeyData, hmacKeyData, rsaKeyData, ecdsaKeyData}; + } + bool IsSecure() const { return securityLevel_ != SecurityLevel::SOFTWARE; } +@@ -279,6 +284,7 @@ class KeyMintAidlTestBase : public ::testing::TestWithParam { + vector InvalidCurves(); + + vector ValidDigests(bool withNone, bool withMD5); ++ vector ValidExponents(); + + static vector build_params() { + auto params = ::android::getAidlHalInstanceNames(IKeyMintDevice::descriptor); +diff --git a/security/keymint/aidl/vts/functional/KeyMintTest.cpp b/security/keymint/aidl/vts/functional/KeyMintTest.cpp +index 651b21f19..0e6e882d3 100644 +--- a/security/keymint/aidl/vts/functional/KeyMintTest.cpp ++++ b/security/keymint/aidl/vts/functional/KeyMintTest.cpp +@@ -908,8 +908,8 @@ TEST_P(NewKeyGenerationTest, RsaWithAttestation) { + for (auto key_size : ValidKeySizes(Algorithm::RSA)) { + vector key_blob; + vector key_characteristics; +- ASSERT_EQ(ErrorCode::OK, +- GenerateKey(AuthorizationSetBuilder() ++ ++ auto result = GenerateKey(AuthorizationSetBuilder() + .RsaSigningKey(key_size, 65537) + .Digest(Digest::NONE) + .Padding(PaddingMode::NONE) +@@ -919,8 +919,14 @@ TEST_P(NewKeyGenerationTest, RsaWithAttestation) { + .Authorization(TAG_CERTIFICATE_SERIAL, serial_blob) + .Authorization(TAG_CERTIFICATE_SUBJECT, subject_der) + .SetDefaultValidity(), +- &key_blob, &key_characteristics)); ++ &key_blob, &key_characteristics); + ++ // Strongbox does not support Factory provisioned attestation key ++ if (SecLevel() == SecurityLevel::STRONGBOX) { ++ ASSERT_EQ(ErrorCode::ATTESTATION_KEYS_NOT_PROVISIONED, result); ++ return; ++ } ++ ASSERT_EQ(ErrorCode::OK, result); + ASSERT_GT(key_blob.size(), 0U); + CheckBaseParams(key_characteristics); + CheckCharacteristics(key_blob, key_characteristics); +@@ -1037,8 +1043,7 @@ TEST_P(NewKeyGenerationTest, RsaEncryptionWithAttestation) { + + vector key_blob; + vector key_characteristics; +- ASSERT_EQ(ErrorCode::OK, +- GenerateKey(AuthorizationSetBuilder() ++ auto result = GenerateKey(AuthorizationSetBuilder() + .RsaEncryptionKey(key_size, 65537) + .Padding(PaddingMode::NONE) + .AttestationChallenge(challenge) +@@ -1047,8 +1052,14 @@ TEST_P(NewKeyGenerationTest, RsaEncryptionWithAttestation) { + .Authorization(TAG_CERTIFICATE_SERIAL, serial_blob) + .Authorization(TAG_CERTIFICATE_SUBJECT, subject_der) + .SetDefaultValidity(), +- &key_blob, &key_characteristics)); ++ &key_blob, &key_characteristics); + ++ // Strongbox does not support Factory provisioned attestation key ++ if (SecLevel() == SecurityLevel::STRONGBOX) { ++ ASSERT_EQ(ErrorCode::ATTESTATION_KEYS_NOT_PROVISIONED, result); ++ return; ++ } ++ ASSERT_EQ(ErrorCode::OK, result); + ASSERT_GT(key_blob.size(), 0U); + AuthorizationSet auths; + for (auto& entry : key_characteristics) { +@@ -1149,15 +1160,21 @@ TEST_P(NewKeyGenerationTest, RsaWithAttestationMissAppId) { + vector key_blob; + vector key_characteristics; + +- ASSERT_EQ(ErrorCode::ATTESTATION_APPLICATION_ID_MISSING, +- GenerateKey(AuthorizationSetBuilder() ++ auto result = GenerateKey(AuthorizationSetBuilder() + .RsaSigningKey(2048, 65537) + .Digest(Digest::NONE) + .Padding(PaddingMode::NONE) + .AttestationChallenge(challenge) + .Authorization(TAG_NO_AUTH_REQUIRED) + .SetDefaultValidity(), +- &key_blob, &key_characteristics)); ++ &key_blob, &key_characteristics); ++ ++ // Strongbox does not support Factory provisioned attestation key ++ if (SecLevel() == SecurityLevel::STRONGBOX) { ++ ASSERT_EQ(ErrorCode::ATTESTATION_KEYS_NOT_PROVISIONED, result); ++ return; ++ } ++ ASSERT_EQ(ErrorCode::ATTESTATION_APPLICATION_ID_MISSING, result); + } + + /* +@@ -1267,8 +1284,8 @@ TEST_P(NewKeyGenerationTest, LimitedUsageRsaWithAttestation) { + for (auto key_size : ValidKeySizes(Algorithm::RSA)) { + vector key_blob; + vector key_characteristics; +- ASSERT_EQ(ErrorCode::OK, +- GenerateKey(AuthorizationSetBuilder() ++ ++ auto result = GenerateKey(AuthorizationSetBuilder() + .RsaSigningKey(key_size, 65537) + .Digest(Digest::NONE) + .Padding(PaddingMode::NONE) +@@ -1279,7 +1296,14 @@ TEST_P(NewKeyGenerationTest, LimitedUsageRsaWithAttestation) { + .Authorization(TAG_CERTIFICATE_SERIAL, serial_blob) + .Authorization(TAG_CERTIFICATE_SUBJECT, subject_der) + .SetDefaultValidity(), +- &key_blob, &key_characteristics)); ++ &key_blob, &key_characteristics); ++ ++ //Strongbox does not support Factory provisioned attestation key ++ if (SecLevel() == SecurityLevel::STRONGBOX) { ++ ASSERT_EQ(ErrorCode::ATTESTATION_KEYS_NOT_PROVISIONED, result); ++ return; ++ } ++ ASSERT_EQ(ErrorCode::OK, result); + + ASSERT_GT(key_blob.size(), 0U); + CheckBaseParams(key_characteristics); +@@ -1410,8 +1434,8 @@ TEST_P(NewKeyGenerationTest, EcdsaAttestation) { + for (auto curve : ValidCurves()) { + vector key_blob; + vector key_characteristics; +- ASSERT_EQ(ErrorCode::OK, +- GenerateKey(AuthorizationSetBuilder() ++ ++ auto result = GenerateKey(AuthorizationSetBuilder() + .Authorization(TAG_NO_AUTH_REQUIRED) + .EcdsaSigningKey(curve) + .Digest(Digest::NONE) +@@ -1420,7 +1444,15 @@ TEST_P(NewKeyGenerationTest, EcdsaAttestation) { + .Authorization(TAG_CERTIFICATE_SERIAL, serial_blob) + .Authorization(TAG_CERTIFICATE_SUBJECT, subject_der) + .SetDefaultValidity(), +- &key_blob, &key_characteristics)); ++ &key_blob, &key_characteristics); ++ ++ //Strongbox does not support Factory provisioned attestation key ++ if (SecLevel() == SecurityLevel::STRONGBOX) { ++ ASSERT_EQ(ErrorCode::ATTESTATION_KEYS_NOT_PROVISIONED, result); ++ return; ++ } ++ ASSERT_EQ(ErrorCode::OK, result); ++ + ASSERT_GT(key_blob.size(), 0U); + CheckBaseParams(key_characteristics); + CheckCharacteristics(key_blob, key_characteristics); +@@ -1497,6 +1529,12 @@ TEST_P(NewKeyGenerationTest, EcdsaAttestationTags) { + // Tag not required to be supported by all KeyMint implementations. + continue; + } ++ ++ //Strongbox does not support Factory provisioned attestation key ++ if (SecLevel() == SecurityLevel::STRONGBOX) { ++ ASSERT_EQ(ErrorCode::ATTESTATION_KEYS_NOT_PROVISIONED, result); ++ continue; ++ } + ASSERT_EQ(result, ErrorCode::OK); + ASSERT_GT(key_blob.size(), 0U); + +@@ -1546,8 +1584,14 @@ TEST_P(NewKeyGenerationTest, EcdsaAttestationTags) { + .Authorization(TAG_CERTIFICATE_SUBJECT, subject_der) + .SetDefaultValidity(); + builder.push_back(tag); +- ASSERT_EQ(ErrorCode::CANNOT_ATTEST_IDS, +- GenerateKey(builder, &key_blob, &key_characteristics)); ++ ++ auto result = GenerateKey(builder, &key_blob, &key_characteristics); ++ //Strongbox does not support Factory provisioned attestation key ++ if (SecLevel() == SecurityLevel::STRONGBOX) { ++ ASSERT_EQ(ErrorCode::ATTESTATION_KEYS_NOT_PROVISIONED, result); ++ continue; ++ } ++ ASSERT_EQ(ErrorCode::CANNOT_ATTEST_IDS, result); + } + } + +@@ -1583,6 +1627,13 @@ TEST_P(NewKeyGenerationTest, EcdsaAttestationTagNoApplicationId) { + .Authorization(TAG_CERTIFICATE_SUBJECT, subject_der) + .SetDefaultValidity(), + &key_blob, &key_characteristics); ++ ++ // Strongbox does not support Factory provisioned attestation key ++ if (SecLevel() == SecurityLevel::STRONGBOX) { ++ ASSERT_EQ(ErrorCode::ATTESTATION_KEYS_NOT_PROVISIONED, result); ++ return; ++ } ++ + ASSERT_EQ(result, ErrorCode::OK); + ASSERT_GT(key_blob.size(), 0U); + +@@ -1661,13 +1712,19 @@ TEST_P(NewKeyGenerationTest, EcdsaAttestationRequireAppId) { + vector key_blob; + vector key_characteristics; + +- ASSERT_EQ(ErrorCode::ATTESTATION_APPLICATION_ID_MISSING, +- GenerateKey(AuthorizationSetBuilder() ++ auto result = GenerateKey(AuthorizationSetBuilder() + .EcdsaSigningKey(EcCurve::P_256) + .Digest(Digest::NONE) + .AttestationChallenge(challenge) + .SetDefaultValidity(), +- &key_blob, &key_characteristics)); ++ &key_blob, &key_characteristics); ++ ++ // Strongbox does not support Factory provisioned attestation key ++ if (SecLevel() == SecurityLevel::STRONGBOX) { ++ ASSERT_EQ(ErrorCode::ATTESTATION_KEYS_NOT_PROVISIONED, result); ++ return; ++ } ++ ASSERT_EQ(ErrorCode::ATTESTATION_APPLICATION_ID_MISSING, result); + } + + /* +@@ -1724,14 +1781,21 @@ TEST_P(NewKeyGenerationTest, AttestationApplicationIDLengthProperlyEncoded) { + const string app_id(length, 'a'); + vector key_blob; + vector key_characteristics; +- ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder() ++ auto result = GenerateKey(AuthorizationSetBuilder() + .Authorization(TAG_NO_AUTH_REQUIRED) + .EcdsaSigningKey(EcCurve::P_256) + .Digest(Digest::NONE) + .AttestationChallenge(challenge) + .AttestationApplicationId(app_id) + .SetDefaultValidity(), +- &key_blob, &key_characteristics)); ++ &key_blob, &key_characteristics); ++ //Strongbox does not support Factory provisioned attestation key ++ if (SecLevel() == SecurityLevel::STRONGBOX) { ++ ASSERT_EQ(ErrorCode::ATTESTATION_KEYS_NOT_PROVISIONED, result); ++ return; ++ } ++ ASSERT_EQ(ErrorCode::OK, result); ++ + ASSERT_GT(key_blob.size(), 0U); + CheckBaseParams(key_characteristics); + CheckCharacteristics(key_blob, key_characteristics); +@@ -3762,25 +3826,27 @@ typedef KeyMintAidlTestBase EncryptionOperationsTest; + * Verifies that raw RSA decryption works. + */ + TEST_P(EncryptionOperationsTest, RsaNoPaddingSuccess) { +- for (uint64_t exponent : {3, 65537}) { +- ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder() +- .Authorization(TAG_NO_AUTH_REQUIRED) +- .RsaEncryptionKey(2048, exponent) +- .Padding(PaddingMode::NONE) +- .SetDefaultValidity())); + +- string message = string(2048 / 8, 'a'); +- auto params = AuthorizationSetBuilder().Padding(PaddingMode::NONE); +- string ciphertext1 = LocalRsaEncryptMessage(message, params); +- EXPECT_EQ(2048U / 8, ciphertext1.size()); ++ for (uint64_t exponent : ValidExponents()) ++ { ++ ASSERT_EQ(ErrorCode::OK, GenerateKey(AuthorizationSetBuilder() ++ .Authorization(TAG_NO_AUTH_REQUIRED) ++ .RsaEncryptionKey(2048, exponent) ++ .Padding(PaddingMode::NONE) ++ .SetDefaultValidity())); + +- string ciphertext2 = LocalRsaEncryptMessage(message, params); +- EXPECT_EQ(2048U / 8, ciphertext2.size()); ++ string message = string(2048 / 8, 'a'); ++ auto params = AuthorizationSetBuilder().Padding(PaddingMode::NONE); ++ string ciphertext1 = LocalRsaEncryptMessage(message, params); ++ EXPECT_EQ(2048U / 8, ciphertext1.size()); + +- // Unpadded RSA is deterministic +- EXPECT_EQ(ciphertext1, ciphertext2); ++ string ciphertext2 = LocalRsaEncryptMessage(message, params); ++ EXPECT_EQ(2048U / 8, ciphertext2.size()); + +- CheckedDeleteKey(); ++ // Unpadded RSA is deterministic ++ EXPECT_EQ(ciphertext1, ciphertext2); ++ ++ CheckedDeleteKey(); + } + } + +@@ -6297,7 +6363,7 @@ TEST_P(ClearOperationsTest, TooManyOperations) { + size_t i; + + for (i = 0; i < max_operations; i++) { +- result = Begin(KeyPurpose::ENCRYPT, key_blob_, params, &out_params, op_handles[i]); ++ result = Begin(KeyPurpose::DECRYPT, key_blob_, params, &out_params, op_handles[i]); + if (ErrorCode::OK != result) { + break; + } +@@ -6305,12 +6371,12 @@ TEST_P(ClearOperationsTest, TooManyOperations) { + EXPECT_EQ(ErrorCode::TOO_MANY_OPERATIONS, result); + // Try again just in case there's a weird overflow bug + EXPECT_EQ(ErrorCode::TOO_MANY_OPERATIONS, +- Begin(KeyPurpose::ENCRYPT, key_blob_, params, &out_params)); ++ Begin(KeyPurpose::DECRYPT, key_blob_, params, &out_params)); + for (size_t j = 0; j < i; j++) { + EXPECT_EQ(ErrorCode::OK, Abort(op_handles[j])) + << "Aboort failed for i = " << j << std::endl; + } +- EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::ENCRYPT, key_blob_, params, &out_params)); ++ EXPECT_EQ(ErrorCode::OK, Begin(KeyPurpose::DECRYPT, key_blob_, params, &out_params)); + AbortIfNeeded(); + } + +@@ -6409,7 +6475,6 @@ TEST_P(KeyAgreementTest, Ecdh) { + OPENSSL_free(p); + + // Generate EC key in KeyMint (only access to public key material) +- vector challenge = {0x41, 0x42}; + EXPECT_EQ( + ErrorCode::OK, + GenerateKey(AuthorizationSetBuilder() +@@ -6418,7 +6483,6 @@ TEST_P(KeyAgreementTest, Ecdh) { + .Authorization(TAG_PURPOSE, KeyPurpose::AGREE_KEY) + .Authorization(TAG_ALGORITHM, Algorithm::EC) + .Authorization(TAG_ATTESTATION_APPLICATION_ID, {0x61, 0x62}) +- .Authorization(TAG_ATTESTATION_CHALLENGE, challenge) + .SetDefaultValidity())) + << "Failed to generate key"; + ASSERT_GT(cert_chain_.size(), 0); +@@ -6498,14 +6562,24 @@ TEST_P(EarlyBootKeyTest, CreateEarlyBootKeys) { + CreateTestKeys(TAG_EARLY_BOOT_ONLY, ErrorCode::OK); + + for (const auto& keyData : {aesKeyData, hmacKeyData, rsaKeyData, ecdsaKeyData}) { ++ ++ if (SecLevel() == SecurityLevel::STRONGBOX && keyData.blob.size() == 0U) { ++ continue; ++ } + ASSERT_GT(keyData.blob.size(), 0U); + AuthorizationSet crypto_params = SecLevelAuthorizations(keyData.characteristics); + EXPECT_TRUE(crypto_params.Contains(TAG_EARLY_BOOT_ONLY)) << crypto_params; + } + CheckedDeleteKey(&aesKeyData.blob); + CheckedDeleteKey(&hmacKeyData.blob); +- CheckedDeleteKey(&rsaKeyData.blob); +- CheckedDeleteKey(&ecdsaKeyData.blob); ++ ++ if (rsaKeyData.blob.size() != 0U) { ++ CheckedDeleteKey(&rsaKeyData.blob); ++ } ++ if (ecdsaKeyData.blob.size() != 0U) { ++ CheckedDeleteKey(&ecdsaKeyData.blob); ++ } ++ + } + + /* +@@ -6521,14 +6595,21 @@ TEST_P(EarlyBootKeyTest, CreateAttestedEarlyBootKey) { + }); + + for (const auto& keyData : {aesKeyData, hmacKeyData, rsaKeyData, ecdsaKeyData}) { ++ if (SecLevel() == SecurityLevel::STRONGBOX && keyData.blob.size() == 0U) { ++ continue; ++ } + ASSERT_GT(keyData.blob.size(), 0U); + AuthorizationSet crypto_params = SecLevelAuthorizations(keyData.characteristics); + EXPECT_TRUE(crypto_params.Contains(TAG_EARLY_BOOT_ONLY)) << crypto_params; + } + CheckedDeleteKey(&aesKeyData.blob); + CheckedDeleteKey(&hmacKeyData.blob); +- CheckedDeleteKey(&rsaKeyData.blob); +- CheckedDeleteKey(&ecdsaKeyData.blob); ++ if (rsaKeyData.blob.size() != 0U) { ++ CheckedDeleteKey(&rsaKeyData.blob); ++ } ++ if (ecdsaKeyData.blob.size() != 0U) { ++ CheckedDeleteKey(&ecdsaKeyData.blob); ++ } + } + + /* +diff --git a/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp b/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp +index 38f358686..74e44c7b4 100644 +--- a/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp ++++ b/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp +@@ -164,6 +164,7 @@ class VtsRemotelyProvisionedComponentTests : public testing::TestWithParamgetHardwareInfo(&rpcHardwareInfo).isOk()); + } + + static vector build_params() { +@@ -173,6 +174,7 @@ class VtsRemotelyProvisionedComponentTests : public testing::TestWithParam provisionable_; ++ RpcHardwareInfo rpcHardwareInfo; + }; + + using GenerateKeyTests = VtsRemotelyProvisionedComponentTests; +@@ -273,11 +275,10 @@ TEST_P(GenerateKeyTests, generateEcdsaP256Key_testMode) { + class CertificateRequestTest : public VtsRemotelyProvisionedComponentTests { + protected: + CertificateRequestTest() : eekId_(string_to_bytevec("eekid")), challenge_(randomBytes(32)) { +- generateTestEekChain(3); + } + + void generateTestEekChain(size_t eekLength) { +- auto chain = generateEekChain(eekLength, eekId_); ++ auto chain = generateEekChain(rpcHardwareInfo.supportedEekCurve, eekLength, eekId_); + EXPECT_TRUE(chain) << chain.message(); + if (chain) testEekChain_ = chain.moveValue(); + testEekLength_ = eekLength; +@@ -298,6 +299,17 @@ class CertificateRequestTest : public VtsRemotelyProvisionedComponentTests { + } + } + ++ ErrMsgOr getSessionKey(ErrMsgOr>& senderPubkey) { ++ if (rpcHardwareInfo.supportedEekCurve == RpcHardwareInfo::CURVE_25519 || ++ rpcHardwareInfo.supportedEekCurve == RpcHardwareInfo::CURVE_NONE) { ++ return x25519_HKDF_DeriveKey(testEekChain_.last_pubkey, testEekChain_.last_privkey, ++ senderPubkey->first, false /* senderIsA */); ++ } else { ++ return ECDH_HKDF_DeriveKey(testEekChain_.last_pubkey, testEekChain_.last_privkey, ++ senderPubkey->first, false /* senderIsA */); ++ } ++ } ++ + void checkProtectedData(const DeviceInfo& deviceInfo, const cppbor::Array& keysToSign, + const bytevec& keysToSignMac, const ProtectedData& protectedData, + std::vector* bccOutput = nullptr) { +@@ -310,9 +322,7 @@ class CertificateRequestTest : public VtsRemotelyProvisionedComponentTests { + ASSERT_TRUE(senderPubkey) << senderPubkey.message(); + EXPECT_EQ(senderPubkey->second, eekId_); + +- auto sessionKey = +- x25519_HKDF_DeriveKey(testEekChain_.last_pubkey, testEekChain_.last_privkey, +- senderPubkey->first, false /* senderIsA */); ++ auto sessionKey = getSessionKey(senderPubkey); + ASSERT_TRUE(sessionKey) << sessionKey.message(); + + auto protectedDataPayload = +@@ -322,7 +332,7 @@ class CertificateRequestTest : public VtsRemotelyProvisionedComponentTests { + auto [parsedPayload, __, payloadErrMsg] = cppbor::parse(*protectedDataPayload); + ASSERT_TRUE(parsedPayload) << "Failed to parse payload: " << payloadErrMsg; + ASSERT_TRUE(parsedPayload->asArray()); +- EXPECT_EQ(parsedPayload->asArray()->size(), 2U); ++ EXPECT_LE(parsedPayload->asArray()->size(), 3U); + + auto& signedMac = parsedPayload->asArray()->get(0); + auto& bcc = parsedPayload->asArray()->get(1); +@@ -406,6 +416,7 @@ TEST_P(CertificateRequestTest, NewKeyPerCallInTestMode) { + bytevec keysToSignMac; + DeviceInfo deviceInfo; + ProtectedData protectedData; ++ generateTestEekChain(3); + auto status = provisionable_->generateCertificateRequest( + testMode, {} /* keysToSign */, testEekChain_.chain, challenge_, &deviceInfo, + &protectedData, &keysToSignMac); +@@ -445,7 +456,7 @@ TEST_P(CertificateRequestTest, DISABLED_EmptyRequest_prodMode) { + DeviceInfo deviceInfo; + ProtectedData protectedData; + auto status = provisionable_->generateCertificateRequest( +- testMode, {} /* keysToSign */, getProdEekChain(), challenge_, &deviceInfo, ++ testMode, {} /* keysToSign */, getProdEekChain(rpcHardwareInfo.supportedEekCurve), challenge_, &deviceInfo, + &protectedData, &keysToSignMac); + EXPECT_TRUE(status.isOk()); + } +@@ -486,7 +497,7 @@ TEST_P(CertificateRequestTest, DISABLED_NonEmptyRequest_prodMode) { + DeviceInfo deviceInfo; + ProtectedData protectedData; + auto status = provisionable_->generateCertificateRequest( +- testMode, keysToSign_, getProdEekChain(), challenge_, &deviceInfo, &protectedData, ++ testMode, keysToSign_, getProdEekChain(rpcHardwareInfo.supportedEekCurve), challenge_, &deviceInfo, &protectedData, + &keysToSignMac); + EXPECT_TRUE(status.isOk()); + } +@@ -502,6 +513,7 @@ TEST_P(CertificateRequestTest, NonEmptyRequestCorruptMac_testMode) { + bytevec keysToSignMac; + DeviceInfo deviceInfo; + ProtectedData protectedData; ++ generateTestEekChain(3); + auto status = provisionable_->generateCertificateRequest( + testMode, {keyWithCorruptMac}, testEekChain_.chain, challenge_, &deviceInfo, + &protectedData, &keysToSignMac); +@@ -521,7 +533,7 @@ TEST_P(CertificateRequestTest, NonEmptyRequestCorruptMac_prodMode) { + DeviceInfo deviceInfo; + ProtectedData protectedData; + auto status = provisionable_->generateCertificateRequest( +- testMode, {keyWithCorruptMac}, getProdEekChain(), challenge_, &deviceInfo, ++ testMode, {keyWithCorruptMac}, getProdEekChain(rpcHardwareInfo.supportedEekCurve), challenge_, &deviceInfo, + &protectedData, &keysToSignMac); + ASSERT_FALSE(status.isOk()) << status.getMessage(); + EXPECT_EQ(status.getServiceSpecificError(), BnRemotelyProvisionedComponent::STATUS_INVALID_MAC); +@@ -535,7 +547,7 @@ TEST_P(CertificateRequestTest, NonEmptyCorruptEekRequest_prodMode) { + bool testMode = false; + generateKeys(testMode, 4 /* numKeys */); + +- auto prodEekChain = getProdEekChain(); ++ auto prodEekChain = getProdEekChain(rpcHardwareInfo.supportedEekCurve); + auto [parsedChain, _, parseErr] = cppbor::parse(prodEekChain); + ASSERT_NE(parsedChain, nullptr) << parseErr; + ASSERT_NE(parsedChain->asArray(), nullptr); +@@ -566,7 +578,7 @@ TEST_P(CertificateRequestTest, NonEmptyIncompleteEekRequest_prodMode) { + + // Build an EEK chain that omits the first self-signed cert. + auto truncatedChain = cppbor::Array(); +- auto [chain, _, parseErr] = cppbor::parse(getProdEekChain()); ++ auto [chain, _, parseErr] = cppbor::parse(getProdEekChain(rpcHardwareInfo.supportedEekCurve)); + ASSERT_TRUE(chain); + auto eekChain = chain->asArray(); + ASSERT_NE(eekChain, nullptr); +@@ -594,6 +606,7 @@ TEST_P(CertificateRequestTest, NonEmptyRequest_prodKeyInTestCert) { + bytevec keysToSignMac; + DeviceInfo deviceInfo; + ProtectedData protectedData; ++ generateTestEekChain(3); + auto status = provisionable_->generateCertificateRequest( + true /* testMode */, keysToSign_, testEekChain_.chain, challenge_, &deviceInfo, + &protectedData, &keysToSignMac); +@@ -612,6 +625,7 @@ TEST_P(CertificateRequestTest, NonEmptyRequest_testKeyInProdCert) { + bytevec keysToSignMac; + DeviceInfo deviceInfo; + ProtectedData protectedData; ++ generateTestEekChain(3); + auto status = provisionable_->generateCertificateRequest( + false /* testMode */, keysToSign_, testEekChain_.chain, challenge_, &deviceInfo, + &protectedData, &keysToSignMac); +diff --git a/security/keymint/support/Android.bp b/security/keymint/support/Android.bp +index bdb4cdfea..e1ca68809 100644 +--- a/security/keymint/support/Android.bp ++++ b/security/keymint/support/Android.bp +@@ -62,6 +62,7 @@ cc_library { + "libcppcose_rkp", + "libcrypto", + "libjsoncpp", ++ "android.hardware.security.keymint-V1-ndk", + ], + } + +diff --git a/security/keymint/support/include/remote_prov/remote_prov_utils.h b/security/keymint/support/include/remote_prov/remote_prov_utils.h +index 406b7a9b7..4d9ed2b0c 100644 +--- a/security/keymint/support/include/remote_prov/remote_prov_utils.h ++++ b/security/keymint/support/include/remote_prov/remote_prov_utils.h +@@ -52,6 +52,20 @@ inline constexpr uint8_t kCoseEncodedGeekCert[] = { + 0x31, 0xbf, 0x6b, 0xe8, 0x1e, 0x35, 0xe2, 0xf0, 0x2d, 0xce, 0x6c, 0x2f, 0x4f, 0xf2, + 0xf5, 0x4f, 0xa5, 0xd4, 0x83, 0xad, 0x96, 0xa2, 0xf1, 0x87, 0x58, 0x04}; + ++// The Google ECDSA root key for the Endpoint Encryption Key chain, encoded as COSE_Sign1 ++inline constexpr uint8_t kCoseEncodedEcdsaRootCert[] = { ++ 0x84, 0x43, 0xa1, 0x01, 0x26, 0xa0, 0x58, 0x4d, 0xa5, 0x01, 0x02, 0x03, 0x26, 0x20, 0x01, ++ 0x21, 0x58, 0x20, 0xf7, 0x14, 0x8a, 0xdb, 0x97, 0xf4, 0xcc, 0x53, 0xef, 0xd2, 0x64, 0x11, ++ 0xc4, 0xe3, 0x75, 0x1f, 0x66, 0x1f, 0xa4, 0x71, 0x0c, 0x6c, 0xcf, 0xfa, 0x09, 0x46, 0x80, ++ 0x74, 0x87, 0x54, 0xf2, 0xad, 0x22, 0x58, 0x20, 0x5e, 0x7f, 0x5b, 0xf6, 0xec, 0xe4, 0xf6, ++ 0x19, 0xcc, 0xff, 0x13, 0x37, 0xfd, 0x0f, 0xa1, 0xc8, 0x93, 0xdb, 0x18, 0x06, 0x76, 0xc4, ++ 0x5d, 0xe6, 0xd7, 0x6a, 0x77, 0x86, 0xc3, 0x2d, 0xaf, 0x8f, 0x58, 0x47, 0x30, 0x45, 0x02, ++ 0x20, 0x2f, 0x97, 0x8e, 0x42, 0xfb, 0xbe, 0x07, 0x2d, 0x95, 0x47, 0x85, 0x47, 0x93, 0x40, ++ 0xb0, 0x1f, 0xd4, 0x9b, 0x47, 0xa4, 0xc4, 0x44, 0xa9, 0xf2, 0xa1, 0x07, 0x87, 0x10, 0xc7, ++ 0x9f, 0xcb, 0x11, 0x02, 0x21, 0x00, 0xf4, 0xbf, 0x9f, 0xe8, 0x3b, 0xe0, 0xe7, 0x34, 0x4c, ++ 0x15, 0xfc, 0x7b, 0xc3, 0x7e, 0x33, 0x05, 0xf4, 0xd1, 0x34, 0x3c, 0xed, 0x02, 0x04, 0x60, ++ 0x7a, 0x15, 0xe0, 0x79, 0xd3, 0x8a, 0xff, 0x24}; ++ + /** + * Generates random bytes. + */ +@@ -67,12 +81,12 @@ struct EekChain { + * Generates an X25518 EEK with the specified eekId and an Ed25519 chain of the + * specified length. All keys are generated randomly. + */ +-ErrMsgOr generateEekChain(size_t length, const bytevec& eekId); ++ErrMsgOr generateEekChain(int32_t supportedEekCurve, size_t length, const bytevec& eekId); + + /** + * Returns the CBOR-encoded, production Google Endpoint Encryption Key chain. + */ +-bytevec getProdEekChain(); ++bytevec getProdEekChain(int32_t supportedEekCurve); + + struct BccEntryData { + bytevec pubKey; +diff --git a/security/keymint/support/remote_prov_utils.cpp b/security/keymint/support/remote_prov_utils.cpp +index 0cbee5104..ae5120f8b 100644 +--- a/security/keymint/support/remote_prov_utils.cpp ++++ b/security/keymint/support/remote_prov_utils.cpp +@@ -17,15 +17,195 @@ + #include + #include + ++#include + #include + #include + #include ++#include ++#include ++#include ++#include + #include ++#include + #include + #include + + namespace aidl::android::hardware::security::keymint::remote_prov { + ++constexpr int kP256AffinePointSize = 32; ++ ++using EC_KEY_Ptr = bssl::UniquePtr; ++using EVP_PKEY_Ptr = bssl::UniquePtr; ++using EVP_PKEY_CTX_Ptr = bssl::UniquePtr; ++ ++ErrMsgOr ecKeyGetPrivateKey(const EC_KEY* ecKey) { ++ // Extract private key. ++ const BIGNUM* bignum = EC_KEY_get0_private_key(ecKey); ++ if (bignum == nullptr) { ++ return "Error getting bignum from private key"; ++ } ++ int size = BN_num_bytes(bignum); ++ // Pad with zeros incase the length is lesser than 32. ++ bytevec privKey(32, 0); ++ BN_bn2bin(bignum, privKey.data() + 32 - size); ++ return privKey; ++} ++ ++ErrMsgOr ecKeyGetPublicKey(const EC_KEY* ecKey) { ++ // Extract public key. ++ auto group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); ++ if (group.get() == nullptr) { ++ return "Error creating EC group by curve name"; ++ } ++ const EC_POINT* point = EC_KEY_get0_public_key(ecKey); ++ if (point == nullptr) return "Error getting ecpoint from public key"; ++ ++ int size = EC_POINT_point2oct(group.get(), point, ++ POINT_CONVERSION_UNCOMPRESSED, nullptr, 0, ++ nullptr); ++ if (size == 0) { ++ return "Error generating public key encoding"; ++ } ++ ++ bytevec publicKey; ++ publicKey.resize(size); ++ EC_POINT_point2oct(group.get(), point, ++ POINT_CONVERSION_UNCOMPRESSED, publicKey.data(), ++ publicKey.size(), nullptr); ++ return publicKey; ++} ++ ++ErrMsgOr> getAffineCoordinates( ++ const bytevec& pubKey) { ++ auto group = EC_GROUP_Ptr( ++ EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); ++ if (group.get() == nullptr) { ++ return "Error creating EC group by curve name"; ++ } ++ auto point = EC_POINT_Ptr(EC_POINT_new(group.get())); ++ if (EC_POINT_oct2point(group.get(), point.get(), pubKey.data(), ++ pubKey.size(), nullptr) != 1) { ++ return "Error decoding publicKey"; ++ } ++ BIGNUM_Ptr x(BN_new()); ++ BIGNUM_Ptr y(BN_new()); ++ BN_CTX_Ptr ctx(BN_CTX_new()); ++ if (!ctx.get()) return "Failed to create BN_CTX instance"; ++ ++ if (!EC_POINT_get_affine_coordinates_GFp(group.get(), point.get(), ++ x.get(), y.get(), ++ ctx.get())) { ++ return "Failed to get affine coordinates from ECPoint"; ++ } ++ bytevec pubX(kP256AffinePointSize); ++ bytevec pubY(kP256AffinePointSize); ++ if (BN_bn2binpad(x.get(), pubX.data(), kP256AffinePointSize) != ++ kP256AffinePointSize) { ++ return "Error in converting absolute value of x cordinate to big-endian"; ++ } ++ if (BN_bn2binpad(y.get(), pubY.data(), kP256AffinePointSize) != ++ kP256AffinePointSize) { ++ return "Error in converting absolute value of y cordinate to big-endian"; ++ } ++ return std::make_tuple(std::move(pubX), std::move(pubY)); ++} ++ ++ErrMsgOr> generateEc256KeyPair() { ++ auto ec_key = EC_KEY_Ptr(EC_KEY_new()); ++ if (ec_key.get() == nullptr) { ++ return "Failed to allocate ec key"; ++ } ++ ++ auto group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); ++ if (group.get() == nullptr) { ++ return "Error creating EC group by curve name"; ++ } ++ ++ if (EC_KEY_set_group(ec_key.get(), group.get()) != 1 || ++ EC_KEY_generate_key(ec_key.get()) != 1 || EC_KEY_check_key(ec_key.get()) < 0) { ++ return "Error generating key"; ++ } ++ ++ auto privKey = ecKeyGetPrivateKey(ec_key.get()); ++ if (!privKey) return privKey.moveMessage(); ++ ++ auto pubKey = ecKeyGetPublicKey(ec_key.get()); ++ if (!pubKey) return pubKey.moveMessage(); ++ ++ return std::make_tuple(pubKey.moveValue(), privKey.moveValue()); ++} ++ ++ErrMsgOr> generateX25519KeyPair() { ++ /* Generate X25519 key pair */ ++ bytevec pubKey(X25519_PUBLIC_VALUE_LEN); ++ bytevec privKey(X25519_PRIVATE_KEY_LEN); ++ X25519_keypair(pubKey.data(), privKey.data()); ++ return std::make_tuple(std::move(pubKey), std::move(privKey)); ++} ++ ++ErrMsgOr> generateED25519KeyPair() { ++ /* Generate ED25519 key pair */ ++ bytevec pubKey(ED25519_PUBLIC_KEY_LEN); ++ bytevec privKey(ED25519_PRIVATE_KEY_LEN); ++ ED25519_keypair(pubKey.data(), privKey.data()); ++ return std::make_tuple(std::move(pubKey), std::move(privKey)); ++} ++ ++ErrMsgOr> generateKeyPair( ++ int32_t supportedEekCurve, bool isEek) { ++ ++ switch (supportedEekCurve) { ++ case RpcHardwareInfo::CURVE_NONE: ++ case RpcHardwareInfo::CURVE_25519: ++ if (isEek) { ++ return generateX25519KeyPair(); ++ } ++ return generateED25519KeyPair(); ++ case RpcHardwareInfo::CURVE_P256: ++ return generateEc256KeyPair(); ++ default: ++ return "Unknown EEK Curve."; ++ } ++} ++ ++ErrMsgOr constructCoseKey(int32_t supportedEekCurve, const bytevec& eekId, ++ const bytevec& pubKey) { ++ CoseKeyType keyType; ++ CoseKeyAlgorithm algorithm; ++ CoseKeyCurve curve; ++ bytevec pubX; ++ bytevec pubY; ++ switch (supportedEekCurve) { ++ case RpcHardwareInfo::CURVE_NONE: ++ case RpcHardwareInfo::CURVE_25519: ++ keyType = OCTET_KEY_PAIR; ++ algorithm = (eekId.empty()) ? EDDSA : ECDH_ES_HKDF_256; ++ curve = (eekId.empty()) ? ED25519 : cppcose::X25519; ++ pubX = pubKey; ++ break; ++ case RpcHardwareInfo::CURVE_P256: { ++ keyType = EC2; ++ algorithm = (eekId.empty()) ? ES256 : ECDH_ES_HKDF_256; ++ curve = P256; ++ auto affineCoordinates = getAffineCoordinates(pubKey); ++ if (!affineCoordinates) return affineCoordinates.moveMessage(); ++ std::tie(pubX, pubY) = affineCoordinates.moveValue(); ++ } break; ++ default: ++ return "Unknown EEK Curve."; ++ } ++ cppbor::Map coseKey = cppbor::Map() ++ .add(CoseKey::KEY_TYPE, keyType) ++ .add(CoseKey::ALGORITHM, algorithm) ++ .add(CoseKey::CURVE, curve) ++ .add(CoseKey::PUBKEY_X, pubX); ++ ++ if (!pubY.empty()) coseKey.add(CoseKey::PUBKEY_Y, pubY); ++ if (!eekId.empty()) coseKey.add(CoseKey::KEY_ID, eekId); ++ ++ return coseKey.canonicalize().encode(); ++} ++ + bytevec kTestMacKey(32 /* count */, 0 /* byte value */); + + bytevec randomBytes(size_t numBytes) { +@@ -34,7 +214,17 @@ bytevec randomBytes(size_t numBytes) { + return retval; + } + +-ErrMsgOr generateEekChain(size_t length, const bytevec& eekId) { ++ErrMsgOr constructCoseSign1(int32_t supportedEekCurve, const bytevec& key, ++ const bytevec& payload, const bytevec& aad) { ++ if (supportedEekCurve == RpcHardwareInfo::CURVE_P256) { ++ return constructECDSACoseSign1(key, {} /* protectedParams */, payload, aad); ++ } else { ++ return cppcose::constructCoseSign1(key, payload, aad); ++ } ++} ++ ++ErrMsgOr generateEekChain(int32_t supportedEekCurve, size_t length, ++ const bytevec& eekId) { + if (length < 2) { + return "EEK chain must contain at least 2 certs."; + } +@@ -43,42 +233,31 @@ ErrMsgOr generateEekChain(size_t length, const bytevec& eekId) { + + bytevec prev_priv_key; + for (size_t i = 0; i < length - 1; ++i) { +- bytevec pub_key(ED25519_PUBLIC_KEY_LEN); +- bytevec priv_key(ED25519_PRIVATE_KEY_LEN); +- +- ED25519_keypair(pub_key.data(), priv_key.data()); ++ auto keyPair = generateKeyPair(supportedEekCurve, false); ++ if (!keyPair) keyPair.moveMessage(); ++ auto [pub_key, priv_key] = keyPair.moveValue(); + + // The first signing key is self-signed. + if (prev_priv_key.empty()) prev_priv_key = priv_key; + +- auto coseSign1 = constructCoseSign1(prev_priv_key, +- cppbor::Map() /* payload CoseKey */ +- .add(CoseKey::KEY_TYPE, OCTET_KEY_PAIR) +- .add(CoseKey::ALGORITHM, EDDSA) +- .add(CoseKey::CURVE, ED25519) +- .add(CoseKey::PUBKEY_X, pub_key) +- .canonicalize() +- .encode(), ++ auto coseKey = constructCoseKey(supportedEekCurve, {}, pub_key); ++ if (!coseKey) return coseKey.moveMessage(); ++ ++ auto coseSign1 = constructCoseSign1(supportedEekCurve, prev_priv_key, coseKey.moveValue(), + {} /* AAD */); + if (!coseSign1) return coseSign1.moveMessage(); + eekChain.add(coseSign1.moveValue()); + + prev_priv_key = priv_key; + } ++ auto keyPair = generateKeyPair(supportedEekCurve, true); ++ if (!keyPair) keyPair.moveMessage(); ++ auto [pub_key, priv_key] = keyPair.moveValue(); + +- bytevec pub_key(X25519_PUBLIC_VALUE_LEN); +- bytevec priv_key(X25519_PRIVATE_KEY_LEN); +- X25519_keypair(pub_key.data(), priv_key.data()); ++ auto coseKey = constructCoseKey(supportedEekCurve, eekId, pub_key); ++ if (!coseKey) return coseKey.moveMessage(); + +- auto coseSign1 = constructCoseSign1(prev_priv_key, +- cppbor::Map() /* payload CoseKey */ +- .add(CoseKey::KEY_TYPE, OCTET_KEY_PAIR) +- .add(CoseKey::KEY_ID, eekId) +- .add(CoseKey::ALGORITHM, ECDH_ES_HKDF_256) +- .add(CoseKey::CURVE, cppcose::X25519) +- .add(CoseKey::PUBKEY_X, pub_key) +- .canonicalize() +- .encode(), ++ auto coseSign1 = constructCoseSign1(supportedEekCurve, prev_priv_key, coseKey.moveValue(), + {} /* AAD */); + if (!coseSign1) return coseSign1.moveMessage(); + eekChain.add(coseSign1.moveValue()); +@@ -86,16 +265,15 @@ ErrMsgOr generateEekChain(size_t length, const bytevec& eekId) { + return EekChain{eekChain.encode(), pub_key, priv_key}; + } + +-bytevec getProdEekChain() { +- bytevec prodEek; +- prodEek.reserve(1 + sizeof(kCoseEncodedRootCert) + sizeof(kCoseEncodedGeekCert)); +- +- // In CBOR encoding, 0x82 indicates an array of two items +- prodEek.push_back(0x82); +- prodEek.insert(prodEek.end(), std::begin(kCoseEncodedRootCert), std::end(kCoseEncodedRootCert)); +- prodEek.insert(prodEek.end(), std::begin(kCoseEncodedGeekCert), std::end(kCoseEncodedGeekCert)); +- +- return prodEek; ++bytevec getProdEekChain(int32_t supportedEekCurve) { ++ cppbor::Array chain; ++ if (supportedEekCurve == RpcHardwareInfo::CURVE_P256) { ++ chain.add(cppbor::EncodedItem(bytevec(std::begin(kCoseEncodedEcdsaRootCert), std::end(kCoseEncodedEcdsaRootCert)))); ++ } else { ++ chain.add(cppbor::EncodedItem(bytevec(std::begin(kCoseEncodedRootCert), std::end(kCoseEncodedRootCert)))); ++ chain.add(cppbor::EncodedItem(bytevec(std::begin(kCoseEncodedGeekCert), std::end(kCoseEncodedGeekCert)))); ++ } ++ return chain.encode(); + } + + ErrMsgOr verifyAndParseCoseSign1Cwt(const cppbor::Array* coseSign1, +@@ -122,7 +300,8 @@ ErrMsgOr verifyAndParseCoseSign1Cwt(const cppbor::Array* coseSign1, + } + + auto& algorithm = parsedProtParams->asMap()->get(ALGORITHM); +- if (!algorithm || !algorithm->asInt() || algorithm->asInt()->value() != EDDSA) { ++ if (!algorithm || !algorithm->asInt() || (algorithm->asInt()->value() != EDDSA && ++ algorithm->asInt()->value() != ES256)) { + return "Unsupported signature algorithm"; + } + +@@ -136,16 +315,35 @@ ErrMsgOr verifyAndParseCoseSign1Cwt(const cppbor::Array* coseSign1, + if (!serializedKey || !serializedKey->asBstr()) return "Could not find key entry"; + + bool selfSigned = signingCoseKey.empty(); +- auto key = ++ bytevec key; ++ if (algorithm->asInt()->value() == EDDSA) { ++ auto key = + CoseKey::parseEd25519(selfSigned ? serializedKey->asBstr()->value() : signingCoseKey); +- if (!key) return "Bad signing key: " + key.moveMessage(); ++ if (!key) return "Bad signing key: " + key.moveMessage(); + +- bytevec signatureInput = ++ bytevec signatureInput = + cppbor::Array().add("Signature1").add(*protectedParams).add(aad).add(*payload).encode(); + +- if (!ED25519_verify(signatureInput.data(), signatureInput.size(), signature->value().data(), +- key->getBstrValue(CoseKey::PUBKEY_X)->data())) { +- return "Signature verification failed"; ++ if (!ED25519_verify(signatureInput.data(), signatureInput.size(), signature->value().data(), ++ key->getBstrValue(CoseKey::PUBKEY_X)->data())) { ++ return "Signature verification failed"; ++ } ++ } else { // P256 ++ auto key = ++ CoseKey::parseP256(selfSigned ? serializedKey->asBstr()->value() : signingCoseKey); ++ if (!key || key->getBstrValue(CoseKey::PUBKEY_X)->empty() || ++ key->getBstrValue(CoseKey::PUBKEY_Y)->empty()) { ++ return "Bad signing key: " + key.moveMessage(); ++ } ++ auto publicKey = key->getEcPublicKey(); ++ if (!publicKey) return publicKey.moveMessage(); ++ ++ bytevec signatureInput = ++ cppbor::Array().add("Signature1").add(*protectedParams).add(aad).add(*payload).encode(); ++ ++ if (!verifyEcdsaDigest(publicKey.moveValue(), sha256(signatureInput), signature->value())) { ++ return "Signature verification failed"; ++ } + } + + return serializedKey->asBstr()->value(); +diff --git a/security/keymint/support/remote_prov_utils_test.cpp b/security/keymint/support/remote_prov_utils_test.cpp +index 8697c5190..0009bf713 100644 +--- a/security/keymint/support/remote_prov_utils_test.cpp ++++ b/security/keymint/support/remote_prov_utils_test.cpp +@@ -14,6 +14,7 @@ + * limitations under the License. + */ + ++#include + #include + #include + #include +@@ -35,13 +36,13 @@ using ::keymaster::validateAndExtractEekPubAndId; + using ::testing::ElementsAreArray; + + TEST(RemoteProvUtilsTest, GenerateEekChainInvalidLength) { +- ASSERT_FALSE(generateEekChain(1, /*eekId=*/{})); ++ ASSERT_FALSE(generateEekChain(CURVE_25519, 1, /*eekId=*/{})); + } + + TEST(RemoteProvUtilsTest, GenerateEekChain) { + bytevec kTestEekId = {'t', 'e', 's', 't', 'I', 'd', 0}; + for (size_t length : {2, 3, 31}) { +- auto get_eek_result = generateEekChain(length, kTestEekId); ++ auto get_eek_result = generateEekChain(CURVE_25519, length, kTestEekId); + ASSERT_TRUE(get_eek_result) << get_eek_result.message(); + + auto& [chain, pubkey, privkey] = *get_eek_result; diff --git a/aosp_integration_patches/system_keymaster.patch b/aosp_integration_patches/system_keymaster.patch new file mode 100644 index 00000000..b994b768 --- /dev/null +++ b/aosp_integration_patches/system_keymaster.patch @@ -0,0 +1,441 @@ +diff --git a/cppcose/cppcose.cpp b/cppcose/cppcose.cpp +index bfe9928..5009bfe 100644 +--- a/cppcose/cppcose.cpp ++++ b/cppcose/cppcose.cpp +@@ -21,10 +21,17 @@ + + #include + #include ++#include + + #include + + namespace cppcose { ++constexpr int kP256AffinePointSize = 32; ++ ++using EVP_PKEY_Ptr = bssl::UniquePtr; ++using EVP_PKEY_CTX_Ptr = bssl::UniquePtr; ++using ECDSA_SIG_Ptr = bssl::UniquePtr; ++using EC_KEY_Ptr = bssl::UniquePtr; + + namespace { + +@@ -51,6 +58,92 @@ ErrMsgOr> aesGcmInitAndProcessAad(const bytevec& + return std::move(ctx); + } + ++ ++ErrMsgOr signEcdsaDigest(const bytevec& key, const bytevec& data) { ++ auto bn = BIGNUM_Ptr(BN_bin2bn(key.data(), key.size(), nullptr)); ++ if (bn.get() == nullptr) { ++ return "Error creating BIGNUM"; ++ } ++ ++ auto ec_key = EC_KEY_Ptr(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); ++ if (EC_KEY_set_private_key(ec_key.get(), bn.get()) != 1) { ++ return "Error setting private key from BIGNUM"; ++ } ++ ++ ECDSA_SIG* sig = ECDSA_do_sign(data.data(), data.size(), ec_key.get()); ++ if (sig == nullptr) { ++ return "Error signing digest"; ++ } ++ size_t len = i2d_ECDSA_SIG(sig, nullptr); ++ bytevec signature(len); ++ unsigned char* p = (unsigned char*)signature.data(); ++ i2d_ECDSA_SIG(sig, &p); ++ ECDSA_SIG_free(sig); ++ return signature; ++} ++ ++ErrMsgOr ecdh(const bytevec& publicKey, const bytevec& privateKey) { ++ auto group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); ++ auto point = EC_POINT_Ptr(EC_POINT_new(group.get())); ++ if (EC_POINT_oct2point(group.get(), point.get(), publicKey.data(), publicKey.size(), nullptr) != ++ 1) { ++ return "Error decoding publicKey"; ++ } ++ auto ecKey = EC_KEY_Ptr(EC_KEY_new()); ++ auto pkey = EVP_PKEY_Ptr(EVP_PKEY_new()); ++ if (ecKey.get() == nullptr || pkey.get() == nullptr) { ++ return "Memory allocation failed"; ++ } ++ if (EC_KEY_set_group(ecKey.get(), group.get()) != 1) { ++ return "Error setting group"; ++ } ++ if (EC_KEY_set_public_key(ecKey.get(), point.get()) != 1) { ++ return "Error setting point"; ++ } ++ if (EVP_PKEY_set1_EC_KEY(pkey.get(), ecKey.get()) != 1) { ++ return "Error setting key"; ++ } ++ ++ auto bn = BIGNUM_Ptr(BN_bin2bn(privateKey.data(), privateKey.size(), nullptr)); ++ if (bn.get() == nullptr) { ++ return "Error creating BIGNUM for private key"; ++ } ++ auto privEcKey = EC_KEY_Ptr(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); ++ if (EC_KEY_set_private_key(privEcKey.get(), bn.get()) != 1) { ++ return "Error setting private key from BIGNUM"; ++ } ++ auto privPkey = EVP_PKEY_Ptr(EVP_PKEY_new()); ++ if (EVP_PKEY_set1_EC_KEY(privPkey.get(), privEcKey.get()) != 1) { ++ return "Error setting private key"; ++ } ++ ++ auto ctx = EVP_PKEY_CTX_Ptr(EVP_PKEY_CTX_new(privPkey.get(), NULL)); ++ if (ctx.get() == nullptr) { ++ return "Error creating context"; ++ } ++ ++ if (EVP_PKEY_derive_init(ctx.get()) != 1) { ++ return "Error initializing context"; ++ } ++ ++ if (EVP_PKEY_derive_set_peer(ctx.get(), pkey.get()) != 1) { ++ return "Error setting peer"; ++ } ++ ++ /* Determine buffer length for shared secret */ ++ size_t secretLen = 0; ++ if (EVP_PKEY_derive(ctx.get(), NULL, &secretLen) != 1) { ++ return "Error determing length of shared secret"; ++ } ++ bytevec sharedSecret; ++ sharedSecret.resize(secretLen); ++ ++ if (EVP_PKEY_derive(ctx.get(), sharedSecret.data(), &secretLen) != 1) { ++ return "Error deriving shared secret"; ++ } ++ return sharedSecret; ++} ++ + } // namespace + + ErrMsgOr generateHmacSha256(const bytevec& key, const bytevec& data) { +@@ -134,6 +227,17 @@ ErrMsgOr verifyAndParseCoseMac0(const cppbor::Item* macIt + return payload->value(); + } + ++ErrMsgOr createECDSACoseSign1Signature(const bytevec& key, const bytevec& protectedParams, ++ const bytevec& payload, const bytevec& aad) { ++ bytevec signatureInput = cppbor::Array() ++ .add("Signature1") // ++ .add(protectedParams) ++ .add(aad) ++ .add(payload) ++ .encode(); ++ return signEcdsaDigest(key, sha256(signatureInput)); ++} ++ + ErrMsgOr createCoseSign1Signature(const bytevec& key, const bytevec& protectedParams, + const bytevec& payload, const bytevec& aad) { + bytevec signatureInput = cppbor::Array() +@@ -152,6 +256,19 @@ ErrMsgOr createCoseSign1Signature(const bytevec& key, const bytevec& pr + return signature; + } + ++ErrMsgOr constructECDSACoseSign1(const bytevec& key, cppbor::Map protectedParams, ++ const bytevec& payload, const bytevec& aad) { ++ bytevec protParms = protectedParams.add(ALGORITHM, ES256).canonicalize().encode(); ++ auto signature = createECDSACoseSign1Signature(key, protParms, payload, aad); ++ if (!signature) return signature.moveMessage(); ++ ++ return cppbor::Array() ++ .add(std::move(protParms)) ++ .add(cppbor::Map() /* unprotected parameters */) ++ .add(std::move(payload)) ++ .add(std::move(*signature)); ++} ++ + ErrMsgOr constructCoseSign1(const bytevec& key, cppbor::Map protectedParams, + const bytevec& payload, const bytevec& aad) { + bytevec protParms = protectedParams.add(ALGORITHM, EDDSA).canonicalize().encode(); +@@ -193,7 +310,8 @@ ErrMsgOr verifyAndParseCoseSign1(const cppbor::Array* coseSign1, + } + + auto& algorithm = parsedProtParams->asMap()->get(ALGORITHM); +- if (!algorithm || !algorithm->asInt() || algorithm->asInt()->value() != EDDSA) { ++ if (!algorithm || !algorithm->asInt() || ++ !(algorithm->asInt()->value() == EDDSA || algorithm->asInt()->value() == ES256)) { + return "Unsupported signature algorithm"; + } + +@@ -203,17 +321,30 @@ ErrMsgOr verifyAndParseCoseSign1(const cppbor::Array* coseSign1, + } + + bool selfSigned = signingCoseKey.empty(); +- auto key = CoseKey::parseEd25519(selfSigned ? payload->value() : signingCoseKey); +- if (!key || key->getBstrValue(CoseKey::PUBKEY_X)->empty()) { +- return "Bad signing key: " + key.moveMessage(); +- } +- + bytevec signatureInput = + cppbor::Array().add("Signature1").add(*protectedParams).add(aad).add(*payload).encode(); +- +- if (!ED25519_verify(signatureInput.data(), signatureInput.size(), signature->value().data(), +- key->getBstrValue(CoseKey::PUBKEY_X)->data())) { +- return "Signature verification failed"; ++ if (algorithm->asInt()->value() == EDDSA) { ++ auto key = CoseKey::parseEd25519(selfSigned ? payload->value() : signingCoseKey); ++ if (!key || key->getBstrValue(CoseKey::PUBKEY_X)->empty()) { ++ return "Bad signing key: " + key.moveMessage(); ++ } ++ ++ if (!ED25519_verify(signatureInput.data(), signatureInput.size(), signature->value().data(), ++ key->getBstrValue(CoseKey::PUBKEY_X)->data())) { ++ return "Signature verification failed"; ++ } ++ } else { // P256 ++ auto key = CoseKey::parseP256(selfSigned ? payload->value() : signingCoseKey); ++ if (!key || key->getBstrValue(CoseKey::PUBKEY_X)->empty() || ++ key->getBstrValue(CoseKey::PUBKEY_Y)->empty()) { ++ return "Bad signing key: " + key.moveMessage(); ++ } ++ auto publicKey = key->getEcPublicKey(); ++ if (!publicKey) return publicKey.moveMessage(); ++ ++ if (!verifyEcdsaDigest(publicKey.moveValue(), sha256(signatureInput), signature->value())) { ++ return "Signature verification failed"; ++ } + } + + return payload->value(); +@@ -294,28 +425,47 @@ getSenderPubKeyFromCoseEncrypt(const cppbor::Item* coseEncrypt) { + if (!senderCoseKey || !senderCoseKey->asMap()) return "Invalid sender COSE_Key"; + + auto& keyType = senderCoseKey->asMap()->get(CoseKey::KEY_TYPE); +- if (!keyType || !keyType->asInt() || keyType->asInt()->value() != OCTET_KEY_PAIR) { ++ if (!keyType || !keyType->asInt() || (keyType->asInt()->value() != OCTET_KEY_PAIR && ++ keyType->asInt()->value() != EC2)) { + return "Invalid key type"; + } + + auto& curve = senderCoseKey->asMap()->get(CoseKey::CURVE); +- if (!curve || !curve->asInt() || curve->asInt()->value() != X25519) { ++ if (!curve || !curve->asInt() || ++ (keyType->asInt()->value() == OCTET_KEY_PAIR && curve->asInt()->value() != X25519) || ++ (keyType->asInt()->value() == EC2 && curve->asInt()->value() != P256)) { + return "Unsupported curve"; + } + +- auto& pubkey = senderCoseKey->asMap()->get(CoseKey::PUBKEY_X); +- if (!pubkey || !pubkey->asBstr() || +- pubkey->asBstr()->value().size() != X25519_PUBLIC_VALUE_LEN) { +- return "Invalid X25519 public key"; ++ bytevec publicKey; ++ if (keyType->asInt()->value() == EC2) { ++ auto& pubX = senderCoseKey->asMap()->get(CoseKey::PUBKEY_X); ++ if (!pubX || !pubX->asBstr() || pubX->asBstr()->value().size() != kP256AffinePointSize) { ++ return "Invalid EC public key"; ++ } ++ auto& pubY = senderCoseKey->asMap()->get(CoseKey::PUBKEY_Y); ++ if (!pubY || !pubY->asBstr() || pubY->asBstr()->value().size() != kP256AffinePointSize) { ++ return "Invalid EC public key"; ++ } ++ auto key = CoseKey::getEcPublicKey(pubX->asBstr()->value(), pubY->asBstr()->value()); ++ if (!key) return key.moveMessage(); ++ publicKey = key.moveValue(); ++ } else { ++ auto& pubkey = senderCoseKey->asMap()->get(CoseKey::PUBKEY_X); ++ if (!pubkey || !pubkey->asBstr() || ++ pubkey->asBstr()->value().size() != X25519_PUBLIC_VALUE_LEN) { ++ return "Invalid X25519 public key"; ++ } ++ publicKey = pubkey->asBstr()->value(); + } + + auto& key_id = unprotParms->asMap()->get(KEY_ID); + if (key_id && key_id->asBstr()) { +- return std::make_pair(pubkey->asBstr()->value(), key_id->asBstr()->value()); ++ return std::make_pair(publicKey, key_id->asBstr()->value()); + } + + // If no key ID, just return an empty vector. +- return std::make_pair(pubkey->asBstr()->value(), bytevec{}); ++ return std::make_pair(publicKey, bytevec{}); + } + + ErrMsgOr decryptCoseEncrypt(const bytevec& key, const cppbor::Item* coseEncrypt, +@@ -367,6 +517,43 @@ ErrMsgOr decryptCoseEncrypt(const bytevec& key, const cppbor::Item* cos + return aesGcmDecrypt(key, nonce->asBstr()->value(), aad, ciphertext->asBstr()->value()); + } + ++ErrMsgOr ECDH_HKDF_DeriveKey(const bytevec& pubKeyA, const bytevec& privKeyA, ++ const bytevec& pubKeyB, bool senderIsA) { ++ if (privKeyA.empty() || pubKeyA.empty() || pubKeyB.empty()) { ++ return "Missing input key parameters"; ++ } ++ ++ auto rawSharedKey = ecdh(pubKeyB, privKeyA); ++ if (!rawSharedKey) return rawSharedKey.moveMessage(); ++ ++ bytevec kdfContext = cppbor::Array() ++ .add(AES_GCM_256) ++ .add(cppbor::Array() // Sender Info ++ .add(cppbor::Bstr("client")) ++ .add(bytevec{} /* nonce */) ++ .add(senderIsA ? pubKeyA : pubKeyB)) ++ .add(cppbor::Array() // Recipient Info ++ .add(cppbor::Bstr("server")) ++ .add(bytevec{} /* nonce */) ++ .add(senderIsA ? pubKeyB : pubKeyA)) ++ .add(cppbor::Array() // SuppPubInfo ++ .add(kAesGcmKeySizeBits) // output key length ++ .add(bytevec{})) // protected ++ .encode(); ++ ++ bytevec retval(SHA256_DIGEST_LENGTH); ++ bytevec salt{}; ++ if (!HKDF(retval.data(), retval.size(), // ++ EVP_sha256(), // ++ rawSharedKey->data(), rawSharedKey->size(), // ++ salt.data(), salt.size(), // ++ kdfContext.data(), kdfContext.size())) { ++ return "ECDH HKDF failed"; ++ } ++ ++ return retval; ++} ++ + ErrMsgOr x25519_HKDF_DeriveKey(const bytevec& pubKeyA, const bytevec& privKeyA, + const bytevec& pubKeyB, bool senderIsA) { + if (privKeyA.empty() || pubKeyA.empty() || pubKeyB.empty()) { +@@ -460,4 +647,43 @@ ErrMsgOr aesGcmDecrypt(const bytevec& key, const bytevec& nonce, const + return plaintext; + } + ++bytevec sha256(const bytevec& data) { ++ bytevec ret(SHA256_DIGEST_LENGTH); ++ SHA256_CTX ctx; ++ SHA256_Init(&ctx); ++ SHA256_Update(&ctx, data.data(), data.size()); ++ SHA256_Final((unsigned char*)ret.data(), &ctx); ++ return ret; ++} ++ ++bool verifyEcdsaDigest(const bytevec& key, const bytevec& digest, const bytevec& signature) { ++ const unsigned char* p = (unsigned char*)signature.data(); ++ auto sig = ECDSA_SIG_Ptr(d2i_ECDSA_SIG(nullptr, &p, signature.size())); ++ if (sig.get() == nullptr) { ++ return false; ++ } ++ ++ auto group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); ++ auto point = EC_POINT_Ptr(EC_POINT_new(group.get())); ++ if (EC_POINT_oct2point(group.get(), point.get(), key.data(), key.size(), nullptr) != 1) { ++ return false; ++ } ++ auto ecKey = EC_KEY_Ptr(EC_KEY_new()); ++ if (ecKey.get() == nullptr) { ++ return false; ++ } ++ if (EC_KEY_set_group(ecKey.get(), group.get()) != 1) { ++ return false; ++ } ++ if (EC_KEY_set_public_key(ecKey.get(), point.get()) != 1) { ++ return false; ++ } ++ ++ int rc = ECDSA_do_verify(digest.data(), digest.size(), sig.get(), ecKey.get()); ++ if (rc != 1) { ++ return false; ++ } ++ return true; ++} ++ + } // namespace cppcose +diff --git a/include/keymaster/cppcose/cppcose.h b/include/keymaster/cppcose/cppcose.h +index 0f97388..03251f1 100644 +--- a/include/keymaster/cppcose/cppcose.h ++++ b/include/keymaster/cppcose/cppcose.h +@@ -24,17 +24,25 @@ + + #include + #include +- ++#include ++#include ++#include + #include + #include + #include + #include ++#include + #include + #include + #include + + namespace cppcose { + ++using BIGNUM_Ptr = bssl::UniquePtr; ++using EC_GROUP_Ptr = bssl::UniquePtr; ++using EC_POINT_Ptr = bssl::UniquePtr; ++using BN_CTX_Ptr = bssl::UniquePtr; ++ + template class ErrMsgOr; + using bytevec = std::vector; + using HmacSha256 = std::array; +@@ -203,6 +211,41 @@ class CoseKey { + return key; + } + ++ static ErrMsgOr getEcPublicKey(const bytevec& pubX, const bytevec& pubY) { ++ auto bnX = BIGNUM_Ptr(BN_bin2bn(pubX.data(), pubX.size(), nullptr)); ++ if (bnX.get() == nullptr) { ++ return "Error creating BIGNUM X Coordinate"; ++ } ++ auto bnY = BIGNUM_Ptr(BN_bin2bn(pubY.data(), pubY.size(), nullptr)); ++ if (bnY.get() == nullptr) { ++ return "Error creating BIGNUM Y Coordinate"; ++ } ++ auto group = EC_GROUP_Ptr(EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1)); ++ auto point = EC_POINT_Ptr(EC_POINT_new(group.get())); ++ if (!point) return "Failed to create EC_POINT instance"; ++ BN_CTX_Ptr ctx(BN_CTX_new()); ++ if (!ctx.get()) return "Failed to create BN_CTX instance"; ++ if (!EC_POINT_set_affine_coordinates_GFp(group.get(), point.get(), bnX.get(), bnY.get(), ++ ctx.get())) { ++ return "Failed to set affine coordinates."; ++ } ++ int size = EC_POINT_point2oct(group.get(), point.get(), POINT_CONVERSION_UNCOMPRESSED, ++ nullptr, 0, nullptr); ++ if (size == 0) { ++ return "Error generating public key encoding"; ++ } ++ bytevec publicKey(size); ++ EC_POINT_point2oct(group.get(), point.get(), POINT_CONVERSION_UNCOMPRESSED, ++ publicKey.data(), publicKey.size(), nullptr); ++ return publicKey; ++ } ++ ++ ErrMsgOr getEcPublicKey() { ++ auto pubX = getBstrValue(PUBKEY_X).value(); ++ auto pubY = getBstrValue(PUBKEY_Y).value(); ++ return getEcPublicKey(pubX, pubY); ++ } ++ + std::optional getIntValue(Label label) { + const auto& value = key_->get(label); + if (!value || !value->asInt()) return {}; +@@ -252,6 +295,8 @@ ErrMsgOr constructCoseSign1(const bytevec& key, const bytevec& pa + const bytevec& aad); + ErrMsgOr constructCoseSign1(const bytevec& key, cppbor::Map extraProtectedFields, + const bytevec& payload, const bytevec& aad); ++ErrMsgOr constructECDSACoseSign1(const bytevec& key, cppbor::Map extraProtectedFields, ++ const bytevec& payload, const bytevec& aad); + /** + * Verify and parse a COSE_Sign1 message, returning the payload. + * +@@ -282,7 +327,10 @@ decryptCoseEncrypt(const bytevec& key, const cppbor::Item* encryptItem, const by + + ErrMsgOr x25519_HKDF_DeriveKey(const bytevec& senderPubKey, const bytevec& senderPrivKey, + const bytevec& recipientPubKey, bool senderIsA); +- ++ErrMsgOr ECDH_HKDF_DeriveKey(const bytevec& pubKeyA, const bytevec& privKeyA, ++ const bytevec& pubKeyB, bool senderIsA); ++bool verifyEcdsaDigest(const bytevec& key, const bytevec& digest, const bytevec& signature); ++bytevec sha256(const bytevec& data); + ErrMsgOr aesGcmEncrypt(const bytevec& key, const bytevec& nonce, + const bytevec& aad, + const bytevec& plaintext); diff --git a/patches/git_sepolicy.patch b/aosp_integration_patches/system_sepolicy.patch similarity index 100% rename from patches/git_sepolicy.patch rename to aosp_integration_patches/system_sepolicy.patch diff --git a/patches/git_keymint.patch b/patches/git_keymint.patch deleted file mode 100644 index a38041b5..00000000 --- a/patches/git_keymint.patch +++ /dev/null @@ -1,34 +0,0 @@ -diff --git a/compatibility_matrices/compatibility_matrix.current.xml b/compatibility_matrices/compatibility_matrix.current.xml -index 0b779eee4..82f7b7df6 100644 ---- a/compatibility_matrices/compatibility_matrix.current.xml -+++ b/compatibility_matrices/compatibility_matrix.current.xml -@@ -491,6 +491,14 @@ - default - - -+ -+ android.hardware.security.sharedsecret -+ 1 -+ -+ ISharedSecret -+ strongbox -+ -+ - - android.hardware.sensors - 1.0 -diff --git a/security/keymint/aidl/vts/functional/Android.bp b/security/keymint/aidl/vts/functional/Android.bp -index 386029f30..62e9b842f 100644 ---- a/security/keymint/aidl/vts/functional/Android.bp -+++ b/security/keymint/aidl/vts/functional/Android.bp -@@ -51,8 +51,8 @@ cc_test { - "keymint_vts_defaults", - ], - srcs: [ -- "AttestKeyTest.cpp", -- "DeviceUniqueAttestationTest.cpp", -+ //"AttestKeyTest.cpp", -+ //"DeviceUniqueAttestationTest.cpp", - "KeyMintTest.cpp", - ], - static_libs: [