Merge "Provide alternate SE RoT provisioning path." am: 07011d9e09
Original change: https://android-review.googlesource.com/c/platform/hardware/interfaces/+/1982797
Change-Id: Ia74ce0dba7595e9eb8c080c150174b49b7c509b1
diff --git a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IKeyMintDevice.aidl b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IKeyMintDevice.aidl
index fa643fc..dcc22c4 100644
--- a/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IKeyMintDevice.aidl
+++ b/security/keymint/aidl/aidl_api/android.hardware.security.keymint/current/android/hardware/security/keymint/IKeyMintDevice.aidl
@@ -49,5 +49,8 @@
void earlyBootEnded();
byte[] convertStorageKeyToEphemeral(in byte[] storageKeyBlob);
android.hardware.security.keymint.KeyCharacteristics[] getKeyCharacteristics(in byte[] keyBlob, in byte[] appId, in byte[] appData);
+ byte[16] getRootOfTrustChallenge();
+ byte[] getRootOfTrust(in byte[16] challenge);
+ void sendRootOfTrust(in byte[] rootOfTrust);
const int AUTH_TOKEN_MAC_LENGTH = 32;
}
diff --git a/security/keymint/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl b/security/keymint/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl
index 4b63799..da02d54 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/IKeyMintDevice.aidl
@@ -851,4 +851,82 @@
*/
KeyCharacteristics[] getKeyCharacteristics(
in byte[] keyBlob, in byte[] appId, in byte[] appData);
+
+ /**
+ * Returns a 16-byte random challenge nonce, used to prove freshness when exchanging root of
+ * trust data.
+ *
+ * This method may only be implemented by StrongBox KeyMint. TEE KeyMint implementations must
+ * return ErrorCode::UNIMPLEMENTED. StrongBox KeyMint implementations MAY return UNIMPLEMENTED,
+ * to indicate that they have an alternative mechanism for getting the data. If the StrongBox
+ * implementation returns UNIMPLEMENTED, the client should not call `getRootofTrust()` or
+ * `sendRootOfTrust()`.
+ */
+ byte[16] getRootOfTrustChallenge();
+
+ /**
+ * Returns the TEE KeyMint Root of Trust data.
+ *
+ * This method is required for TEE KeyMint. StrongBox KeyMint implementations MUST return
+ * ErrorCode::UNIMPLEMENTED.
+ *
+ * The returned data is an encoded COSE_Mac0 structure, denoted MacedRootOfTrust in the
+ * following CDDL schema. Note that K_mac is the shared HMAC key used for auth tokens, etc.:
+ *
+ * MacedRootOfTrust = [ ; COSE_Mac0 (untagged)
+ * protected: bstr .cbor {
+ * 1 : 5, ; Algorithm : HMAC-256
+ * },
+ * unprotected : {},
+ * payload : bstr .cbor RootOfTrust,
+ * tag : bstr HMAC-256(K_mac, MAC_structure)
+ * ]
+ *
+ * MAC_structure = [
+ * context : "MAC0",
+ * protected : bstr .cbor {
+ * 1 : 5, ; Algorithm : HMAC-256
+ * },
+ * external_aad : bstr .size 16 ; Value of challenge argument
+ * payload : bstr .cbor RootOfTrust,
+ * ]
+ *
+ * RootOfTrust = [
+ * verifiedBootKey : bstr .size 32,
+ * deviceLocked : bool,
+ * verifiedBootState : &VerifiedBootState,
+ * verifiedBootHash : bstr .size 32,
+ * bootPatchLevel : int, ; See Tag::BOOT_PATCHLEVEL
+ * ]
+ *
+ * VerifiedBootState = (
+ * Verified : 0,
+ * SelfSigned : 1,
+ * Unverified : 2,
+ * Failed : 3
+ * )
+ */
+ byte[] getRootOfTrust(in byte[16] challenge);
+
+ /**
+ * Delivers the TEE KeyMint Root of Trust data to StrongBox KeyMint. See `getRootOfTrust()`
+ * above for specification of the data format and cryptographic security structure.
+ *
+ * The implementation must verify the MAC on the RootOfTrust data. If it is valid, and if this
+ * is the first time since reboot that StrongBox KeyMint has received this data, it must store
+ * the RoT data for use in key attestation requests, then return ErrorCode::ERROR_OK.
+ *
+ * If the MAC on the Root of Trust data and challenge is incorrect, the implementation must
+ * return ErrorCode::VERIFICATION_FAILED.
+ *
+ * If the RootOfTrust data has already been received since the last boot, the implementation
+ * must validate the data and return ErrorCode::VERIFICATION_FAILED or ErrorCode::ERROR_OK
+ * according to the result, but must not store the data for use in key attestation requests,
+ * even if verification succeeds. On success, the challenge is invalidated and a new challenge
+ * must be requested before the RootOfTrust data may be sent again.
+ *
+ * This method is optional for StrongBox KeyMint, which MUST return ErrorCode::UNIMPLEMENTED if
+ * not implemented. TEE KeyMint implementations must return ErrorCode::UNIMPLEMENTED.
+ */
+ void sendRootOfTrust(in byte[] rootOfTrust);
}
diff --git a/security/keymint/aidl/vts/functional/Android.bp b/security/keymint/aidl/vts/functional/Android.bp
index 91db3c8..1616e65 100644
--- a/security/keymint/aidl/vts/functional/Android.bp
+++ b/security/keymint/aidl/vts/functional/Android.bp
@@ -54,6 +54,7 @@
"AttestKeyTest.cpp",
"DeviceUniqueAttestationTest.cpp",
"KeyMintTest.cpp",
+ "SecureElementProvisioningTest.cpp",
],
static_libs: [
"libkeymint_vts_test_utils",
diff --git a/security/keymint/aidl/vts/functional/SecureElementProvisioningTest.cpp b/security/keymint/aidl/vts/functional/SecureElementProvisioningTest.cpp
new file mode 100644
index 0000000..e16a47b
--- /dev/null
+++ b/security/keymint/aidl/vts/functional/SecureElementProvisioningTest.cpp
@@ -0,0 +1,255 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "keymint_2_se_provisioning_test"
+
+#include <map>
+#include <memory>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android/binder_manager.h>
+
+#include <cppbor_parse.h>
+#include <keymaster/cppcose/cppcose.h>
+#include <keymint_support/key_param_output.h>
+
+#include "KeyMintAidlTestBase.h"
+
+namespace aidl::android::hardware::security::keymint::test {
+
+using std::array;
+using std::map;
+using std::shared_ptr;
+using std::vector;
+
+class SecureElementProvisioningTest : public testing::Test {
+ protected:
+ static void SetupTestSuite() {
+ auto params = ::android::getAidlHalInstanceNames(IKeyMintDevice::descriptor);
+ for (auto& param : params) {
+ ASSERT_TRUE(AServiceManager_isDeclared(param.c_str()))
+ << "IKeyMintDevice instance " << param << " found but not declared.";
+ ::ndk::SpAIBinder binder(AServiceManager_waitForService(param.c_str()));
+ auto keymint = IKeyMintDevice::fromBinder(binder);
+ ASSERT_NE(keymint, nullptr) << "Failed to get IKeyMintDevice instance " << param;
+
+ KeyMintHardwareInfo info;
+ ASSERT_TRUE(keymint->getHardwareInfo(&info).isOk());
+ ASSERT_EQ(keymints_.count(info.securityLevel), 0)
+ << "There must be exactly one IKeyMintDevice with security level "
+ << info.securityLevel;
+
+ keymints_[info.securityLevel] = std::move(keymint);
+ }
+ }
+
+ static map<SecurityLevel, shared_ptr<IKeyMintDevice>> keymints_;
+};
+
+map<SecurityLevel, shared_ptr<IKeyMintDevice>> SecureElementProvisioningTest::keymints_;
+
+TEST_F(SecureElementProvisioningTest, ValidConfigurations) {
+ // TEE is required
+ ASSERT_EQ(keymints_.count(SecurityLevel::TRUSTED_ENVIRONMENT), 1);
+ // StrongBox is optional
+ ASSERT_LE(keymints_.count(SecurityLevel::STRONGBOX), 1);
+}
+
+TEST_F(SecureElementProvisioningTest, TeeOnly) {
+ ASSERT_EQ(keymints_.count(SecurityLevel::TRUSTED_ENVIRONMENT), 1);
+ auto tee = keymints_.find(SecurityLevel::TRUSTED_ENVIRONMENT)->second;
+ ASSERT_NE(tee, nullptr);
+
+ array<uint8_t, 16> challenge1 = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ array<uint8_t, 16> challenge2 = {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+ vector<uint8_t> rootOfTrust1;
+ Status result = tee->getRootOfTrust(challenge1, &rootOfTrust1);
+
+ // TODO: Remove the next line to require TEEs to succeed.
+ if (!result.isOk()) return;
+
+ ASSERT_TRUE(result.isOk());
+
+ // TODO: Parse and validate rootOfTrust1 here
+
+ vector<uint8_t> rootOfTrust2;
+ result = tee->getRootOfTrust(challenge2, &rootOfTrust2);
+ ASSERT_TRUE(result.isOk());
+
+ // TODO: Parse and validate rootOfTrust2 here
+
+ ASSERT_NE(rootOfTrust1, rootOfTrust2);
+
+ vector<uint8_t> rootOfTrust3;
+ result = tee->getRootOfTrust(challenge1, &rootOfTrust3);
+ ASSERT_TRUE(result.isOk());
+
+ ASSERT_EQ(rootOfTrust1, rootOfTrust3);
+
+ // TODO: Parse and validate rootOfTrust3 here
+}
+
+TEST_F(SecureElementProvisioningTest, TeeDoesNotImplementStrongBoxMethods) {
+ ASSERT_EQ(keymints_.count(SecurityLevel::TRUSTED_ENVIRONMENT), 1);
+ auto tee = keymints_.find(SecurityLevel::TRUSTED_ENVIRONMENT)->second;
+ ASSERT_NE(tee, nullptr);
+
+ array<uint8_t, 16> challenge;
+ Status result = tee->getRootOfTrustChallenge(&challenge);
+ ASSERT_FALSE(result.isOk());
+ ASSERT_EQ(result.getExceptionCode(), EX_SERVICE_SPECIFIC);
+ ASSERT_EQ(static_cast<ErrorCode>(result.getServiceSpecificError()), ErrorCode::UNIMPLEMENTED);
+
+ result = tee->sendRootOfTrust({});
+ ASSERT_FALSE(result.isOk());
+ ASSERT_EQ(result.getExceptionCode(), EX_SERVICE_SPECIFIC);
+ ASSERT_EQ(static_cast<ErrorCode>(result.getServiceSpecificError()), ErrorCode::UNIMPLEMENTED);
+}
+
+TEST_F(SecureElementProvisioningTest, StrongBoxDoesNotImplementTeeMethods) {
+ if (keymints_.count(SecurityLevel::STRONGBOX) == 0) return;
+
+ auto sb = keymints_.find(SecurityLevel::STRONGBOX)->second;
+ ASSERT_NE(sb, nullptr);
+
+ vector<uint8_t> rootOfTrust;
+ Status result = sb->getRootOfTrust({}, &rootOfTrust);
+ ASSERT_FALSE(result.isOk());
+ ASSERT_EQ(result.getExceptionCode(), EX_SERVICE_SPECIFIC);
+ ASSERT_EQ(static_cast<ErrorCode>(result.getServiceSpecificError()), ErrorCode::UNIMPLEMENTED);
+}
+
+TEST_F(SecureElementProvisioningTest, UnimplementedTest) {
+ if (keymints_.count(SecurityLevel::STRONGBOX) == 0) return; // Need a StrongBox to provision.
+
+ ASSERT_EQ(keymints_.count(SecurityLevel::TRUSTED_ENVIRONMENT), 1);
+ auto tee = keymints_.find(SecurityLevel::TRUSTED_ENVIRONMENT)->second;
+ ASSERT_NE(tee, nullptr);
+
+ ASSERT_EQ(keymints_.count(SecurityLevel::STRONGBOX), 1);
+ auto sb = keymints_.find(SecurityLevel::STRONGBOX)->second;
+ ASSERT_NE(sb, nullptr);
+
+ array<uint8_t, 16> challenge;
+ Status result = sb->getRootOfTrustChallenge(&challenge);
+ if (!result.isOk()) {
+ // Strongbox does not have to implement this feature if it has uses an alternative mechanism
+ // to provision the root of trust. In that case it MUST return UNIMPLEMENTED, both from
+ // getRootOfTrustChallenge() and from sendRootOfTrust().
+ ASSERT_EQ(result.getExceptionCode(), EX_SERVICE_SPECIFIC);
+ ASSERT_EQ(static_cast<ErrorCode>(result.getServiceSpecificError()),
+ ErrorCode::UNIMPLEMENTED);
+
+ result = sb->sendRootOfTrust({});
+ ASSERT_EQ(result.getExceptionCode(), EX_SERVICE_SPECIFIC);
+ ASSERT_EQ(static_cast<ErrorCode>(result.getServiceSpecificError()),
+ ErrorCode::UNIMPLEMENTED);
+
+ SUCCEED() << "This Strongbox implementation does not use late root of trust delivery.";
+ return;
+ }
+}
+
+TEST_F(SecureElementProvisioningTest, ChallengeQualityTest) {
+ if (keymints_.count(SecurityLevel::STRONGBOX) == 0) return; // Need a StrongBox to provision.
+
+ ASSERT_EQ(keymints_.count(SecurityLevel::STRONGBOX), 1);
+ auto sb = keymints_.find(SecurityLevel::STRONGBOX)->second;
+ ASSERT_NE(sb, nullptr);
+
+ array<uint8_t, 16> challenge1;
+ Status result = sb->getRootOfTrustChallenge(&challenge1);
+ if (!result.isOk()) return;
+
+ array<uint8_t, 16> challenge2;
+ result = sb->getRootOfTrustChallenge(&challenge2);
+ ASSERT_TRUE(result.isOk());
+ ASSERT_NE(challenge1, challenge2);
+
+ // TODO: When we add entropy testing in other relevant places in these tests, add it here, too,
+ // to verify that challenges appear to have adequate entropy.
+}
+
+TEST_F(SecureElementProvisioningTest, ProvisioningTest) {
+ if (keymints_.count(SecurityLevel::STRONGBOX) == 0) return; // Need a StrongBox to provision.
+
+ ASSERT_EQ(keymints_.count(SecurityLevel::TRUSTED_ENVIRONMENT), 1);
+ auto tee = keymints_.find(SecurityLevel::TRUSTED_ENVIRONMENT)->second;
+ ASSERT_NE(tee, nullptr);
+
+ ASSERT_EQ(keymints_.count(SecurityLevel::STRONGBOX), 1);
+ auto sb = keymints_.find(SecurityLevel::STRONGBOX)->second;
+ ASSERT_NE(sb, nullptr);
+
+ array<uint8_t, 16> challenge;
+ Status result = sb->getRootOfTrustChallenge(&challenge);
+ if (!result.isOk()) return;
+
+ vector<uint8_t> rootOfTrust;
+ result = tee->getRootOfTrust(challenge, &rootOfTrust);
+ ASSERT_TRUE(result.isOk());
+
+ // TODO: Verify COSE_Mac0 structure and content here.
+
+ result = sb->sendRootOfTrust(rootOfTrust);
+ ASSERT_TRUE(result.isOk());
+
+ // Sending again must fail, because a new challenge is required.
+ result = sb->sendRootOfTrust(rootOfTrust);
+ ASSERT_FALSE(result.isOk());
+}
+
+TEST_F(SecureElementProvisioningTest, InvalidProvisioningTest) {
+ if (keymints_.count(SecurityLevel::STRONGBOX) == 0) return; // Need a StrongBox to provision.
+
+ ASSERT_EQ(keymints_.count(SecurityLevel::TRUSTED_ENVIRONMENT), 1);
+ auto tee = keymints_.find(SecurityLevel::TRUSTED_ENVIRONMENT)->second;
+ ASSERT_NE(tee, nullptr);
+
+ ASSERT_EQ(keymints_.count(SecurityLevel::STRONGBOX), 1);
+ auto sb = keymints_.find(SecurityLevel::STRONGBOX)->second;
+ ASSERT_NE(sb, nullptr);
+
+ array<uint8_t, 16> challenge;
+ Status result = sb->getRootOfTrustChallenge(&challenge);
+ if (!result.isOk()) return;
+
+ result = sb->sendRootOfTrust({});
+ ASSERT_FALSE(result.isOk());
+ ASSERT_EQ(result.getExceptionCode(), EX_SERVICE_SPECIFIC);
+ ASSERT_EQ(static_cast<ErrorCode>(result.getServiceSpecificError()),
+ ErrorCode::VERIFICATION_FAILED);
+
+ vector<uint8_t> rootOfTrust;
+ result = tee->getRootOfTrust(challenge, &rootOfTrust);
+ ASSERT_TRUE(result.isOk());
+
+ vector<uint8_t> corruptedRootOfTrust = rootOfTrust;
+ corruptedRootOfTrust[corruptedRootOfTrust.size() / 2]++;
+ result = sb->sendRootOfTrust(corruptedRootOfTrust);
+ ASSERT_FALSE(result.isOk());
+ ASSERT_EQ(result.getExceptionCode(), EX_SERVICE_SPECIFIC);
+ ASSERT_EQ(static_cast<ErrorCode>(result.getServiceSpecificError()),
+ ErrorCode::VERIFICATION_FAILED);
+
+ // Now try the correct RoT
+ result = sb->sendRootOfTrust(rootOfTrust);
+ ASSERT_TRUE(result.isOk());
+}
+
+} // namespace aidl::android::hardware::security::keymint::test