From 0cc617cab04a93ec64a865ba4c96113204e71a13 Mon Sep 17 00:00:00 2001 From: Shawn Willden Date: Fri, 11 Feb 2022 12:51:53 -0700 Subject: [PATCH] Provide alternate SE RoT provisioning path. On some devices it is infeasible to provision the KeyMint RoT bits in the Android Bootloader. This provides an alternate path to provision them from the TEE during early boot. Bug: 219076736 Test: VtsAidlKeyMintTargetTest Change-Id: If69f7e25e58edbf4d2190084e2c0a03a94bfa5d6 Merged-In: If69f7e25e58edbf4d2190084e2c0a03a94bfa5d6 --- .../security/keymint/IKeyMintDevice.aidl | 3 + .../security/keymint/IKeyMintDevice.aidl | 78 ++++++ .../keymint/aidl/vts/functional/Android.bp | 1 + .../SecureElementProvisioningTest.cpp | 255 ++++++++++++++++++ 4 files changed, 337 insertions(+) create mode 100644 security/keymint/aidl/vts/functional/SecureElementProvisioningTest.cpp 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 fa643fc494..dcc22c4ba7 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 @@ interface IKeyMintDevice { 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 4b637993f1..da02d54662 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 @@ interface IKeyMintDevice { */ 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 91db3c8954..1616e652de 100644 --- a/security/keymint/aidl/vts/functional/Android.bp +++ b/security/keymint/aidl/vts/functional/Android.bp @@ -54,6 +54,7 @@ cc_test { "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 0000000000..e16a47b0c1 --- /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 +#include +#include + +#include +#include + +#include +#include +#include + +#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> keymints_; +}; + +map> 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 challenge1 = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + array challenge2 = {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + + vector 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 rootOfTrust2; + result = tee->getRootOfTrust(challenge2, &rootOfTrust2); + ASSERT_TRUE(result.isOk()); + + // TODO: Parse and validate rootOfTrust2 here + + ASSERT_NE(rootOfTrust1, rootOfTrust2); + + vector 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 challenge; + Status result = tee->getRootOfTrustChallenge(&challenge); + ASSERT_FALSE(result.isOk()); + ASSERT_EQ(result.getExceptionCode(), EX_SERVICE_SPECIFIC); + ASSERT_EQ(static_cast(result.getServiceSpecificError()), ErrorCode::UNIMPLEMENTED); + + result = tee->sendRootOfTrust({}); + ASSERT_FALSE(result.isOk()); + ASSERT_EQ(result.getExceptionCode(), EX_SERVICE_SPECIFIC); + ASSERT_EQ(static_cast(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 rootOfTrust; + Status result = sb->getRootOfTrust({}, &rootOfTrust); + ASSERT_FALSE(result.isOk()); + ASSERT_EQ(result.getExceptionCode(), EX_SERVICE_SPECIFIC); + ASSERT_EQ(static_cast(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 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(result.getServiceSpecificError()), + ErrorCode::UNIMPLEMENTED); + + result = sb->sendRootOfTrust({}); + ASSERT_EQ(result.getExceptionCode(), EX_SERVICE_SPECIFIC); + ASSERT_EQ(static_cast(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 challenge1; + Status result = sb->getRootOfTrustChallenge(&challenge1); + if (!result.isOk()) return; + + array 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 challenge; + Status result = sb->getRootOfTrustChallenge(&challenge); + if (!result.isOk()) return; + + vector 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 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(result.getServiceSpecificError()), + ErrorCode::VERIFICATION_FAILED); + + vector rootOfTrust; + result = tee->getRootOfTrust(challenge, &rootOfTrust); + ASSERT_TRUE(result.isOk()); + + vector 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(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