diff --git a/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java b/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java index 057d9f8a..2fd17006 100644 --- a/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java +++ b/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEApplet.java @@ -52,21 +52,22 @@ public void onConsolidate() { public void onRestore(Element element) { element.initRead(); byte firstByte = element.readByte(); - short packageVersion_ = 0; - byte provisionStatus_ = firstByte; + short oldPackageVersion = 0; if (firstByte == KMKeymasterApplet.KM_MAGIC_NUMBER) { - packageVersion_ = element.readShort(); - provisionStatus_ = element.readByte(); + oldPackageVersion = element.readShort(); + provisionStatus = element.readByte(); + } else { + // MAGIC_NUMBER is introduced in version 2.0. Upgrade is + // not allowed for Applets having version less than 2.0 + ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } - if (0 != packageVersion_ && !isUpgradeAllowed(packageVersion_)) { + if (!isUpgradeAllowed(oldPackageVersion)) { ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED); } - packageVersion = packageVersion_; - provisionStatus = provisionStatus_; keymasterState = element.readByte(); - repository.onRestore(element, packageVersion, KM_PERSISTENT_DATA_STORAGE_VERSION); - seProvider.onRestore(element, packageVersion, KM_PERSISTENT_DATA_STORAGE_VERSION); - handleDataUpgradeToVersion2_0(); + repository.onRestore(element, oldPackageVersion, KM_APPLET_PACKAGE_VERSION); + seProvider.onRestore(element, oldPackageVersion, KM_APPLET_PACKAGE_VERSION); + handleDataUpgrade(); } @Override @@ -102,12 +103,12 @@ private short computeObjectCount() { return (short) 0; } - public boolean isUpgradeAllowed(short version) { + public boolean isUpgradeAllowed(short oldVersion) { boolean upgradeAllowed = false; - short oldMajorVersion = (short) ((version >> 8) & 0x00FF); - short oldMinorVersion = (short) (version & 0x00FF); - short currentMajorVersion = (short) (KM_PERSISTENT_DATA_STORAGE_VERSION >> 8 & 0x00FF); - short currentMinorVersion = (short) (KM_PERSISTENT_DATA_STORAGE_VERSION & 0x00FF); + short oldMajorVersion = (short) ((oldVersion >> 8) & 0x00FF); + short oldMinorVersion = (short) (oldVersion & 0x00FF); + short currentMajorVersion = (short) (KM_APPLET_PACKAGE_VERSION >> 8 & 0x00FF); + short currentMinorVersion = (short) (KM_APPLET_PACKAGE_VERSION & 0x00FF); // Downgrade of the Applet is not allowed. // Upgrade is not allowed to a next version which is not immediate. if ((short) (currentMajorVersion - oldMajorVersion) == 1) { @@ -121,85 +122,21 @@ public boolean isUpgradeAllowed(short version) { } return upgradeAllowed; } - - public void handleDataUpgradeToVersion2_0() { - - if (packageVersion != 0) { - // No Data upgrade required. - return; - } - byte status = provisionStatus; - // In the current version of the applet set boot parameters is removed from - // provision status so readjust the provision locked flag. - // 0x40 is provision locked flag in the older applet. - // Unset the 5th bit. setboot parameters flag. - status = (byte) (status & 0xDF); - // Readjust the lock provisioned status flag. - if ((status & 0x40) == 0x40) { - // 0x40 to 0x20 - // Unset 6th bit - status = (byte) (status & 0xBF); - // set the 5th bit - status = (byte) (status | 0x20); - } - provisionStatus = status; - packageVersion = KM_PERSISTENT_DATA_STORAGE_VERSION; - - short certExpiryLen = 0; - short issuerLen = 0; - short certExpiry = repository.getCertExpiryTime(); - if (certExpiry != KMType.INVALID_VALUE) { - certExpiryLen = KMByteBlob.cast(certExpiry).length(); - } - short issuer = repository.getIssuer(); - if (issuer != KMType.INVALID_VALUE) { - issuerLen = KMByteBlob.cast(issuer).length(); - } - short certChainLen = seProvider.getProvisionedDataLength(KMSEProvider.CERTIFICATE_CHAIN); - short offset = repository.allocReclaimableMemory((short) (certExpiryLen + issuerLen + certChainLen)); - // Get the start offset of the certificate chain. - short certChaionOff = - decoder.getCborBytesStartOffset( - repository.getHeap(), - offset, - seProvider.readProvisionedData(KMSEProvider.CERTIFICATE_CHAIN, repository.getHeap(), offset)); - certChainLen -= (short) (certChaionOff - offset); - Util.arrayCopyNonAtomic( - KMByteBlob.cast(issuer).getBuffer(), - KMByteBlob.cast(issuer).getStartOff(), - repository.getHeap(), - (short) (certChaionOff + certChainLen), - issuerLen); - Util.arrayCopyNonAtomic( - KMByteBlob.cast(certExpiry).getBuffer(), - KMByteBlob.cast(certExpiry).getStartOff(), - repository.getHeap(), - (short) (certChaionOff + certChainLen + issuerLen), - certExpiryLen); - - seProvider.persistProvisionData( - repository.getHeap(), - certChaionOff, // cert chain offset - certChainLen, - (short) (certChaionOff + certChainLen), // issuer offset - issuerLen, - (short) (certChaionOff + certChainLen + issuerLen), // cert expiry offset - certExpiryLen); - - // Update computed HMAC key. - short blob = repository.getComputedHmacKey(); - if (blob != KMType.INVALID_VALUE) { - seProvider.createComputedHmacKey( - KMByteBlob.cast(blob).getBuffer(), - KMByteBlob.cast(blob).getStartOff(), - KMByteBlob.cast(blob).length() - ); - } else { - // Initialize the Key object. - Util.arrayFillNonAtomic(repository.getHeap(), offset, (short) 32, (byte) 0); - seProvider.createComputedHmacKey(repository.getHeap(), offset,(short) 32); + + public void handleDataUpgrade() { + // In version 3.0, two new provisionStatus states are introduced + // 1. PROVISION_STATUS_SE_LOCKED - bit 6 of provisionStatus + // 2. PROVISION_STATUS_OEM_PUBLIC_KEY - bit 7 of provisionStatus + // In the process of upgrade from 2.0 to 3.0 OEM PUBLIC Key is provisioned + // in SEProvider.so update the state of the provision status by making + // 7th bit HIGH. + provisionStatus |= PROVISION_STATUS_OEM_ROOT_PUBLIC_KEY; + // Check if the provisioning is already locked. If so update + // the state of the provisionStatus by making 6th bit HIGH. + // Lock the SE Factory provisioning as well. + if ( 0 != (provisionStatus & PROVISION_STATUS_OEM_PROVISIONING_LOCKED)) { + provisionStatus |= PROVISION_STATUS_SE_FACTORY_PROVISIONING_LOCKED; } - repository.reclaimMemory((short) (certExpiryLen + issuerLen + certChainLen)); } } diff --git a/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEProvider.java b/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEProvider.java index 54576b59..326cdde7 100644 --- a/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEProvider.java +++ b/Applet/AndroidSEProvider/src/com/android/javacard/keymaster/KMAndroidSEProvider.java @@ -133,6 +133,7 @@ public class KMAndroidSEProvider implements KMSEProvider { //Resource type constants public static final byte RESOURCE_TYPE_CRYPTO = 0x00; public static final byte RESOURCE_TYPE_KEY = 0x01; + public static final byte EC_PUB_KEY_SIZE = 65; final byte[] KEY_ALGS = { AES_128, @@ -210,6 +211,7 @@ public class KMAndroidSEProvider implements KMSEProvider { private KMECPrivateKey attestationKey; private KMHmacKey preSharedKey; private KMHmacKey computedHmacKey; + private byte[] oemRootPublicKey; private static KMAndroidSEProvider androidSEProvider = null; @@ -267,6 +269,7 @@ public KMAndroidSEProvider() { short totalLen = (short) (6 + KMConfigurations.CERT_CHAIN_MAX_SIZE + KMConfigurations.CERT_ISSUER_MAX_SIZE + KMConfigurations.CERT_EXPIRY_MAX_SIZE); provisionData = new byte[totalLen]; + oemRootPublicKey = new byte[EC_PUB_KEY_SIZE]; // Initialize attestationKey and preShared key with zeros. Util.arrayFillNonAtomic(tmpArray, (short) 0, TMP_ARRAY_SIZE, (byte) 0); @@ -1315,6 +1318,7 @@ public void onSave(Element element) { KMECPrivateKey.onSave(element, attestationKey); KMHmacKey.onSave(element, preSharedKey); KMHmacKey.onSave(element, computedHmacKey); + element.write(oemRootPublicKey); } @Override @@ -1323,11 +1327,11 @@ public void onRestore(Element element, short oldVersion, short currentVersion) { masterKey = KMAESKey.onRestore(element); attestationKey = KMECPrivateKey.onRestore(element); preSharedKey = KMHmacKey.onRestore(element); - if (oldVersion == 0) { - // Previous versions does not contain version information. - handleDataUpgradeToVersion2_0(); + computedHmacKey = KMHmacKey.onRestore(element); + if (oldVersion == 0x200) { + createOemRootPublicKey(); } else { - computedHmacKey = KMHmacKey.onRestore(element); + oemRootPublicKey = (byte[]) element.readObject(); } } @@ -1344,7 +1348,7 @@ public short getBackupPrimitiveByteCount() { @Override public short getBackupObjectCount() { short count = - (short) (1 + /* provisionData buffer */ + (short) (2 + /* provisionData buffer + oemRootPublicKey */ KMAESKey.getBackupObjectCount() + KMECPrivateKey.getBackupObjectCount() + KMHmacKey.getBackupObjectCount() + @@ -1446,20 +1450,21 @@ public KMComputedHmacKey getComputedHmacKey() { return computedHmacKey; } - private void handleDataUpgradeToVersion2_0() { - short totalLen = (short) (6 + KMConfigurations.CERT_CHAIN_MAX_SIZE + - KMConfigurations.CERT_ISSUER_MAX_SIZE + KMConfigurations.CERT_EXPIRY_MAX_SIZE); - byte[] oldBuffer = provisionData; - provisionData = new byte[totalLen]; - persistCertificateChain( - oldBuffer, - (short) 2, - Util.getShort(oldBuffer, (short) 0)); - - // Request object deletion - oldBuffer = null; - JCSystem.requestObjectDeletion(); - + private void createOemRootPublicKey() { + // Please note that this is a dummy EC P256 Public Key. Replace below key with a real OEM Root + // EC P256 public key while upgrading the Applet from data version 2.0 to 3.0. This change + // is not required if the Applet is installed first time with version 3.0. + oemRootPublicKey = new byte[]{ + (byte) 0x04, (byte) 0xa7, (byte) 0xf7, (byte) 0x4e, (byte) 0xf2, (byte) 0x21, (byte) 0xdd, + (byte) 0x1f, (byte) 0xdb, (byte) 0x19, (byte) 0x87, (byte) 0xbf, (byte) 0x38, (byte) 0x05, + (byte) 0xed, (byte) 0x4e, (byte) 0x82, (byte) 0x84, (byte) 0xaf, (byte) 0x92, (byte) 0x99, + (byte) 0x36, (byte) 0x7e, (byte) 0xb8, (byte) 0xba, (byte) 0xda, (byte) 0x59, (byte) 0xfe, + (byte) 0xd6, (byte) 0x38, (byte) 0x70, (byte) 0x60, (byte) 0xda, (byte) 0xd5, (byte) 0x05, + (byte) 0xf2, (byte) 0x83, (byte) 0xf6, (byte) 0x0b, (byte) 0xd2, (byte) 0x82, (byte) 0xcb, + (byte) 0x8e, (byte) 0x21, (byte) 0xf5, (byte) 0xf7, (byte) 0x52, (byte) 0xff, (byte) 0x82, + (byte) 0x55, (byte) 0xca, (byte) 0xf2, (byte) 0x57, (byte) 0x07, (byte) 0x8e, (byte) 0xea, + (byte) 0x7a, (byte) 0xb0, (byte) 0x82, (byte) 0x59, (byte) 0x84, (byte) 0xe7, (byte) 0x75, + (byte) 0xfb, (byte) 0xb2}; } @Override @@ -1513,5 +1518,40 @@ private KMKeyObject createKeyObjectInstance(byte alg) { return ptr; } + @Override + public void persistOEMRootPublicKey(byte[] inBuff, short inOffset, short inLength) { + if (inLength != 65) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + Util.arrayCopy(inBuff, inOffset, oemRootPublicKey, (short) 0, inLength); + } + + @Override + public short readOEMRootPublicKey(byte[] buf, short off) { + Util.arrayCopyNonAtomic(oemRootPublicKey, (short) 0, buf, off, (short) oemRootPublicKey.length); + return (short) oemRootPublicKey.length; + } + + @Override + public boolean ecVerify256(byte[] keyBuf, short keyBufStart, short keyBufLen, + byte[] inputDataBuf, short inputDataStart, short inputDataLength, + byte[] signature, short signatureOff, short signatureLen) { + ECPublicKey ecPublicKey = (ECPublicKey) ecKeyPair.getPublic(); + ecPublicKey.setW(keyBuf, keyBufStart, keyBufLen); + Signature.OneShot signer = null; + try { + + signer = Signature.OneShot.open(MessageDigest.ALG_SHA_256, + Signature.SIG_CIPHER_ECDSA, Cipher.PAD_NULL); + signer.init(ecPublicKey, Signature.MODE_VERIFY); + return signer.verify(inputDataBuf, inputDataStart, inputDataLength, + signature, signatureOff, signatureLen); + } finally { + if (signer != null) { + signer.close(); + } + } + } + } diff --git a/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMJCardSimulator.java b/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMJCardSimulator.java index 2086620f..7374ec09 100644 --- a/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMJCardSimulator.java +++ b/Applet/JCardSimProvider/src/com/android/javacard/keymaster/KMJCardSimulator.java @@ -94,6 +94,7 @@ public class KMJCardSimulator implements KMSEProvider { private KMECPrivateKey attestationKey; private KMHmacKey preSharedKey; private KMHmacKey computedHmacKey; + private byte[] oemRootPublicKey; private static KMJCardSimulator jCardSimulator = null; @@ -123,6 +124,7 @@ public KMJCardSimulator() { short totalLen = (short) (6 + KMConfigurations.CERT_CHAIN_MAX_SIZE + KMConfigurations.CERT_ISSUER_MAX_SIZE + KMConfigurations.CERT_EXPIRY_MAX_SIZE); provisionData = new byte[totalLen]; + oemRootPublicKey = new byte[65]; jCardSimulator = this; } @@ -1405,4 +1407,32 @@ public short messageDigest256(byte[] inBuff, short inOffset, return len; } + @Override + public void persistOEMRootPublicKey(byte[] inBuff, short inOffset, short inLength) { + if (inLength != 65) { + KMException.throwIt(KMError.INVALID_INPUT_LENGTH); + } + Util.arrayCopy(inBuff, inOffset, oemRootPublicKey, (short) 0, inLength); + } + + @Override + public short readOEMRootPublicKey(byte[] buf, short off) { + Util.arrayCopyNonAtomic(oemRootPublicKey, (short) 0, buf, off, (short) oemRootPublicKey.length); + return (short) oemRootPublicKey.length; + } + + @Override + public boolean ecVerify256(byte[] keyBuf, short keyBufStart, short keyBufLen, 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(keyBuf, keyBufStart, keyBufLen); + 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); + } + } diff --git a/Applet/JCardSimProvider/test/com/android/javacard/test/KMFunctionalTest.java b/Applet/JCardSimProvider/test/com/android/javacard/test/KMFunctionalTest.java index accdbbcd..ad981fa3 100644 --- a/Applet/JCardSimProvider/test/com/android/javacard/test/KMFunctionalTest.java +++ b/Applet/JCardSimProvider/test/com/android/javacard/test/KMFunctionalTest.java @@ -22,6 +22,7 @@ import com.android.javacard.keymaster.KMByteTag; import com.android.javacard.keymaster.KMComputedHmacKey; import com.android.javacard.keymaster.KMConfigurations; +import com.android.javacard.keymaster.KMECPrivateKey; import com.android.javacard.keymaster.KMHmacKey; import com.android.javacard.keymaster.KMJCardSimApplet; import com.android.javacard.keymaster.KMJCardSimulator; @@ -45,8 +46,11 @@ import com.licel.jcardsim.smartcardio.CardSimulator; import com.licel.jcardsim.utils.AIDUtil; +import java.lang.reflect.Field; import javacard.framework.AID; +import javacard.framework.ISO7816; import javacard.framework.Util; +import javacard.security.ECPrivateKey; import javacard.security.ECPublicKey; import javacard.security.KeyBuilder; import javacard.security.KeyPair; @@ -81,7 +85,10 @@ import javax.smartcardio.CommandAPDU; import javax.smartcardio.ResponseAPDU; +import org.junit.AfterClass; import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; public class KMFunctionalTest { @@ -95,6 +102,9 @@ public class KMFunctionalTest { private static final byte INS_LOCK_PROVISIONING_CMD = INS_BEGIN_KM_CMD + 6; //0x07 private static final byte INS_GET_PROVISION_STATUS_CMD = INS_BEGIN_KM_CMD + 7; //0x08 private static final byte INS_SET_VERSION_PATCHLEVEL_CMD = INS_BEGIN_KM_CMD + 8; //0x09 + private static final byte INS_SE_LOCK_PROVISIONING_CMD = INS_BEGIN_KM_CMD + 10; //0x0A + private static final byte INS_PROVISION_OEM_ROOT_PUBLIC_KEY_CMD = INS_BEGIN_KM_CMD + 11; //0x0B + private static final byte INS_OEM_UNLOCK_PROVISIONING_CMD = INS_BEGIN_KM_CMD + 12; //0x0C // Top 32 commands are reserved for provisioning. private static final byte INS_END_KM_PROVISION_CMD = 0x20; @@ -652,6 +662,16 @@ public class KMFunctionalTest { (byte) 0xe9, (byte) 0x77, (byte) 0x4c, (byte) 0x45, (byte) 0xc3, (byte) 0xa3, (byte) 0xcf, (byte) 0x0d, (byte) 0x16, (byte) 0x10, (byte) 0xe4, (byte) 0x79, (byte) 0x43, (byte) 0x3a, (byte) 0x21, (byte) 0x5a, (byte) 0x30, (byte) 0xcf}; + + // OEM lock / unlock verification constants. + private static final byte[] OEM_LOCK_PROVISION_VERIFICATION_LABEL = { // "OEM Provisioning Lock" + 0x4f, 0x45, 0x4d, 0x20, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x69, 0x6e, + 0x67, 0x20, 0x4c, 0x6f, 0x63, 0x6b + }; + private static final byte[] OEM_UNLOCK_PROVISION_VERIFICATION_LABEL = { // "Enable RMA" + 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x52, 0x4d, 0x41 + }; + private static final int OS_VERSION = 1; private static final int OS_PATCH_LEVEL = 1; private static final int VENDOR_PATCH_LEVEL = 1; @@ -675,6 +695,19 @@ public KMFunctionalTest() { decoder = new KMDecoder(); } + @Before + public void resetStaticVariables() throws Exception { + // Set provisionStatus to default + Field provisionStatusField = KMKeymasterApplet.class.getDeclaredField("provisionStatus"); + provisionStatusField.setAccessible(true); + provisionStatusField.setByte(null, (byte) 0 /* NOT_PROVISIONED */ ); + + // Set keymasterState to default. + Field keymasterStateField = KMKeymasterApplet.class.getDeclaredField("keymasterState"); + keymasterStateField.setAccessible(true); + keymasterStateField.setByte(null, (byte) 1 /* ILLEGAL_STATE */ ); + } + private void init() { // Create simulator AID appletAID = AIDUtil.create("A000000062"); @@ -738,7 +771,7 @@ private void setBootParams(CardSimulator simulator, short bootPatchLevel) { } - private void provisionSigningCertificate(CardSimulator simulator) { + private ResponseAPDU provisionSigningCertificate(CardSimulator simulator) { short arrPtr = KMArray.instance((short) 3); short byteBlobPtr = KMByteBlob.instance( @@ -765,10 +798,44 @@ private void provisionSigningCertificate(CardSimulator simulator) { (byte) INS_PROVISION_ATTESTATION_CERT_DATA_CMD, arrPtr); // print(commandAPDU.getBytes()); ResponseAPDU response = simulator.transmitCommand(apdu); - Assert.assertEquals(0x9000, response.getSW()); + return response; + } + + private ResponseAPDU provisionOEMRootPublicKey(CardSimulator simulator) { + // KeyParameters. + short arrPtr = KMArray.instance((short) 4); + short ecCurve = KMEnumTag.instance(KMType.ECCURVE, KMType.P_256); + short byteBlob = KMByteBlob.instance((short) 1); + KMByteBlob.cast(byteBlob).add((short) 0, KMType.SHA2_256); + short digest = KMEnumArrayTag.instance(KMType.DIGEST, byteBlob); + short byteBlob2 = KMByteBlob.instance((short) 1); + KMByteBlob.cast(byteBlob2).add((short) 0, KMType.VERIFY); + short purpose = KMEnumArrayTag.instance(KMType.PURPOSE, byteBlob2); + KMArray.cast(arrPtr).add((short) 0, ecCurve); + KMArray.cast(arrPtr).add((short) 1, digest); + KMArray.cast(arrPtr).add((short) 2, + KMEnumTag.instance(KMType.ALGORITHM, KMType.EC)); + KMArray.cast(arrPtr).add((short) 3, purpose); + short keyParams = KMKeyParameters.instance(arrPtr); + // Note: VTS uses PKCS8 KeyFormat RAW + short keyFormatPtr = KMEnum.instance(KMType.KEY_FORMAT, KMType.RAW); + + // Key + short signKeyPtr = KMByteBlob.instance(kEcPubKey, (short) 0, (short) kEcPubKey.length); + + short finalArrayPtr = KMArray.instance((short) 3); + KMArray.cast(finalArrayPtr).add((short) 0, keyParams); + KMArray.cast(finalArrayPtr).add((short) 1, keyFormatPtr); + KMArray.cast(finalArrayPtr).add((short) 2, signKeyPtr); + + CommandAPDU apdu = encodeApdu((byte) INS_PROVISION_OEM_ROOT_PUBLIC_KEY_CMD, + finalArrayPtr); + // print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(apdu); + return response; } - private void provisionSigningKey(CardSimulator simulator) { + private ResponseAPDU provisionSigningKey(CardSimulator simulator) { // KeyParameters. short arrPtr = KMArray.instance((short) 4); short ecCurve = KMEnumTag.instance(KMType.ECCURVE, KMType.P_256); @@ -806,10 +873,10 @@ private void provisionSigningKey(CardSimulator simulator) { finalArrayPtr); // print(commandAPDU.getBytes()); ResponseAPDU response = simulator.transmitCommand(apdu); - Assert.assertEquals(0x9000, response.getSW()); + return response; } - private void provisionSharedSecret(CardSimulator simulator) { + private ResponseAPDU provisionSharedSecret(CardSimulator simulator) { byte[] sharedKeySecret = { 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}; @@ -822,10 +889,10 @@ private void provisionSharedSecret(CardSimulator simulator) { arrPtr); // print(commandAPDU.getBytes()); ResponseAPDU response = simulator.transmitCommand(apdu); - Assert.assertEquals(0x9000, response.getSW()); + return response; } - private void provisionAttestIds(CardSimulator simulator) { + private ResponseAPDU provisionAttestIds(CardSimulator simulator) { short arrPtr = KMArray.instance((short) 8); byte[] buf = "Attestation Id".getBytes(); @@ -861,28 +928,82 @@ private void provisionAttestIds(CardSimulator simulator) { outerArrPtr); // print(commandAPDU.getBytes()); ResponseAPDU response = simulator.transmitCommand(apdu); - Assert.assertEquals(0x9000, response.getSW()); + return response; + } + + private ResponseAPDU provisionLocked(CardSimulator simulator) { + // Sign the Lock message + byte[] signature = new byte[120]; + ECPrivateKey key = (ECPrivateKey) KeyBuilder.buildKey( + KeyBuilder.TYPE_EC_FP_PRIVATE, KeyBuilder.LENGTH_EC_FP_256, false); + key.setS(kEcPrivKey, (short) 0, (short) kEcPrivKey.length); + Signature ecSigner = Signature.getInstance(Signature.ALG_ECDSA_SHA_256, false); + ecSigner.init(key, Signature.MODE_SIGN); + short len = + ecSigner.sign( + OEM_LOCK_PROVISION_VERIFICATION_LABEL, + (short) 0, + (short) OEM_LOCK_PROVISION_VERIFICATION_LABEL.length, + signature, + (short) 0); + + short arr = KMArray.instance((short) 1); + KMArray.cast(arr).add((short) 0, KMByteBlob.instance(signature, (short) 0, len)); + + CommandAPDU apdu = encodeApdu((byte) INS_LOCK_PROVISIONING_CMD, + arr); + // print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(apdu); + return response; } - private void provisionLocked(CardSimulator simulator) { - CommandAPDU commandAPDU = new CommandAPDU(0x80, INS_LOCK_PROVISIONING_CMD, + private ResponseAPDU provisionOemUnLock(CardSimulator simulator) { + // Sign the Lock message + byte[] signature = new byte[120]; + ECPrivateKey key = (ECPrivateKey) KeyBuilder.buildKey( + KeyBuilder.TYPE_EC_FP_PRIVATE, KeyBuilder.LENGTH_EC_FP_256, false); + key.setS(kEcPrivKey, (short) 0, (short) kEcPrivKey.length); + Signature ecSigner = Signature.getInstance(Signature.ALG_ECDSA_SHA_256, false); + ecSigner.init(key, Signature.MODE_SIGN); + short len = + ecSigner.sign( + OEM_UNLOCK_PROVISION_VERIFICATION_LABEL, + (short) 0, + (short) OEM_UNLOCK_PROVISION_VERIFICATION_LABEL.length, + signature, + (short) 0); + + short arr = KMArray.instance((short) 1); + KMArray.cast(arr).add((short) 0, KMByteBlob.instance(signature, (short) 0, len)); + + CommandAPDU apdu = encodeApdu((byte) INS_OEM_UNLOCK_PROVISIONING_CMD, + arr); + // print(commandAPDU.getBytes()); + ResponseAPDU response = simulator.transmitCommand(apdu); + return response; + } + + private ResponseAPDU provisionSeLocked(CardSimulator simulator) { + CommandAPDU commandAPDU = new CommandAPDU(0x80, INS_SE_LOCK_PROVISIONING_CMD, 0x40, 0x00); // print(commandAPDU.getBytes()); ResponseAPDU response = simulator.transmitCommand(commandAPDU); - Assert.assertEquals(0x9000, response.getSW()); + return response; } private void provisionCmd(CardSimulator simulator) { - provisionSigningKey(simulator); - provisionSigningCertificate(simulator); - provisionSharedSecret(simulator); - provisionAttestIds(simulator); + Assert.assertEquals(0x9000, provisionSigningKey(simulator).getSW()); + Assert.assertEquals(0x9000, provisionSigningCertificate(simulator).getSW()); + Assert.assertEquals(0x9000, provisionSeLocked(simulator).getSW()); + Assert.assertEquals(0x9000, provisionSharedSecret(simulator).getSW()); + Assert.assertEquals(0x9000, provisionAttestIds(simulator).getSW()); + Assert.assertEquals(0x9000, provisionOEMRootPublicKey(simulator).getSW()); // set bootup parameters setBootParams(simulator, (short) BOOT_PATCH_LEVEL); // set android system properties setAndroidOSSystemProperties(simulator, (short) OS_VERSION, (short) OS_PATCH_LEVEL, (short) VENDOR_PATCH_LEVEL); - provisionLocked(simulator); + Assert.assertEquals(0x9000, provisionLocked(simulator).getSW()); } private void cleanUp() { @@ -2969,6 +3090,174 @@ public void testSignVerifyWithRsaSHA256Pkcs1WithUpdate() { cleanUp(); } + @Test + public void testVerifyOemLockWithOutSeLockFailure() { + AID appletAID1 = AIDUtil.create("A000000062"); + simulator.installApplet(appletAID1, KMJCardSimApplet.class); + // Select applet + simulator.selectApplet(appletAID1); + // provision attest key + Assert.assertEquals(0x9000, provisionSigningKey(simulator).getSW()); + Assert.assertEquals(0x9000, provisionSigningCertificate(simulator).getSW()); + Assert.assertEquals(0x9000, provisionSharedSecret(simulator).getSW()); + Assert.assertEquals(0x9000, provisionAttestIds(simulator).getSW()); + Assert.assertEquals(0x9000, provisionOEMRootPublicKey(simulator).getSW()); + ResponseAPDU response = provisionLocked(simulator); + Assert.assertEquals(0x9000, response.getSW()); + byte[] respBuf = response.getBytes(); + short len = (short) respBuf.length; + short ret = decoder.decode(KMInteger.exp(), respBuf, (short) 0, len); + short error = KMInteger.cast(ret).getShort(); + Assert.assertEquals(error, KMError.CMD_NOT_ALLOWED); + cleanUp(); + } + + @Test + public void testVerifyOemUnLockAfterOemLockSuccess() { + AID appletAID1 = AIDUtil.create("A000000062"); + simulator.installApplet(appletAID1, KMJCardSimApplet.class); + // Select applet + simulator.selectApplet(appletAID1); + // provision attest key + Assert.assertEquals(0x9000, provisionSigningKey(simulator).getSW()); + Assert.assertEquals(0x9000, provisionSigningCertificate(simulator).getSW()); + Assert.assertEquals(0x9000, provisionSeLocked(simulator).getSW()); + Assert.assertEquals(0x9000, provisionSharedSecret(simulator).getSW()); + Assert.assertEquals(0x9000, provisionAttestIds(simulator).getSW()); + Assert.assertEquals(0x9000, provisionOEMRootPublicKey(simulator).getSW()); + Assert.assertEquals(0x9000, provisionLocked(simulator).getSW()); + // set bootup parameters + setBootParams(simulator, (short) BOOT_PATCH_LEVEL); + // set android system properties + setAndroidOSSystemProperties(simulator, (short) OS_VERSION, (short) OS_PATCH_LEVEL, + (short) VENDOR_PATCH_LEVEL); + Assert.assertEquals(0x9000, provisionOemUnLock(simulator).getSW()); + Assert.assertEquals(0x9000, provisionSharedSecret(simulator).getSW()); + Assert.assertEquals(0x9000, provisionAttestIds(simulator).getSW()); + Assert.assertEquals(0x9000, provisionLocked(simulator).getSW()); + // try generating key + generateRsaKey(null, null); + cleanUp(); + } + + @Test + public void testVerifyOemLockWithOutOemRootKeyFailure() { + AID appletAID1 = AIDUtil.create("A000000062"); + simulator.installApplet(appletAID1, KMJCardSimApplet.class); + // Select applet + simulator.selectApplet(appletAID1); + // provision attest key + Assert.assertEquals(0x9000, provisionSigningKey(simulator).getSW()); + Assert.assertEquals(0x9000, provisionSigningCertificate(simulator).getSW()); + Assert.assertEquals(0x9000, provisionSeLocked(simulator).getSW()); + Assert.assertEquals(0x9000, provisionSharedSecret(simulator).getSW()); + Assert.assertEquals(0x9000, provisionAttestIds(simulator).getSW()); + ResponseAPDU response = provisionLocked(simulator); + Assert.assertEquals(0x9000, response.getSW()); + byte[] respBuf = response.getBytes(); + short len = (short) respBuf.length; + short ret = decoder.decode(KMInteger.exp(), respBuf, (short) 0, len); + short error = KMInteger.cast(ret).getShort(); + Assert.assertEquals(error, KMError.CMD_NOT_ALLOWED); + cleanUp(); + } + + @Test + public void testVerifySeLockWithOutSigningKeyFailure() { + AID appletAID1 = AIDUtil.create("A000000062"); + simulator.installApplet(appletAID1, KMJCardSimApplet.class); + // Select applet + simulator.selectApplet(appletAID1); + // provision attest key + Assert.assertEquals(0x9000, provisionSigningCertificate(simulator).getSW()); + ResponseAPDU response = provisionSeLocked(simulator); + Assert.assertEquals(0x9000, response.getSW()); + byte[] respBuf = response.getBytes(); + short len = (short) respBuf.length; + short ret = decoder.decode(KMInteger.exp(), respBuf, (short) 0, len); + short error = KMInteger.cast(ret).getShort(); + Assert.assertEquals(error, KMError.CMD_NOT_ALLOWED); + cleanUp(); + } + + @Test + public void testVerifySeLockWithOutCertDataFailure() { + AID appletAID1 = AIDUtil.create("A000000062"); + simulator.installApplet(appletAID1, KMJCardSimApplet.class); + // Select applet + simulator.selectApplet(appletAID1); + // provision attest key + Assert.assertEquals(0x9000, provisionSigningKey(simulator).getSW()); + ResponseAPDU response = provisionSeLocked(simulator); + Assert.assertEquals(0x9000, response.getSW()); + byte[] respBuf = response.getBytes(); + short len = (short) respBuf.length; + short ret = decoder.decode(KMInteger.exp(), respBuf, (short) 0, len); + short error = KMInteger.cast(ret).getShort(); + Assert.assertEquals(error, KMError.CMD_NOT_ALLOWED); + cleanUp(); + } + + @Test + public void testVerifyProvisionSeDataAfterSeLockFailure() { + AID appletAID1 = AIDUtil.create("A000000062"); + simulator.installApplet(appletAID1, KMJCardSimApplet.class); + // Select applet + simulator.selectApplet(appletAID1); + // provision attest key + Assert.assertEquals(0x9000, provisionSigningKey(simulator).getSW()); + Assert.assertEquals(0x9000, provisionSigningCertificate(simulator).getSW()); + Assert.assertEquals(0x9000, provisionSeLocked(simulator).getSW()); + ResponseAPDU response = provisionSigningKey(simulator); + Assert.assertEquals(0x9000, response.getSW()); + byte[] respBuf = response.getBytes(); + short len = (short) respBuf.length; + short ret = decoder.decode(KMInteger.exp(), respBuf, (short) 0, len); + short error = KMInteger.cast(ret).getShort(); + Assert.assertEquals(error, KMError.CMD_NOT_ALLOWED); + + response = provisionSigningCertificate(simulator); + Assert.assertEquals(0x9000, response.getSW()); + respBuf = response.getBytes(); + len = (short) respBuf.length; + ret = decoder.decode(KMInteger.exp(), respBuf, (short) 0, len); + error = KMInteger.cast(ret).getShort(); + Assert.assertEquals(error, KMError.CMD_NOT_ALLOWED); + cleanUp(); + } + + @Test + public void testVerifyOemProvisionAfterOemLockFailure() { + AID appletAID1 = AIDUtil.create("A000000062"); + simulator.installApplet(appletAID1, KMJCardSimApplet.class); + // Select applet + simulator.selectApplet(appletAID1); + // provision attest key + Assert.assertEquals(0x9000, provisionSigningKey(simulator).getSW()); + Assert.assertEquals(0x9000, provisionSigningCertificate(simulator).getSW()); + Assert.assertEquals(0x9000, provisionSeLocked(simulator).getSW()); + Assert.assertEquals(0x9000, provisionSharedSecret(simulator).getSW()); + Assert.assertEquals(0x9000, provisionAttestIds(simulator).getSW()); + Assert.assertEquals(0x9000, provisionOEMRootPublicKey(simulator).getSW()); + Assert.assertEquals(0x9000, provisionLocked(simulator).getSW()); + ResponseAPDU response = provisionSharedSecret(simulator); + Assert.assertEquals(0x9000, response.getSW()); + byte[] respBuf = response.getBytes(); + short len = (short) respBuf.length; + short ret = decoder.decode(KMInteger.exp(), respBuf, (short) 0, len); + short error = KMInteger.cast(ret).getShort(); + Assert.assertEquals(error, KMError.CMD_NOT_ALLOWED); + + response = provisionAttestIds(simulator); + Assert.assertEquals(0x9000, response.getSW()); + respBuf = response.getBytes(); + len = (short) respBuf.length; + ret = decoder.decode(KMInteger.exp(), respBuf, (short) 0, len); + error = KMInteger.cast(ret).getShort(); + Assert.assertEquals(error, KMError.CMD_NOT_ALLOWED); + cleanUp(); + } + @Test public void testProvisionSuccess() { AID appletAID1 = AIDUtil.create("A000000062"); diff --git a/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java b/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java index c72cb517..e30f1ca0 100644 --- a/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java +++ b/Applet/src/com/android/javacard/keymaster/KMKeymasterApplet.java @@ -46,7 +46,8 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe // Magic number version public static final byte KM_MAGIC_NUMBER = (byte) 0x81; // MSB byte is for Major version and LSB byte is for Minor version. - public static final short KM_PERSISTENT_DATA_STORAGE_VERSION = 0x0200; // 2.0 + // Whenever there is an applet upgrade change the version. + public static final short KM_APPLET_PACKAGE_VERSION = 0x0300; // 3.0 // "Keymaster HMAC Verification" - used for HMAC key verification. public static final byte[] sharingCheck = { @@ -80,6 +81,15 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe }; private static final byte[] GOOGLE = {0x47, 0x6F, 0x6F, 0x67, 0x6C, 0x65}; + // OEM lock / unlock verification constants. + private static final byte[] OEM_LOCK_VERIFICATION_LABEL = { // "OEM Provisioning Lock" + 0x4f, 0x45, 0x4d, 0x20, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x69, 0x6e, + 0x67, 0x20, 0x4c, 0x6f, 0x63, 0x6b + }; + private static final byte[] OEM_UNLOCK_VERIFICATION_LABEL = { // "Enable RMA" + 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x20, 0x52, 0x4d, 0x41 + }; + // Possible states of the applet. private static final byte KM_BEGIN_STATE = 0x00; @@ -96,11 +106,14 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe private static final byte INS_PROVISION_ATTEST_IDS_CMD = INS_BEGIN_KM_CMD + 3; //0x03 private static final byte INS_PROVISION_PRESHARED_SECRET_CMD = INS_BEGIN_KM_CMD + 4; //0x04 private static final byte INS_SET_BOOT_PARAMS_CMD = INS_BEGIN_KM_CMD + 5; //0x05 - private static final byte INS_LOCK_PROVISIONING_CMD = INS_BEGIN_KM_CMD + 6; //0x06 + private static final byte INS_OEM_LOCK_PROVISIONING_CMD = INS_BEGIN_KM_CMD + 6; //0x06 private static final byte INS_GET_PROVISION_STATUS_CMD = INS_BEGIN_KM_CMD + 7; //0x07 private static final byte INS_SET_VERSION_PATCHLEVEL_CMD = INS_BEGIN_KM_CMD + 8; //0x08 private static final byte INS_SET_BOOT_ENDED_CMD = INS_BEGIN_KM_CMD + 9; //0x09 - + private static final byte INS_SE_FACTORY_LOCK_PROVISIONING_CMD = INS_BEGIN_KM_CMD + 10; //0x0A + private static final byte INS_PROVISION_OEM_ROOT_PUBLIC_KEY_CMD = INS_BEGIN_KM_CMD + 11; //0x0B + private static final byte INS_OEM_UNLOCK_PROVISIONING_CMD = INS_BEGIN_KM_CMD + 12; //0x0C + // Top 32 commands are reserved for provisioning. private static final byte INS_END_KM_PROVISION_CMD = 0x20; @@ -136,7 +149,9 @@ public class KMKeymasterApplet extends Applet implements AppletEvent, ExtendedLe private static final byte PROVISION_STATUS_ATTESTATION_CERT_PARAMS = 0x04; protected static final byte PROVISION_STATUS_ATTEST_IDS = 0x08; protected static final byte PROVISION_STATUS_PRESHARED_SECRET = 0x10; - protected static final byte PROVISION_STATUS_PROVISIONING_LOCKED = 0x20; + protected static final byte PROVISION_STATUS_OEM_PROVISIONING_LOCKED = 0x20; + protected static final byte PROVISION_STATUS_SE_FACTORY_PROVISIONING_LOCKED = 0x40; + protected static final byte PROVISION_STATUS_OEM_ROOT_PUBLIC_KEY = (byte) 0x80; // Data Dictionary items public static final byte DATA_ARRAY_SIZE = 31; @@ -240,8 +255,8 @@ protected KMKeymasterApplet(KMSEProvider seImpl) { if (!isUpgrading) { keymasterState = KMKeymasterApplet.INIT_STATE; seProvider.createMasterKey((short) (KMRepository.MASTER_KEY_SIZE * 8)); - packageVersion = KM_PERSISTENT_DATA_STORAGE_VERSION; } + packageVersion = KM_APPLET_PACKAGE_VERSION; KMType.initialize(); encoder = new KMEncoder(); decoder = new KMDecoder(); @@ -385,15 +400,38 @@ public void process(APDU apdu) { if (keymasterState == KMKeymasterApplet.IN_PROVISION_STATE) { switch (apduIns) { case INS_PROVISION_ATTESTATION_KEY_CMD: - processProvisionAttestationKey(apdu); - provisionStatus |= KMKeymasterApplet.PROVISION_STATUS_ATTESTATION_KEY; - sendError(apdu, KMError.OK); + if (!isSEFactoryProvisioningLocked()) { + processProvisionAttestationKey(apdu); + provisionStatus |= KMKeymasterApplet.PROVISION_STATUS_ATTESTATION_KEY; + sendError(apdu, KMError.OK); + } else { + ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED); + } return; case INS_PROVISION_ATTESTATION_CERT_DATA_CMD: - processProvisionAttestationCertDataCmd(apdu); - provisionStatus |= (KMKeymasterApplet.PROVISION_STATUS_ATTESTATION_CERT_CHAIN | - KMKeymasterApplet.PROVISION_STATUS_ATTESTATION_CERT_PARAMS); + if (!isSEFactoryProvisioningLocked()) { + processProvisionAttestationCertDataCmd(apdu); + provisionStatus |= (KMKeymasterApplet.PROVISION_STATUS_ATTESTATION_CERT_CHAIN | + KMKeymasterApplet.PROVISION_STATUS_ATTESTATION_CERT_PARAMS); + sendError(apdu, KMError.OK); + } else { + ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED); + } + return; + + case INS_SE_FACTORY_LOCK_PROVISIONING_CMD: + if (isSEFactoryProvisioningComplete()) { + provisionStatus |= KMKeymasterApplet.PROVISION_STATUS_SE_FACTORY_PROVISIONING_LOCKED; + sendError(apdu, KMError.OK); + } else { + ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED); + } + return; + + case INS_PROVISION_OEM_ROOT_PUBLIC_KEY_CMD: + processProvisionOEMRootPublicKeyCmd(apdu); + provisionStatus |= KMKeymasterApplet.PROVISION_STATUS_OEM_ROOT_PUBLIC_KEY; sendError(apdu, KMError.OK); return; @@ -409,15 +447,24 @@ public void process(APDU apdu) { sendError(apdu, KMError.OK); return; - case INS_LOCK_PROVISIONING_CMD: - if (isProvisioningComplete()) { - provisionStatus |= KMKeymasterApplet.PROVISION_STATUS_PROVISIONING_LOCKED; - keymasterState = KMKeymasterApplet.ACTIVE_STATE; - sendError(apdu, KMError.OK); + case INS_OEM_LOCK_PROVISIONING_CMD: + // Allow lock only when + // 1. All the necessary provisioning commands are successfully executed + // 2. SE provision is locked + // 3. OEM Root Public is provisioned. + if (isProvisioningComplete() && + (0 != (provisionStatus & PROVISION_STATUS_OEM_ROOT_PUBLIC_KEY)) && + (0 != (provisionStatus & PROVISION_STATUS_SE_FACTORY_PROVISIONING_LOCKED)) ) { + processOEMLockProvisionCmd(apdu); } else { ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED); } return; + + case INS_OEM_UNLOCK_PROVISIONING_CMD: + // UNLOCK command not allowed in IN_PROVISION_STATE + ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED); + return; } } @@ -527,6 +574,19 @@ && isProvisioningComplete())) { case INS_SET_VERSION_PATCHLEVEL_CMD: processSetVersionAndPatchLevels(apdu); break; + case INS_OEM_UNLOCK_PROVISIONING_CMD: + processOEMUnlockProvisionCmd(apdu); + break; + case INS_PROVISION_ATTEST_IDS_CMD: + case INS_PROVISION_ATTESTATION_KEY_CMD: + case INS_PROVISION_ATTESTATION_CERT_DATA_CMD: + case INS_PROVISION_OEM_ROOT_PUBLIC_KEY_CMD: + case INS_PROVISION_PRESHARED_SECRET_CMD: + case INS_SE_FACTORY_LOCK_PROVISIONING_CMD: + case INS_OEM_LOCK_PROVISIONING_CMD: + // Provision commands are not allowed in ACTIVE_STATE + ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED); + break; default: ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED); } @@ -557,6 +617,20 @@ private void generateUniqueOperationHandle(byte[] buf, short offset, short len) } while (null != repository.findOperation(buf, offset, len)); } + private boolean isSEFactoryProvisioningLocked() { + return (0 != (provisionStatus & PROVISION_STATUS_SE_FACTORY_PROVISIONING_LOCKED)); + } + + private boolean isSEFactoryProvisioningComplete() { + if ((0 != (provisionStatus & PROVISION_STATUS_ATTESTATION_KEY)) + && (0 != (provisionStatus & PROVISION_STATUS_ATTESTATION_CERT_CHAIN)) + && (0 != (provisionStatus & PROVISION_STATUS_ATTESTATION_CERT_PARAMS))) { + return true; + } else { + return false; + } + } + private boolean isProvisioningComplete() { if ((0 != (provisionStatus & PROVISION_STATUS_ATTESTATION_KEY)) && (0 != (provisionStatus & PROVISION_STATUS_ATTESTATION_CERT_CHAIN)) @@ -568,6 +642,46 @@ private boolean isProvisioningComplete() { } } + private void processOEMUnlockProvisionCmd(APDU apdu) { + authenticateOEM(OEM_UNLOCK_VERIFICATION_LABEL, apdu); + // Set the OEM Lock bit LOW in provisionStatus. + provisionStatus &= ~KMKeymasterApplet.PROVISION_STATUS_OEM_PROVISIONING_LOCKED; + keymasterState = IN_PROVISION_STATE; + sendError(apdu, KMError.OK); + } + + private void processOEMLockProvisionCmd(APDU apdu) { + authenticateOEM(OEM_LOCK_VERIFICATION_LABEL, apdu); + // Set the OEM Lock bit HIGH in provisionStatus. + provisionStatus |= KMKeymasterApplet.PROVISION_STATUS_OEM_PROVISIONING_LOCKED; + keymasterState = ACTIVE_STATE; + sendError(apdu, KMError.OK); + } + + private void authenticateOEM(byte[] plainMsg, APDU apdu) { + receiveIncoming(apdu); + byte[] scratchpad = apdu.getBuffer(); + tmpVariables[0] = KMArray.instance((short) 1); + KMArray.cast(tmpVariables[0]).add((short) 0, KMByteBlob.exp()); + // Decode the arguments + tmpVariables[0] = decoder.decode(tmpVariables[0], (byte[]) bufferRef[0], + bufferProp[BUF_START_OFFSET], bufferProp[BUF_LEN_OFFSET]); + //reclaim memory + repository.reclaimMemory(bufferProp[BUF_LEN_OFFSET]); + // Get the signature input. + short signature = KMArray.cast(tmpVariables[0]).get((short) 0); + short ecPubKeyLen = seProvider.readOEMRootPublicKey(scratchpad, (short) 0); + + if (!seProvider.ecVerify256( + scratchpad, (short) 0, (short) ecPubKeyLen, + plainMsg, (short) 0, (short) plainMsg.length, + KMByteBlob.cast(signature).getBuffer(), + KMByteBlob.cast(signature).getStartOff(), + KMByteBlob.cast(signature).length())) { + KMException.throwIt(KMError.VERIFICATION_FAILED); + } + } + private void freeOperations() { if (data[OP_HANDLE] != KMType.INVALID_VALUE) { KMOperationState op = repository.findOperation(data[OP_HANDLE]); @@ -875,6 +989,76 @@ private void processProvisionAttestationKey(APDU apdu) { KMByteBlob.cast(data[SECRET]).length()); } + private void processProvisionOEMRootPublicKeyCmd(APDU apdu) { + receiveIncoming(apdu); + // Re-purpose the apdu buffer as scratch pad. + byte[] scratchPad = apdu.getBuffer(); + // Arguments + short keyparams = KMKeyParameters.exp(); + short keyFormatPtr = KMEnum.instance(KMType.KEY_FORMAT); + short blob = KMByteBlob.exp(); + short argsProto = KMArray.instance((short) 3); + KMArray.cast(argsProto).add((short) 0, keyparams); + KMArray.cast(argsProto).add((short) 1, keyFormatPtr); + KMArray.cast(argsProto).add((short) 2, blob); + + // Decode the argument + short args = decoder.decode(argsProto, (byte[]) bufferRef[0], bufferProp[BUF_START_OFFSET], + bufferProp[BUF_LEN_OFFSET]); + //reclaim memory + repository.reclaimMemory(bufferProp[BUF_LEN_OFFSET]); + + // key params should have os patch, os version and verified root of trust + data[KEY_PARAMETERS] = KMArray.cast(args).get((short) 0); + tmpVariables[0] = KMArray.cast(args).get((short) 1); + // Key format must be RAW format + byte keyFormat = KMEnum.cast(tmpVariables[0]).getVal(); + if (keyFormat != KMType.RAW) { + KMException.throwIt(KMError.UNIMPLEMENTED); + } + + // get algorithm - only EC keys expected + tmpVariables[0] = KMEnumTag.getValue(KMType.ALGORITHM, data[KEY_PARAMETERS]); + if (tmpVariables[0] != KMType.EC) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // get digest - only SHA256 supported + tmpVariables[0] = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.DIGEST, data[KEY_PARAMETERS]); + if (tmpVariables[0] != KMType.INVALID_VALUE) { + if (KMEnumArrayTag.cast(tmpVariables[0]).length() != 1) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + tmpVariables[0] = KMEnumArrayTag.cast(tmpVariables[0]).get((short) 0); + if (tmpVariables[0] != KMType.SHA2_256) { + KMException.throwIt(KMError.INCOMPATIBLE_DIGEST); + } + } else { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + // Purpose should be VERIFY + tmpVariables[0] = + KMKeyParameters.findTag(KMType.ENUM_ARRAY_TAG, KMType.PURPOSE, data[KEY_PARAMETERS]); + if (tmpVariables[0] != KMType.INVALID_VALUE) { + if (KMEnumArrayTag.cast(tmpVariables[0]).length() != 1) { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + tmpVariables[0] = KMEnumArrayTag.cast(tmpVariables[0]).get((short) 0); + if (tmpVariables[0] != KMType.VERIFY) { + KMException.throwIt(KMError.INCOMPATIBLE_PURPOSE); + } + } else { + KMException.throwIt(KMError.INVALID_ARGUMENT); + } + + tmpVariables[0] = KMArray.cast(args).get((short) 2); + // persist OEM Root Public Key. + seProvider.persistOEMRootPublicKey( + KMByteBlob.cast(tmpVariables[0]).getBuffer(), + KMByteBlob.cast(tmpVariables[0]).getStartOff(), + KMByteBlob.cast(tmpVariables[0]).length()); + } + private void processProvisionAttestIdsCmd(APDU apdu) { receiveIncoming(apdu); // Arguments @@ -917,7 +1101,7 @@ private void processProvisionSharedSecretCmd(APDU apdu) { private void processGetProvisionStatusCmd(APDU apdu) { tmpVariables[0] = KMArray.instance((short) 2); KMArray.cast(tmpVariables[0]).add((short) 0, buildErrorStatus(KMError.OK)); - KMArray.cast(tmpVariables[0]).add((short) 1, KMInteger.uint_16(provisionStatus)); + KMArray.cast(tmpVariables[0]).add((short) 1, KMInteger.uint_8(provisionStatus)); bufferProp[BUF_START_OFFSET] = repository.allocAvailableMemory(); bufferProp[BUF_LEN_OFFSET] = encoder.encode(tmpVariables[0], (byte[]) bufferRef[0], bufferProp[BUF_START_OFFSET]); diff --git a/Applet/src/com/android/javacard/keymaster/KMRepository.java b/Applet/src/com/android/javacard/keymaster/KMRepository.java index 1d018d22..3479594b 100644 --- a/Applet/src/com/android/javacard/keymaster/KMRepository.java +++ b/Applet/src/com/android/javacard/keymaster/KMRepository.java @@ -947,12 +947,7 @@ public void onSave(Element ele) { public void onRestore(Element ele, short oldVersion, short currentVersion) { dataIndex = ele.readShort(); dataTable = (byte[]) ele.readObject(); - if (oldVersion == 0) { - // Previous versions does not contain version information. - handleDataUpgradeToVersion2_0(); - } else { - attestIdsIndex = ele.readShort(); - } + attestIdsIndex = ele.readShort(); } @Override @@ -1002,29 +997,4 @@ public void setEarlyBootEndedStatus(boolean flag) { } writeDataEntry(EARLY_BOOT_ENDED_STATUS, getHeap(), start, EARLY_BOOT_ENDED_FLAG_SIZE); } - - public void handleDataUpgradeToVersion2_0() { - byte[] oldDataTable = dataTable; - dataTable = new byte[2048]; - attestIdsIndex = (short) (DATA_INDEX_SIZE * DATA_INDEX_ENTRY_SIZE); - dataIndex = (short) (attestIdsIndex + KMConfigurations.TOTAL_ATTEST_IDS_SIZE); - // temp buffer. - short startOffset = alloc((short) 256); - - short index = ATT_ID_BRAND; - short len = 0; - while (index <= DEVICE_LOCKED) { - len = readData(oldDataTable, index, heap, startOffset, (short) 256); - writeDataEntry(index, heap, startOffset, len); - index++; - } - // set default values for the new IDS. - setDeviceLockPasswordOnly(false); - setBootEndedStatus(false); - setEarlyBootEndedStatus(false); - - // Request object deletion - oldDataTable = null; - JCSystem.requestObjectDeletion(); - } } diff --git a/Applet/src/com/android/javacard/keymaster/KMSEProvider.java b/Applet/src/com/android/javacard/keymaster/KMSEProvider.java index dbfa3710..ed72cd8f 100644 --- a/Applet/src/com/android/javacard/keymaster/KMSEProvider.java +++ b/Applet/src/com/android/javacard/keymaster/KMSEProvider.java @@ -611,4 +611,51 @@ void persistProvisionData(byte[] buf, short certChainOff, short certChainLen, short messageDigest256(byte[] inBuff, short inOffset, short inLength, byte[] outBuff, short outOffset); + /** + * This function persists the root public key of the OEM. + * + * @param inBuff is the public key buffer. + * @param inOffset is the start offset of the public key buffer. + * @param inLength is the length of the public key buffer. + */ + void persistOEMRootPublicKey(byte[] inBuff, short inOffset, short inLength); + + /** + * Returns the persisted OEM Root Public Key buffer. + * + * @return returns the persisted OEM Root Public Key buffer. + */ + /** + * Returns the persisted OEM Root EC P256 Public Key. + * + * @param buf is the output buffer where public key is copied. + * @param off is the start of the output buffer + * @return length of the public key. + */ + short readOEMRootPublicKey(byte[] buf, short off); + + /** + * The function verifies the EC 256 signature. + * + * @param keyBuf is the public key buffer. + * @param keyBufStart is the start of the public key buffer. + * @param keyBufLen is the length of the public key buffer. + * @param inputDataBuf is the buffer containing the input data. + * @param inputDataStart is the start offset of the input data. + * @param inputDataLength is the length of the input data. + * @param signature is the buffer containing the signature. + * @param signatureOff is the start offset of the signature buffer. + * @param signatureLen is the length of the signature buffer. + * @return true if signature verification is successful, otherwise false. + */ + boolean ecVerify256( + byte[] keyBuf, + short keyBufStart, + short keyBufLen, + byte[] inputDataBuf, + short inputDataStart, + short inputDataLength, + byte[] signature, + short signatureOff, + short signatureLen); } diff --git a/ProvisioningTool/include/constants.h b/ProvisioningTool/include/constants.h index ffc0011f..38efbd76 100644 --- a/ProvisioningTool/include/constants.h +++ b/ProvisioningTool/include/constants.h @@ -51,6 +51,9 @@ DEFINE_OPENSSL_OBJECT_POINTER(EC_KEY) DEFINE_OPENSSL_OBJECT_POINTER(EVP_PKEY) DEFINE_OPENSSL_OBJECT_POINTER(X509) +// OEM Lock / Unlock Verification message +constexpr char kOemProvisioningLock[] = "OEM Provisioning Lock"; +constexpr char kEnableRma[] = "Enable RMA"; // Tags constexpr uint64_t kTagAlgorithm = 268435458u; @@ -71,6 +74,7 @@ constexpr uint64_t kCurveP256 = 1; constexpr uint64_t kAlgorithmEc = 3; constexpr uint64_t kDigestSha256 = 4; constexpr uint64_t kPurposeAttest = 0x7F; +constexpr uint64_t kPurposeVerify = 3; constexpr uint64_t kKeyFormatRaw = 3; // json keys @@ -83,6 +87,9 @@ constexpr char kDeviceUniqueKey[] = "device_unique_key"; constexpr char kAdditionalCertChain[] = "additional_cert_chain"; constexpr char kProvisionStatus[] = "provision_status"; constexpr char kLockProvision[] = "lock_provision"; +constexpr char kOEMRootKey[] = "oem_root_key"; +constexpr char kSeFactoryProvisionLock[] = "se_factory_lock"; +constexpr char kUnLockProvision[] = "unlock_provision"; // Instruction constatnts constexpr int kAttestationKeyCmd = INS_BEGIN_KM_CMD + 1; @@ -90,6 +97,10 @@ constexpr int kAttestCertDataCmd = INS_BEGIN_KM_CMD + 2; constexpr int kAttestationIdsCmd = INS_BEGIN_KM_CMD + 3; constexpr int kPresharedSecretCmd = INS_BEGIN_KM_CMD + 4; constexpr int kBootParamsCmd = INS_BEGIN_KM_CMD + 5; -constexpr int kLockProvisionCmd = INS_BEGIN_KM_CMD + 6; +constexpr int kOemLockProvisionCmd = INS_BEGIN_KM_CMD + 6; constexpr int kGetProvisionStatusCmd = INS_BEGIN_KM_CMD + 7; constexpr int kSetVersionPatchLevelCmd = INS_BEGIN_KM_CMD + 8; +constexpr int kSeFactoryLockCmd = INS_BEGIN_KM_CMD + 10; +constexpr int kOemRootPublicKeyCmd = INS_BEGIN_KM_CMD + 11; +constexpr int kOemUnLockProvisionCmd = INS_BEGIN_KM_CMD + 12; + diff --git a/ProvisioningTool/sample_json_cf.txt b/ProvisioningTool/sample_json_cf.txt index 486374f2..a83df4b5 100644 --- a/ProvisioningTool/sample_json_cf.txt +++ b/ProvisioningTool/sample_json_cf.txt @@ -22,5 +22,6 @@ "test_resources/batch_cert.der", "test_resources/intermediate_cert.der", "test_resources/ca_cert.der" - ] + ], + "oem_root_key": "test_resources/oem_root_key.der" } diff --git a/ProvisioningTool/sample_json_gf.txt b/ProvisioningTool/sample_json_gf.txt index 89ad6c3b..07405fce 100644 --- a/ProvisioningTool/sample_json_gf.txt +++ b/ProvisioningTool/sample_json_gf.txt @@ -22,5 +22,6 @@ "test_resources/batch_cert.der", "test_resources/intermediate_cert.der", "test_resources/ca_cert.der" - ] + ], + "oem_root_key": "test_resources/oem_root_key.der" } diff --git a/ProvisioningTool/src/construct_apdus.cpp b/ProvisioningTool/src/construct_apdus.cpp index fdad6be0..102a6bb8 100644 --- a/ProvisioningTool/src/construct_apdus.cpp +++ b/ProvisioningTool/src/construct_apdus.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -28,6 +29,8 @@ #include #include #include +#include + #include #include #include #include "cppbor/cppbor.h" @@ -51,6 +54,8 @@ static int processAttestationCertificateData(); static int processAttestationIds(); static int processSharedSecret(); static int processSetBootParameters(); +static int processOEMRootPublicKey(); +static int processSEFactoryProvisioningLock(); static int readDataFromFile(const char *fileName, std::vector& data); static int addApduHeader(const int ins, std::vector& inputData); static int ecRawKeyFromPKCS8(const std::vector& pkcs8Blob, std::vector& secret, std::vector&publicKey); @@ -59,6 +64,13 @@ static int getNotAfter(X509* x509, std::vector& notAfterDate); static int getDerSubjectName(X509* x509, std::vector& subject); static int getBootParameterIntValue(Json::Value& bootParamsObj, const char* key, uint32_t *value); static int getBootParameterBlobValue(Json::Value& bootParamsObj, const char* key, std::vector& blob); +static int signEcdsaDigest(const std::vector& key, const std::vector& data, + std::vector& out); +static int sha256(const std::vector& data, std::vector& out); +static int sendOEMAuthenticationToken(const char* toBeSigned, int oemCmd, const char* mapKey); +static int processOEMFactoryProvisioningLock(); +static int processOEMFactoryProvisioningUnLock(); +static int processGetProvisionStatus(); // Print usage. @@ -156,6 +168,54 @@ int getBootParameterBlobValue(Json::Value& bootParamsObj, const char* key, std:: return SUCCESS; } +std::vector sha256(const std::vector& data) { + std::vector ret(32); // SHA256 digest output len + SHA256_CTX ctx; + SHA256_Init(&ctx); + SHA256_Update(&ctx, data.data(), data.size()); + SHA256_Final((unsigned char*)ret.data(), &ctx); + return ret; +} + +// TODO use unique_ptr +int signEcdsaDigest(const std::vector& key, const std::vector& data, + std::vector& out) { + size_t len; + unsigned char* p = nullptr; + ECDSA_SIG *sig = nullptr; + EC_KEY *ec_key = nullptr; + std::vector signature; + int result = FAILURE; + BIGNUM *bn = BN_bin2bn(key.data(), key.size(), nullptr); + if (bn == nullptr) { + printf("Error creating BIGNUM"); + goto exit; + } + + ec_key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); + if (EC_KEY_set_private_key(ec_key, bn) != 1) { + printf("Error setting private key from BIGNUM"); + goto exit; + } + + sig = ECDSA_do_sign(data.data(), data.size(), ec_key); + if (sig == nullptr) { + printf("Error signing digest"); + goto exit; + } + len = i2d_ECDSA_SIG(sig, nullptr); + signature.resize(len); + p = (unsigned char*)signature.data(); + i2d_ECDSA_SIG(sig, &p); + out = signature; + result = SUCCESS; +exit: + if (bn != nullptr) BN_free(bn); + if (ec_key != nullptr) EC_KEY_free(ec_key); + if (sig != nullptr) ECDSA_SIG_free(sig); + return result; +} + // 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. @@ -170,6 +230,11 @@ int processInputFile() { 0 != processAttestationCertificateData() || 0 != processAttestationIds() || 0 != processSharedSecret() || + 0 != processOEMRootPublicKey() || + 0 != processOEMFactoryProvisioningLock() || + 0 != processOEMFactoryProvisioningUnLock() || + 0 != processGetProvisionStatus() || + 0 != processSEFactoryProvisioningLock() || 0 != processSetBootParameters()) { return FAILURE; } @@ -180,6 +245,118 @@ int processInputFile() { return SUCCESS; } +int sendOEMAuthenticationToken(const char* toBeSigned, int oemCmd, const char* mapKey) { + Json::Value keyFile = root.get(kOEMRootKey, Json::Value::nullRef); + if (!keyFile.isNull()) { + std::vector data; + std::vector privateKey; + std::vector publicKey; + std::vector signature; + std::vector plainMsg(toBeSigned, toBeSigned + strlen(toBeSigned)); + + std::string keyFileName = keyFile.asString(); + if(SUCCESS != readDataFromFile(keyFileName.data(), data)) { + printf("\n Failed to read the oem root key from the file.\n"); + return FAILURE; + } + if (SUCCESS != ecRawKeyFromPKCS8(data, privateKey, publicKey)) { + return FAILURE; + } + if (SUCCESS != signEcdsaDigest(privateKey, sha256(plainMsg), signature)) { + printf("\n Failed to sign the message.\n"); + return FAILURE; + } + // Prepare cbor input. + Array input; + input.add(signature); + std::vector cborData = input.encode(); + + if(SUCCESS != addApduHeader(oemCmd, cborData)) { + return FAILURE; + } + // Write to json. + writerRoot[mapKey] = getHexString(cborData); + } else { + printf("\n Improper value for oem_root_key in json file \n"); + return FAILURE; + } + const char *lockCmd = (oemCmd == kOemLockProvisionCmd) ? "lock" : "unlock"; + printf("\n Constructed OEM Factory provision %s successfully. \n", lockCmd); + return SUCCESS; +} + +int processOEMFactoryProvisioningLock() { + return sendOEMAuthenticationToken(kOemProvisioningLock, kOemLockProvisionCmd, kLockProvision); +} + +int processOEMFactoryProvisioningUnLock() { + return sendOEMAuthenticationToken(kEnableRma, kOemUnLockProvisionCmd, kUnLockProvision); +} + +int processGetProvisionStatus() { + std::vector cborData; + if (SUCCESS != addApduHeader(kGetProvisionStatusCmd, cborData)) { + return FAILURE; + } + // Write to json. + writerRoot[kProvisionStatus] = getHexString(cborData); + printf("\n Constructed get Provision status APDU successfully. \n"); + return SUCCESS; +} + +int processSEFactoryProvisioningLock() { + std::vector cborData; + if (SUCCESS != addApduHeader(kSeFactoryLockCmd, cborData)) { + return FAILURE; + } + // Write to json. + writerRoot[kSeFactoryProvisionLock] = getHexString(cborData); + printf("\n Constructed SE factory lock APDU successfully. \n"); + return SUCCESS; +} + +int processOEMRootPublicKey() { + Json::Value keyFile = root.get(kOEMRootKey, Json::Value::nullRef); + if (!keyFile.isNull()) { + std::vector data; + std::vector privateKey; + std::vector publicKey; + + std::string keyFileName = keyFile.asString(); + if(SUCCESS != readDataFromFile(keyFileName.data(), data)) { + printf("\n Failed to read the oem root key from the file.\n"); + return FAILURE; + } + if (SUCCESS != ecRawKeyFromPKCS8(data, privateKey, publicKey)) { + return FAILURE; + } + + // Prepare cbor input. + Array input; + Map map; + map.add(kTagAlgorithm, kAlgorithmEc); + map.add(kTagDigest, std::vector({kDigestSha256})); + map.add(kTagCurve, kCurveP256); + map.add(kTagPurpose, std::vector({kPurposeVerify})); + // Add elements inside cbor array. + input.add(std::move(map)); + input.add(kKeyFormatRaw); + input.add(publicKey); + std::vector cborData = input.encode(); + + if(SUCCESS != addApduHeader(kOemRootPublicKeyCmd, cborData)) { + return FAILURE; + } + // Write to json. + writerRoot[kOEMRootKey] = getHexString(cborData); + } else { + printf("\n Improper value for oem_root_key in json file \n"); + return FAILURE; + } + printf("\n Constructed OemRootPublicKey APDU successfully. \n"); + return SUCCESS; +} + int processAttestationKey() { Json::Value keyFile = root.get(kAttestKey, Json::Value::nullRef); if (!keyFile.isNull()) { diff --git a/ProvisioningTool/src/provision.cpp b/ProvisioningTool/src/provision.cpp index bf3f96e8..86ba2e78 100644 --- a/ProvisioningTool/src/provision.cpp +++ b/ProvisioningTool/src/provision.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include "socket.h" #include @@ -34,11 +35,10 @@ enum ProvisionStatus { PROVISION_STATUS_ATTEST_IDS = 0x08, PROVISION_STATUS_PRESHARED_SECRET = 0x10, PROVISION_STATUS_PROVISIONING_LOCKED = 0x20, + PROVISION_STATUS_SE_LOCKED = 0x40, + PROVISION_STATUS_OEM_PUBLIC_KEY = 0x80 }; -std::string provisionStatusApdu = hex2str("80074000000000"); -std::string lockProvisionApdu = hex2str("80064000000000"); - Json::Value root; static double keymasterVersion = -1; static std::string inputFileName; @@ -46,6 +46,7 @@ using cppbor::Item; using cppbor::Array; using cppbor::Uint; using cppbor::MajorType; +bool printProvisionStatus = false; // static function declarations static uint16_t getApduStatus(std::vector& inputData); @@ -53,6 +54,7 @@ static int sendData(std::shared_ptr& pSocket, std::string input static int provisionData(std::shared_ptr& pSocket, const char* jsonKey); static int provisionData(std::shared_ptr& pSocket, std::string apdu, std::vector& response); static int getUint64(const std::unique_ptr &item, const uint32_t pos, uint64_t &value); +static int getProvisionStatus(uint64_t *provisionStatus); // Print usage. @@ -64,7 +66,9 @@ void usage() { printf("-v, --km_version version \t Version of the keymaster(4.1 for keymaster; 5 for keymint \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"); + printf("-l, --lock_provision jsonFile \t OEM provisioning lock. \n"); + printf("-f, --se_factory_lock jsonFile \t SE Factory provisioning lock. \n"); + printf("-u, --unlock_provision jsonFile \t Unlock OEM provisioning. \n"); } @@ -151,6 +155,14 @@ int provisionData(std::shared_ptr& pSocket, std::string apdu, s return SUCCESS; } +bool isSEFactoryProvisioningLocked(uint64_t provisionStatus) { + return (0 != (provisionStatus & PROVISION_STATUS_SE_LOCKED)); +} + +bool isOEMProvisioningLocked(uint64_t provisionStatus) { + return (0 != (provisionStatus & PROVISION_STATUS_PROVISIONING_LOCKED)); +} + int provisionData(std::shared_ptr& pSocket, const char* jsonKey) { std::vector response; Json::Value val = root.get(jsonKey, Json::Value::nullRef); @@ -171,10 +183,10 @@ int provisionData(std::shared_ptr& pSocket, const char* jsonKey int openConnection(std::shared_ptr& pSocket) { if (!pSocket->isConnected()) { - if (!pSocket->openConnection()) + if (!pSocket->openConnection()) { + printf("\nFailed to open connection.\n"); return FAILURE; - } else { - printf("\n Socket already opened.\n"); + } } return SUCCESS; } @@ -196,14 +208,25 @@ int processInputFile() { printf("\n Failed to open connection \n"); return FAILURE; } - std::vector response; printf("\n Selected Keymaster version(%f) for provisioning \n", keymasterVersion); - if (0 != provisionData(pSocket, kAttestKey) || - 0 != provisionData(pSocket, kAttestCertChain) || - 0 != provisionData(pSocket, kAttestationIds) || - 0 != provisionData(pSocket, kSharedSecret) || - 0 != provisionData(pSocket, kBootParams)) { + uint64_t provisionStatus = 0; + if (SUCCESS != getProvisionStatus(&provisionStatus)) { + return false; + } + if (!isSEFactoryProvisioningLocked(provisionStatus) && + ((0 != provisionData(pSocket, kAttestKey)) || + (0 != provisionData(pSocket, kAttestCertChain)))) { + return FAILURE; + } + if (!isOEMProvisioningLocked(provisionStatus) && + ((0 != provisionData(pSocket, kAttestationIds)) || + (0 != provisionData(pSocket, kSharedSecret)) || + (0 != provisionData(pSocket, kOEMRootKey)))) { + return FAILURE; + } + + if (0 != provisionData(pSocket, kBootParams)) { return FAILURE; } return SUCCESS; @@ -212,60 +235,127 @@ int processInputFile() { int lockProvision() { std::vector response; std::shared_ptr pSocket = SocketTransport::getInstance(); + + // Parse Json file + if (0 != readJsonFile(root, inputFileName)) { + return FAILURE; + } if (SUCCESS != openConnection(pSocket)) { printf("\n Failed to open connection \n"); return FAILURE; } - if (SUCCESS != provisionData(pSocket, lockProvisionApdu, response)) { + if (SUCCESS != provisionData(pSocket, kLockProvision)) { printf("\n Failed to lock provision.\n"); return FAILURE; } - printf("\n Provision lock is successfull.\n"); return SUCCESS; } -int getProvisionStatus() { +int unlockProvision() { + std::vector response; + std::shared_ptr pSocket = SocketTransport::getInstance(); + + // Parse Json file + if (0 != readJsonFile(root, inputFileName)) { + return FAILURE; + } + if (SUCCESS != openConnection(pSocket)) { + printf("\n Failed to open connection \n"); + return FAILURE; + } + if (SUCCESS != provisionData(pSocket, kUnLockProvision)) { + printf("\n Failed to unlock provision.\n"); + return FAILURE; + } + return SUCCESS; +} + +int lockSEFactoryProvisioning() { + std::vector response; + std::shared_ptr pSocket = SocketTransport::getInstance(); + + // Parse Json file + if (0 != readJsonFile(root, inputFileName)) { + return FAILURE; + } + if (SUCCESS != openConnection(pSocket)) { + printf("\n Failed to open connection \n"); + return FAILURE; + } + if (SUCCESS != provisionData(pSocket, kSeFactoryProvisionLock)) { + printf("\n Failed to lock SE factory provision.\n"); + return FAILURE; + } + return SUCCESS; +} + +int getProvisionStatus(uint64_t *provisionStatus) { std::vector response; std::shared_ptr pSocket = SocketTransport::getInstance(); + // Parse Json file + if (0 != readJsonFile(root, inputFileName)) { + return FAILURE; + } 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"); + Json::Value val = root.get(kProvisionStatus, Json::Value::nullRef); + if (!val.isNull()) { + if (val.isString()) { + if (SUCCESS != provisionData(pSocket, hex2str(val.asString()), response)) { + printf("\n Error while provisioning %s \n", kProvisionStatus); + return FAILURE; + } + } else { + printf("\n Fail: Expected (%s) tag value is string. \n", kProvisionStatus); + return FAILURE; + } + } else { return FAILURE; } auto [item, pos, message] = cppbor::parse(response); + uint64_t status; if(item != nullptr) { - uint64_t status; if(SUCCESS != getUint64(item, 1, status)) { printf("\n Failed to get the provision status.\n"); return FAILURE; } - 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))) { + if (printProvisionStatus) { + 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))) { 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"); + 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_OEM_PUBLIC_KEY)) { + printf("\n OEM Root Public Key is not provisioned \n"); + } } + printf("\n provisionStatus:%ld\n", status); + printProvisionStatus = false; } } else { printf("\n Fail to parse the response \n"); return FAILURE; } + if (provisionStatus != nullptr) { + *provisionStatus = status; + } return SUCCESS; } @@ -273,12 +363,16 @@ int main(int argc, char* argv[]) { int c; bool provisionStatusSet = false; bool lockProvisionSet = false; + bool unlockProvisionSet = false; + bool seFactoryLockSet = false; struct option longOpts[] = { {"km_version", required_argument, NULL, 'v'}, {"input", required_argument, NULL, 'i'}, {"provision_status", no_argument, NULL, 's'}, - {"lock_provision", no_argument, NULL, 'l'}, + {"oem_lock_provision", no_argument, NULL, 'l'}, + {"oem_unlock_provision", no_argument, NULL, 'u'}, + {"se_factory_lock", no_argument, NULL, 'f'}, {"help", no_argument, NULL, 'h'}, {0,0,0,0} }; @@ -290,7 +384,7 @@ int main(int argc, char* argv[]) { } /* getopt_long stores the option index here. */ - while ((c = getopt_long(argc, argv, ":hlsv:i:", longOpts, NULL)) != -1) { + while ((c = getopt_long(argc, argv, ":hlufsv:i:", longOpts, NULL)) != -1) { switch(c) { case 'v': // keymaster version @@ -308,6 +402,12 @@ int main(int argc, char* argv[]) { case 'l': lockProvisionSet = true; break; + case 'u': + unlockProvisionSet = true; + break; + case 'f': + seFactoryLockSet = true; + break; case 'h': // help usage(); @@ -323,20 +423,32 @@ int main(int argc, char* argv[]) { return FAILURE; } } - // Process input file; send apuds to JCServer over socket. - if (argc >= 5) { - if (SUCCESS != processInputFile()) { - return FAILURE; + if (argc < 5) { + usage(); + return FAILURE; + } + if (keymasterVersion == -1 || inputFileName.empty()) { + printf("\n For provisioning km_version and input json file arguments are mandatory.\n"); + usage(); + return FAILURE; + } + if (argc == 6) { + if (provisionStatusSet) { + printProvisionStatus = true; + getProvisionStatus(nullptr); + return SUCCESS; } - } else if (keymasterVersion != -1 || !inputFileName.empty()) { - printf("\n For provisioning km_version and input json file arguments are mandatory.\n"); - usage(); - return FAILURE; } - if (provisionStatusSet) - getProvisionStatus(); + // Process input file; send apuds to JCServer over socket. + if (SUCCESS != processInputFile()) { + return FAILURE; + } + if (seFactoryLockSet) + lockSEFactoryProvisioning(); if (lockProvisionSet) lockProvision(); + if (unlockProvisionSet) + unlockProvision(); return SUCCESS; } diff --git a/ProvisioningTool/src/utils.cpp b/ProvisioningTool/src/utils.cpp index 41ad8a6c..b497bf9b 100644 --- a/ProvisioningTool/src/utils.cpp +++ b/ProvisioningTool/src/utils.cpp @@ -63,8 +63,8 @@ int readJsonFile(Json::Value& root, std::string& inputFileName) { std::string errorMessage; if(!root.empty()) { - printf("\n Already parsed \n"); - return 1; + // Already parsed. + return 0; } std::ifstream stream(inputFileName); if (Json::parseFromStream(builder, stream, &root, &errorMessage)) { diff --git a/ProvisioningTool/test_resources/oem_root_key.der b/ProvisioningTool/test_resources/oem_root_key.der new file mode 100644 index 00000000..6940e35e Binary files /dev/null and b/ProvisioningTool/test_resources/oem_root_key.der differ