From 0d6204e2b7a6f15367df41e06d20ab231c6498cc Mon Sep 17 00:00:00 2001 From: Tri Vo Date: Thu, 29 Sep 2022 16:15:34 -0700 Subject: [PATCH] Update VtsHalRemotelyProvisionedComponentTargetTest to v3 Bug: 235265072 Test: atest VtsHalRemotelyProvisionedComponentTargetTest Change-Id: I01e387a0784c3548a4661a73d7bd3d5bec9fb42e --- .../VtsRemotelyProvisionedComponentTests.cpp | 163 +++++++++- .../include/remote_prov/remote_prov_utils.h | 15 + .../keymint/support/remote_prov_utils.cpp | 296 +++++++++++++++++- 3 files changed, 453 insertions(+), 21 deletions(-) diff --git a/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp b/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp index 9b21e4e98b..4f361bb464 100644 --- a/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp +++ b/security/keymint/aidl/vts/functional/VtsRemotelyProvisionedComponentTests.cpp @@ -46,6 +46,7 @@ using ::std::vector; namespace { constexpr int32_t VERSION_WITH_UNIQUE_ID_SUPPORT = 2; +constexpr int32_t VERSION_WITHOUT_TEST_MODE = 3; #define INSTANTIATE_REM_PROV_AIDL_TEST(name) \ GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(name); \ @@ -180,6 +181,15 @@ class VtsRemotelyProvisionedComponentTests : public testing::TestWithParam* payload_value) { + if (rpcHardwareInfo.versionNumber >= VERSION_WITHOUT_TEST_MODE) { + check_maced_pubkey(macedPubKey, false, payload_value); + } else { + check_maced_pubkey(macedPubKey, testMode, payload_value); + } + } + protected: std::shared_ptr provisionable_; RpcHardwareInfo rpcHardwareInfo; @@ -256,7 +266,7 @@ TEST_P(GenerateKeyTests, generateEcdsaP256Key_prodMode) { auto status = provisionable_->generateEcdsaP256KeyPair(testMode, &macedPubKey, &privateKeyBlob); ASSERT_TRUE(status.isOk()); vector coseKeyData; - check_maced_pubkey(macedPubKey, testMode, &coseKeyData); + checkMacedPubkeyVersioned(macedPubKey, testMode, &coseKeyData); } /** @@ -279,7 +289,7 @@ TEST_P(GenerateKeyTests, generateAndUseEcdsaP256Key_prodMode) { auto status = provisionable_->generateEcdsaP256KeyPair(testMode, &macedPubKey, &privateKeyBlob); ASSERT_TRUE(status.isOk()); vector coseKeyData; - check_maced_pubkey(macedPubKey, testMode, &coseKeyData); + checkMacedPubkeyVersioned(macedPubKey, testMode, &coseKeyData); AttestationKey attestKey; attestKey.keyBlob = std::move(privateKeyBlob); @@ -334,13 +344,13 @@ TEST_P(GenerateKeyTests, generateEcdsaP256Key_testMode) { bool testMode = true; auto status = provisionable_->generateEcdsaP256KeyPair(testMode, &macedPubKey, &privateKeyBlob); ASSERT_TRUE(status.isOk()); - - check_maced_pubkey(macedPubKey, testMode, nullptr); + checkMacedPubkeyVersioned(macedPubKey, testMode, nullptr); } -class CertificateRequestTest : public VtsRemotelyProvisionedComponentTests { +class CertificateRequestTestBase : public VtsRemotelyProvisionedComponentTests { protected: - CertificateRequestTest() : eekId_(string_to_bytevec("eekid")), challenge_(randomBytes(64)) {} + CertificateRequestTestBase() + : eekId_(string_to_bytevec("eekid")), challenge_(randomBytes(64)) {} void generateTestEekChain(size_t eekLength) { auto chain = generateEekChain(rpcHardwareInfo.supportedEekCurve, eekLength, eekId_); @@ -359,7 +369,7 @@ class CertificateRequestTest : public VtsRemotelyProvisionedComponentTests { ASSERT_TRUE(status.isOk()) << status.getMessage(); vector payload_value; - check_maced_pubkey(key, testMode, &payload_value); + checkMacedPubkeyVersioned(key, testMode, &payload_value); cborKeysToSign_.add(cppbor::EncodedItem(payload_value)); } } @@ -372,6 +382,18 @@ class CertificateRequestTest : public VtsRemotelyProvisionedComponentTests { cppbor::Array cborKeysToSign_; }; +class CertificateRequestTest : public CertificateRequestTestBase { + protected: + void SetUp() override { + CertificateRequestTestBase::SetUp(); + + if (rpcHardwareInfo.versionNumber >= VERSION_WITHOUT_TEST_MODE) { + GTEST_SKIP() << "This test case only applies to RKP v1 and v2. " + << "RKP version discovered: " << rpcHardwareInfo.versionNumber; + } + } +}; + /** * Generate an empty certificate request in test mode, and decrypt and verify the structure and * content. @@ -638,4 +660,131 @@ TEST_P(CertificateRequestTest, NonEmptyRequest_testKeyInProdCert) { INSTANTIATE_REM_PROV_AIDL_TEST(CertificateRequestTest); +class CertificateRequestV2Test : public CertificateRequestTestBase { + void SetUp() override { + CertificateRequestTestBase::SetUp(); + + if (rpcHardwareInfo.versionNumber < VERSION_WITHOUT_TEST_MODE) { + GTEST_SKIP() << "This test case only applies to RKP v3 and above. " + << "RKP version discovered: " << rpcHardwareInfo.versionNumber; + } + } +}; + +/** + * Generate an empty certificate request, and decrypt and verify the structure and content. + */ +TEST_P(CertificateRequestV2Test, EmptyRequest) { + bytevec csr; + + auto status = + provisionable_->generateCertificateRequestV2({} /* keysToSign */, challenge_, &csr); + ASSERT_TRUE(status.isOk()) << status.getMessage(); + + auto result = verifyProductionCsr(cppbor::Array(), csr, provisionable_.get(), challenge_); + ASSERT_TRUE(result) << result.message(); +} + +/** + * Generate a non-empty certificate request. Decrypt, parse and validate the contents. + */ +TEST_P(CertificateRequestV2Test, NonEmptyRequest) { + generateKeys(false /* testMode */, 1 /* numKeys */); + + bytevec csr; + + auto status = provisionable_->generateCertificateRequestV2(keysToSign_, challenge_, &csr); + ASSERT_TRUE(status.isOk()) << status.getMessage(); + + auto result = verifyProductionCsr(cborKeysToSign_, csr, provisionable_.get(), challenge_); + ASSERT_TRUE(result) << result.message(); +} + +/** + * Generate a non-empty certificate request. Make sure contents are reproducible. + */ +TEST_P(CertificateRequestV2Test, NonEmptyRequestReproducible) { + generateKeys(false /* testMode */, 1 /* numKeys */); + + bytevec csr; + + auto status = provisionable_->generateCertificateRequestV2(keysToSign_, challenge_, &csr); + ASSERT_TRUE(status.isOk()) << status.getMessage(); + + auto firstBcc = verifyProductionCsr(cborKeysToSign_, csr, provisionable_.get(), challenge_); + ASSERT_TRUE(firstBcc) << firstBcc.message(); + + status = provisionable_->generateCertificateRequestV2(keysToSign_, challenge_, &csr); + ASSERT_TRUE(status.isOk()) << status.getMessage(); + + auto secondBcc = verifyProductionCsr(cborKeysToSign_, csr, provisionable_.get(), challenge_); + ASSERT_TRUE(secondBcc) << secondBcc.message(); + + ASSERT_EQ(firstBcc->size(), secondBcc->size()); + for (auto i = 0; i < firstBcc->size(); i++) { + ASSERT_EQ(firstBcc->at(i).pubKey, secondBcc->at(i).pubKey); + } +} + +/** + * Generate a non-empty certificate request with multiple keys. + */ +TEST_P(CertificateRequestV2Test, NonEmptyRequestMultipleKeys) { + // TODO(b/254137722): define a minimum number of keys that must be supported. + generateKeys(false /* testMode */, 5 /* numKeys */); + + bytevec csr; + + auto status = provisionable_->generateCertificateRequestV2(keysToSign_, challenge_, &csr); + ASSERT_TRUE(status.isOk()) << status.getMessage(); + + auto result = verifyProductionCsr(cborKeysToSign_, csr, provisionable_.get(), challenge_); + ASSERT_TRUE(result) << result.message(); +} + +/** + * Generate a non-empty certificate request, but with the MAC corrupted on the keypair. + */ +TEST_P(CertificateRequestV2Test, NonEmptyRequestCorruptMac) { + generateKeys(false /* testMode */, 1 /* numKeys */); + auto result = corrupt_maced_key(keysToSign_[0]); + ASSERT_TRUE(result) << result.moveMessage(); + MacedPublicKey keyWithCorruptMac = result.moveValue(); + + bytevec csr; + auto status = + provisionable_->generateCertificateRequestV2({keyWithCorruptMac}, challenge_, &csr); + ASSERT_FALSE(status.isOk()) << status.getMessage(); + EXPECT_EQ(status.getServiceSpecificError(), BnRemotelyProvisionedComponent::STATUS_INVALID_MAC); +} + +/** + * Generate a non-empty certificate request in prod mode, with test keys. Test mode must be + * ignored, i.e. test must pass. + */ +TEST_P(CertificateRequestV2Test, NonEmptyRequest_testKeyInProdCert) { + generateKeys(true /* testMode */, 1 /* numKeys */); + + bytevec csr; + auto status = provisionable_->generateCertificateRequestV2(keysToSign_, challenge_, &csr); + ASSERT_TRUE(status.isOk()) << status.getMessage(); +} + +/** + * Call generateCertificateRequest(). Make sure it's removed. + */ +TEST_P(CertificateRequestV2Test, CertificateRequestV1Removed) { + generateTestEekChain(2); + bytevec keysToSignMac; + DeviceInfo deviceInfo; + ProtectedData protectedData; + auto status = provisionable_->generateCertificateRequest( + true /* testMode */, {} /* keysToSign */, testEekChain_.chain, challenge_, &deviceInfo, + &protectedData, &keysToSignMac); + ASSERT_FALSE(status.isOk()) << status.getMessage(); + EXPECT_EQ(status.getServiceSpecificError(), BnRemotelyProvisionedComponent::STATUS_REMOVED); +} + +INSTANTIATE_REM_PROV_AIDL_TEST(CertificateRequestV2Test); + } // namespace aidl::android::hardware::security::keymint::test 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 9ea20ace0e..6871e1b272 100644 --- a/security/keymint/support/include/remote_prov/remote_prov_utils.h +++ b/security/keymint/support/include/remote_prov/remote_prov_utils.h @@ -177,4 +177,19 @@ ErrMsgOr> verifyProductionProtectedData( const EekChain& eekChain, const std::vector& eekId, int32_t supportedEekCurve, IRemotelyProvisionedComponent* provisionable, const std::vector& challenge); +/** + * Verify the CSR as if the device is still early in the factory process and may not + * have all device identifiers provisioned yet. + */ +ErrMsgOr> verifyFactoryCsr(const cppbor::Array& keysToSign, + const std::vector& csr, + IRemotelyProvisionedComponent* provisionable, + const std::vector& challenge); +/** + * Verify the CSR as if the device is a final production sample. + */ +ErrMsgOr> verifyProductionCsr( + const cppbor::Array& keysToSign, const std::vector& csr, + 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 06514961da..f7ab3ac6fd 100644 --- a/security/keymint/support/remote_prov_utils.cpp +++ b/security/keymint/support/remote_prov_utils.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include namespace aidl::android::hardware::security::keymint::remote_prov { @@ -45,6 +46,8 @@ constexpr int kP256AffinePointSize = 32; using EC_KEY_Ptr = bssl::UniquePtr; using EVP_PKEY_Ptr = bssl::UniquePtr; using EVP_PKEY_CTX_Ptr = bssl::UniquePtr; +using X509_Ptr = bssl::UniquePtr; +using CRYPTO_BUFFER_Ptr = bssl::UniquePtr; ErrMsgOr ecKeyGetPrivateKey(const EC_KEY* ecKey) { // Extract private key. @@ -527,22 +530,27 @@ ErrMsgOr> parseAndValidateDeviceInfo( if (parsed->clone()->asMap()->canonicalize().encode() != deviceInfoBytes) { return "DeviceInfo ordering is non-canonical."; } - const std::unique_ptr& version = parsed->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) + ")."; + if (info.versionNumber < 3) { + const std::unique_ptr& version = parsed->get("version"); + if (!version) { + return "Device info is missing version"; + } + if (!version->asUint()) { + return "version must be an unsigned integer"; + } + 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 error; - switch (version->asUint()->value()) { + switch (info.versionNumber) { + case 3: case 2: for (const auto& entry : kAttestationIdEntrySet) { error += checkMapEntry(isFactory && !entry.alwaysValidate, *parsed, cppbor::TSTR, @@ -579,7 +587,7 @@ ErrMsgOr> parseAndValidateDeviceInfo( kValidAttIdStates); break; default: - return "Unrecognized version: " + std::to_string(version->asUint()->value()); + return "Unrecognized version: " + std::to_string(info.versionNumber); } if (!error.empty()) { @@ -734,4 +742,264 @@ ErrMsgOr> verifyProductionProtectedData( /*isFactory=*/false); } -} // namespace aidl::android::hardware::security::keymint::remote_prov \ No newline at end of file +ErrMsgOr parseX509Cert(const std::vector& cert) { + CRYPTO_BUFFER_Ptr certBuf(CRYPTO_BUFFER_new(cert.data(), cert.size(), nullptr)); + if (!certBuf.get()) { + return "Failed to create crypto buffer."; + } + X509_Ptr result(X509_parse_from_buffer(certBuf.get())); + if (!result.get()) { + return "Failed to parse certificate."; + } + return result; +} + +std::string getX509IssuerName(const X509_Ptr& cert) { + char* name = X509_NAME_oneline(X509_get_issuer_name(cert.get()), nullptr, 0); + std::string result(name); + OPENSSL_free(name); + return result; +} + +std::string getX509SubjectName(const X509_Ptr& cert) { + char* name = X509_NAME_oneline(X509_get_subject_name(cert.get()), nullptr, 0); + std::string result(name); + OPENSSL_free(name); + return result; +} + +// Validates the certificate chain and returns the leaf public key. +ErrMsgOr validateCertChain(const cppbor::Array& chain) { + uint8_t rawPubKey[64]; + size_t rawPubKeySize = sizeof(rawPubKey); + for (size_t i = 0; i < chain.size(); ++i) { + // Root must be self-signed. + size_t signingCertIndex = (i > 1) ? i - 1 : i; + auto& keyCertItem = chain[i]; + auto& signingCertItem = chain[signingCertIndex]; + if (!keyCertItem || !keyCertItem->asBstr()) { + return "Key certificate must be a Bstr."; + } + if (!signingCertItem || !signingCertItem->asBstr()) { + return "Signing certificate must be a Bstr."; + } + + auto keyCert = parseX509Cert(keyCertItem->asBstr()->value()); + if (!keyCert) { + return keyCert.message(); + } + auto signingCert = parseX509Cert(keyCertItem->asBstr()->value()); + if (!signingCert) { + return signingCert.message(); + } + + EVP_PKEY_Ptr pubKey(X509_get_pubkey(keyCert->get())); + if (!pubKey.get()) { + return "Failed to get public key."; + } + EVP_PKEY_Ptr signingPubKey(X509_get_pubkey(signingCert->get())); + if (!signingPubKey.get()) { + return "Failed to get signing public key."; + } + + if (!X509_verify(keyCert->get(), signingPubKey.get())) { + return "Verification of certificate " + std::to_string(i) + + " faile. OpenSSL error string: " + ERR_error_string(ERR_get_error(), NULL); + } + + auto certIssuer = getX509IssuerName(*keyCert); + auto signerSubj = getX509SubjectName(*signingCert); + if (certIssuer != signerSubj) { + return "Certificate " + std::to_string(i) + " has wrong issuer. Signer subject is " + + signerSubj + " Issuer subject is " + certIssuer; + } + + rawPubKeySize = sizeof(rawPubKey); + if (!EVP_PKEY_get_raw_public_key(pubKey.get(), rawPubKey, &rawPubKeySize)) { + return "Failed to get raw public key."; + } + } + + return bytevec(rawPubKey, rawPubKey + rawPubKeySize); +} + +std::string validateUdsCerts(const cppbor::Map& udsCerts, const bytevec& udsPub) { + for (const auto& [signerName, udsCertChain] : udsCerts) { + if (!signerName || !signerName->asTstr()) { + return "Signer Name must be a Tstr."; + } + if (!udsCertChain || !udsCertChain->asArray()) { + return "UDS certificate chain must be an Array."; + } + if (udsCertChain->asArray()->size() < 2) { + return "UDS certificate chain must have at least two entries: root and leaf."; + } + + auto leafPubKey = validateCertChain(*udsCertChain->asArray()); + if (!leafPubKey) { + return leafPubKey.message(); + } + if (*leafPubKey != udsPub) { + return "Leaf public key in UDS certificat chain doesn't match UDS public key."; + } + } + return ""; +} + +ErrMsgOr parseAndValidateCsrPayload(const cppbor::Array& keysToSign, + const std::vector& csrPayload, + IRemotelyProvisionedComponent* provisionable, + const std::vector& challenge, + bool isFactory) { + auto [parsedCsrPayload, _, errMsg] = cppbor::parse(csrPayload); + if (!parsedCsrPayload) { + return errMsg; + } + if (!parsedCsrPayload->asArray()) { + return "CSR payload is not a CBOR array."; + } + if (parsedCsrPayload->asArray()->size() != 5U) { + return "CSR payload must contain version, certificate type, device info, challenge, keys. " + "However, the parsed CSR payload has " + + std::to_string(parsedCsrPayload->asArray()->size()) + " entries."; + } + + auto& signedVersion = parsedCsrPayload->asArray()->get(0); + auto& signedCertificateType = parsedCsrPayload->asArray()->get(1); + auto& signedDeviceInfo = parsedCsrPayload->asArray()->get(2); + auto& signedChallenge = parsedCsrPayload->asArray()->get(3); + auto& signedKeys = parsedCsrPayload->asArray()->get(4); + + if (!signedVersion || !signedVersion->asUint() || signedVersion->asUint()->value() != 1U) { + return "CSR payload version must be an unsigned integer and must be equal to 1."; + } + if (!signedCertificateType || !signedCertificateType->asTstr()) { + // Certificate type is allowed to be extendend by vendor, i.e. we can't + // enforce its value. + return "Certificate type must be a Tstr."; + } + if (!signedDeviceInfo || !signedDeviceInfo->asMap()) { + return "Device info must be an Map."; + } + if (!signedChallenge || !signedChallenge->asBstr()) { + return "Challenge must be a Bstr."; + } + if (!signedKeys || !signedKeys->asArray()) { + return "Keys must be an Array."; + } + + auto result = parseAndValidateDeviceInfo(signedDeviceInfo->asMap()->encode(), provisionable, + isFactory); + if (!result) { + return result.message(); + } + + if (challenge.size() < 32 || challenge.size() > 64) { + return "Challenge size must be between 32 and 64 bytes inclusive. " + "However, challenge is " + + std::to_string(challenge.size()) + " bytes long."; + } + + auto challengeBstr = cppbor::Bstr(challenge); + if (*signedChallenge->asBstr() != challengeBstr) { + return "Signed challenge does not match." + "\n Actual: " + + cppbor::prettyPrint(signedChallenge->asBstr(), 64 /* maxBStrSize */) + + "\nExpected: " + cppbor::prettyPrint(&challengeBstr, 64 /* maxBStrSize */); + } + + if (signedKeys->asArray()->encode() != keysToSign.encode()) { + return "Signed keys do not match."; + } + + return std::move(*parsedCsrPayload->asArray()); +} + +ErrMsgOr> verifyCsr(const cppbor::Array& keysToSign, + const std::vector& csr, + IRemotelyProvisionedComponent* provisionable, + const std::vector& challenge, + bool isFactory) { + auto [parsedCsr, _, csrErrMsg] = cppbor::parse(csr); + if (!parsedCsr) { + return csrErrMsg; + } + if (!parsedCsr->asArray()) { + return "CSR is not a CBOR array."; + } + if (parsedCsr->asArray()->size() != 4U) { + return "CSR must contain version, UDS certificates, DICE chain, and signed data. " + "However, the parsed CSR has " + + std::to_string(parsedCsr->asArray()->size()) + " entries."; + } + + auto& version = parsedCsr->asArray()->get(0); + auto& udsCerts = parsedCsr->asArray()->get(1); + auto& diceCertChain = parsedCsr->asArray()->get(2); + auto& signedData = parsedCsr->asArray()->get(3); + + if (!version || !version->asUint() || version->asUint()->value() != 3U) { + return "Version must be an unsigned integer and must be equal to 3."; + } + if (!udsCerts || !udsCerts->asMap()) { + return "UdsCerts must be an Map."; + } + if (!diceCertChain || !diceCertChain->asArray()) { + return "DiceCertChain must be an Array."; + } + if (!signedData || !signedData->asArray()) { + return "SignedData must be an Array."; + } + + RpcHardwareInfo info; + provisionable->getHardwareInfo(&info); + if (version->asUint()->value() != info.versionNumber) { + return "CSR version (" + std::to_string(version->asUint()->value()) + + ") does not match the remotely provisioned component version (" + + std::to_string(info.versionNumber) + ")."; + } + + // DICE chain is [ pubkey, + DiceChainEntry ]. Its format is the same as BCC from RKP v1-2. + auto diceContents = validateBcc(diceCertChain->asArray()); + if (!diceContents) { + return diceContents.message() + "\n" + prettyPrint(diceCertChain.get()); + } + if (diceContents->size() == 0U) { + return "The DICE chain is empty. It must contain at least one entry."; + } + + auto& udsPub = diceContents->back().pubKey; + + auto error = validateUdsCerts(*udsCerts->asMap(), udsPub); + if (!error.empty()) { + return error; + } + + auto csrPayload = verifyAndParseCoseSign1(signedData->asArray(), udsPub, {} /* aad */); + if (!csrPayload) { + return csrPayload.message(); + } + + auto parsedCsrPayload = parseAndValidateCsrPayload(keysToSign, *csrPayload, provisionable, + challenge, isFactory); + if (!parsedCsrPayload) { + return parsedCsrPayload.message(); + } + + return *diceContents; +} + +ErrMsgOr> verifyFactoryCsr(const cppbor::Array& keysToSign, + const std::vector& csr, + IRemotelyProvisionedComponent* provisionable, + const std::vector& challenge) { + return verifyCsr(keysToSign, csr, provisionable, challenge, /*isFactory=*/true); +} + +ErrMsgOr> verifyProductionCsr( + const cppbor::Array& keysToSign, const std::vector& csr, + IRemotelyProvisionedComponent* provisionable, const std::vector& challenge) { + return verifyCsr(keysToSign, csr, provisionable, challenge, /*isFactory=*/false); +} + +} // namespace aidl::android::hardware::security::keymint::remote_prov