| // Copyright 2016 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "path_builder.h" |
| |
| #include <algorithm> |
| |
| #include "cert_error_params.h" |
| #include "cert_issuer_source_static.h" |
| #include "common_cert_errors.h" |
| #include "input.h" |
| #include "mock_signature_verify_cache.h" |
| #include "parsed_certificate.h" |
| #include "simple_path_builder_delegate.h" |
| #include "test_helpers.h" |
| #include "trust_store_collection.h" |
| #include "trust_store_in_memory.h" |
| #include "verify_certificate_chain.h" |
| |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| #include <openssl/pool.h> |
| |
| BSSL_NAMESPACE_BEGIN |
| |
| // TODO(crbug.com/634443): Assert the errors for each ResultPath. |
| |
| namespace { |
| |
| using ::testing::_; |
| using ::testing::Invoke; |
| using ::testing::StrictMock; |
| |
| class TestPathBuilderDelegate : public SimplePathBuilderDelegate { |
| public: |
| TestPathBuilderDelegate(size_t min_rsa_modulus_length_bits, |
| DigestPolicy digest_policy) |
| : SimplePathBuilderDelegate(min_rsa_modulus_length_bits, digest_policy) {} |
| |
| bool IsDeadlineExpired() override { return deadline_is_expired_; } |
| |
| void SetDeadlineExpiredForTesting(bool deadline_is_expired) { |
| deadline_is_expired_ = deadline_is_expired; |
| } |
| |
| SignatureVerifyCache *GetVerifyCache() override { |
| return use_signature_cache_ ? &cache_ : nullptr; |
| } |
| |
| void ActivateCache() { use_signature_cache_ = true; } |
| |
| void DeActivateCache() { use_signature_cache_ = false; } |
| |
| MockSignatureVerifyCache *GetMockVerifyCache() { return &cache_; } |
| |
| void AllowPrecert() { allow_precertificate_ = true; } |
| |
| void DisallowPrecert() { allow_precertificate_ = false; } |
| |
| bool AcceptPreCertificates() override { |
| return allow_precertificate_; |
| } |
| |
| private: |
| bool deadline_is_expired_ = false; |
| bool use_signature_cache_ = false; |
| bool allow_precertificate_ = false; |
| MockSignatureVerifyCache cache_; |
| }; |
| |
| class CertPathBuilderDelegateBase : public SimplePathBuilderDelegate { |
| public: |
| CertPathBuilderDelegateBase() |
| : SimplePathBuilderDelegate( |
| 1024, SimplePathBuilderDelegate::DigestPolicy::kWeakAllowSha1) {} |
| void CheckPathAfterVerification(const CertPathBuilder &path_builder, |
| CertPathBuilderResultPath *path) override { |
| ADD_FAILURE() << "Tests must override this"; |
| } |
| }; |
| |
| class MockPathBuilderDelegate : public CertPathBuilderDelegateBase { |
| public: |
| MOCK_METHOD2(CheckPathAfterVerification, |
| void(const CertPathBuilder &path_builder, |
| CertPathBuilderResultPath *path)); |
| }; |
| |
| // AsyncCertIssuerSourceStatic always returns its certs asynchronously. |
| class AsyncCertIssuerSourceStatic : public CertIssuerSource { |
| public: |
| class StaticAsyncRequest : public Request { |
| public: |
| explicit StaticAsyncRequest(ParsedCertificateList &&issuers) { |
| issuers_.swap(issuers); |
| issuers_iter_ = issuers_.begin(); |
| } |
| |
| StaticAsyncRequest(const StaticAsyncRequest &) = delete; |
| StaticAsyncRequest &operator=(const StaticAsyncRequest &) = delete; |
| |
| ~StaticAsyncRequest() override = default; |
| |
| void GetNext(ParsedCertificateList *out_certs) override { |
| if (issuers_iter_ != issuers_.end()) { |
| out_certs->push_back(std::move(*issuers_iter_++)); |
| } |
| } |
| |
| ParsedCertificateList issuers_; |
| ParsedCertificateList::iterator issuers_iter_; |
| }; |
| |
| ~AsyncCertIssuerSourceStatic() override = default; |
| |
| void SetAsyncGetCallback(std::function<void()> closure) { |
| async_get_callback_ = std::move(closure); |
| } |
| |
| void AddCert(std::shared_ptr<const ParsedCertificate> cert) { |
| static_cert_issuer_source_.AddCert(std::move(cert)); |
| } |
| |
| void SyncGetIssuersOf(const ParsedCertificate *cert, |
| ParsedCertificateList *issuers) override {} |
| void AsyncGetIssuersOf(const ParsedCertificate *cert, |
| std::unique_ptr<Request> *out_req) override { |
| num_async_gets_++; |
| ParsedCertificateList issuers; |
| static_cert_issuer_source_.SyncGetIssuersOf(cert, &issuers); |
| auto req = std::make_unique<StaticAsyncRequest>(std::move(issuers)); |
| *out_req = std::move(req); |
| if (async_get_callback_) { |
| async_get_callback_(); |
| } |
| } |
| int num_async_gets() const { return num_async_gets_; } |
| |
| private: |
| CertIssuerSourceStatic static_cert_issuer_source_; |
| |
| int num_async_gets_ = 0; |
| std::function<void()> async_get_callback_ = nullptr; |
| }; |
| |
| ::testing::AssertionResult ReadTestPem(const std::string &file_name, |
| const std::string &block_name, |
| std::string *result) { |
| const PemBlockMapping mappings[] = { |
| {block_name.c_str(), result}, |
| }; |
| |
| return ReadTestDataFromPemFile(file_name, mappings); |
| } |
| |
| ::testing::AssertionResult ReadTestCert( |
| const std::string &file_name, |
| std::shared_ptr<const ParsedCertificate> *result) { |
| std::string der; |
| ::testing::AssertionResult r = ReadTestPem( |
| "testdata/path_builder_unittest/" + file_name, "CERTIFICATE", &der); |
| if (!r) { |
| return r; |
| } |
| CertErrors errors; |
| *result = ParsedCertificate::Create( |
| bssl::UniquePtr<CRYPTO_BUFFER>(CRYPTO_BUFFER_new( |
| reinterpret_cast<const uint8_t *>(der.data()), der.size(), nullptr)), |
| {}, &errors); |
| if (!*result) { |
| return ::testing::AssertionFailure() |
| << "ParseCertificate::Create() failed:\n" |
| << errors.ToDebugString(); |
| } |
| return ::testing::AssertionSuccess(); |
| } |
| |
| class PathBuilderMultiRootTest : public ::testing::Test { |
| public: |
| PathBuilderMultiRootTest() |
| : delegate_(1024, TestPathBuilderDelegate::DigestPolicy::kWeakAllowSha1) { |
| } |
| |
| void SetUp() override { |
| ASSERT_TRUE(ReadTestCert("multi-root-A-by-B.pem", &a_by_b_)); |
| ASSERT_TRUE(ReadTestCert("multi-root-B-by-C.pem", &b_by_c_)); |
| ASSERT_TRUE(ReadTestCert("multi-root-B-by-F.pem", &b_by_f_)); |
| ASSERT_TRUE(ReadTestCert("multi-root-C-by-D.pem", &c_by_d_)); |
| ASSERT_TRUE(ReadTestCert("multi-root-C-by-E.pem", &c_by_e_)); |
| ASSERT_TRUE(ReadTestCert("multi-root-D-by-D.pem", &d_by_d_)); |
| ASSERT_TRUE(ReadTestCert("multi-root-E-by-E.pem", &e_by_e_)); |
| ASSERT_TRUE(ReadTestCert("multi-root-F-by-E.pem", &f_by_e_)); |
| } |
| |
| protected: |
| std::shared_ptr<const ParsedCertificate> a_by_b_, b_by_c_, b_by_f_, c_by_d_, |
| c_by_e_, d_by_d_, e_by_e_, f_by_e_; |
| |
| TestPathBuilderDelegate delegate_; |
| der::GeneralizedTime time_ = {2017, 3, 1, 0, 0, 0}; |
| |
| const InitialExplicitPolicy initial_explicit_policy_ = |
| InitialExplicitPolicy::kFalse; |
| const std::set<der::Input> user_initial_policy_set_ = { |
| der::Input(kAnyPolicyOid)}; |
| const InitialPolicyMappingInhibit initial_policy_mapping_inhibit_ = |
| InitialPolicyMappingInhibit::kFalse; |
| const InitialAnyPolicyInhibit initial_any_policy_inhibit_ = |
| InitialAnyPolicyInhibit::kFalse; |
| }; |
| |
| // Tests when the target cert has the same name and key as a trust anchor, |
| // however is signed by a different trust anchor. This should successfully build |
| // a path, however the trust anchor will be the signer of this cert. |
| // |
| // (This test is very similar to TestEndEntityHasSameNameAndSpkiAsTrustAnchor |
| // but with different data; also in this test the target cert itself is in the |
| // trust store). |
| TEST_F(PathBuilderMultiRootTest, TargetHasNameAndSpkiOfTrustAnchor) { |
| TrustStoreInMemory trust_store; |
| trust_store.AddTrustAnchor(a_by_b_); |
| trust_store.AddTrustAnchor(b_by_f_); |
| |
| CertPathBuilder path_builder( |
| a_by_b_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| |
| auto result = path_builder.Run(); |
| |
| ASSERT_TRUE(result.HasValidPath()); |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_VERIFIED) |
| << error.DiagnosticString(); |
| const auto &path = *result.GetBestValidPath(); |
| ASSERT_EQ(2U, path.certs.size()); |
| EXPECT_EQ(a_by_b_, path.certs[0]); |
| EXPECT_EQ(b_by_f_, path.certs[1]); |
| } |
| |
| // If the target cert is has the same name and key as a trust anchor, however |
| // is NOT itself signed by a trust anchor, it fails. Although the provided SPKI |
| // is trusted, the certificate contents cannot be verified. |
| TEST_F(PathBuilderMultiRootTest, TargetWithSameNameAsTrustAnchorFails) { |
| TrustStoreInMemory trust_store; |
| trust_store.AddTrustAnchor(a_by_b_); |
| |
| CertPathBuilder path_builder( |
| a_by_b_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| |
| auto result = path_builder.Run(); |
| |
| EXPECT_FALSE(result.HasValidPath()); |
| EXPECT_EQ(1U, result.max_depth_seen); |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_NOT_FOUND) |
| << error.DiagnosticString(); |
| } |
| |
| // Test a failed path building when the trust anchor is provided as a |
| // supplemental certificate. Conceptually the following paths could be built: |
| // |
| // B(C) <- C(D) <- [Trust anchor D] |
| // B(C) <- C(D) <- D(D) <- [Trust anchor D] |
| // |
| // However the second one is extraneous given the shorter path. |
| TEST_F(PathBuilderMultiRootTest, SelfSignedTrustAnchorSupplementalCert) { |
| TrustStoreInMemory trust_store; |
| trust_store.AddTrustAnchor(d_by_d_); |
| |
| // The (extraneous) trust anchor D(D) is supplied as a certificate, as is the |
| // intermediate needed for path building C(D). |
| CertIssuerSourceStatic sync_certs; |
| sync_certs.AddCert(d_by_d_); |
| sync_certs.AddCert(c_by_d_); |
| |
| // C(D) is not valid at this time, so path building will fail. |
| der::GeneralizedTime expired_time = {2016, 1, 1, 0, 0, 0}; |
| |
| CertPathBuilder path_builder( |
| b_by_c_, &trust_store, &delegate_, expired_time, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| path_builder.AddCertIssuerSource(&sync_certs); |
| |
| auto result = path_builder.Run(); |
| |
| EXPECT_FALSE(result.HasValidPath()); |
| ASSERT_EQ(1U, result.paths.size()); |
| |
| EXPECT_FALSE(result.paths[0]->IsValid()); |
| const auto &path0 = *result.paths[0]; |
| ASSERT_EQ(3U, path0.certs.size()); |
| EXPECT_EQ(b_by_c_, path0.certs[0]); |
| EXPECT_EQ(c_by_d_, path0.certs[1]); |
| EXPECT_EQ(d_by_d_, path0.certs[2]); |
| |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::CERTIFICATE_NOT_YET_VALID) |
| << error.DiagnosticString(); |
| } |
| |
| // Test verifying a certificate that is a trust anchor. |
| TEST_F(PathBuilderMultiRootTest, TargetIsSelfSignedTrustAnchor) { |
| TrustStoreInMemory trust_store; |
| trust_store.AddTrustAnchor(e_by_e_); |
| // This is not necessary for the test, just an extra... |
| trust_store.AddTrustAnchor(f_by_e_); |
| |
| CertPathBuilder path_builder( |
| e_by_e_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| |
| auto result = path_builder.Run(); |
| |
| ASSERT_TRUE(result.HasValidPath()); |
| |
| // Verifying a trusted leaf certificate is not permitted, however this |
| // certificate is self-signed, and can chain to itself. |
| const auto &path = *result.GetBestValidPath(); |
| ASSERT_EQ(2U, path.certs.size()); |
| EXPECT_EQ(e_by_e_, path.certs[0]); |
| EXPECT_EQ(e_by_e_, path.certs[1]); |
| |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_VERIFIED) |
| << error.DiagnosticString(); |
| } |
| |
| // If the target cert is directly issued by a trust anchor, it should verify |
| // without any intermediate certs being provided. |
| TEST_F(PathBuilderMultiRootTest, TargetDirectlySignedByTrustAnchor) { |
| TrustStoreInMemory trust_store; |
| trust_store.AddTrustAnchor(b_by_f_); |
| |
| CertPathBuilder path_builder( |
| a_by_b_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| |
| auto result = path_builder.Run(); |
| |
| ASSERT_TRUE(result.HasValidPath()); |
| const auto &path = *result.GetBestValidPath(); |
| ASSERT_EQ(2U, path.certs.size()); |
| EXPECT_EQ(a_by_b_, path.certs[0]); |
| EXPECT_EQ(b_by_f_, path.certs[1]); |
| |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_VERIFIED) |
| << error.DiagnosticString(); |
| } |
| |
| // Test that async cert queries are not made if the path can be successfully |
| // built with synchronously available certs. |
| TEST_F(PathBuilderMultiRootTest, TriesSyncFirst) { |
| TrustStoreInMemory trust_store; |
| trust_store.AddTrustAnchor(e_by_e_); |
| |
| CertIssuerSourceStatic sync_certs; |
| sync_certs.AddCert(b_by_f_); |
| sync_certs.AddCert(f_by_e_); |
| |
| AsyncCertIssuerSourceStatic async_certs; |
| async_certs.AddCert(b_by_c_); |
| async_certs.AddCert(c_by_e_); |
| |
| CertPathBuilder path_builder( |
| a_by_b_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| path_builder.AddCertIssuerSource(&async_certs); |
| path_builder.AddCertIssuerSource(&sync_certs); |
| |
| auto result = path_builder.Run(); |
| |
| EXPECT_TRUE(result.HasValidPath()); |
| EXPECT_EQ(0, async_certs.num_async_gets()); |
| |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_VERIFIED) |
| << error.DiagnosticString(); |
| } |
| |
| // If async queries are needed, all async sources will be queried |
| // simultaneously. |
| TEST_F(PathBuilderMultiRootTest, TestAsyncSimultaneous) { |
| TrustStoreInMemory trust_store; |
| trust_store.AddTrustAnchor(e_by_e_); |
| |
| CertIssuerSourceStatic sync_certs; |
| sync_certs.AddCert(b_by_c_); |
| sync_certs.AddCert(b_by_f_); |
| |
| AsyncCertIssuerSourceStatic async_certs1; |
| async_certs1.AddCert(c_by_e_); |
| |
| AsyncCertIssuerSourceStatic async_certs2; |
| async_certs2.AddCert(f_by_e_); |
| |
| CertPathBuilder path_builder( |
| a_by_b_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| path_builder.AddCertIssuerSource(&async_certs1); |
| path_builder.AddCertIssuerSource(&async_certs2); |
| path_builder.AddCertIssuerSource(&sync_certs); |
| |
| auto result = path_builder.Run(); |
| |
| EXPECT_TRUE(result.HasValidPath()); |
| EXPECT_EQ(1, async_certs1.num_async_gets()); |
| EXPECT_EQ(1, async_certs2.num_async_gets()); |
| |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_VERIFIED) |
| << error.DiagnosticString(); |
| } |
| |
| // Test that PathBuilder does not generate longer paths than necessary if one of |
| // the supplied certs is itself a trust anchor. |
| TEST_F(PathBuilderMultiRootTest, TestLongChain) { |
| // Both D(D) and C(D) are trusted roots. |
| TrustStoreInMemory trust_store; |
| trust_store.AddTrustAnchor(d_by_d_); |
| trust_store.AddTrustAnchor(c_by_d_); |
| |
| // Certs B(C), and C(D) are all supplied. |
| CertIssuerSourceStatic sync_certs; |
| sync_certs.AddCert(b_by_c_); |
| sync_certs.AddCert(c_by_d_); |
| |
| CertPathBuilder path_builder( |
| a_by_b_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| path_builder.AddCertIssuerSource(&sync_certs); |
| |
| auto result = path_builder.Run(); |
| |
| ASSERT_TRUE(result.HasValidPath()); |
| |
| // The result path should be A(B) <- B(C) <- C(D) |
| // not the longer but also valid A(B) <- B(C) <- C(D) <- D(D) |
| EXPECT_EQ(3U, result.GetBestValidPath()->certs.size()); |
| |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_VERIFIED) |
| << error.DiagnosticString(); |
| } |
| |
| // Test that PathBuilder will backtrack and try a different path if the first |
| // one doesn't work out. |
| TEST_F(PathBuilderMultiRootTest, TestBacktracking) { |
| // Only D(D) is a trusted root. |
| TrustStoreInMemory trust_store; |
| trust_store.AddTrustAnchor(d_by_d_); |
| |
| // Certs B(F) and F(E) are supplied synchronously, thus the path |
| // A(B) <- B(F) <- F(E) should be built first, though it won't verify. |
| CertIssuerSourceStatic sync_certs; |
| sync_certs.AddCert(b_by_f_); |
| sync_certs.AddCert(f_by_e_); |
| |
| // Certs B(C), and C(D) are supplied asynchronously, so the path |
| // A(B) <- B(C) <- C(D) <- D(D) should be tried second. |
| AsyncCertIssuerSourceStatic async_certs; |
| async_certs.AddCert(b_by_c_); |
| async_certs.AddCert(c_by_d_); |
| |
| CertPathBuilder path_builder( |
| a_by_b_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| path_builder.AddCertIssuerSource(&sync_certs); |
| path_builder.AddCertIssuerSource(&async_certs); |
| |
| auto result = path_builder.Run(); |
| |
| ASSERT_TRUE(result.HasValidPath()); |
| |
| // The partial path should be returned even though it didn't reach a trust |
| // anchor. |
| ASSERT_EQ(2U, result.paths.size()); |
| EXPECT_FALSE(result.paths[0]->IsValid()); |
| ASSERT_EQ(3U, result.paths[0]->certs.size()); |
| EXPECT_EQ(a_by_b_, result.paths[0]->certs[0]); |
| EXPECT_EQ(b_by_f_, result.paths[0]->certs[1]); |
| EXPECT_EQ(f_by_e_, result.paths[0]->certs[2]); |
| |
| // The result path should be A(B) <- B(C) <- C(D) <- D(D) |
| EXPECT_EQ(1U, result.best_result_index); |
| EXPECT_TRUE(result.paths[1]->IsValid()); |
| const auto &path = *result.GetBestValidPath(); |
| ASSERT_EQ(4U, path.certs.size()); |
| EXPECT_EQ(a_by_b_, path.certs[0]); |
| EXPECT_EQ(b_by_c_, path.certs[1]); |
| EXPECT_EQ(c_by_d_, path.certs[2]); |
| EXPECT_EQ(d_by_d_, path.certs[3]); |
| |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_VERIFIED) |
| << error.DiagnosticString(); |
| } |
| |
| // Test that if no path to a trust anchor was found, the partial path is |
| // returned. |
| TEST_F(PathBuilderMultiRootTest, TestOnlyPartialPathResult) { |
| TrustStoreInMemory trust_store; |
| |
| // Certs B(F) and F(E) are supplied synchronously, thus the path |
| // A(B) <- B(F) <- F(E) should be built first, though it won't verify. |
| CertIssuerSourceStatic sync_certs; |
| sync_certs.AddCert(b_by_f_); |
| sync_certs.AddCert(f_by_e_); |
| |
| CertPathBuilder path_builder( |
| a_by_b_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| path_builder.AddCertIssuerSource(&sync_certs); |
| |
| auto result = path_builder.Run(); |
| |
| EXPECT_FALSE(result.HasValidPath()); |
| |
| // The partial path should be returned even though it didn't reach a trust |
| // anchor. |
| ASSERT_EQ(1U, result.paths.size()); |
| EXPECT_FALSE(result.paths[0]->IsValid()); |
| ASSERT_EQ(3U, result.paths[0]->certs.size()); |
| EXPECT_EQ(a_by_b_, result.paths[0]->certs[0]); |
| EXPECT_EQ(b_by_f_, result.paths[0]->certs[1]); |
| EXPECT_EQ(f_by_e_, result.paths[0]->certs[2]); |
| |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_NOT_FOUND) |
| << error.DiagnosticString(); |
| } |
| |
| // Test that if two partial paths are returned, the first is marked as the best |
| // path. |
| TEST_F(PathBuilderMultiRootTest, TestTwoPartialPathResults) { |
| TrustStoreInMemory trust_store; |
| |
| // Certs B(F) and F(E) are supplied synchronously, thus the path |
| // A(B) <- B(F) <- F(E) should be built first, though it won't verify. |
| CertIssuerSourceStatic sync_certs; |
| sync_certs.AddCert(b_by_f_); |
| sync_certs.AddCert(f_by_e_); |
| |
| // Certs B(C), and C(D) are supplied asynchronously, so the path |
| // A(B) <- B(C) <- C(D) <- D(D) should be tried second. |
| AsyncCertIssuerSourceStatic async_certs; |
| async_certs.AddCert(b_by_c_); |
| async_certs.AddCert(c_by_d_); |
| |
| CertPathBuilder path_builder( |
| a_by_b_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| path_builder.AddCertIssuerSource(&sync_certs); |
| path_builder.AddCertIssuerSource(&async_certs); |
| |
| auto result = path_builder.Run(); |
| |
| EXPECT_FALSE(result.HasValidPath()); |
| |
| // First partial path found should be marked as the best one. |
| EXPECT_EQ(0U, result.best_result_index); |
| |
| ASSERT_EQ(2U, result.paths.size()); |
| EXPECT_FALSE(result.paths[0]->IsValid()); |
| ASSERT_EQ(3U, result.paths[0]->certs.size()); |
| EXPECT_EQ(a_by_b_, result.paths[0]->certs[0]); |
| EXPECT_EQ(b_by_f_, result.paths[0]->certs[1]); |
| EXPECT_EQ(f_by_e_, result.paths[0]->certs[2]); |
| |
| EXPECT_FALSE(result.paths[1]->IsValid()); |
| ASSERT_EQ(3U, result.paths[1]->certs.size()); |
| EXPECT_EQ(a_by_b_, result.paths[1]->certs[0]); |
| EXPECT_EQ(b_by_c_, result.paths[1]->certs[1]); |
| EXPECT_EQ(c_by_d_, result.paths[1]->certs[2]); |
| |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_NOT_FOUND) |
| << error.DiagnosticString(); |
| } |
| |
| // Test that if no valid path is found, and the first invalid path is a partial |
| // path, but the 2nd invalid path ends with a cert with a trust record, the 2nd |
| // path should be preferred. |
| TEST_F(PathBuilderMultiRootTest, TestDistrustedPathPreferredOverPartialPath) { |
| // Only D(D) has a trust record, but it is distrusted. |
| TrustStoreInMemory trust_store; |
| trust_store.AddDistrustedCertificateForTest(d_by_d_); |
| |
| // Certs B(F) and F(E) are supplied synchronously, thus the path |
| // A(B) <- B(F) <- F(E) should be built first, though it won't verify. |
| CertIssuerSourceStatic sync_certs; |
| sync_certs.AddCert(b_by_f_); |
| sync_certs.AddCert(f_by_e_); |
| |
| // Certs B(C), and C(D) are supplied asynchronously, so the path |
| // A(B) <- B(C) <- C(D) <- D(D) should be tried second. |
| AsyncCertIssuerSourceStatic async_certs; |
| async_certs.AddCert(b_by_c_); |
| async_certs.AddCert(c_by_d_); |
| |
| CertPathBuilder path_builder( |
| a_by_b_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| path_builder.AddCertIssuerSource(&sync_certs); |
| path_builder.AddCertIssuerSource(&async_certs); |
| |
| auto result = path_builder.Run(); |
| |
| EXPECT_FALSE(result.HasValidPath()); |
| |
| // The partial path should be returned even though it didn't reach a trust |
| // anchor. |
| ASSERT_EQ(2U, result.paths.size()); |
| EXPECT_FALSE(result.paths[0]->IsValid()); |
| ASSERT_EQ(3U, result.paths[0]->certs.size()); |
| EXPECT_EQ(a_by_b_, result.paths[0]->certs[0]); |
| EXPECT_EQ(b_by_f_, result.paths[0]->certs[1]); |
| EXPECT_EQ(f_by_e_, result.paths[0]->certs[2]); |
| |
| // The result path should be A(B) <- B(C) <- C(D) <- D(D) |
| EXPECT_EQ(1U, result.best_result_index); |
| EXPECT_FALSE(result.paths[1]->IsValid()); |
| const auto &path = *result.GetBestPathPossiblyInvalid(); |
| ASSERT_EQ(4U, path.certs.size()); |
| EXPECT_EQ(a_by_b_, path.certs[0]); |
| EXPECT_EQ(b_by_c_, path.certs[1]); |
| EXPECT_EQ(c_by_d_, path.certs[2]); |
| EXPECT_EQ(d_by_d_, path.certs[3]); |
| |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_NOT_FOUND) |
| << error.DiagnosticString(); |
| } |
| |
| // Test that whichever order CertIssuerSource returns the issuers, the path |
| // building still succeeds. |
| TEST_F(PathBuilderMultiRootTest, TestCertIssuerOrdering) { |
| // Only D(D) is a trusted root. |
| TrustStoreInMemory trust_store; |
| trust_store.AddTrustAnchor(d_by_d_); |
| |
| for (bool reverse_order : {false, true}) { |
| SCOPED_TRACE(reverse_order); |
| std::vector<std::shared_ptr<const ParsedCertificate>> certs = { |
| b_by_c_, b_by_f_, f_by_e_, c_by_d_, c_by_e_}; |
| CertIssuerSourceStatic sync_certs; |
| if (reverse_order) { |
| for (auto it = certs.rbegin(); it != certs.rend(); ++it) { |
| sync_certs.AddCert(*it); |
| } |
| } else { |
| for (const auto &cert : certs) { |
| sync_certs.AddCert(cert); |
| } |
| } |
| |
| CertPathBuilder path_builder( |
| a_by_b_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| path_builder.AddCertIssuerSource(&sync_certs); |
| |
| auto result = path_builder.Run(); |
| |
| ASSERT_TRUE(result.HasValidPath()); |
| |
| // The result path should be A(B) <- B(C) <- C(D) <- D(D) |
| const auto &path = *result.GetBestValidPath(); |
| ASSERT_EQ(4U, path.certs.size()); |
| EXPECT_EQ(a_by_b_, path.certs[0]); |
| EXPECT_EQ(b_by_c_, path.certs[1]); |
| EXPECT_EQ(c_by_d_, path.certs[2]); |
| EXPECT_EQ(d_by_d_, path.certs[3]); |
| |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_VERIFIED) |
| << error.DiagnosticString(); |
| } |
| } |
| |
| TEST_F(PathBuilderMultiRootTest, TestIterationLimit) { |
| // D(D) is the trust root. |
| TrustStoreInMemory trust_store; |
| trust_store.AddTrustAnchor(d_by_d_); |
| |
| // Certs B(C) and C(D) are supplied. |
| CertIssuerSourceStatic sync_certs; |
| sync_certs.AddCert(b_by_c_); |
| sync_certs.AddCert(c_by_d_); |
| |
| for (const bool insufficient_limit : {true, false}) { |
| SCOPED_TRACE(insufficient_limit); |
| |
| StrictMock<MockPathBuilderDelegate> mock_delegate; |
| // The CheckPathAfterVerification delegate should be called regardless if |
| // the iteration limit is reached. |
| EXPECT_CALL(mock_delegate, CheckPathAfterVerification(_, _)); |
| |
| CertPathBuilder path_builder( |
| a_by_b_, &trust_store, &mock_delegate, time_, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| path_builder.AddCertIssuerSource(&sync_certs); |
| |
| if (insufficient_limit) { |
| // A limit of one is insufficient to build a path in this case. Therefore |
| // building is expected to fail in this case. |
| path_builder.SetIterationLimit(1); |
| } else { |
| // The other tests in this file exercise the case that |SetIterationLimit| |
| // isn't called. Therefore set a sufficient limit for the path to be |
| // found. |
| path_builder.SetIterationLimit(5); |
| } |
| |
| auto result = path_builder.Run(); |
| |
| EXPECT_EQ(!insufficient_limit, result.HasValidPath()); |
| EXPECT_EQ(insufficient_limit, result.exceeded_iteration_limit); |
| |
| VerifyError error = result.GetBestPathVerifyError(); |
| if (insufficient_limit) { |
| EXPECT_EQ(2U, result.iteration_count); |
| ASSERT_EQ(error.Code(), |
| VerifyError::StatusCode::PATH_ITERATION_COUNT_EXCEEDED) |
| << error.DiagnosticString(); |
| } else { |
| EXPECT_EQ(3U, result.iteration_count); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_VERIFIED) |
| << error.DiagnosticString(); |
| } |
| } |
| } |
| |
| TEST_F(PathBuilderMultiRootTest, TestTrivialDeadline) { |
| // C(D) is the trust root. |
| TrustStoreInMemory trust_store; |
| trust_store.AddTrustAnchor(c_by_d_); |
| |
| // Cert B(C) is supplied. |
| CertIssuerSourceStatic sync_certs; |
| sync_certs.AddCert(b_by_c_); |
| |
| for (const bool insufficient_limit : {true, false}) { |
| SCOPED_TRACE(insufficient_limit); |
| |
| CertPathBuilder path_builder( |
| a_by_b_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| path_builder.AddCertIssuerSource(&sync_certs); |
| |
| // Make the deadline either expired or not. |
| delegate_.SetDeadlineExpiredForTesting(insufficient_limit); |
| |
| auto result = path_builder.Run(); |
| |
| EXPECT_EQ(!insufficient_limit, result.HasValidPath()); |
| EXPECT_EQ(insufficient_limit, result.exceeded_deadline); |
| EXPECT_EQ(delegate_.IsDeadlineExpired(), insufficient_limit); |
| |
| if (insufficient_limit) { |
| ASSERT_EQ(1U, result.paths.size()); |
| EXPECT_FALSE(result.paths[0]->IsValid()); |
| ASSERT_EQ(1U, result.paths[0]->certs.size()); |
| EXPECT_EQ(a_by_b_, result.paths[0]->certs[0]); |
| EXPECT_TRUE(result.paths[0]->errors.ContainsError( |
| cert_errors::kDeadlineExceeded)); |
| } else { |
| ASSERT_EQ(1U, result.paths.size()); |
| EXPECT_TRUE(result.paths[0]->IsValid()); |
| ASSERT_EQ(3U, result.paths[0]->certs.size()); |
| EXPECT_EQ(a_by_b_, result.paths[0]->certs[0]); |
| EXPECT_EQ(b_by_c_, result.paths[0]->certs[1]); |
| EXPECT_EQ(c_by_d_, result.paths[0]->certs[2]); |
| } |
| } |
| } |
| |
| TEST_F(PathBuilderMultiRootTest, TestVerifyCache) { |
| // C(D) is the trust root. |
| TrustStoreInMemory trust_store; |
| trust_store.AddTrustAnchor(c_by_d_); |
| |
| // Cert B(C) is supplied. |
| CertIssuerSourceStatic sync_certs; |
| sync_certs.AddCert(b_by_c_); |
| |
| // Test Activation / DeActivation of the cache. |
| EXPECT_FALSE(delegate_.GetVerifyCache()); |
| delegate_.ActivateCache(); |
| EXPECT_TRUE(delegate_.GetVerifyCache()); |
| delegate_.DeActivateCache(); |
| EXPECT_FALSE(delegate_.GetVerifyCache()); |
| delegate_.ActivateCache(); |
| EXPECT_TRUE(delegate_.GetVerifyCache()); |
| for (size_t i = 0; i < 3; i++) { |
| SCOPED_TRACE(i); |
| |
| CertPathBuilder path_builder( |
| a_by_b_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| path_builder.AddCertIssuerSource(&sync_certs); |
| |
| auto result = path_builder.Run(); |
| |
| ASSERT_EQ(1U, result.paths.size()); |
| EXPECT_TRUE(result.paths[0]->IsValid()); |
| ASSERT_EQ(3U, result.paths[0]->certs.size()); |
| EXPECT_EQ(a_by_b_, result.paths[0]->certs[0]); |
| EXPECT_EQ(b_by_c_, result.paths[0]->certs[1]); |
| EXPECT_EQ(c_by_d_, result.paths[0]->certs[2]); |
| |
| // The path is 3 certificates long, so requires 2 distinct signature |
| // verifications. The first time through the loop will cause 2 cache misses |
| // and stores, subsequent iterations will repeat the same verifications, |
| // causing 2 cache hits. |
| EXPECT_EQ(delegate_.GetMockVerifyCache()->CacheHits(), i * 2); |
| EXPECT_EQ(delegate_.GetMockVerifyCache()->CacheMisses(), 2U); |
| EXPECT_EQ(delegate_.GetMockVerifyCache()->CacheStores(), 2U); |
| |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_VERIFIED) |
| << error.DiagnosticString(); |
| } |
| } |
| |
| TEST_F(PathBuilderMultiRootTest, TestDeadline) { |
| TrustStoreInMemory trust_store; |
| trust_store.AddTrustAnchor(d_by_d_); |
| |
| // Cert B(C) is supplied statically. |
| CertIssuerSourceStatic sync_certs; |
| sync_certs.AddCert(b_by_c_); |
| |
| // Cert C(D) is supplied asynchronously and will expire the deadline before |
| // returning the async result. |
| AsyncCertIssuerSourceStatic async_certs; |
| async_certs.AddCert(c_by_d_); |
| async_certs.SetAsyncGetCallback( |
| [&] { delegate_.SetDeadlineExpiredForTesting(true); }); |
| |
| CertPathBuilder path_builder( |
| a_by_b_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| path_builder.AddCertIssuerSource(&sync_certs); |
| path_builder.AddCertIssuerSource(&async_certs); |
| |
| auto result = path_builder.Run(); |
| |
| EXPECT_FALSE(result.HasValidPath()); |
| EXPECT_TRUE(result.exceeded_deadline); |
| EXPECT_TRUE(delegate_.IsDeadlineExpired()); |
| |
| // The chain returned should end in c_by_d_, since the deadline would only be |
| // checked again after the async results had been checked (since |
| // AsyncCertIssuerSourceStatic makes the async results available immediately.) |
| ASSERT_EQ(1U, result.paths.size()); |
| EXPECT_FALSE(result.paths[0]->IsValid()); |
| ASSERT_EQ(3U, result.paths[0]->certs.size()); |
| EXPECT_EQ(a_by_b_, result.paths[0]->certs[0]); |
| EXPECT_EQ(b_by_c_, result.paths[0]->certs[1]); |
| EXPECT_EQ(c_by_d_, result.paths[0]->certs[2]); |
| EXPECT_TRUE( |
| result.paths[0]->errors.ContainsError(cert_errors::kDeadlineExceeded)); |
| |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_DEADLINE_EXCEEDED) |
| << error.DiagnosticString(); |
| } |
| |
| TEST_F(PathBuilderMultiRootTest, TestDepthLimit) { |
| // D(D) is the trust root. |
| TrustStoreInMemory trust_store; |
| trust_store.AddTrustAnchor(d_by_d_); |
| |
| // Certs B(C) and C(D) are supplied. |
| CertIssuerSourceStatic sync_certs; |
| sync_certs.AddCert(b_by_c_); |
| sync_certs.AddCert(c_by_d_); |
| |
| for (const bool insufficient_limit : {true, false}) { |
| CertPathBuilder path_builder( |
| a_by_b_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| path_builder.AddCertIssuerSource(&sync_certs); |
| |
| if (insufficient_limit) { |
| // A limit of depth equal to 2 is insufficient to build the path. |
| // Therefore, building is expected to fail. |
| path_builder.SetDepthLimit(2); |
| } else { |
| // The other tests in this file exercise the case that |SetDepthLimit| |
| // isn't called. Therefore, set a sufficient limit for the path to be |
| // found. |
| path_builder.SetDepthLimit(5); |
| } |
| |
| auto result = path_builder.Run(); |
| |
| EXPECT_EQ(!insufficient_limit, result.HasValidPath()); |
| EXPECT_EQ(insufficient_limit, |
| result.AnyPathContainsError(cert_errors::kDepthLimitExceeded)); |
| VerifyError error = result.GetBestPathVerifyError(); |
| if (insufficient_limit) { |
| EXPECT_EQ(2U, result.max_depth_seen); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_DEPTH_LIMIT_REACHED) |
| << error.DiagnosticString(); |
| } else { |
| EXPECT_EQ(4U, result.max_depth_seen); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_VERIFIED) |
| << error.DiagnosticString(); |
| } |
| } |
| } |
| |
| TEST_F(PathBuilderMultiRootTest, TestDepthLimitMultiplePaths) { |
| // This case tests path building backtracking due to reaching the path depth |
| // limit. Given the root and issuer certificates below, there can be two paths |
| // from between the leaf to a trusted root, one has length of 3 and the other |
| // has length of 4. These certificates are specifically chosen because path |
| // building will first explore the 4-certificate long path then the |
| // 3-certificate long path. So with a depth limit of 3, we can test the |
| // backtracking code path. |
| |
| // E(E) and C(D) are the trust roots. |
| TrustStoreInMemory trust_store; |
| trust_store.AddTrustAnchor(e_by_e_); |
| trust_store.AddTrustAnchor(c_by_d_); |
| |
| // Certs B(C). B(F) and F(E) are supplied. |
| CertIssuerSourceStatic sync_certs; |
| sync_certs.AddCert(b_by_c_); |
| sync_certs.AddCert(b_by_f_); |
| sync_certs.AddCert(f_by_e_); |
| |
| CertPathBuilder path_builder( |
| a_by_b_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| path_builder.AddCertIssuerSource(&sync_certs); |
| |
| path_builder.SetDepthLimit(3); |
| |
| auto result = path_builder.Run(); |
| |
| EXPECT_TRUE(result.HasValidPath()); |
| EXPECT_TRUE(result.AnyPathContainsError(cert_errors::kDepthLimitExceeded)); |
| |
| ASSERT_EQ(result.paths.size(), 2u); |
| |
| const CertPathBuilderResultPath *truncated_path = result.paths[0].get(); |
| EXPECT_FALSE(truncated_path->IsValid()); |
| EXPECT_TRUE( |
| truncated_path->errors.ContainsError(cert_errors::kDepthLimitExceeded)); |
| ASSERT_EQ(truncated_path->certs.size(), 3u); |
| EXPECT_EQ(a_by_b_, truncated_path->certs[0]); |
| EXPECT_EQ(b_by_f_, truncated_path->certs[1]); |
| EXPECT_EQ(f_by_e_, truncated_path->certs[2]); |
| |
| const CertPathBuilderResultPath *valid_path = result.paths[1].get(); |
| EXPECT_TRUE(valid_path->IsValid()); |
| EXPECT_FALSE( |
| valid_path->errors.ContainsError(cert_errors::kDepthLimitExceeded)); |
| ASSERT_EQ(valid_path->certs.size(), 3u); |
| EXPECT_EQ(a_by_b_, valid_path->certs[0]); |
| EXPECT_EQ(b_by_c_, valid_path->certs[1]); |
| EXPECT_EQ(c_by_d_, valid_path->certs[2]); |
| |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_VERIFIED) |
| << error.DiagnosticString(); |
| } |
| |
| TEST_F(PathBuilderMultiRootTest, TestPreCertificate) { |
| |
| std::string test_dir = |
| "testdata/path_builder_unittest/precertificate/"; |
| std::shared_ptr<const ParsedCertificate> root1 = |
| ReadCertFromFile(test_dir + "root.pem"); |
| ASSERT_TRUE(root1); |
| std::shared_ptr<const ParsedCertificate> target = |
| ReadCertFromFile(test_dir + "precertificate.pem"); |
| ASSERT_TRUE(target); |
| |
| der::GeneralizedTime precert_time = {2023, 10, 1, 0, 0, 0}; |
| |
| TrustStoreInMemory trust_store; |
| trust_store.AddTrustAnchor(root1); |
| |
| // PreCertificate should be rejected by default. |
| EXPECT_FALSE(delegate_.AcceptPreCertificates()); |
| CertPathBuilder path_builder( |
| target, &trust_store, &delegate_, precert_time, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| auto result = path_builder.Run(); |
| ASSERT_EQ(1U, result.paths.size()); |
| ASSERT_FALSE(result.paths[0]->IsValid()) |
| << result.paths[0]->errors.ToDebugString(result.paths[0]->certs); |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::CERTIFICATE_INVALID) |
| << error.DiagnosticString(); |
| |
| |
| // PreCertificate should be accepted if configured. |
| delegate_.AllowPrecert(); |
| EXPECT_TRUE(delegate_.AcceptPreCertificates()); |
| CertPathBuilder path_builder2( |
| target, &trust_store, &delegate_, precert_time, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| auto result2 = path_builder2.Run(); |
| ASSERT_EQ(1U, result2.paths.size()); |
| ASSERT_TRUE(result2.paths[0]->IsValid()) |
| << result2.paths[0]->errors.ToDebugString(result.paths[0]->certs); |
| VerifyError error2 = result2.GetBestPathVerifyError(); |
| ASSERT_EQ(error2.Code(), VerifyError::StatusCode::PATH_VERIFIED) |
| << error2.DiagnosticString(); |
| } |
| |
| class PathBuilderKeyRolloverTest : public ::testing::Test { |
| public: |
| PathBuilderKeyRolloverTest() |
| : delegate_(1024, |
| SimplePathBuilderDelegate::DigestPolicy::kWeakAllowSha1) {} |
| |
| void SetUp() override { |
| ParsedCertificateList path; |
| |
| VerifyCertChainTest test; |
| ASSERT_TRUE(ReadVerifyCertChainTestFromFile( |
| "testdata/verify_certificate_chain_unittest/key-rollover/oldchain.test", |
| &test)); |
| path = test.chain; |
| ASSERT_EQ(3U, path.size()); |
| target_ = path[0]; |
| oldintermediate_ = path[1]; |
| oldroot_ = path[2]; |
| time_ = test.time; |
| |
| ASSERT_TRUE(target_); |
| ASSERT_TRUE(oldintermediate_); |
| |
| ASSERT_TRUE(ReadVerifyCertChainTestFromFile( |
| "testdata/verify_certificate_chain_unittest/" |
| "key-rollover/longrolloverchain.test", |
| &test)); |
| path = test.chain; |
| |
| ASSERT_EQ(5U, path.size()); |
| newintermediate_ = path[1]; |
| newroot_ = path[2]; |
| newrootrollover_ = path[3]; |
| ASSERT_TRUE(newintermediate_); |
| ASSERT_TRUE(newroot_); |
| ASSERT_TRUE(newrootrollover_); |
| } |
| |
| protected: |
| // oldroot-------->newrootrollover newroot |
| // | | | |
| // v v v |
| // oldintermediate newintermediate |
| // | | |
| // +------------+-------------+ |
| // | |
| // v |
| // target |
| std::shared_ptr<const ParsedCertificate> target_; |
| std::shared_ptr<const ParsedCertificate> oldintermediate_; |
| std::shared_ptr<const ParsedCertificate> newintermediate_; |
| std::shared_ptr<const ParsedCertificate> oldroot_; |
| std::shared_ptr<const ParsedCertificate> newroot_; |
| std::shared_ptr<const ParsedCertificate> newrootrollover_; |
| |
| SimplePathBuilderDelegate delegate_; |
| der::GeneralizedTime time_; |
| |
| const InitialExplicitPolicy initial_explicit_policy_ = |
| InitialExplicitPolicy::kFalse; |
| const std::set<der::Input> user_initial_policy_set_ = { |
| der::Input(kAnyPolicyOid)}; |
| const InitialPolicyMappingInhibit initial_policy_mapping_inhibit_ = |
| InitialPolicyMappingInhibit::kFalse; |
| const InitialAnyPolicyInhibit initial_any_policy_inhibit_ = |
| InitialAnyPolicyInhibit::kFalse; |
| }; |
| |
| // Tests that if only the old root cert is trusted, the path builder can build a |
| // path through the new intermediate and rollover cert to the old root. |
| TEST_F(PathBuilderKeyRolloverTest, TestRolloverOnlyOldRootTrusted) { |
| // Only oldroot is trusted. |
| TrustStoreInMemory trust_store; |
| trust_store.AddTrustAnchor(oldroot_); |
| |
| // Old intermediate cert is not provided, so the pathbuilder will need to go |
| // through the rollover cert. |
| CertIssuerSourceStatic sync_certs; |
| sync_certs.AddCert(newintermediate_); |
| sync_certs.AddCert(newrootrollover_); |
| |
| CertPathBuilder path_builder( |
| target_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| path_builder.AddCertIssuerSource(&sync_certs); |
| |
| auto result = path_builder.Run(); |
| |
| EXPECT_TRUE(result.HasValidPath()); |
| |
| // Due to authorityKeyIdentifier prioritization, path builder will first |
| // attempt: target <- newintermediate <- newrootrollover <- oldroot |
| // which will succeed. |
| ASSERT_EQ(1U, result.paths.size()); |
| const auto &path0 = *result.paths[0]; |
| EXPECT_EQ(0U, result.best_result_index); |
| EXPECT_TRUE(path0.IsValid()); |
| ASSERT_EQ(4U, path0.certs.size()); |
| EXPECT_EQ(target_, path0.certs[0]); |
| EXPECT_EQ(newintermediate_, path0.certs[1]); |
| EXPECT_EQ(newrootrollover_, path0.certs[2]); |
| EXPECT_EQ(oldroot_, path0.certs[3]); |
| |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_VERIFIED) |
| << error.DiagnosticString(); |
| } |
| |
| // Tests that if both old and new roots are trusted it builds a path through |
| // the new intermediate. |
| TEST_F(PathBuilderKeyRolloverTest, TestRolloverBothRootsTrusted) { |
| // Both oldroot and newroot are trusted. |
| TrustStoreInMemory trust_store; |
| trust_store.AddTrustAnchor(oldroot_); |
| trust_store.AddTrustAnchor(newroot_); |
| |
| // Both old and new intermediates + rollover cert are provided. |
| CertIssuerSourceStatic sync_certs; |
| sync_certs.AddCert(oldintermediate_); |
| sync_certs.AddCert(newintermediate_); |
| sync_certs.AddCert(newrootrollover_); |
| |
| CertPathBuilder path_builder( |
| target_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| path_builder.AddCertIssuerSource(&sync_certs); |
| |
| auto result = path_builder.Run(); |
| |
| EXPECT_TRUE(result.HasValidPath()); |
| |
| ASSERT_EQ(1U, result.paths.size()); |
| const auto &path = *result.paths[0]; |
| EXPECT_TRUE(result.paths[0]->IsValid()); |
| ASSERT_EQ(3U, path.certs.size()); |
| EXPECT_EQ(target_, path.certs[0]); |
| // The newer intermediate should be used as newer certs are prioritized in |
| // path building. |
| EXPECT_EQ(newintermediate_, path.certs[1]); |
| EXPECT_EQ(newroot_, path.certs[2]); |
| |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_VERIFIED) |
| << error.DiagnosticString(); |
| } |
| |
| // If trust anchor query returned no results, and there are no issuer |
| // sources, path building should fail at that point. |
| TEST_F(PathBuilderKeyRolloverTest, TestAnchorsNoMatchAndNoIssuerSources) { |
| TrustStoreInMemory trust_store; |
| trust_store.AddTrustAnchor(newroot_); |
| |
| CertPathBuilder path_builder( |
| target_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| |
| auto result = path_builder.Run(); |
| |
| EXPECT_FALSE(result.HasValidPath()); |
| |
| ASSERT_EQ(1U, result.paths.size()); |
| const auto &path = *result.paths[0]; |
| EXPECT_FALSE(result.paths[0]->IsValid()); |
| ASSERT_EQ(1U, path.certs.size()); |
| EXPECT_EQ(target_, path.certs[0]); |
| |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_NOT_FOUND) |
| << error.DiagnosticString(); |
| } |
| |
| // If a path to a trust anchor could not be found, and the last issuer(s) in |
| // the chain were culled by the loop checker, the partial path up to that point |
| // should be returned. |
| TEST_F(PathBuilderKeyRolloverTest, TestReturnsPartialPathEndedByLoopChecker) { |
| TrustStoreInMemory trust_store; |
| |
| CertIssuerSourceStatic sync_certs; |
| sync_certs.AddCert(newintermediate_); |
| sync_certs.AddCert(newroot_); |
| |
| CertIssuerSourceStatic rollover_certs; |
| rollover_certs.AddCert(newrootrollover_); |
| |
| CertPathBuilder path_builder( |
| target_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| path_builder.AddCertIssuerSource(&sync_certs); |
| // The rollover root is added as a second issuer source to ensure we get paths |
| // back in a deterministic order, otherwise newroot and newrootrollover do not |
| // differ in any way that the path builder would use for prioritizing which |
| // path comes back first. |
| path_builder.AddCertIssuerSource(&rollover_certs); |
| |
| auto result = path_builder.Run(); |
| |
| EXPECT_FALSE(result.HasValidPath()); |
| ASSERT_EQ(2U, result.paths.size()); |
| |
| // Since none of the certs are trusted, the path builder should build 4 |
| // candidate paths, all of which are disallowed due to the loop checker: |
| // target->newintermediate->newroot->newroot |
| // target->newintermediate->newroot->newrootrollover |
| // target->newintermediate->newrootrollover->newroot |
| // target->newintermediate->newrootrollover->newrootrollover |
| // This should end up returning the 2 partial paths which are the longest |
| // paths for which no acceptable issuers could be found: |
| // target->newintermediate->newroot |
| // target->newintermediate->newrootrollover |
| |
| { |
| const auto &path = *result.paths[0]; |
| EXPECT_FALSE(path.IsValid()); |
| ASSERT_EQ(3U, path.certs.size()); |
| EXPECT_EQ(target_, path.certs[0]); |
| EXPECT_EQ(newintermediate_, path.certs[1]); |
| EXPECT_EQ(newroot_, path.certs[2]); |
| EXPECT_TRUE(path.errors.ContainsError(cert_errors::kNoIssuersFound)); |
| } |
| |
| { |
| const auto &path = *result.paths[1]; |
| EXPECT_FALSE(path.IsValid()); |
| ASSERT_EQ(3U, path.certs.size()); |
| EXPECT_EQ(target_, path.certs[0]); |
| EXPECT_EQ(newintermediate_, path.certs[1]); |
| EXPECT_EQ(newrootrollover_, path.certs[2]); |
| EXPECT_TRUE(path.errors.ContainsError(cert_errors::kNoIssuersFound)); |
| } |
| |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_NOT_FOUND) |
| << error.DiagnosticString(); |
| } |
| |
| // Tests that multiple trust root matches on a single path will be considered. |
| // Both roots have the same subject but different keys. Only one of them will |
| // verify. |
| TEST_F(PathBuilderKeyRolloverTest, TestMultipleRootMatchesOnlyOneWorks) { |
| TrustStoreCollection trust_store_collection; |
| TrustStoreInMemory trust_store1; |
| TrustStoreInMemory trust_store2; |
| trust_store_collection.AddTrustStore(&trust_store1); |
| trust_store_collection.AddTrustStore(&trust_store2); |
| // Add two trust anchors (newroot_ and oldroot_). Path building will attempt |
| // them in this same order, as trust_store1 was added to |
| // trust_store_collection first. |
| trust_store1.AddTrustAnchor(newroot_); |
| trust_store2.AddTrustAnchor(oldroot_); |
| |
| // Only oldintermediate is supplied, so the path with newroot should fail, |
| // oldroot should succeed. |
| CertIssuerSourceStatic sync_certs; |
| sync_certs.AddCert(oldintermediate_); |
| |
| CertPathBuilder path_builder( |
| target_, &trust_store_collection, &delegate_, time_, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| path_builder.AddCertIssuerSource(&sync_certs); |
| |
| auto result = path_builder.Run(); |
| |
| EXPECT_TRUE(result.HasValidPath()); |
| ASSERT_EQ(1U, result.paths.size()); |
| |
| // Due to authorityKeyIdentifier prioritization, path builder will first |
| // attempt: target <- old intermediate <- oldroot |
| // which should succeed. |
| EXPECT_TRUE(result.paths[result.best_result_index]->IsValid()); |
| const auto &path = *result.paths[result.best_result_index]; |
| ASSERT_EQ(3U, path.certs.size()); |
| EXPECT_EQ(target_, path.certs[0]); |
| EXPECT_EQ(oldintermediate_, path.certs[1]); |
| EXPECT_EQ(oldroot_, path.certs[2]); |
| |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_VERIFIED) |
| << error.DiagnosticString(); |
| } |
| |
| // Tests that the path builder doesn't build longer than necessary paths, |
| // by skipping certs where the same Name+SAN+SPKI is already in the current |
| // path. |
| TEST_F(PathBuilderKeyRolloverTest, TestRolloverLongChain) { |
| // Only oldroot is trusted. |
| TrustStoreInMemory trust_store; |
| trust_store.AddTrustAnchor(oldroot_); |
| |
| // New intermediate and new root are provided synchronously. |
| CertIssuerSourceStatic sync_certs; |
| sync_certs.AddCert(newintermediate_); |
| sync_certs.AddCert(newroot_); |
| |
| // Rollover cert is only provided asynchronously. This will force the |
| // pathbuilder to first try building a longer than necessary path. |
| AsyncCertIssuerSourceStatic async_certs; |
| async_certs.AddCert(newrootrollover_); |
| |
| CertPathBuilder path_builder( |
| target_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| path_builder.AddCertIssuerSource(&sync_certs); |
| path_builder.AddCertIssuerSource(&async_certs); |
| |
| auto result = path_builder.Run(); |
| |
| EXPECT_TRUE(result.HasValidPath()); |
| ASSERT_EQ(3U, result.paths.size()); |
| |
| // Path builder will first attempt: |
| // target <- newintermediate <- newroot <- oldroot |
| // but it will fail since newroot is self-signed. |
| EXPECT_FALSE(result.paths[0]->IsValid()); |
| const auto &path0 = *result.paths[0]; |
| ASSERT_EQ(4U, path0.certs.size()); |
| EXPECT_EQ(target_, path0.certs[0]); |
| EXPECT_EQ(newintermediate_, path0.certs[1]); |
| EXPECT_EQ(newroot_, path0.certs[2]); |
| EXPECT_EQ(oldroot_, path0.certs[3]); |
| |
| // Path builder will next attempt: target <- newintermediate <- oldroot |
| // but it will fail since newintermediate is signed by newroot. |
| EXPECT_FALSE(result.paths[1]->IsValid()); |
| const auto &path1 = *result.paths[1]; |
| ASSERT_EQ(3U, path1.certs.size()); |
| EXPECT_EQ(target_, path1.certs[0]); |
| EXPECT_EQ(newintermediate_, path1.certs[1]); |
| EXPECT_EQ(oldroot_, path1.certs[2]); |
| |
| // Path builder will skip: |
| // target <- newintermediate <- newroot <- newrootrollover <- ... |
| // Since newroot and newrootrollover have the same Name+SAN+SPKI. |
| |
| // Finally path builder will use: |
| // target <- newintermediate <- newrootrollover <- oldroot |
| EXPECT_EQ(2U, result.best_result_index); |
| EXPECT_TRUE(result.paths[2]->IsValid()); |
| const auto &path2 = *result.paths[2]; |
| ASSERT_EQ(4U, path2.certs.size()); |
| EXPECT_EQ(target_, path2.certs[0]); |
| EXPECT_EQ(newintermediate_, path2.certs[1]); |
| EXPECT_EQ(newrootrollover_, path2.certs[2]); |
| EXPECT_EQ(oldroot_, path2.certs[3]); |
| |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_VERIFIED) |
| << error.DiagnosticString(); |
| } |
| |
| // Tests that when SetExploreAllPaths is combined with SetIterationLimit the |
| // path builder will return all the paths that were able to be built before the |
| // iteration limit was reached. |
| TEST_F(PathBuilderKeyRolloverTest, ExploreAllPathsWithIterationLimit) { |
| struct Expectation { |
| int iteration_limit; |
| size_t expected_num_paths; |
| std::vector<std::shared_ptr<const ParsedCertificate>> partial_path; |
| } kExpectations[] = { |
| // No iteration limit. All possible paths should be built. |
| {0, 4, {}}, |
| // Limit 1 is only enough to reach the intermediate, no complete path |
| // should be built. |
| {1, 0, {target_, newintermediate_}}, |
| // Limit 2 allows reaching the root on the first path. |
| {2, 1, {target_, newintermediate_}}, |
| // Next iteration uses oldroot instead of newroot. |
| {3, 2, {target_, newintermediate_}}, |
| // Backtracking to the target cert. |
| {4, 2, {target_}}, |
| // Adding oldintermediate. |
| {5, 2, {target_, oldintermediate_}}, |
| // Trying oldroot. |
| {6, 3, {target_, oldintermediate_}}, |
| // Trying newroot. |
| {7, 4, {target_, oldintermediate_}}, |
| }; |
| |
| // Trust both old and new roots. |
| TrustStoreInMemory trust_store; |
| trust_store.AddTrustAnchor(oldroot_); |
| trust_store.AddTrustAnchor(newroot_); |
| |
| // Intermediates and root rollover are all provided synchronously. |
| CertIssuerSourceStatic sync_certs; |
| sync_certs.AddCert(oldintermediate_); |
| sync_certs.AddCert(newintermediate_); |
| |
| for (const auto &expectation : kExpectations) { |
| SCOPED_TRACE(expectation.iteration_limit); |
| |
| CertPathBuilder path_builder( |
| target_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| path_builder.AddCertIssuerSource(&sync_certs); |
| |
| // Explore all paths, rather than stopping at the first valid path. |
| path_builder.SetExploreAllPaths(true); |
| |
| // Limit the number of iterations. |
| path_builder.SetIterationLimit(expectation.iteration_limit); |
| |
| auto result = path_builder.Run(); |
| |
| EXPECT_EQ(expectation.expected_num_paths > 0, result.HasValidPath()); |
| VerifyError error = result.GetBestPathVerifyError(); |
| if (expectation.expected_num_paths > 0) { |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_VERIFIED) |
| << error.DiagnosticString(); |
| } else { |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_ITERATION_COUNT_EXCEEDED) |
| << error.DiagnosticString(); |
| } |
| |
| if (expectation.partial_path.empty()) { |
| ASSERT_EQ(expectation.expected_num_paths, result.paths.size()); |
| } else { |
| ASSERT_EQ(1 + expectation.expected_num_paths, result.paths.size()); |
| const auto &path = *result.paths[result.paths.size() - 1]; |
| EXPECT_FALSE(path.IsValid()); |
| EXPECT_EQ(expectation.partial_path, path.certs); |
| EXPECT_TRUE( |
| path.errors.ContainsError(cert_errors::kIterationLimitExceeded)); |
| } |
| |
| if (expectation.expected_num_paths > 0) { |
| // Path builder will first build path: target <- newintermediate <- |
| // newroot |
| const auto &path0 = *result.paths[0]; |
| EXPECT_TRUE(path0.IsValid()); |
| ASSERT_EQ(3U, path0.certs.size()); |
| EXPECT_EQ(target_, path0.certs[0]); |
| EXPECT_EQ(newintermediate_, path0.certs[1]); |
| EXPECT_EQ(newroot_, path0.certs[2]); |
| EXPECT_EQ(3U, result.max_depth_seen); |
| } |
| |
| if (expectation.expected_num_paths > 1) { |
| // Next path: target <- newintermediate <- oldroot |
| const auto &path1 = *result.paths[1]; |
| EXPECT_FALSE(path1.IsValid()); |
| ASSERT_EQ(3U, path1.certs.size()); |
| EXPECT_EQ(target_, path1.certs[0]); |
| EXPECT_EQ(newintermediate_, path1.certs[1]); |
| EXPECT_EQ(oldroot_, path1.certs[2]); |
| EXPECT_EQ(3U, result.max_depth_seen); |
| } |
| |
| if (expectation.expected_num_paths > 2) { |
| // Next path: target <- oldintermediate <- oldroot |
| const auto &path2 = *result.paths[2]; |
| EXPECT_TRUE(path2.IsValid()); |
| ASSERT_EQ(3U, path2.certs.size()); |
| EXPECT_EQ(target_, path2.certs[0]); |
| EXPECT_EQ(oldintermediate_, path2.certs[1]); |
| EXPECT_EQ(oldroot_, path2.certs[2]); |
| EXPECT_EQ(3U, result.max_depth_seen); |
| } |
| |
| if (expectation.expected_num_paths > 3) { |
| // Final path: target <- oldintermediate <- newroot |
| const auto &path3 = *result.paths[3]; |
| EXPECT_FALSE(path3.IsValid()); |
| ASSERT_EQ(3U, path3.certs.size()); |
| EXPECT_EQ(target_, path3.certs[0]); |
| EXPECT_EQ(oldintermediate_, path3.certs[1]); |
| EXPECT_EQ(newroot_, path3.certs[2]); |
| EXPECT_EQ(3U, result.max_depth_seen); |
| } |
| } |
| } |
| |
| // Tests that when SetValidPathLimit is used path builder returns the number of |
| // valid paths we expect before the valid path limit was reached. |
| TEST_F(PathBuilderKeyRolloverTest, ExplorePathsWithPathLimit) { |
| struct Expectation { |
| size_t valid_path_limit; |
| size_t expected_num_paths; |
| } kExpectations[] = { |
| {0, 4}, // No path limit. Three valid, one partial path should be built |
| {1, 1}, // One valid path |
| {2, 3}, // Two valid, one partial |
| {3, 4}, {4, 4}, {5, 4}, |
| }; |
| |
| // Trust both old and new roots. |
| TrustStoreInMemory trust_store; |
| trust_store.AddTrustAnchor(oldroot_); |
| trust_store.AddTrustAnchor(newroot_); |
| |
| // Intermediates and root rollover are all provided synchronously. |
| CertIssuerSourceStatic sync_certs; |
| sync_certs.AddCert(oldintermediate_); |
| sync_certs.AddCert(newintermediate_); |
| |
| for (const auto &expectation : kExpectations) { |
| SCOPED_TRACE(expectation.valid_path_limit); |
| |
| CertPathBuilder path_builder( |
| target_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| path_builder.AddCertIssuerSource(&sync_certs); |
| |
| // Stop after finding enough valid paths. |
| path_builder.SetValidPathLimit(expectation.valid_path_limit); |
| |
| auto result = path_builder.Run(); |
| |
| EXPECT_TRUE(result.HasValidPath()); |
| ASSERT_EQ(expectation.expected_num_paths, result.paths.size()); |
| |
| if (result.paths.size() > 0) { |
| // Path builder will first build path: target <- newintermediate <- |
| // newroot |
| const auto &path0 = *result.paths[0]; |
| EXPECT_TRUE(path0.IsValid()); |
| ASSERT_EQ(3U, path0.certs.size()); |
| EXPECT_EQ(target_, path0.certs[0]); |
| EXPECT_EQ(newintermediate_, path0.certs[1]); |
| EXPECT_EQ(newroot_, path0.certs[2]); |
| EXPECT_EQ(3U, result.max_depth_seen); |
| } |
| |
| if (result.paths.size() > 1) { |
| // Next path: target <- newintermediate <- oldroot |
| const auto &path1 = *result.paths[1]; |
| EXPECT_FALSE(path1.IsValid()); |
| ASSERT_EQ(3U, path1.certs.size()); |
| EXPECT_EQ(target_, path1.certs[0]); |
| EXPECT_EQ(newintermediate_, path1.certs[1]); |
| EXPECT_EQ(oldroot_, path1.certs[2]); |
| EXPECT_EQ(3U, result.max_depth_seen); |
| } |
| |
| if (result.paths.size() > 2) { |
| // Next path: target <- oldintermediate <- oldroot |
| const auto &path2 = *result.paths[2]; |
| EXPECT_TRUE(path2.IsValid()); |
| ASSERT_EQ(3U, path2.certs.size()); |
| EXPECT_EQ(target_, path2.certs[0]); |
| EXPECT_EQ(oldintermediate_, path2.certs[1]); |
| EXPECT_EQ(oldroot_, path2.certs[2]); |
| EXPECT_EQ(3U, result.max_depth_seen); |
| } |
| |
| if (result.paths.size() > 3) { |
| // Final path: target <- oldintermediate <- newroot |
| const auto &path3 = *result.paths[3]; |
| EXPECT_FALSE(path3.IsValid()); |
| ASSERT_EQ(3U, path3.certs.size()); |
| EXPECT_EQ(target_, path3.certs[0]); |
| EXPECT_EQ(oldintermediate_, path3.certs[1]); |
| EXPECT_EQ(newroot_, path3.certs[2]); |
| EXPECT_EQ(3U, result.max_depth_seen); |
| } |
| |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_VERIFIED) |
| << error.DiagnosticString(); |
| } |
| } |
| |
| // If the target cert is a trust anchor, however is not itself *signed* by a |
| // trust anchor, then it is not considered valid (the SPKI and name of the |
| // trust anchor matches the SPKI and subject of the target certificate, but the |
| // rest of the certificate cannot be verified). |
| TEST_F(PathBuilderKeyRolloverTest, TestEndEntityIsTrustRoot) { |
| // Trust newintermediate. |
| TrustStoreInMemory trust_store; |
| trust_store.AddTrustAnchor(newintermediate_); |
| |
| // Newintermediate is also the target cert. |
| CertPathBuilder path_builder( |
| newintermediate_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| |
| auto result = path_builder.Run(); |
| |
| EXPECT_FALSE(result.HasValidPath()); |
| |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_NOT_FOUND) |
| << error.DiagnosticString(); |
| } |
| |
| // If target has same Name+SAN+SPKI as a necessary intermediate, test if a path |
| // can still be built. |
| // Since LoopChecker will prevent the intermediate from being included, this |
| // currently does NOT verify. This case shouldn't occur in the web PKI. |
| TEST_F(PathBuilderKeyRolloverTest, |
| TestEndEntityHasSameNameAndSpkiAsIntermediate) { |
| // Trust oldroot. |
| TrustStoreInMemory trust_store; |
| trust_store.AddTrustAnchor(oldroot_); |
| |
| // New root rollover is provided synchronously. |
| CertIssuerSourceStatic sync_certs; |
| sync_certs.AddCert(newrootrollover_); |
| |
| // Newroot is the target cert. |
| CertPathBuilder path_builder( |
| newroot_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| path_builder.AddCertIssuerSource(&sync_certs); |
| |
| auto result = path_builder.Run(); |
| |
| // This could actually be OK, but CertPathBuilder does not build the |
| // newroot <- newrootrollover <- oldroot path. |
| EXPECT_FALSE(result.HasValidPath()); |
| |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), |
| VerifyError::StatusCode::CERTIFICATE_INVALID_SIGNATURE) |
| << error.DiagnosticString(); |
| } |
| |
| // If target has same Name+SAN+SPKI as the trust root, test that a (trivial) |
| // path can still be built. |
| TEST_F(PathBuilderKeyRolloverTest, |
| TestEndEntityHasSameNameAndSpkiAsTrustAnchor) { |
| // Trust newrootrollover. |
| TrustStoreInMemory trust_store; |
| trust_store.AddTrustAnchor(newrootrollover_); |
| |
| // Newroot is the target cert. |
| CertPathBuilder path_builder( |
| newroot_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| |
| auto result = path_builder.Run(); |
| |
| ASSERT_TRUE(result.HasValidPath()); |
| |
| const CertPathBuilderResultPath *best_result = result.GetBestValidPath(); |
| |
| // Newroot has same name+SPKI as newrootrollover, thus the path is valid and |
| // only contains newroot. |
| EXPECT_TRUE(best_result->IsValid()); |
| ASSERT_EQ(2U, best_result->certs.size()); |
| EXPECT_EQ(newroot_, best_result->certs[0]); |
| EXPECT_EQ(newrootrollover_, best_result->certs[1]); |
| |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_VERIFIED) |
| << error.DiagnosticString(); |
| } |
| |
| // Test that PathBuilder will not try the same path twice if multiple |
| // CertIssuerSources provide the same certificate. |
| TEST_F(PathBuilderKeyRolloverTest, TestDuplicateIntermediates) { |
| // Create a separate copy of oldintermediate. |
| std::shared_ptr<const ParsedCertificate> oldintermediate_dupe( |
| ParsedCertificate::Create( |
| bssl::UniquePtr<CRYPTO_BUFFER>( |
| CRYPTO_BUFFER_new(oldintermediate_->der_cert().data(), |
| oldintermediate_->der_cert().size(), nullptr)), |
| {}, nullptr)); |
| |
| // Only newroot is a trusted root. |
| TrustStoreInMemory trust_store; |
| trust_store.AddTrustAnchor(newroot_); |
| |
| // The oldintermediate is supplied synchronously by |sync_certs1| and |
| // another copy of oldintermediate is supplied synchronously by |sync_certs2|. |
| // The path target <- oldintermediate <- newroot should be built first, |
| // though it won't verify. It should not be attempted again even though |
| // oldintermediate was supplied twice. |
| CertIssuerSourceStatic sync_certs1; |
| sync_certs1.AddCert(oldintermediate_); |
| CertIssuerSourceStatic sync_certs2; |
| sync_certs2.AddCert(oldintermediate_dupe); |
| |
| // The newintermediate is supplied asynchronously, so the path |
| // target <- newintermediate <- newroot should be tried second. |
| AsyncCertIssuerSourceStatic async_certs; |
| async_certs.AddCert(newintermediate_); |
| |
| CertPathBuilder path_builder( |
| target_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| path_builder.AddCertIssuerSource(&sync_certs1); |
| path_builder.AddCertIssuerSource(&sync_certs2); |
| path_builder.AddCertIssuerSource(&async_certs); |
| |
| auto result = path_builder.Run(); |
| |
| EXPECT_TRUE(result.HasValidPath()); |
| ASSERT_EQ(2U, result.paths.size()); |
| |
| // Path builder will first attempt: target <- oldintermediate <- newroot |
| // but it will fail since oldintermediate is signed by oldroot. |
| EXPECT_FALSE(result.paths[0]->IsValid()); |
| const auto &path0 = *result.paths[0]; |
| |
| ASSERT_EQ(3U, path0.certs.size()); |
| EXPECT_EQ(target_, path0.certs[0]); |
| // Compare the DER instead of ParsedCertificate pointer, don't care which copy |
| // of oldintermediate was used in the path. |
| EXPECT_EQ(oldintermediate_->der_cert(), path0.certs[1]->der_cert()); |
| EXPECT_EQ(newroot_, path0.certs[2]); |
| |
| // Path builder will next attempt: target <- newintermediate <- newroot |
| // which will succeed. |
| EXPECT_EQ(1U, result.best_result_index); |
| EXPECT_TRUE(result.paths[1]->IsValid()); |
| const auto &path1 = *result.paths[1]; |
| ASSERT_EQ(3U, path1.certs.size()); |
| EXPECT_EQ(target_, path1.certs[0]); |
| EXPECT_EQ(newintermediate_, path1.certs[1]); |
| EXPECT_EQ(newroot_, path1.certs[2]); |
| |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_VERIFIED) |
| << error.DiagnosticString(); |
| } |
| |
| // Test when PathBuilder is given a cert via CertIssuerSources that has the same |
| // SPKI as a trust anchor. |
| TEST_F(PathBuilderKeyRolloverTest, TestDuplicateIntermediateAndRoot) { |
| // Create a separate copy of newroot. |
| std::shared_ptr<const ParsedCertificate> newroot_dupe( |
| ParsedCertificate::Create( |
| bssl::UniquePtr<CRYPTO_BUFFER>( |
| CRYPTO_BUFFER_new(newroot_->der_cert().data(), |
| newroot_->der_cert().size(), nullptr)), |
| {}, nullptr)); |
| |
| // Only newroot is a trusted root. |
| TrustStoreInMemory trust_store; |
| trust_store.AddTrustAnchor(newroot_); |
| |
| // The oldintermediate and newroot are supplied synchronously by |sync_certs|. |
| CertIssuerSourceStatic sync_certs; |
| sync_certs.AddCert(oldintermediate_); |
| sync_certs.AddCert(newroot_dupe); |
| |
| CertPathBuilder path_builder( |
| target_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| path_builder.AddCertIssuerSource(&sync_certs); |
| |
| auto result = path_builder.Run(); |
| |
| EXPECT_FALSE(result.HasValidPath()); |
| ASSERT_EQ(1U, result.paths.size()); |
| |
| // Path builder attempt: target <- oldintermediate <- newroot |
| // but it will fail since oldintermediate is signed by oldroot. |
| EXPECT_FALSE(result.paths[0]->IsValid()); |
| const auto &path = *result.paths[0]; |
| ASSERT_EQ(3U, path.certs.size()); |
| EXPECT_EQ(target_, path.certs[0]); |
| EXPECT_EQ(oldintermediate_, path.certs[1]); |
| // Compare the DER instead of ParsedCertificate pointer, don't care which copy |
| // of newroot was used in the path. |
| EXPECT_EQ(newroot_->der_cert(), path.certs[2]->der_cert()); |
| |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), |
| VerifyError::StatusCode::CERTIFICATE_INVALID_SIGNATURE) |
| << error.DiagnosticString(); |
| } |
| |
| class MockCertIssuerSourceRequest : public CertIssuerSource::Request { |
| public: |
| MOCK_METHOD1(GetNext, void(ParsedCertificateList *)); |
| }; |
| |
| class MockCertIssuerSource : public CertIssuerSource { |
| public: |
| MOCK_METHOD2(SyncGetIssuersOf, |
| void(const ParsedCertificate *, ParsedCertificateList *)); |
| MOCK_METHOD2(AsyncGetIssuersOf, |
| void(const ParsedCertificate *, std::unique_ptr<Request> *)); |
| }; |
| |
| // Helper class to pass the Request to the PathBuilder when it calls |
| // AsyncGetIssuersOf. (GoogleMock has a ByMove helper, but it apparently can |
| // only be used with Return, not SetArgPointee.) |
| class CertIssuerSourceRequestMover { |
| public: |
| explicit CertIssuerSourceRequestMover( |
| std::unique_ptr<CertIssuerSource::Request> req) |
| : request_(std::move(req)) {} |
| void MoveIt(const ParsedCertificate *cert, |
| std::unique_ptr<CertIssuerSource::Request> *out_req) { |
| *out_req = std::move(request_); |
| } |
| |
| private: |
| std::unique_ptr<CertIssuerSource::Request> request_; |
| }; |
| |
| // Functor that when called with a ParsedCertificateList* will append the |
| // specified certificate. |
| class AppendCertToList { |
| public: |
| explicit AppendCertToList( |
| const std::shared_ptr<const ParsedCertificate> &cert) |
| : cert_(cert) {} |
| |
| void operator()(ParsedCertificateList *out) { out->push_back(cert_); } |
| |
| private: |
| std::shared_ptr<const ParsedCertificate> cert_; |
| }; |
| |
| // Test that a single CertIssuerSource returning multiple async batches of |
| // issuers is handled correctly. Due to the StrictMocks, it also tests that path |
| // builder does not request issuers of certs that it shouldn't. |
| TEST_F(PathBuilderKeyRolloverTest, TestMultipleAsyncIssuersFromSingleSource) { |
| StrictMock<MockCertIssuerSource> cert_issuer_source; |
| |
| // Only newroot is a trusted root. |
| TrustStoreInMemory trust_store; |
| trust_store.AddTrustAnchor(newroot_); |
| |
| CertPathBuilder path_builder( |
| target_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| path_builder.AddCertIssuerSource(&cert_issuer_source); |
| |
| // Create the mock CertIssuerSource::Request... |
| auto target_issuers_req_owner = |
| std::make_unique<StrictMock<MockCertIssuerSourceRequest>>(); |
| // Keep a raw pointer to the Request... |
| StrictMock<MockCertIssuerSourceRequest> *target_issuers_req = |
| target_issuers_req_owner.get(); |
| // Setup helper class to pass ownership of the Request to the PathBuilder when |
| // it calls AsyncGetIssuersOf. |
| CertIssuerSourceRequestMover req_mover(std::move(target_issuers_req_owner)); |
| { |
| ::testing::InSequence s; |
| EXPECT_CALL(cert_issuer_source, SyncGetIssuersOf(target_.get(), _)); |
| EXPECT_CALL(cert_issuer_source, AsyncGetIssuersOf(target_.get(), _)) |
| .WillOnce(Invoke(&req_mover, &CertIssuerSourceRequestMover::MoveIt)); |
| } |
| |
| EXPECT_CALL(*target_issuers_req, GetNext(_)) |
| // First async batch: return oldintermediate_. |
| .WillOnce(Invoke(AppendCertToList(oldintermediate_))) |
| // Second async batch: return newintermediate_. |
| .WillOnce(Invoke(AppendCertToList(newintermediate_))); |
| { |
| ::testing::InSequence s; |
| // oldintermediate_ does not create a valid path, so both sync and async |
| // lookups are expected. |
| EXPECT_CALL(cert_issuer_source, |
| SyncGetIssuersOf(oldintermediate_.get(), _)); |
| EXPECT_CALL(cert_issuer_source, |
| AsyncGetIssuersOf(oldintermediate_.get(), _)); |
| } |
| |
| // newroot_ is in the trust store, so this path will be completed |
| // synchronously. AsyncGetIssuersOf will not be called on newintermediate_. |
| EXPECT_CALL(cert_issuer_source, SyncGetIssuersOf(newintermediate_.get(), _)); |
| |
| // Ensure pathbuilder finished and filled result. |
| auto result = path_builder.Run(); |
| |
| // Note that VerifyAndClearExpectations(target_issuers_req) is not called |
| // here. PathBuilder could have destroyed it already, so just let the |
| // expectations get checked by the destructor. |
| ::testing::Mock::VerifyAndClearExpectations(&cert_issuer_source); |
| |
| EXPECT_TRUE(result.HasValidPath()); |
| ASSERT_EQ(2U, result.paths.size()); |
| |
| // Path builder first attempts: target <- oldintermediate <- newroot |
| // but it will fail since oldintermediate is signed by oldroot. |
| EXPECT_FALSE(result.paths[0]->IsValid()); |
| const auto &path0 = *result.paths[0]; |
| ASSERT_EQ(3U, path0.certs.size()); |
| EXPECT_EQ(target_, path0.certs[0]); |
| EXPECT_EQ(oldintermediate_, path0.certs[1]); |
| EXPECT_EQ(newroot_, path0.certs[2]); |
| |
| // After the second batch of async results, path builder will attempt: |
| // target <- newintermediate <- newroot which will succeed. |
| EXPECT_TRUE(result.paths[1]->IsValid()); |
| const auto &path1 = *result.paths[1]; |
| ASSERT_EQ(3U, path1.certs.size()); |
| EXPECT_EQ(target_, path1.certs[0]); |
| EXPECT_EQ(newintermediate_, path1.certs[1]); |
| EXPECT_EQ(newroot_, path1.certs[2]); |
| |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_VERIFIED) |
| << error.DiagnosticString(); |
| } |
| |
| // Test that PathBuilder will not try the same path twice if CertIssuerSources |
| // asynchronously provide the same certificate multiple times. |
| TEST_F(PathBuilderKeyRolloverTest, TestDuplicateAsyncIntermediates) { |
| StrictMock<MockCertIssuerSource> cert_issuer_source; |
| |
| // Only newroot is a trusted root. |
| TrustStoreInMemory trust_store; |
| trust_store.AddTrustAnchor(newroot_); |
| |
| CertPathBuilder path_builder( |
| target_, &trust_store, &delegate_, time_, KeyPurpose::ANY_EKU, |
| initial_explicit_policy_, user_initial_policy_set_, |
| initial_policy_mapping_inhibit_, initial_any_policy_inhibit_); |
| path_builder.AddCertIssuerSource(&cert_issuer_source); |
| |
| // Create the mock CertIssuerSource::Request... |
| auto target_issuers_req_owner = |
| std::make_unique<StrictMock<MockCertIssuerSourceRequest>>(); |
| // Keep a raw pointer to the Request... |
| StrictMock<MockCertIssuerSourceRequest> *target_issuers_req = |
| target_issuers_req_owner.get(); |
| // Setup helper class to pass ownership of the Request to the PathBuilder when |
| // it calls AsyncGetIssuersOf. |
| CertIssuerSourceRequestMover req_mover(std::move(target_issuers_req_owner)); |
| { |
| ::testing::InSequence s; |
| EXPECT_CALL(cert_issuer_source, SyncGetIssuersOf(target_.get(), _)); |
| EXPECT_CALL(cert_issuer_source, AsyncGetIssuersOf(target_.get(), _)) |
| .WillOnce(Invoke(&req_mover, &CertIssuerSourceRequestMover::MoveIt)); |
| } |
| |
| std::shared_ptr<const ParsedCertificate> oldintermediate_dupe( |
| ParsedCertificate::Create( |
| bssl::UniquePtr<CRYPTO_BUFFER>( |
| CRYPTO_BUFFER_new(oldintermediate_->der_cert().data(), |
| oldintermediate_->der_cert().size(), nullptr)), |
| {}, nullptr)); |
| |
| EXPECT_CALL(*target_issuers_req, GetNext(_)) |
| // First async batch: return oldintermediate_. |
| .WillOnce(Invoke(AppendCertToList(oldintermediate_))) |
| // Second async batch: return a different copy of oldintermediate_ again. |
| .WillOnce(Invoke(AppendCertToList(oldintermediate_dupe))) |
| // Third async batch: return newintermediate_. |
| .WillOnce(Invoke(AppendCertToList(newintermediate_))); |
| |
| { |
| ::testing::InSequence s; |
| // oldintermediate_ does not create a valid path, so both sync and async |
| // lookups are expected. |
| EXPECT_CALL(cert_issuer_source, |
| SyncGetIssuersOf(oldintermediate_.get(), _)); |
| EXPECT_CALL(cert_issuer_source, |
| AsyncGetIssuersOf(oldintermediate_.get(), _)); |
| } |
| |
| // newroot_ is in the trust store, so this path will be completed |
| // synchronously. AsyncGetIssuersOf will not be called on newintermediate_. |
| EXPECT_CALL(cert_issuer_source, SyncGetIssuersOf(newintermediate_.get(), _)); |
| |
| // Ensure pathbuilder finished and filled result. |
| auto result = path_builder.Run(); |
| |
| ::testing::Mock::VerifyAndClearExpectations(&cert_issuer_source); |
| |
| EXPECT_TRUE(result.HasValidPath()); |
| ASSERT_EQ(2U, result.paths.size()); |
| |
| // Path builder first attempts: target <- oldintermediate <- newroot |
| // but it will fail since oldintermediate is signed by oldroot. |
| EXPECT_FALSE(result.paths[0]->IsValid()); |
| const auto &path0 = *result.paths[0]; |
| ASSERT_EQ(3U, path0.certs.size()); |
| EXPECT_EQ(target_, path0.certs[0]); |
| EXPECT_EQ(oldintermediate_, path0.certs[1]); |
| EXPECT_EQ(newroot_, path0.certs[2]); |
| |
| // The second async result does not generate any path. |
| |
| // After the third batch of async results, path builder will attempt: |
| // target <- newintermediate <- newroot which will succeed. |
| EXPECT_TRUE(result.paths[1]->IsValid()); |
| const auto &path1 = *result.paths[1]; |
| ASSERT_EQ(3U, path1.certs.size()); |
| EXPECT_EQ(target_, path1.certs[0]); |
| EXPECT_EQ(newintermediate_, path1.certs[1]); |
| EXPECT_EQ(newroot_, path1.certs[2]); |
| |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_VERIFIED) |
| << error.DiagnosticString(); |
| } |
| |
| class PathBuilderSimpleChainTest : public ::testing::Test { |
| public: |
| PathBuilderSimpleChainTest() = default; |
| |
| protected: |
| void SetUp() override { |
| // Read a simple test chain comprised of a target, intermediate, and root. |
| ASSERT_TRUE(ReadVerifyCertChainTestFromFile( |
| "testdata/verify_certificate_chain_unittest/target-and-intermediate/" |
| "main.test", |
| &test_)); |
| ASSERT_EQ(3u, test_.chain.size()); |
| } |
| |
| // Runs the path builder for the target certificate while |distrusted_cert| is |
| // blocked, and |delegate| if non-null. |
| CertPathBuilder::Result RunPathBuilder( |
| const std::shared_ptr<const ParsedCertificate> &distrusted_cert, |
| CertPathBuilderDelegate *optional_delegate) { |
| // Set up the trust store such that |distrusted_cert| is blocked, and |
| // the root is trusted (except if it was |distrusted_cert|). |
| TrustStoreInMemory trust_store; |
| if (distrusted_cert != test_.chain.back()) { |
| trust_store.AddTrustAnchor(test_.chain.back()); |
| } |
| if (distrusted_cert) { |
| trust_store.AddDistrustedCertificateForTest(distrusted_cert); |
| } |
| |
| // Add the single intermediate. |
| CertIssuerSourceStatic intermediates; |
| intermediates.AddCert(test_.chain[1]); |
| |
| SimplePathBuilderDelegate default_delegate( |
| 1024, SimplePathBuilderDelegate::DigestPolicy::kWeakAllowSha1); |
| CertPathBuilderDelegate *delegate = |
| optional_delegate ? optional_delegate : &default_delegate; |
| |
| const InitialExplicitPolicy initial_explicit_policy = |
| InitialExplicitPolicy::kFalse; |
| const std::set<der::Input> user_initial_policy_set = { |
| der::Input(kAnyPolicyOid)}; |
| const InitialPolicyMappingInhibit initial_policy_mapping_inhibit = |
| InitialPolicyMappingInhibit::kFalse; |
| const InitialAnyPolicyInhibit initial_any_policy_inhibit = |
| InitialAnyPolicyInhibit::kFalse; |
| |
| CertPathBuilder path_builder( |
| test_.chain.front(), &trust_store, delegate, test_.time, |
| KeyPurpose::ANY_EKU, initial_explicit_policy, user_initial_policy_set, |
| initial_policy_mapping_inhibit, initial_any_policy_inhibit); |
| path_builder.AddCertIssuerSource(&intermediates); |
| return path_builder.Run(); |
| } |
| |
| protected: |
| VerifyCertChainTest test_; |
| }; |
| |
| // Test fixture for running the path builder over a simple chain, while varying |
| // the trustedness of certain certificates. |
| class PathBuilderDistrustTest : public PathBuilderSimpleChainTest { |
| public: |
| PathBuilderDistrustTest() = default; |
| |
| protected: |
| // Runs the path builder for the target certificate while |distrusted_cert| is |
| // blocked. |
| CertPathBuilder::Result RunPathBuilderWithDistrustedCert( |
| const std::shared_ptr<const ParsedCertificate> &distrusted_cert) { |
| return RunPathBuilder(distrusted_cert, nullptr); |
| } |
| }; |
| |
| // Tests that path building fails when the target, intermediate, or root are |
| // distrusted (but the path is otherwise valid). |
| TEST_F(PathBuilderDistrustTest, TargetIntermediateRoot) { |
| // First do a control test -- path building without any blocked |
| // certificates should work. |
| CertPathBuilder::Result result = RunPathBuilderWithDistrustedCert(nullptr); |
| { |
| ASSERT_TRUE(result.HasValidPath()); |
| // The built path should be identical the the one read from disk. |
| const auto &path = *result.GetBestValidPath(); |
| ASSERT_EQ(test_.chain.size(), path.certs.size()); |
| for (size_t i = 0; i < test_.chain.size(); ++i) { |
| EXPECT_EQ(test_.chain[i], path.certs[i]); |
| } |
| } |
| |
| // Try path building when only the target is blocked - should fail. |
| result = RunPathBuilderWithDistrustedCert(test_.chain[0]); |
| { |
| EXPECT_FALSE(result.HasValidPath()); |
| ASSERT_LT(result.best_result_index, result.paths.size()); |
| const auto &best_path = result.paths[result.best_result_index]; |
| |
| // The built chain has length 1 since path building stopped once |
| // it encountered the blocked certificate (target). |
| ASSERT_EQ(1u, best_path->certs.size()); |
| EXPECT_EQ(best_path->certs[0], test_.chain[0]); |
| EXPECT_TRUE(best_path->errors.ContainsHighSeverityErrors()); |
| best_path->errors.ContainsError(cert_errors::kDistrustedByTrustStore); |
| } |
| |
| // Try path building when only the intermediate is blocked - should fail. |
| result = RunPathBuilderWithDistrustedCert(test_.chain[1]); |
| { |
| EXPECT_FALSE(result.HasValidPath()); |
| ASSERT_LT(result.best_result_index, result.paths.size()); |
| const auto &best_path = result.paths[result.best_result_index]; |
| |
| // The built chain has length 2 since path building stopped once |
| // it encountered the blocked certificate (intermediate). |
| ASSERT_EQ(2u, best_path->certs.size()); |
| EXPECT_EQ(best_path->certs[0], test_.chain[0]); |
| EXPECT_EQ(best_path->certs[1], test_.chain[1]); |
| EXPECT_TRUE(best_path->errors.ContainsHighSeverityErrors()); |
| best_path->errors.ContainsError(cert_errors::kDistrustedByTrustStore); |
| } |
| |
| // Try path building when only the root is blocked - should fail. |
| result = RunPathBuilderWithDistrustedCert(test_.chain[2]); |
| { |
| EXPECT_FALSE(result.HasValidPath()); |
| ASSERT_LT(result.best_result_index, result.paths.size()); |
| const auto &best_path = result.paths[result.best_result_index]; |
| |
| // The built chain has length 3 since path building stopped once |
| // it encountered the blocked certificate (root). |
| ASSERT_EQ(3u, best_path->certs.size()); |
| EXPECT_EQ(best_path->certs[0], test_.chain[0]); |
| EXPECT_EQ(best_path->certs[1], test_.chain[1]); |
| EXPECT_EQ(best_path->certs[2], test_.chain[2]); |
| EXPECT_TRUE(best_path->errors.ContainsHighSeverityErrors()); |
| best_path->errors.ContainsError(cert_errors::kDistrustedByTrustStore); |
| } |
| } |
| |
| // Test fixture for running the path builder over a simple chain, while varying |
| // what CheckPathAfterVerification() does. |
| class PathBuilderCheckPathAfterVerificationTest |
| : public PathBuilderSimpleChainTest {}; |
| |
| TEST_F(PathBuilderCheckPathAfterVerificationTest, NoOpToValidPath) { |
| StrictMock<MockPathBuilderDelegate> delegate; |
| // Just verify that the hook is called. |
| EXPECT_CALL(delegate, CheckPathAfterVerification(_, _)); |
| |
| CertPathBuilder::Result result = RunPathBuilder(nullptr, &delegate); |
| EXPECT_TRUE(result.HasValidPath()); |
| } |
| |
| DEFINE_CERT_ERROR_ID(kWarningFromDelegate, "Warning from delegate"); |
| |
| class AddWarningPathBuilderDelegate : public CertPathBuilderDelegateBase { |
| public: |
| void CheckPathAfterVerification(const CertPathBuilder &path_builder, |
| CertPathBuilderResultPath *path) override { |
| path->errors.GetErrorsForCert(1)->AddWarning(kWarningFromDelegate, nullptr); |
| } |
| }; |
| |
| TEST_F(PathBuilderCheckPathAfterVerificationTest, AddsWarningToValidPath) { |
| AddWarningPathBuilderDelegate delegate; |
| CertPathBuilder::Result result = RunPathBuilder(nullptr, &delegate); |
| ASSERT_TRUE(result.HasValidPath()); |
| |
| // A warning should have been added to certificate at index 1 in the path. |
| const CertErrors *cert1_errors = |
| result.GetBestValidPath()->errors.GetErrorsForCert(1); |
| ASSERT_TRUE(cert1_errors); |
| EXPECT_TRUE(cert1_errors->ContainsErrorWithSeverity( |
| kWarningFromDelegate, CertError::SEVERITY_WARNING)); |
| |
| // The warning should not affect the VerifyError |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_VERIFIED) |
| << error.DiagnosticString(); |
| } |
| |
| TEST_F(PathBuilderCheckPathAfterVerificationTest, TestVerifyErrorMapping) { |
| struct error_mapping { |
| CertErrorId internal_error; |
| VerifyError::StatusCode code; |
| }; |
| struct error_mapping AllErrors[] = { |
| {cert_errors::kInternalError, |
| VerifyError::StatusCode::VERIFICATION_FAILURE}, |
| {cert_errors::kValidityFailedNotAfter, |
| VerifyError::StatusCode::CERTIFICATE_EXPIRED}, |
| {cert_errors::kValidityFailedNotBefore, |
| VerifyError::StatusCode::CERTIFICATE_NOT_YET_VALID}, |
| {cert_errors::kDistrustedByTrustStore, |
| VerifyError::StatusCode::PATH_NOT_FOUND}, |
| {cert_errors::kSignatureAlgorithmMismatch, |
| VerifyError::StatusCode::CERTIFICATE_INVALID}, |
| {cert_errors::kChainIsEmpty, |
| VerifyError::StatusCode::VERIFICATION_FAILURE}, |
| {cert_errors::kUnconsumedCriticalExtension, |
| VerifyError::StatusCode::CERTIFICATE_INVALID}, |
| {cert_errors::kKeyCertSignBitNotSet, |
| VerifyError::StatusCode::CERTIFICATE_INVALID}, |
| {cert_errors::kMaxPathLengthViolated, |
| VerifyError::StatusCode::PATH_NOT_FOUND}, |
| {cert_errors::kBasicConstraintsIndicatesNotCa, |
| VerifyError::StatusCode::CERTIFICATE_INVALID}, |
| {cert_errors::kTargetCertShouldNotBeCa, |
| VerifyError::StatusCode::CERTIFICATE_INVALID}, |
| {cert_errors::kMissingBasicConstraints, |
| VerifyError::StatusCode::CERTIFICATE_INVALID}, |
| {cert_errors::kNotPermittedByNameConstraints, |
| VerifyError::StatusCode::CERTIFICATE_INVALID}, |
| {cert_errors::kTooManyNameConstraintChecks, |
| VerifyError::StatusCode::CERTIFICATE_INVALID}, |
| {cert_errors::kSubjectDoesNotMatchIssuer, |
| VerifyError::StatusCode::PATH_NOT_FOUND}, |
| {cert_errors::kVerifySignedDataFailed, |
| VerifyError::StatusCode::CERTIFICATE_INVALID_SIGNATURE}, |
| {cert_errors::kSignatureAlgorithmsDifferentEncoding, |
| VerifyError::StatusCode::CERTIFICATE_INVALID}, |
| {cert_errors::kEkuLacksServerAuth, |
| VerifyError::StatusCode::CERTIFICATE_NO_MATCHING_EKU}, |
| {cert_errors::kEkuLacksServerAuthButHasAnyEKU, |
| VerifyError::StatusCode::CERTIFICATE_NO_MATCHING_EKU}, |
| {cert_errors::kEkuLacksClientAuth, |
| VerifyError::StatusCode::CERTIFICATE_NO_MATCHING_EKU}, |
| {cert_errors::kEkuLacksClientAuthButHasAnyEKU, |
| VerifyError::StatusCode::CERTIFICATE_NO_MATCHING_EKU}, |
| {cert_errors::kEkuLacksClientAuthOrServerAuth, |
| VerifyError::StatusCode::CERTIFICATE_NO_MATCHING_EKU}, |
| {cert_errors::kEkuHasProhibitedOCSPSigning, |
| VerifyError::StatusCode::CERTIFICATE_INVALID}, |
| {cert_errors::kEkuHasProhibitedTimeStamping, |
| VerifyError::StatusCode::CERTIFICATE_INVALID}, |
| {cert_errors::kEkuHasProhibitedCodeSigning, |
| VerifyError::StatusCode::CERTIFICATE_INVALID}, |
| {cert_errors::kEkuNotPresent, |
| VerifyError::StatusCode::CERTIFICATE_INVALID}, |
| {cert_errors::kCertIsNotTrustAnchor, |
| VerifyError::StatusCode::PATH_NOT_FOUND}, |
| {cert_errors::kNoValidPolicy, |
| VerifyError::StatusCode::CERTIFICATE_INVALID}, |
| {cert_errors::kPolicyMappingAnyPolicy, |
| VerifyError::StatusCode::CERTIFICATE_INVALID}, |
| {cert_errors::kFailedParsingSpki, |
| VerifyError::StatusCode::CERTIFICATE_INVALID}, |
| {cert_errors::kUnacceptableSignatureAlgorithm, |
| VerifyError::StatusCode::CERTIFICATE_UNSUPPORTED_SIGNATURE_ALGORITHM}, |
| {cert_errors::kUnacceptablePublicKey, |
| VerifyError::StatusCode::CERTIFICATE_UNSUPPORTED_KEY}, |
| {cert_errors::kCertificateRevoked, |
| VerifyError::StatusCode::CERTIFICATE_REVOKED}, |
| {cert_errors::kNoRevocationMechanism, |
| VerifyError::StatusCode::CERTIFICATE_NO_REVOCATION_MECHANISM}, |
| {cert_errors::kUnableToCheckRevocation, |
| VerifyError::StatusCode::CERTIFICATE_UNABLE_TO_CHECK_REVOCATION}, |
| {cert_errors::kNoIssuersFound, VerifyError::StatusCode::PATH_NOT_FOUND}, |
| {cert_errors::kDeadlineExceeded, |
| VerifyError::StatusCode::PATH_DEADLINE_EXCEEDED}, |
| {cert_errors::kIterationLimitExceeded, |
| VerifyError::StatusCode::PATH_ITERATION_COUNT_EXCEEDED}, |
| {cert_errors::kDepthLimitExceeded, |
| VerifyError::StatusCode::PATH_DEPTH_LIMIT_REACHED}, |
| }; |
| |
| for (struct error_mapping mapping : AllErrors) { |
| AddWarningPathBuilderDelegate delegate; |
| CertPathBuilder::Result result = RunPathBuilder(nullptr, &delegate); |
| ASSERT_TRUE(result.HasValidPath()); |
| |
| CertErrors *errors = |
| (CertErrors *)result.GetBestValidPath()->errors.GetErrorsForCert(1); |
| errors->AddError(mapping.internal_error, nullptr); |
| |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), mapping.code) |
| << error.DiagnosticString(); |
| } |
| } |
| |
| TEST_F(PathBuilderCheckPathAfterVerificationTest, |
| TestVerifyErrorMulipleMapping) { |
| AddWarningPathBuilderDelegate delegate; |
| CertPathBuilder::Result result = RunPathBuilder(nullptr, &delegate); |
| ASSERT_TRUE(result.HasValidPath()); |
| |
| CertErrors *errors = |
| (CertErrors *)result.GetBestValidPath()->errors.GetErrorsForCert(1); |
| errors->AddError(cert_errors::kEkuNotPresent, nullptr); |
| errors->AddError(cert_errors::kNoValidPolicy, nullptr); |
| |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_MULTIPLE_ERRORS) |
| << error.DiagnosticString(); |
| } |
| |
| |
| DEFINE_CERT_ERROR_ID(kErrorFromDelegate, "Error from delegate"); |
| |
| class AddErrorPathBuilderDelegate : public CertPathBuilderDelegateBase { |
| public: |
| void CheckPathAfterVerification(const CertPathBuilder &path_builder, |
| CertPathBuilderResultPath *path) override { |
| path->errors.GetErrorsForCert(2)->AddError(kErrorFromDelegate, nullptr); |
| } |
| }; |
| |
| TEST_F(PathBuilderCheckPathAfterVerificationTest, AddsErrorToValidPath) { |
| AddErrorPathBuilderDelegate delegate; |
| CertPathBuilder::Result result = RunPathBuilder(nullptr, &delegate); |
| |
| // Verification failed. |
| ASSERT_FALSE(result.HasValidPath()); |
| |
| ASSERT_LT(result.best_result_index, result.paths.size()); |
| const CertPathBuilderResultPath *failed_path = |
| result.paths[result.best_result_index].get(); |
| ASSERT_TRUE(failed_path); |
| |
| // An error should have been added to certificate at index 2 in the path. |
| const CertErrors *cert2_errors = failed_path->errors.GetErrorsForCert(2); |
| ASSERT_TRUE(cert2_errors); |
| EXPECT_TRUE(cert2_errors->ContainsError(kErrorFromDelegate)); |
| |
| // The newly defined delegate error should map to CERTIFICATE_INVALID |
| // since it is associated with a certificate (at index 2) |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::CERTIFICATE_INVALID) |
| << error.DiagnosticString(); |
| } |
| |
| class AddOtherErrorPathBuilderDelegate : public CertPathBuilderDelegateBase { |
| public: |
| void CheckPathAfterVerification(const CertPathBuilder &path_builder, |
| CertPathBuilderResultPath *path) override { |
| path->errors.GetOtherErrors()->AddError(kErrorFromDelegate, nullptr); |
| } |
| }; |
| |
| TEST_F(PathBuilderCheckPathAfterVerificationTest, AddsErrorToOtherErrors) { |
| AddOtherErrorPathBuilderDelegate delegate; |
| CertPathBuilder::Result result = RunPathBuilder(nullptr, &delegate); |
| |
| // Verification failed. |
| ASSERT_FALSE(result.HasValidPath()); |
| |
| ASSERT_LT(result.best_result_index, result.paths.size()); |
| const CertPathBuilderResultPath *failed_path = |
| result.paths[result.best_result_index].get(); |
| ASSERT_TRUE(failed_path); |
| |
| // An error should have been added to other errors |
| const CertErrors *other_errors = failed_path->errors.GetOtherErrors(); |
| ASSERT_TRUE(other_errors); |
| EXPECT_TRUE(other_errors->ContainsError(kErrorFromDelegate)); |
| |
| // The newly defined delegate error should map to VERIFICATION_FAILURE |
| // since the error is not associated to a certificate. |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::VERIFICATION_FAILURE) |
| << error.DiagnosticString(); |
| } |
| |
| |
| TEST_F(PathBuilderCheckPathAfterVerificationTest, NoopToAlreadyInvalidPath) { |
| StrictMock<MockPathBuilderDelegate> delegate; |
| // Just verify that the hook is called (on an invalid path). |
| EXPECT_CALL(delegate, CheckPathAfterVerification(_, _)); |
| |
| // Run the pathbuilder with certificate at index 1 actively distrusted. |
| CertPathBuilder::Result result = RunPathBuilder(test_.chain[1], &delegate); |
| EXPECT_FALSE(result.HasValidPath()); |
| |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_NOT_FOUND) |
| << error.DiagnosticString(); |
| } |
| |
| struct DelegateData : public CertPathBuilderDelegateData { |
| int value = 0xB33F; |
| }; |
| |
| class SetsDelegateDataPathBuilderDelegate : public CertPathBuilderDelegateBase { |
| public: |
| void CheckPathAfterVerification(const CertPathBuilder &path_builder, |
| CertPathBuilderResultPath *path) override { |
| path->delegate_data = std::make_unique<DelegateData>(); |
| } |
| }; |
| |
| TEST_F(PathBuilderCheckPathAfterVerificationTest, SetsDelegateData) { |
| SetsDelegateDataPathBuilderDelegate delegate; |
| CertPathBuilder::Result result = RunPathBuilder(nullptr, &delegate); |
| ASSERT_TRUE(result.HasValidPath()); |
| |
| DelegateData *data = reinterpret_cast<DelegateData *>( |
| result.GetBestValidPath()->delegate_data.get()); |
| |
| EXPECT_EQ(0xB33F, data->value); |
| } |
| |
| TEST(PathBuilderPrioritizationTest, DatePrioritization) { |
| std::string test_dir = |
| "testdata/path_builder_unittest/validity_date_prioritization/"; |
| std::shared_ptr<const ParsedCertificate> root = |
| ReadCertFromFile(test_dir + "root.pem"); |
| ASSERT_TRUE(root); |
| std::shared_ptr<const ParsedCertificate> int_ac = |
| ReadCertFromFile(test_dir + "int_ac.pem"); |
| ASSERT_TRUE(int_ac); |
| std::shared_ptr<const ParsedCertificate> int_ad = |
| ReadCertFromFile(test_dir + "int_ad.pem"); |
| ASSERT_TRUE(int_ad); |
| std::shared_ptr<const ParsedCertificate> int_bc = |
| ReadCertFromFile(test_dir + "int_bc.pem"); |
| ASSERT_TRUE(int_bc); |
| std::shared_ptr<const ParsedCertificate> int_bd = |
| ReadCertFromFile(test_dir + "int_bd.pem"); |
| ASSERT_TRUE(int_bd); |
| std::shared_ptr<const ParsedCertificate> target = |
| ReadCertFromFile(test_dir + "target.pem"); |
| ASSERT_TRUE(target); |
| |
| SimplePathBuilderDelegate delegate( |
| 1024, SimplePathBuilderDelegate::DigestPolicy::kWeakAllowSha1); |
| der::GeneralizedTime verify_time = {2017, 3, 1, 0, 0, 0}; |
| |
| // Distrust the root certificate. This will force the path builder to attempt |
| // all possible paths. |
| TrustStoreInMemory trust_store; |
| trust_store.AddDistrustedCertificateForTest(root); |
| |
| for (bool reverse_input_order : {false, true}) { |
| SCOPED_TRACE(reverse_input_order); |
| |
| CertIssuerSourceStatic intermediates; |
| // Test with the intermediates supplied in two different orders to ensure |
| // the results don't depend on input ordering. |
| if (reverse_input_order) { |
| intermediates.AddCert(int_bd); |
| intermediates.AddCert(int_bc); |
| intermediates.AddCert(int_ad); |
| intermediates.AddCert(int_ac); |
| } else { |
| intermediates.AddCert(int_ac); |
| intermediates.AddCert(int_ad); |
| intermediates.AddCert(int_bc); |
| intermediates.AddCert(int_bd); |
| } |
| |
| CertPathBuilder path_builder( |
| target, &trust_store, &delegate, verify_time, KeyPurpose::ANY_EKU, |
| InitialExplicitPolicy::kFalse, {der::Input(kAnyPolicyOid)}, |
| InitialPolicyMappingInhibit::kFalse, InitialAnyPolicyInhibit::kFalse); |
| path_builder.AddCertIssuerSource(&intermediates); |
| |
| CertPathBuilder::Result result = path_builder.Run(); |
| EXPECT_FALSE(result.HasValidPath()); |
| |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_NOT_FOUND) |
| << error.DiagnosticString(); |
| |
| ASSERT_EQ(4U, result.paths.size()); |
| |
| // Path builder should have attempted paths using the intermediates in |
| // order: bd, bc, ad, ac |
| |
| EXPECT_FALSE(result.paths[0]->IsValid()); |
| ASSERT_EQ(3U, result.paths[0]->certs.size()); |
| EXPECT_EQ(target, result.paths[0]->certs[0]); |
| EXPECT_EQ(int_bd, result.paths[0]->certs[1]); |
| EXPECT_EQ(root, result.paths[0]->certs[2]); |
| |
| EXPECT_FALSE(result.paths[1]->IsValid()); |
| ASSERT_EQ(3U, result.paths[1]->certs.size()); |
| EXPECT_EQ(target, result.paths[1]->certs[0]); |
| EXPECT_EQ(int_bc, result.paths[1]->certs[1]); |
| EXPECT_EQ(root, result.paths[1]->certs[2]); |
| |
| EXPECT_FALSE(result.paths[2]->IsValid()); |
| ASSERT_EQ(3U, result.paths[2]->certs.size()); |
| EXPECT_EQ(target, result.paths[2]->certs[0]); |
| EXPECT_EQ(int_ad, result.paths[2]->certs[1]); |
| EXPECT_EQ(root, result.paths[2]->certs[2]); |
| |
| EXPECT_FALSE(result.paths[3]->IsValid()); |
| ASSERT_EQ(3U, result.paths[3]->certs.size()); |
| EXPECT_EQ(target, result.paths[3]->certs[0]); |
| EXPECT_EQ(int_ac, result.paths[3]->certs[1]); |
| EXPECT_EQ(root, result.paths[3]->certs[2]); |
| } |
| } |
| |
| TEST(PathBuilderPrioritizationTest, KeyIdPrioritization) { |
| std::string test_dir = |
| "testdata/path_builder_unittest/key_id_prioritization/"; |
| std::shared_ptr<const ParsedCertificate> root = |
| ReadCertFromFile(test_dir + "root.pem"); |
| ASSERT_TRUE(root); |
| std::shared_ptr<const ParsedCertificate> int_matching_ski_a = |
| ReadCertFromFile(test_dir + "int_matching_ski_a.pem"); |
| ASSERT_TRUE(int_matching_ski_a); |
| std::shared_ptr<const ParsedCertificate> int_matching_ski_b = |
| ReadCertFromFile(test_dir + "int_matching_ski_b.pem"); |
| ASSERT_TRUE(int_matching_ski_b); |
| std::shared_ptr<const ParsedCertificate> int_no_ski_a = |
| ReadCertFromFile(test_dir + "int_no_ski_a.pem"); |
| ASSERT_TRUE(int_no_ski_a); |
| std::shared_ptr<const ParsedCertificate> int_no_ski_b = |
| ReadCertFromFile(test_dir + "int_no_ski_b.pem"); |
| ASSERT_TRUE(int_no_ski_b); |
| std::shared_ptr<const ParsedCertificate> int_different_ski_a = |
| ReadCertFromFile(test_dir + "int_different_ski_a.pem"); |
| ASSERT_TRUE(int_different_ski_a); |
| std::shared_ptr<const ParsedCertificate> int_different_ski_b = |
| ReadCertFromFile(test_dir + "int_different_ski_b.pem"); |
| ASSERT_TRUE(int_different_ski_b); |
| std::shared_ptr<const ParsedCertificate> target = |
| ReadCertFromFile(test_dir + "target.pem"); |
| ASSERT_TRUE(target); |
| |
| SimplePathBuilderDelegate delegate( |
| 1024, SimplePathBuilderDelegate::DigestPolicy::kWeakAllowSha1); |
| der::GeneralizedTime verify_time = {2017, 3, 1, 0, 0, 0}; |
| |
| // Distrust the root certificate. This will force the path builder to attempt |
| // all possible paths. |
| TrustStoreInMemory trust_store; |
| trust_store.AddDistrustedCertificateForTest(root); |
| |
| for (bool reverse_input_order : {false, true}) { |
| SCOPED_TRACE(reverse_input_order); |
| |
| CertIssuerSourceStatic intermediates; |
| // Test with the intermediates supplied in two different orders to ensure |
| // the results don't depend on input ordering. |
| if (reverse_input_order) { |
| intermediates.AddCert(int_different_ski_b); |
| intermediates.AddCert(int_different_ski_a); |
| intermediates.AddCert(int_no_ski_b); |
| intermediates.AddCert(int_no_ski_a); |
| intermediates.AddCert(int_matching_ski_b); |
| intermediates.AddCert(int_matching_ski_a); |
| } else { |
| intermediates.AddCert(int_matching_ski_a); |
| intermediates.AddCert(int_matching_ski_b); |
| intermediates.AddCert(int_no_ski_a); |
| intermediates.AddCert(int_no_ski_b); |
| intermediates.AddCert(int_different_ski_a); |
| intermediates.AddCert(int_different_ski_b); |
| } |
| |
| CertPathBuilder path_builder( |
| target, &trust_store, &delegate, verify_time, KeyPurpose::ANY_EKU, |
| InitialExplicitPolicy::kFalse, {der::Input(kAnyPolicyOid)}, |
| InitialPolicyMappingInhibit::kFalse, InitialAnyPolicyInhibit::kFalse); |
| path_builder.AddCertIssuerSource(&intermediates); |
| |
| CertPathBuilder::Result result = path_builder.Run(); |
| EXPECT_FALSE(result.HasValidPath()); |
| ASSERT_EQ(6U, result.paths.size()); |
| |
| // Path builder should have attempted paths using the intermediates in |
| // order: matching_ski_b, matching_ski_a, no_ski_b, no_ski_a, |
| // different_ski_b, different_ski_a |
| |
| EXPECT_FALSE(result.paths[0]->IsValid()); |
| ASSERT_EQ(3U, result.paths[0]->certs.size()); |
| EXPECT_EQ(target, result.paths[0]->certs[0]); |
| EXPECT_EQ(int_matching_ski_b, result.paths[0]->certs[1]); |
| EXPECT_EQ(root, result.paths[0]->certs[2]); |
| |
| EXPECT_FALSE(result.paths[1]->IsValid()); |
| ASSERT_EQ(3U, result.paths[1]->certs.size()); |
| EXPECT_EQ(target, result.paths[1]->certs[0]); |
| EXPECT_EQ(int_matching_ski_a, result.paths[1]->certs[1]); |
| EXPECT_EQ(root, result.paths[1]->certs[2]); |
| |
| EXPECT_FALSE(result.paths[2]->IsValid()); |
| ASSERT_EQ(3U, result.paths[2]->certs.size()); |
| EXPECT_EQ(target, result.paths[2]->certs[0]); |
| EXPECT_EQ(int_no_ski_b, result.paths[2]->certs[1]); |
| EXPECT_EQ(root, result.paths[2]->certs[2]); |
| |
| EXPECT_FALSE(result.paths[3]->IsValid()); |
| ASSERT_EQ(3U, result.paths[3]->certs.size()); |
| EXPECT_EQ(target, result.paths[3]->certs[0]); |
| EXPECT_EQ(int_no_ski_a, result.paths[3]->certs[1]); |
| EXPECT_EQ(root, result.paths[3]->certs[2]); |
| |
| EXPECT_FALSE(result.paths[4]->IsValid()); |
| ASSERT_EQ(3U, result.paths[4]->certs.size()); |
| EXPECT_EQ(target, result.paths[4]->certs[0]); |
| EXPECT_EQ(int_different_ski_b, result.paths[4]->certs[1]); |
| EXPECT_EQ(root, result.paths[4]->certs[2]); |
| |
| EXPECT_FALSE(result.paths[5]->IsValid()); |
| ASSERT_EQ(3U, result.paths[5]->certs.size()); |
| EXPECT_EQ(target, result.paths[5]->certs[0]); |
| EXPECT_EQ(int_different_ski_a, result.paths[5]->certs[1]); |
| EXPECT_EQ(root, result.paths[5]->certs[2]); |
| } |
| } |
| |
| TEST(PathBuilderPrioritizationTest, TrustAndKeyIdPrioritization) { |
| std::string test_dir = |
| "testdata/path_builder_unittest/key_id_prioritization/"; |
| std::shared_ptr<const ParsedCertificate> root = |
| ReadCertFromFile(test_dir + "root.pem"); |
| ASSERT_TRUE(root); |
| std::shared_ptr<const ParsedCertificate> trusted_and_matching = |
| ReadCertFromFile(test_dir + "int_matching_ski_a.pem"); |
| ASSERT_TRUE(trusted_and_matching); |
| std::shared_ptr<const ParsedCertificate> matching = |
| ReadCertFromFile(test_dir + "int_matching_ski_b.pem"); |
| ASSERT_TRUE(matching); |
| std::shared_ptr<const ParsedCertificate> distrusted_and_matching = |
| ReadCertFromFile(test_dir + "int_matching_ski_c.pem"); |
| ASSERT_TRUE(distrusted_and_matching); |
| std::shared_ptr<const ParsedCertificate> trusted_and_no_match_data = |
| ReadCertFromFile(test_dir + "int_no_ski_a.pem"); |
| ASSERT_TRUE(trusted_and_no_match_data); |
| std::shared_ptr<const ParsedCertificate> no_match_data = |
| ReadCertFromFile(test_dir + "int_no_ski_b.pem"); |
| ASSERT_TRUE(no_match_data); |
| std::shared_ptr<const ParsedCertificate> distrusted_and_no_match_data = |
| ReadCertFromFile(test_dir + "int_no_ski_c.pem"); |
| ASSERT_TRUE(distrusted_and_no_match_data); |
| std::shared_ptr<const ParsedCertificate> trusted_and_mismatch = |
| ReadCertFromFile(test_dir + "int_different_ski_a.pem"); |
| ASSERT_TRUE(trusted_and_mismatch); |
| std::shared_ptr<const ParsedCertificate> mismatch = |
| ReadCertFromFile(test_dir + "int_different_ski_b.pem"); |
| ASSERT_TRUE(mismatch); |
| std::shared_ptr<const ParsedCertificate> distrusted_and_mismatch = |
| ReadCertFromFile(test_dir + "int_different_ski_c.pem"); |
| ASSERT_TRUE(distrusted_and_mismatch); |
| std::shared_ptr<const ParsedCertificate> target = |
| ReadCertFromFile(test_dir + "target.pem"); |
| ASSERT_TRUE(target); |
| |
| SimplePathBuilderDelegate delegate( |
| 1024, SimplePathBuilderDelegate::DigestPolicy::kWeakAllowSha1); |
| der::GeneralizedTime verify_time = {2017, 3, 1, 0, 0, 0}; |
| |
| for (bool reverse_input_order : {false, true}) { |
| SCOPED_TRACE(reverse_input_order); |
| |
| TrustStoreInMemory trust_store; |
| // Test with the intermediates supplied in two different orders to ensure |
| // the results don't depend on input ordering. |
| if (reverse_input_order) { |
| trust_store.AddTrustAnchor(trusted_and_matching); |
| trust_store.AddCertificateWithUnspecifiedTrust(matching); |
| trust_store.AddDistrustedCertificateForTest(distrusted_and_matching); |
| trust_store.AddTrustAnchor(trusted_and_no_match_data); |
| trust_store.AddCertificateWithUnspecifiedTrust(no_match_data); |
| trust_store.AddDistrustedCertificateForTest(distrusted_and_no_match_data); |
| trust_store.AddTrustAnchor(trusted_and_mismatch); |
| trust_store.AddCertificateWithUnspecifiedTrust(mismatch); |
| trust_store.AddDistrustedCertificateForTest(distrusted_and_mismatch); |
| } else { |
| trust_store.AddDistrustedCertificateForTest(distrusted_and_matching); |
| trust_store.AddCertificateWithUnspecifiedTrust(no_match_data); |
| trust_store.AddTrustAnchor(trusted_and_no_match_data); |
| trust_store.AddTrustAnchor(trusted_and_matching); |
| trust_store.AddCertificateWithUnspecifiedTrust(matching); |
| trust_store.AddCertificateWithUnspecifiedTrust(mismatch); |
| trust_store.AddDistrustedCertificateForTest(distrusted_and_no_match_data); |
| trust_store.AddTrustAnchor(trusted_and_mismatch); |
| trust_store.AddDistrustedCertificateForTest(distrusted_and_mismatch); |
| } |
| // Also distrust the root certificate. This will force the path builder to |
| // report paths that included an unspecified trust intermediate. |
| trust_store.AddDistrustedCertificateForTest(root); |
| |
| CertPathBuilder path_builder( |
| target, &trust_store, &delegate, verify_time, KeyPurpose::ANY_EKU, |
| InitialExplicitPolicy::kFalse, {der::Input(kAnyPolicyOid)}, |
| InitialPolicyMappingInhibit::kFalse, InitialAnyPolicyInhibit::kFalse); |
| path_builder.SetValidPathLimit(0); |
| |
| CertPathBuilder::Result result = path_builder.Run(); |
| EXPECT_TRUE(result.HasValidPath()); |
| ASSERT_EQ(9U, result.paths.size()); |
| |
| // Path builder should have attempted paths using the intermediates in |
| // order: trusted_and_matching, trusted_and_no_match_data, matching, |
| // no_match_data, trusted_and_mismatch, mismatch, distrusted_and_matching, |
| // distrusted_and_no_match_data, distrusted_and_mismatch. |
| |
| EXPECT_TRUE(result.paths[0]->IsValid()); |
| ASSERT_EQ(2U, result.paths[0]->certs.size()); |
| EXPECT_EQ(target, result.paths[0]->certs[0]); |
| EXPECT_EQ(trusted_and_matching, result.paths[0]->certs[1]); |
| |
| EXPECT_TRUE(result.paths[1]->IsValid()); |
| ASSERT_EQ(2U, result.paths[1]->certs.size()); |
| EXPECT_EQ(target, result.paths[1]->certs[0]); |
| EXPECT_EQ(trusted_and_no_match_data, result.paths[1]->certs[1]); |
| |
| EXPECT_FALSE(result.paths[2]->IsValid()); |
| ASSERT_EQ(3U, result.paths[2]->certs.size()); |
| EXPECT_EQ(target, result.paths[2]->certs[0]); |
| EXPECT_EQ(matching, result.paths[2]->certs[1]); |
| EXPECT_EQ(root, result.paths[2]->certs[2]); |
| |
| EXPECT_FALSE(result.paths[3]->IsValid()); |
| ASSERT_EQ(3U, result.paths[3]->certs.size()); |
| EXPECT_EQ(target, result.paths[3]->certs[0]); |
| EXPECT_EQ(no_match_data, result.paths[3]->certs[1]); |
| EXPECT_EQ(root, result.paths[3]->certs[2]); |
| |
| // Although this intermediate is trusted, it has the wrong key, so |
| // the path should not be valid. |
| EXPECT_FALSE(result.paths[4]->IsValid()); |
| ASSERT_EQ(2U, result.paths[4]->certs.size()); |
| EXPECT_EQ(target, result.paths[4]->certs[0]); |
| EXPECT_EQ(trusted_and_mismatch, result.paths[4]->certs[1]); |
| |
| EXPECT_FALSE(result.paths[5]->IsValid()); |
| ASSERT_EQ(3U, result.paths[5]->certs.size()); |
| EXPECT_EQ(target, result.paths[5]->certs[0]); |
| EXPECT_EQ(mismatch, result.paths[5]->certs[1]); |
| EXPECT_EQ(root, result.paths[5]->certs[2]); |
| |
| EXPECT_FALSE(result.paths[6]->IsValid()); |
| ASSERT_EQ(2U, result.paths[6]->certs.size()); |
| EXPECT_EQ(target, result.paths[6]->certs[0]); |
| EXPECT_EQ(distrusted_and_matching, result.paths[6]->certs[1]); |
| |
| EXPECT_FALSE(result.paths[7]->IsValid()); |
| ASSERT_EQ(2U, result.paths[7]->certs.size()); |
| EXPECT_EQ(target, result.paths[7]->certs[0]); |
| EXPECT_EQ(distrusted_and_no_match_data, result.paths[7]->certs[1]); |
| |
| EXPECT_FALSE(result.paths[8]->IsValid()); |
| ASSERT_EQ(2U, result.paths[8]->certs.size()); |
| EXPECT_EQ(target, result.paths[8]->certs[0]); |
| EXPECT_EQ(distrusted_and_mismatch, result.paths[8]->certs[1]); |
| } |
| } |
| |
| // PathBuilder does not support prioritization based on the issuer name & |
| // serial in authorityKeyIdentifier, so this test just ensures that it does not |
| // affect prioritization order and that it is generally just ignored |
| // completely. |
| TEST(PathBuilderPrioritizationTest, KeyIdNameAndSerialPrioritization) { |
| std::string test_dir = |
| "testdata/path_builder_unittest/key_id_name_and_serial_prioritization/"; |
| std::shared_ptr<const ParsedCertificate> root = |
| ReadCertFromFile(test_dir + "root.pem"); |
| ASSERT_TRUE(root); |
| std::shared_ptr<const ParsedCertificate> root2 = |
| ReadCertFromFile(test_dir + "root2.pem"); |
| ASSERT_TRUE(root2); |
| std::shared_ptr<const ParsedCertificate> int_matching = |
| ReadCertFromFile(test_dir + "int_matching.pem"); |
| ASSERT_TRUE(int_matching); |
| std::shared_ptr<const ParsedCertificate> int_match_name_only = |
| ReadCertFromFile(test_dir + "int_match_name_only.pem"); |
| ASSERT_TRUE(int_match_name_only); |
| std::shared_ptr<const ParsedCertificate> int_mismatch = |
| ReadCertFromFile(test_dir + "int_mismatch.pem"); |
| ASSERT_TRUE(int_mismatch); |
| std::shared_ptr<const ParsedCertificate> target = |
| ReadCertFromFile(test_dir + "target.pem"); |
| ASSERT_TRUE(target); |
| |
| SimplePathBuilderDelegate delegate( |
| 1024, SimplePathBuilderDelegate::DigestPolicy::kWeakAllowSha1); |
| der::GeneralizedTime verify_time = {2017, 3, 1, 0, 0, 0}; |
| |
| // Distrust the root certificates. This will force the path builder to attempt |
| // all possible paths. |
| TrustStoreInMemory trust_store; |
| trust_store.AddDistrustedCertificateForTest(root); |
| trust_store.AddDistrustedCertificateForTest(root2); |
| |
| for (bool reverse_input_order : {false, true}) { |
| SCOPED_TRACE(reverse_input_order); |
| |
| CertIssuerSourceStatic intermediates; |
| // Test with the intermediates supplied in two different orders to ensure |
| // the results don't depend on input ordering. |
| if (reverse_input_order) { |
| intermediates.AddCert(int_mismatch); |
| intermediates.AddCert(int_match_name_only); |
| intermediates.AddCert(int_matching); |
| } else { |
| intermediates.AddCert(int_matching); |
| intermediates.AddCert(int_match_name_only); |
| intermediates.AddCert(int_mismatch); |
| } |
| |
| CertPathBuilder path_builder( |
| target, &trust_store, &delegate, verify_time, KeyPurpose::ANY_EKU, |
| InitialExplicitPolicy::kFalse, {der::Input(kAnyPolicyOid)}, |
| InitialPolicyMappingInhibit::kFalse, InitialAnyPolicyInhibit::kFalse); |
| path_builder.AddCertIssuerSource(&intermediates); |
| |
| CertPathBuilder::Result result = path_builder.Run(); |
| EXPECT_FALSE(result.HasValidPath()); |
| ASSERT_EQ(3U, result.paths.size()); |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_NOT_FOUND) |
| << error.DiagnosticString(); |
| |
| // The serial & issuer method is not used in prioritization, so the certs |
| // should have been prioritized based on dates. The test certs have the |
| // date priority order in the reverse of what authorityKeyIdentifier |
| // prioritization would have done if it were supported. |
| // Path builder should have attempted paths using the intermediates in |
| // order: mismatch, match_name_only, matching |
| |
| EXPECT_FALSE(result.paths[0]->IsValid()); |
| ASSERT_EQ(3U, result.paths[0]->certs.size()); |
| EXPECT_EQ(target, result.paths[0]->certs[0]); |
| EXPECT_EQ(int_mismatch, result.paths[0]->certs[1]); |
| EXPECT_EQ(root2, result.paths[0]->certs[2]); |
| |
| EXPECT_FALSE(result.paths[1]->IsValid()); |
| ASSERT_EQ(3U, result.paths[1]->certs.size()); |
| EXPECT_EQ(target, result.paths[1]->certs[0]); |
| EXPECT_EQ(int_match_name_only, result.paths[1]->certs[1]); |
| EXPECT_EQ(root, result.paths[1]->certs[2]); |
| |
| EXPECT_FALSE(result.paths[2]->IsValid()); |
| ASSERT_EQ(3U, result.paths[2]->certs.size()); |
| EXPECT_EQ(target, result.paths[2]->certs[0]); |
| EXPECT_EQ(int_matching, result.paths[2]->certs[1]); |
| EXPECT_EQ(root, result.paths[2]->certs[2]); |
| } |
| } |
| |
| TEST(PathBuilderPrioritizationTest, SelfIssuedPrioritization) { |
| std::string test_dir = |
| "testdata/path_builder_unittest/self_issued_prioritization/"; |
| std::shared_ptr<const ParsedCertificate> root1 = |
| ReadCertFromFile(test_dir + "root1.pem"); |
| ASSERT_TRUE(root1); |
| std::shared_ptr<const ParsedCertificate> root1_cross = |
| ReadCertFromFile(test_dir + "root1_cross.pem"); |
| ASSERT_TRUE(root1_cross); |
| std::shared_ptr<const ParsedCertificate> target = |
| ReadCertFromFile(test_dir + "target.pem"); |
| ASSERT_TRUE(target); |
| |
| SimplePathBuilderDelegate delegate( |
| 1024, SimplePathBuilderDelegate::DigestPolicy::kWeakAllowSha1); |
| der::GeneralizedTime verify_time = {2017, 3, 1, 0, 0, 0}; |
| |
| TrustStoreInMemory trust_store; |
| trust_store.AddTrustAnchor(root1); |
| trust_store.AddTrustAnchor(root1_cross); |
| CertPathBuilder path_builder( |
| target, &trust_store, &delegate, verify_time, KeyPurpose::ANY_EKU, |
| InitialExplicitPolicy::kFalse, {der::Input(kAnyPolicyOid)}, |
| InitialPolicyMappingInhibit::kFalse, InitialAnyPolicyInhibit::kFalse); |
| path_builder.SetValidPathLimit(0); |
| |
| CertPathBuilder::Result result = path_builder.Run(); |
| EXPECT_TRUE(result.HasValidPath()); |
| VerifyError error = result.GetBestPathVerifyError(); |
| ASSERT_EQ(error.Code(), VerifyError::StatusCode::PATH_VERIFIED) |
| << error.DiagnosticString(); |
| |
| // Path builder should have built paths to both trusted roots. |
| ASSERT_EQ(2U, result.paths.size()); |
| |
| // |root1| should have been preferred because it is self-issued, even though |
| // the notBefore date is older than |root1_cross|. |
| EXPECT_TRUE(result.paths[0]->IsValid()); |
| ASSERT_EQ(2U, result.paths[0]->certs.size()); |
| EXPECT_EQ(target, result.paths[0]->certs[0]); |
| EXPECT_EQ(root1, result.paths[0]->certs[1]); |
| |
| EXPECT_TRUE(result.paths[1]->IsValid()); |
| ASSERT_EQ(2U, result.paths[1]->certs.size()); |
| EXPECT_EQ(target, result.paths[1]->certs[0]); |
| EXPECT_EQ(root1_cross, result.paths[1]->certs[1]); |
| } |
| |
| } // namespace |
| |
| BSSL_NAMESPACE_END |