From f1f6215c09d050f5de2424d833663d3b2590fe52 Mon Sep 17 00:00:00 2001 From: Seth Moore Date: Tue, 13 Sep 2022 12:00:30 -0700 Subject: [PATCH 1/2] Move the device info validation to a helper library rkp_factory_extraction_tool now reuses the VTS logic for validating the DeviceInfo. This way, partners doing RKP testing can see locally if they are getting bad DeviceInfo before they try to upload the data to the google service. Test: atest VtsHalRemotelyProvisionedComponentTargetTest Test: rkp_factory_extraction_tool Bug: 239838563 Change-Id: I80fba3e624e1f5ab6da7aac889a0168f7cb8dbe4 --- .../VtsRemotelyProvisionedComponentTests.cpp | 124 +---------------- security/keymint/support/Android.bp | 1 + .../include/remote_prov/remote_prov_utils.h | 10 ++ .../keymint/support/remote_prov_utils.cpp | 131 ++++++++++++++++++ 4 files changed, 147 insertions(+), 119 deletions(-) diff --git a/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp b/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp index 2e282e0925..bafe1613bb 100644 --- a/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp +++ b/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include #define LOG_TAG "VtsRemotelyProvisionableComponentTests" #include @@ -58,26 +59,6 @@ using testing::MatchesRegex; using namespace remote_prov; using namespace keymaster; -std::set getAllowedVbStates() { - return {"green", "yellow", "orange"}; -} - -std::set getAllowedBootloaderStates() { - return {"locked", "unlocked"}; -} - -std::set getAllowedSecurityLevels() { - return {"tee", "strongbox"}; -} - -std::set getAllowedAttIdStates() { - return {"locked", "open"}; -} - -std::set getAttestationIdEntrySet() { - return {"brand", "manufacturer", "product", "model", "device"}; -} - bytevec string_to_bytevec(const char* s) { const uint8_t* p = reinterpret_cast(s); return bytevec(p, p + strlen(s)); @@ -433,12 +414,11 @@ class CertificateRequestTest : public VtsRemotelyProvisionedComponentTests { ASSERT_TRUE(bccContents) << "\n" << bccContents.message() << "\n" << prettyPrint(bcc.get()); ASSERT_GT(bccContents->size(), 0U); - auto [deviceInfoMap, __2, deviceInfoErrMsg] = cppbor::parse(deviceInfo.deviceInfo); - ASSERT_TRUE(deviceInfoMap) << "Failed to parse deviceInfo: " << deviceInfoErrMsg; - ASSERT_TRUE(deviceInfoMap->asMap()); - checkDeviceInfo(*deviceInfoMap->asMap(), deviceInfo.deviceInfo); + auto deviceInfoResult = + parseAndValidateDeviceInfo(deviceInfo.deviceInfo, provisionable_.get()); + ASSERT_TRUE(deviceInfoResult) << deviceInfoResult.message(); + std::unique_ptr deviceInfoMap = deviceInfoResult.moveValue(); auto& signingKey = bccContents->back().pubKey; - deviceInfoMap->asMap()->canonicalize(); auto macKey = verifyAndParseCoseSign1(signedMac->asArray(), signingKey, cppbor::Array() // SignedMacAad .add(challenge_) @@ -464,100 +444,6 @@ class CertificateRequestTest : public VtsRemotelyProvisionedComponentTests { } } - std::optional assertAttribute(const cppbor::Map& devInfo, - cppbor::MajorType majorType, std::string entryName) { - const auto& val = devInfo.get(entryName); - if (!val) return entryName + " is missing.\n"; - if (val->type() != majorType) return entryName + " has the wrong type.\n"; - switch (majorType) { - case cppbor::TSTR: - if (val->asTstr()->value().size() <= 0) { - return entryName + " is present but the value is empty.\n"; - } - break; - case cppbor::BSTR: - if (val->asBstr()->value().size() <= 0) { - return entryName + " is present but the value is empty.\n"; - } - break; - default: - break; - } - return {}; - } - - void checkType(const cppbor::Map& devInfo, cppbor::MajorType majorType, std::string entryName) { - if (auto error = assertAttribute(devInfo, majorType, entryName)) { - FAIL() << *error; - } - } - - void checkDeviceInfo(const cppbor::Map& deviceInfo, bytevec deviceInfoBytes) { - EXPECT_EQ(deviceInfo.clone()->asMap()->canonicalize().encode(), deviceInfoBytes) - << "DeviceInfo ordering is non-canonical."; - const auto& version = deviceInfo.get("version"); - ASSERT_TRUE(version); - ASSERT_TRUE(version->asUint()); - RpcHardwareInfo info; - provisionable_->getHardwareInfo(&info); - ASSERT_EQ(version->asUint()->value(), info.versionNumber); - std::set allowList; - std::string problemEntries; - switch (version->asUint()->value()) { - // These fields became mandated in version 2. - case 2: - for (auto attId : getAttestationIdEntrySet()) { - if (auto errMsg = assertAttribute(deviceInfo, cppbor::TSTR, attId)) { - problemEntries += *errMsg; - } - } - EXPECT_EQ("", problemEntries) - << problemEntries - << "Attestation IDs are missing or malprovisioned. If this test is being " - "run against an early proto or EVT build, this error is probably WAI " - "and indicates that Device IDs were not provisioned in the factory. If " - "this error is returned on a DVT or later build revision, then " - "something is likely wrong with the factory provisioning process."; - // TODO: Refactor the KeyMint code that validates these fields and include it here. - checkType(deviceInfo, cppbor::TSTR, "vb_state"); - allowList = getAllowedVbStates(); - EXPECT_NE(allowList.find(deviceInfo.get("vb_state")->asTstr()->value()), - allowList.end()); - checkType(deviceInfo, cppbor::TSTR, "bootloader_state"); - allowList = getAllowedBootloaderStates(); - EXPECT_NE(allowList.find(deviceInfo.get("bootloader_state")->asTstr()->value()), - allowList.end()); - checkType(deviceInfo, cppbor::BSTR, "vbmeta_digest"); - checkType(deviceInfo, cppbor::UINT, "system_patch_level"); - checkType(deviceInfo, cppbor::UINT, "boot_patch_level"); - checkType(deviceInfo, cppbor::UINT, "vendor_patch_level"); - checkType(deviceInfo, cppbor::UINT, "fused"); - EXPECT_LT(deviceInfo.get("fused")->asUint()->value(), 2); // Must be 0 or 1. - checkType(deviceInfo, cppbor::TSTR, "security_level"); - allowList = getAllowedSecurityLevels(); - EXPECT_NE(allowList.find(deviceInfo.get("security_level")->asTstr()->value()), - allowList.end()); - if (deviceInfo.get("security_level")->asTstr()->value() == "tee") { - checkType(deviceInfo, cppbor::TSTR, "os_version"); - } - break; - case 1: - checkType(deviceInfo, cppbor::TSTR, "security_level"); - allowList = getAllowedSecurityLevels(); - EXPECT_NE(allowList.find(deviceInfo.get("security_level")->asTstr()->value()), - allowList.end()); - if (version->asUint()->value() == 1) { - checkType(deviceInfo, cppbor::TSTR, "att_id_state"); - allowList = getAllowedAttIdStates(); - EXPECT_NE(allowList.find(deviceInfo.get("att_id_state")->asTstr()->value()), - allowList.end()); - } - break; - default: - FAIL() << "Unrecognized version: " << version->asUint()->value(); - } - } - bytevec eekId_; size_t testEekLength_; EekChain testEekChain_; diff --git a/security/keymint/support/Android.bp b/security/keymint/support/Android.bp index bf2ab02cb2..3f4832085c 100644 --- a/security/keymint/support/Android.bp +++ b/security/keymint/support/Android.bp @@ -65,6 +65,7 @@ cc_library { ], shared_libs: [ "libbase", + "libbinder_ndk", "libcppbor_external", "libcppcose_rkp", "libcrypto", diff --git a/security/keymint/support/include/remote_prov/remote_prov_utils.h b/security/keymint/support/include/remote_prov/remote_prov_utils.h index f3b8608b36..b1a6880a95 100644 --- a/security/keymint/support/include/remote_prov/remote_prov_utils.h +++ b/security/keymint/support/include/remote_prov/remote_prov_utils.h @@ -16,7 +16,9 @@ #pragma once +#include #include +#include "aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.h" #include @@ -139,4 +141,12 @@ struct JsonOutput { JsonOutput jsonEncodeCsrWithBuild(const std::string instance_name, const cppbor::Array& csr); +/** + * Parses a DeviceInfo structure from the given CBOR data. The parsed data is then validated to + * ensure it is formatted correctly and that it contains the required values for Remote Key + * Provisioning. + */ +ErrMsgOr> parseAndValidateDeviceInfo( + const std::vector& deviceInfoBytes, IRemotelyProvisionedComponent* provisionable); + } // namespace aidl::android::hardware::security::keymint::remote_prov diff --git a/security/keymint/support/remote_prov_utils.cpp b/security/keymint/support/remote_prov_utils.cpp index 0dbea5b68c..a553f16ed7 100644 --- a/security/keymint/support/remote_prov_utils.cpp +++ b/security/keymint/support/remote_prov_utils.cpp @@ -15,7 +15,11 @@ */ #include +#include +#include +#include #include +#include "aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.h" #include #include @@ -441,4 +445,131 @@ JsonOutput jsonEncodeCsrWithBuild(const std::string instance_name, const cppbor: return JsonOutput::Ok(Json::writeString(factory, json)); } +std::string checkMapEntry(const cppbor::Map& devInfo, cppbor::MajorType majorType, + const std::string& entryName) { + const std::unique_ptr& val = devInfo.get(entryName); + if (!val) { + return entryName + " is missing.\n"; + } + if (val->type() != majorType) { + return entryName + " has the wrong type.\n"; + } + switch (majorType) { + case cppbor::TSTR: + if (val->asTstr()->value().size() <= 0) { + return entryName + " is present but the value is empty.\n"; + } + break; + case cppbor::BSTR: + if (val->asBstr()->value().size() <= 0) { + return entryName + " is present but the value is empty.\n"; + } + break; + default: + break; + } + return ""; +} + +std::string checkMapEntry(const cppbor::Map& devInfo, cppbor::MajorType majorType, + const std::string& entryName, const cppbor::Array& allowList) { + std::string error = checkMapEntry(devInfo, majorType, entryName); + if (!error.empty()) { + return error; + } + + const std::unique_ptr& val = devInfo.get(entryName); + for (auto i = allowList.begin(); i != allowList.end(); ++i) { + if (**i == *val) { + return ""; + } + } + return entryName + " has an invalid value.\n"; +} + +ErrMsgOr> parseAndValidateDeviceInfo( + const std::vector& deviceInfoBytes, IRemotelyProvisionedComponent* provisionable) { + const cppbor::Array kAllowedVbStates = {"green", "yellow", "orange"}; + const cppbor::Array kAllowedBootloaderStates = {"locked", "unlocked"}; + const cppbor::Array kAllowedSecurityLevels = {"tee", "strongbox"}; + const cppbor::Array kAllowedAttIdStates = {"locked", "open"}; + const cppbor::Array kAllowedFused = {0, 1}; + + constexpr std::array kAttestationIdEntrySet = {"brand", "manufacturer", "product", "model", + "device"}; + + auto [parsedVerifiedDeviceInfo, ignore1, errMsg] = cppbor::parse(deviceInfoBytes); + if (!parsedVerifiedDeviceInfo) { + return errMsg; + } + + std::unique_ptr deviceInfo(parsedVerifiedDeviceInfo->asMap()); + if (!deviceInfo) { + return "DeviceInfo must be a CBOR map."; + } + parsedVerifiedDeviceInfo.release(); + + if (deviceInfo->clone()->asMap()->canonicalize().encode() != deviceInfoBytes) { + return "DeviceInfo ordering is non-canonical."; + } + const std::unique_ptr& version = deviceInfo->get("version"); + if (!version) { + return "Device info is missing version"; + } + if (!version->asUint()) { + return "version must be an unsigned integer"; + } + RpcHardwareInfo info; + provisionable->getHardwareInfo(&info); + if (version->asUint()->value() != info.versionNumber) { + return "DeviceInfo version (" + std::to_string(version->asUint()->value()) + + ") does not match the remotely provisioned component version (" + + std::to_string(info.versionNumber) + ")."; + } + std::string errorString; + switch (version->asUint()->value()) { + case 2: + for (const auto& attId : kAttestationIdEntrySet) { + errorString += checkMapEntry(*deviceInfo, cppbor::TSTR, attId); + } + if (!errorString.empty()) { + return errorString + + "Attestation IDs are missing or malprovisioned. If this test is being\n" + "run against an early proto or EVT build, this error is probably WAI\n" + "and indicates that Device IDs were not provisioned in the factory. If\n" + "this error is returned on a DVT or later build revision, then\n" + "something is likely wrong with the factory provisioning process."; + } + // TODO: Refactor the KeyMint code that validates these fields and include it here. + errorString += checkMapEntry(*deviceInfo, cppbor::TSTR, "vb_state", kAllowedVbStates); + errorString += checkMapEntry(*deviceInfo, cppbor::TSTR, "bootloader_state", + kAllowedBootloaderStates); + errorString += checkMapEntry(*deviceInfo, cppbor::BSTR, "vbmeta_digest"); + errorString += checkMapEntry(*deviceInfo, cppbor::UINT, "system_patch_level"); + errorString += checkMapEntry(*deviceInfo, cppbor::UINT, "boot_patch_level"); + errorString += checkMapEntry(*deviceInfo, cppbor::UINT, "vendor_patch_level"); + errorString += checkMapEntry(*deviceInfo, cppbor::UINT, "fused", kAllowedFused); + errorString += checkMapEntry(*deviceInfo, cppbor::TSTR, "security_level", + kAllowedSecurityLevels); + if (deviceInfo->get("security_level")->asTstr()->value() == "tee") { + errorString += checkMapEntry(*deviceInfo, cppbor::TSTR, "os_version"); + } + break; + case 1: + errorString += checkMapEntry(*deviceInfo, cppbor::TSTR, "security_level", + kAllowedSecurityLevels); + errorString += + checkMapEntry(*deviceInfo, cppbor::TSTR, "att_id_state", kAllowedAttIdStates); + break; + default: + return "Unrecognized version: " + std::to_string(version->asUint()->value()); + } + + if (!errorString.empty()) { + return errorString; + } + + return std::move(deviceInfo); +} + } // namespace aidl::android::hardware::security::keymint::remote_prov From 2fc6f83df6f42339ad45cae740a520c37a6d29a2 Mon Sep 17 00:00:00 2001 From: Seth Moore Date: Tue, 13 Sep 2022 16:10:11 -0700 Subject: [PATCH 2/2] Move verifyProtectedData into remote_prov_utils This way, rkp_factory_extraction_tool can reuse the code to perform a test on the factory line if a partner so chooses. Test: rkp_factory_extraction_tool --self_test Test: atest VtsHalRemotelyProvisionedComponentTargetTest Bug: 239839050 Change-Id: I3989ba606750be77f1945a50fe2307a631d19d11 --- .../VtsRemotelyProvisionedComponentTests.cpp | 105 ++------ .../include/remote_prov/remote_prov_utils.h | 34 ++- .../keymint/support/remote_prov_utils.cpp | 242 +++++++++++++++--- 3 files changed, 252 insertions(+), 129 deletions(-) diff --git a/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp b/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp index bafe1613bb..e1f65fec78 100644 --- a/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp +++ b/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp @@ -15,6 +15,7 @@ */ #include +#include #define LOG_TAG "VtsRemotelyProvisionableComponentTests" #include @@ -368,82 +369,6 @@ class CertificateRequestTest : public VtsRemotelyProvisionedComponentTests { } } - ErrMsgOr getSessionKey(ErrMsgOr>& senderPubkey) { - if (rpcHardwareInfo.supportedEekCurve == RpcHardwareInfo::CURVE_25519 || - rpcHardwareInfo.supportedEekCurve == RpcHardwareInfo::CURVE_NONE) { - return x25519_HKDF_DeriveKey(testEekChain_.last_pubkey, testEekChain_.last_privkey, - senderPubkey->first, false /* senderIsA */); - } else { - return ECDH_HKDF_DeriveKey(testEekChain_.last_pubkey, testEekChain_.last_privkey, - senderPubkey->first, false /* senderIsA */); - } - } - - void checkProtectedData(const DeviceInfo& deviceInfo, const cppbor::Array& keysToSign, - const bytevec& keysToSignMac, const ProtectedData& protectedData, - std::vector* bccOutput = nullptr) { - auto [parsedProtectedData, _, protDataErrMsg] = cppbor::parse(protectedData.protectedData); - ASSERT_TRUE(parsedProtectedData) << protDataErrMsg; - ASSERT_TRUE(parsedProtectedData->asArray()); - ASSERT_EQ(parsedProtectedData->asArray()->size(), kCoseEncryptEntryCount); - - auto senderPubkey = getSenderPubKeyFromCoseEncrypt(parsedProtectedData); - ASSERT_TRUE(senderPubkey) << senderPubkey.message(); - EXPECT_EQ(senderPubkey->second, eekId_); - - auto sessionKey = getSessionKey(senderPubkey); - ASSERT_TRUE(sessionKey) << sessionKey.message(); - - auto protectedDataPayload = - decryptCoseEncrypt(*sessionKey, parsedProtectedData.get(), bytevec{} /* aad */); - ASSERT_TRUE(protectedDataPayload) << protectedDataPayload.message(); - - auto [parsedPayload, __, payloadErrMsg] = cppbor::parse(*protectedDataPayload); - ASSERT_TRUE(parsedPayload) << "Failed to parse payload: " << payloadErrMsg; - ASSERT_TRUE(parsedPayload->asArray()); - // Strongbox may contain additional certificate chain. - EXPECT_LE(parsedPayload->asArray()->size(), 3U); - - auto& signedMac = parsedPayload->asArray()->get(0); - auto& bcc = parsedPayload->asArray()->get(1); - ASSERT_TRUE(signedMac && signedMac->asArray()); - ASSERT_TRUE(bcc && bcc->asArray()); - - // BCC is [ pubkey, + BccEntry] - auto bccContents = validateBcc(bcc->asArray()); - ASSERT_TRUE(bccContents) << "\n" << bccContents.message() << "\n" << prettyPrint(bcc.get()); - ASSERT_GT(bccContents->size(), 0U); - - auto deviceInfoResult = - parseAndValidateDeviceInfo(deviceInfo.deviceInfo, provisionable_.get()); - ASSERT_TRUE(deviceInfoResult) << deviceInfoResult.message(); - std::unique_ptr deviceInfoMap = deviceInfoResult.moveValue(); - auto& signingKey = bccContents->back().pubKey; - auto macKey = verifyAndParseCoseSign1(signedMac->asArray(), signingKey, - cppbor::Array() // SignedMacAad - .add(challenge_) - .add(std::move(deviceInfoMap)) - .add(keysToSignMac) - .encode()); - ASSERT_TRUE(macKey) << macKey.message(); - - auto coseMac0 = cppbor::Array() - .add(cppbor::Map() // protected - .add(ALGORITHM, HMAC_256) - .canonicalize() - .encode()) - .add(cppbor::Map()) // unprotected - .add(keysToSign.encode()) // payload (keysToSign) - .add(keysToSignMac); // tag - - auto macPayload = verifyAndParseCoseMac0(&coseMac0, *macKey); - ASSERT_TRUE(macPayload) << macPayload.message(); - - if (bccOutput) { - *bccOutput = std::move(*bccContents); - } - } - bytevec eekId_; size_t testEekLength_; EekChain testEekChain_; @@ -470,7 +395,10 @@ TEST_P(CertificateRequestTest, EmptyRequest_testMode) { &protectedData, &keysToSignMac); ASSERT_TRUE(status.isOk()) << status.getMessage(); - checkProtectedData(deviceInfo, cppbor::Array(), keysToSignMac, protectedData); + auto result = verifyProductionProtectedData( + deviceInfo, cppbor::Array(), keysToSignMac, protectedData, testEekChain_, eekId_, + rpcHardwareInfo.supportedEekCurve, provisionable_.get(), challenge_); + ASSERT_TRUE(result) << result.message(); } } @@ -492,22 +420,24 @@ TEST_P(CertificateRequestTest, NewKeyPerCallInTestMode) { &protectedData, &keysToSignMac); ASSERT_TRUE(status.isOk()) << status.getMessage(); - std::vector firstBcc; - checkProtectedData(deviceInfo, /*keysToSign=*/cppbor::Array(), keysToSignMac, protectedData, - &firstBcc); + auto firstBcc = verifyProductionProtectedData( + deviceInfo, /*keysToSign=*/cppbor::Array(), keysToSignMac, protectedData, testEekChain_, + eekId_, rpcHardwareInfo.supportedEekCurve, provisionable_.get(), challenge_); + ASSERT_TRUE(firstBcc) << firstBcc.message(); status = provisionable_->generateCertificateRequest( testMode, {} /* keysToSign */, testEekChain_.chain, challenge_, &deviceInfo, &protectedData, &keysToSignMac); ASSERT_TRUE(status.isOk()) << status.getMessage(); - std::vector secondBcc; - checkProtectedData(deviceInfo, /*keysToSign=*/cppbor::Array(), keysToSignMac, protectedData, - &secondBcc); + auto secondBcc = verifyProductionProtectedData( + deviceInfo, /*keysToSign=*/cppbor::Array(), keysToSignMac, protectedData, testEekChain_, + eekId_, rpcHardwareInfo.supportedEekCurve, provisionable_.get(), challenge_); + ASSERT_TRUE(secondBcc) << secondBcc.message(); // Verify that none of the keys in the first BCC are repeated in the second one. - for (const auto& i : firstBcc) { - for (auto& j : secondBcc) { + for (const auto& i : *firstBcc) { + for (auto& j : *secondBcc) { ASSERT_THAT(i.pubKey, testing::Not(testing::ElementsAreArray(j.pubKey))) << "Found a repeated pubkey in two generateCertificateRequest test mode calls"; } @@ -550,7 +480,10 @@ TEST_P(CertificateRequestTest, NonEmptyRequest_testMode) { &keysToSignMac); ASSERT_TRUE(status.isOk()) << status.getMessage(); - checkProtectedData(deviceInfo, cborKeysToSign_, keysToSignMac, protectedData); + auto result = verifyProductionProtectedData( + deviceInfo, cborKeysToSign_, keysToSignMac, protectedData, testEekChain_, eekId_, + rpcHardwareInfo.supportedEekCurve, provisionable_.get(), challenge_); + ASSERT_TRUE(result) << result.message(); } } diff --git a/security/keymint/support/include/remote_prov/remote_prov_utils.h b/security/keymint/support/include/remote_prov/remote_prov_utils.h index b1a6880a95..9ea20ace0e 100644 --- a/security/keymint/support/include/remote_prov/remote_prov_utils.h +++ b/security/keymint/support/include/remote_prov/remote_prov_utils.h @@ -143,10 +143,38 @@ JsonOutput jsonEncodeCsrWithBuild(const std::string instance_name, /** * Parses a DeviceInfo structure from the given CBOR data. The parsed data is then validated to - * ensure it is formatted correctly and that it contains the required values for Remote Key - * Provisioning. + * ensure it contains the minimum required data at the time of manufacturing. This is only a + * partial validation, as some fields may not be provisioned yet at the time this information + * is parsed in the manufacturing process. */ -ErrMsgOr> parseAndValidateDeviceInfo( +ErrMsgOr> parseAndValidateFactoryDeviceInfo( const std::vector& deviceInfoBytes, IRemotelyProvisionedComponent* provisionable); +/** + * Parses a DeviceInfo structure from the given CBOR data. The parsed data is then validated to + * ensure it is formatted correctly and that it contains the required values for Remote Key + * Provisioning. This is a full validation, and assumes the device is provisioned as if it is + * suitable for the end user. + */ +ErrMsgOr> parseAndValidateProductionDeviceInfo( + const std::vector& deviceInfoBytes, IRemotelyProvisionedComponent* provisionable); + +/** + * Verify the protected data as if the device is still early in the factory process and may not + * have all device identifiers provisioned yet. + */ +ErrMsgOr> verifyFactoryProtectedData( + const DeviceInfo& deviceInfo, const cppbor::Array& keysToSign, + const std::vector& keysToSignMac, const ProtectedData& protectedData, + const EekChain& eekChain, const std::vector& eekId, int32_t supportedEekCurve, + IRemotelyProvisionedComponent* provisionable, const std::vector& challenge); +/** + * Verify the protected data as if the device is a final production sample. + */ +ErrMsgOr> verifyProductionProtectedData( + const DeviceInfo& deviceInfo, const cppbor::Array& keysToSign, + const std::vector& keysToSignMac, const ProtectedData& protectedData, + const EekChain& eekChain, const std::vector& eekId, int32_t supportedEekCurve, + IRemotelyProvisionedComponent* provisionable, const std::vector& challenge); + } // namespace aidl::android::hardware::security::keymint::remote_prov diff --git a/security/keymint/support/remote_prov_utils.cpp b/security/keymint/support/remote_prov_utils.cpp index a553f16ed7..06514961da 100644 --- a/security/keymint/support/remote_prov_utils.cpp +++ b/security/keymint/support/remote_prov_utils.cpp @@ -445,7 +445,7 @@ JsonOutput jsonEncodeCsrWithBuild(const std::string instance_name, const cppbor: return JsonOutput::Ok(Json::writeString(factory, json)); } -std::string checkMapEntry(const cppbor::Map& devInfo, cppbor::MajorType majorType, +std::string checkMapEntry(bool isFactory, const cppbor::Map& devInfo, cppbor::MajorType majorType, const std::string& entryName) { const std::unique_ptr& val = devInfo.get(entryName); if (!val) { @@ -454,6 +454,9 @@ std::string checkMapEntry(const cppbor::Map& devInfo, cppbor::MajorType majorTyp if (val->type() != majorType) { return entryName + " has the wrong type.\n"; } + if (isFactory) { + return ""; + } switch (majorType) { case cppbor::TSTR: if (val->asTstr()->value().size() <= 0) { @@ -471,13 +474,17 @@ std::string checkMapEntry(const cppbor::Map& devInfo, cppbor::MajorType majorTyp return ""; } -std::string checkMapEntry(const cppbor::Map& devInfo, cppbor::MajorType majorType, +std::string checkMapEntry(bool isFactory, const cppbor::Map& devInfo, cppbor::MajorType majorType, const std::string& entryName, const cppbor::Array& allowList) { - std::string error = checkMapEntry(devInfo, majorType, entryName); + std::string error = checkMapEntry(isFactory, devInfo, majorType, entryName); if (!error.empty()) { return error; } + if (isFactory) { + return ""; + } + const std::unique_ptr& val = devInfo.get(entryName); for (auto i = allowList.begin(); i != allowList.end(); ++i) { if (**i == *val) { @@ -488,31 +495,39 @@ std::string checkMapEntry(const cppbor::Map& devInfo, cppbor::MajorType majorTyp } ErrMsgOr> parseAndValidateDeviceInfo( - const std::vector& deviceInfoBytes, IRemotelyProvisionedComponent* provisionable) { - const cppbor::Array kAllowedVbStates = {"green", "yellow", "orange"}; - const cppbor::Array kAllowedBootloaderStates = {"locked", "unlocked"}; - const cppbor::Array kAllowedSecurityLevels = {"tee", "strongbox"}; - const cppbor::Array kAllowedAttIdStates = {"locked", "open"}; - const cppbor::Array kAllowedFused = {0, 1}; + const std::vector& deviceInfoBytes, IRemotelyProvisionedComponent* provisionable, + bool isFactory) { + const cppbor::Array kValidVbStates = {"green", "yellow", "orange"}; + const cppbor::Array kValidBootloaderStates = {"locked", "unlocked"}; + const cppbor::Array kValidSecurityLevels = {"tee", "strongbox"}; + const cppbor::Array kValidAttIdStates = {"locked", "open"}; + const cppbor::Array kValidFused = {0, 1}; - constexpr std::array kAttestationIdEntrySet = {"brand", "manufacturer", "product", "model", - "device"}; + struct AttestationIdEntry { + const char* id; + bool alwaysValidate; + }; + constexpr AttestationIdEntry kAttestationIdEntrySet[] = {{"brand", false}, + {"manufacturer", true}, + {"product", false}, + {"model", false}, + {"device", false}}; auto [parsedVerifiedDeviceInfo, ignore1, errMsg] = cppbor::parse(deviceInfoBytes); if (!parsedVerifiedDeviceInfo) { return errMsg; } - std::unique_ptr deviceInfo(parsedVerifiedDeviceInfo->asMap()); - if (!deviceInfo) { + std::unique_ptr parsed(parsedVerifiedDeviceInfo->asMap()); + if (!parsed) { return "DeviceInfo must be a CBOR map."; } parsedVerifiedDeviceInfo.release(); - if (deviceInfo->clone()->asMap()->canonicalize().encode() != deviceInfoBytes) { + if (parsed->clone()->asMap()->canonicalize().encode() != deviceInfoBytes) { return "DeviceInfo ordering is non-canonical."; } - const std::unique_ptr& version = deviceInfo->get("version"); + const std::unique_ptr& version = parsed->get("version"); if (!version) { return "Device info is missing version"; } @@ -526,14 +541,15 @@ ErrMsgOr> parseAndValidateDeviceInfo( ") does not match the remotely provisioned component version (" + std::to_string(info.versionNumber) + ")."; } - std::string errorString; + std::string error; switch (version->asUint()->value()) { case 2: - for (const auto& attId : kAttestationIdEntrySet) { - errorString += checkMapEntry(*deviceInfo, cppbor::TSTR, attId); + for (const auto& entry : kAttestationIdEntrySet) { + error += checkMapEntry(isFactory && !entry.alwaysValidate, *parsed, cppbor::TSTR, + entry.id); } - if (!errorString.empty()) { - return errorString + + if (!error.empty()) { + return error + "Attestation IDs are missing or malprovisioned. If this test is being\n" "run against an early proto or EVT build, this error is probably WAI\n" "and indicates that Device IDs were not provisioned in the factory. If\n" @@ -541,35 +557,181 @@ ErrMsgOr> parseAndValidateDeviceInfo( "something is likely wrong with the factory provisioning process."; } // TODO: Refactor the KeyMint code that validates these fields and include it here. - errorString += checkMapEntry(*deviceInfo, cppbor::TSTR, "vb_state", kAllowedVbStates); - errorString += checkMapEntry(*deviceInfo, cppbor::TSTR, "bootloader_state", - kAllowedBootloaderStates); - errorString += checkMapEntry(*deviceInfo, cppbor::BSTR, "vbmeta_digest"); - errorString += checkMapEntry(*deviceInfo, cppbor::UINT, "system_patch_level"); - errorString += checkMapEntry(*deviceInfo, cppbor::UINT, "boot_patch_level"); - errorString += checkMapEntry(*deviceInfo, cppbor::UINT, "vendor_patch_level"); - errorString += checkMapEntry(*deviceInfo, cppbor::UINT, "fused", kAllowedFused); - errorString += checkMapEntry(*deviceInfo, cppbor::TSTR, "security_level", - kAllowedSecurityLevels); - if (deviceInfo->get("security_level")->asTstr()->value() == "tee") { - errorString += checkMapEntry(*deviceInfo, cppbor::TSTR, "os_version"); + error += checkMapEntry(isFactory, *parsed, cppbor::TSTR, "vb_state", kValidVbStates); + error += checkMapEntry(isFactory, *parsed, cppbor::TSTR, "bootloader_state", + kValidBootloaderStates); + error += checkMapEntry(isFactory, *parsed, cppbor::BSTR, "vbmeta_digest"); + error += checkMapEntry(isFactory, *parsed, cppbor::UINT, "system_patch_level"); + error += checkMapEntry(isFactory, *parsed, cppbor::UINT, "boot_patch_level"); + error += checkMapEntry(isFactory, *parsed, cppbor::UINT, "vendor_patch_level"); + error += checkMapEntry(isFactory, *parsed, cppbor::UINT, "fused", kValidFused); + error += checkMapEntry(isFactory, *parsed, cppbor::TSTR, "security_level", + kValidSecurityLevels); + if (parsed->get("security_level") && parsed->get("security_level")->asTstr() && + parsed->get("security_level")->asTstr()->value() == "tee") { + error += checkMapEntry(isFactory, *parsed, cppbor::TSTR, "os_version"); } break; case 1: - errorString += checkMapEntry(*deviceInfo, cppbor::TSTR, "security_level", - kAllowedSecurityLevels); - errorString += - checkMapEntry(*deviceInfo, cppbor::TSTR, "att_id_state", kAllowedAttIdStates); + error += checkMapEntry(isFactory, *parsed, cppbor::TSTR, "security_level", + kValidSecurityLevels); + error += checkMapEntry(isFactory, *parsed, cppbor::TSTR, "att_id_state", + kValidAttIdStates); break; default: return "Unrecognized version: " + std::to_string(version->asUint()->value()); } - if (!errorString.empty()) { - return errorString; + if (!error.empty()) { + return error; } - return std::move(deviceInfo); + return std::move(parsed); } -} // namespace aidl::android::hardware::security::keymint::remote_prov +ErrMsgOr> parseAndValidateFactoryDeviceInfo( + const std::vector& deviceInfoBytes, IRemotelyProvisionedComponent* provisionable) { + return parseAndValidateDeviceInfo(deviceInfoBytes, provisionable, /*isFactory=*/true); +} + +ErrMsgOr> parseAndValidateProductionDeviceInfo( + const std::vector& deviceInfoBytes, IRemotelyProvisionedComponent* provisionable) { + return parseAndValidateDeviceInfo(deviceInfoBytes, provisionable, /*isFactory=*/false); +} + +ErrMsgOr getSessionKey(ErrMsgOr>& senderPubkey, + const EekChain& eekChain, int32_t supportedEekCurve) { + if (supportedEekCurve == RpcHardwareInfo::CURVE_25519 || + supportedEekCurve == RpcHardwareInfo::CURVE_NONE) { + return x25519_HKDF_DeriveKey(eekChain.last_pubkey, eekChain.last_privkey, + senderPubkey->first, false /* senderIsA */); + } else { + return ECDH_HKDF_DeriveKey(eekChain.last_pubkey, eekChain.last_privkey, senderPubkey->first, + false /* senderIsA */); + } +} + +ErrMsgOr> verifyProtectedData( + const DeviceInfo& deviceInfo, const cppbor::Array& keysToSign, + const std::vector& keysToSignMac, const ProtectedData& protectedData, + const EekChain& eekChain, const std::vector& eekId, int32_t supportedEekCurve, + IRemotelyProvisionedComponent* provisionable, const std::vector& challenge, + bool isFactory) { + auto [parsedProtectedData, _, protDataErrMsg] = cppbor::parse(protectedData.protectedData); + if (!parsedProtectedData) { + return protDataErrMsg; + } + if (!parsedProtectedData->asArray()) { + return "Protected data is not a CBOR array."; + } + if (parsedProtectedData->asArray()->size() != kCoseEncryptEntryCount) { + return "The protected data COSE_encrypt structure must have " + + std::to_string(kCoseEncryptEntryCount) + " entries, but it only has " + + std::to_string(parsedProtectedData->asArray()->size()); + } + + auto senderPubkey = getSenderPubKeyFromCoseEncrypt(parsedProtectedData); + if (!senderPubkey) { + return senderPubkey.message(); + } + if (senderPubkey->second != eekId) { + return "The COSE_encrypt recipient does not match the expected EEK identifier"; + } + + auto sessionKey = getSessionKey(senderPubkey, eekChain, supportedEekCurve); + if (!sessionKey) { + return sessionKey.message(); + } + + auto protectedDataPayload = + decryptCoseEncrypt(*sessionKey, parsedProtectedData.get(), bytevec{} /* aad */); + if (!protectedDataPayload) { + return protectedDataPayload.message(); + } + + auto [parsedPayload, __, payloadErrMsg] = cppbor::parse(*protectedDataPayload); + if (!parsedPayload) { + return "Failed to parse payload: " + payloadErrMsg; + } + if (!parsedPayload->asArray()) { + return "The protected data payload must be an Array."; + } + if (parsedPayload->asArray()->size() != 3U && parsedPayload->asArray()->size() != 2U) { + return "The protected data payload must contain SignedMAC and BCC. It may optionally " + "contain AdditionalDKSignatures. However, the parsed payload has " + + std::to_string(parsedPayload->asArray()->size()) + " entries."; + } + + auto& signedMac = parsedPayload->asArray()->get(0); + auto& bcc = parsedPayload->asArray()->get(1); + if (!signedMac->asArray()) { + return "The SignedMAC in the protected data payload is not an Array."; + } + if (!bcc->asArray()) { + return "The BCC in the protected data payload is not an Array."; + } + + // BCC is [ pubkey, + BccEntry] + auto bccContents = validateBcc(bcc->asArray()); + if (!bccContents) { + return bccContents.message() + "\n" + prettyPrint(bcc.get()); + } + if (bccContents->size() == 0U) { + return "The BCC is empty. It must contain at least one entry."; + } + + auto deviceInfoResult = + parseAndValidateDeviceInfo(deviceInfo.deviceInfo, provisionable, isFactory); + if (!deviceInfoResult) { + return deviceInfoResult.message(); + } + std::unique_ptr deviceInfoMap = deviceInfoResult.moveValue(); + auto& signingKey = bccContents->back().pubKey; + auto macKey = verifyAndParseCoseSign1(signedMac->asArray(), signingKey, + cppbor::Array() // SignedMacAad + .add(challenge) + .add(std::move(deviceInfoMap)) + .add(keysToSignMac) + .encode()); + if (!macKey) { + return macKey.message(); + } + + auto coseMac0 = cppbor::Array() + .add(cppbor::Map() // protected + .add(ALGORITHM, HMAC_256) + .canonicalize() + .encode()) + .add(cppbor::Map()) // unprotected + .add(keysToSign.encode()) // payload (keysToSign) + .add(keysToSignMac); // tag + + auto macPayload = verifyAndParseCoseMac0(&coseMac0, *macKey); + if (!macPayload) { + return macPayload.message(); + } + + return *bccContents; +} + +ErrMsgOr> verifyFactoryProtectedData( + const DeviceInfo& deviceInfo, const cppbor::Array& keysToSign, + const std::vector& keysToSignMac, const ProtectedData& protectedData, + const EekChain& eekChain, const std::vector& eekId, int32_t supportedEekCurve, + IRemotelyProvisionedComponent* provisionable, const std::vector& challenge) { + return verifyProtectedData(deviceInfo, keysToSign, keysToSignMac, protectedData, eekChain, + eekId, supportedEekCurve, provisionable, challenge, + /*isFactory=*/true); +} + +ErrMsgOr> verifyProductionProtectedData( + const DeviceInfo& deviceInfo, const cppbor::Array& keysToSign, + const std::vector& keysToSignMac, const ProtectedData& protectedData, + const EekChain& eekChain, const std::vector& eekId, int32_t supportedEekCurve, + IRemotelyProvisionedComponent* provisionable, const std::vector& challenge) { + return verifyProtectedData(deviceInfo, keysToSign, keysToSignMac, protectedData, eekChain, + eekId, supportedEekCurve, provisionable, challenge, + /*isFactory=*/false); +} + +} // namespace aidl::android::hardware::security::keymint::remote_prov \ No newline at end of file