Index: net/cert/internal/path_builder_unittest.cc |
diff --git a/net/cert/internal/path_builder_unittest.cc b/net/cert/internal/path_builder_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..dfdf25b56fc38b3dd819344aecaf08a7016e351f |
--- /dev/null |
+++ b/net/cert/internal/path_builder_unittest.cc |
@@ -0,0 +1,1108 @@ |
+// Copyright 2016 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "net/cert/internal/path_builder.h" |
+ |
+#include "base/base_paths.h" |
+#include "base/cancelable_callback.h" |
+#include "base/files/file_util.h" |
+#include "base/location.h" |
+#include "base/path_service.h" |
+#include "base/threading/thread_task_runner_handle.h" |
+#include "net/base/net_errors.h" |
+#include "net/base/test_completion_callback.h" |
+#include "net/cert/internal/cert_issuer_source_static.h" |
+#include "net/cert/internal/parsed_certificate.h" |
+#include "net/cert/internal/signature_policy.h" |
+#include "net/cert/internal/test_helpers.h" |
+#include "net/cert/internal/trust_store.h" |
+#include "net/cert/internal/verify_certificate_chain.h" |
+#include "net/cert/pem_tokenizer.h" |
+#include "net/der/input.h" |
+#include "net/test/cert_test_util.h" |
+#include "net/test/test_certificate_data.h" |
+#include "testing/gmock/include/gmock/gmock.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+namespace net { |
+ |
+namespace { |
+ |
+using ::testing::_; |
+using ::testing::Invoke; |
+using ::testing::SaveArg; |
+using ::testing::StrictMock; |
+using ::testing::SetArgPointee; |
+using ::testing::Return; |
+ |
+// AsyncCertIssuerSourceStatic always returns its certs asynchronously. |
+class AsyncCertIssuerSourceStatic : public CertIssuerSource { |
+ public: |
+ class StaticAsyncRequest : public Request { |
+ public: |
+ StaticAsyncRequest(const IssuerCallback& issuers_callback, |
+ ParsedCertificateList&& issuers) |
+ : cancelable_closure_(base::Bind(&StaticAsyncRequest::RunCallback, |
+ base::Unretained(this))), |
+ issuers_callback_(issuers_callback) { |
+ issuers_.swap(issuers); |
+ issuers_iter_ = issuers_.begin(); |
+ } |
+ ~StaticAsyncRequest() override {} |
+ |
+ CompletionStatus GetNext( |
+ scoped_refptr<ParsedCertificate>* out_cert) override { |
+ if (issuers_iter_ == issuers_.end()) |
+ *out_cert = nullptr; |
+ else |
+ *out_cert = std::move(*issuers_iter_++); |
+ return CompletionStatus::SYNC; |
+ } |
+ |
+ base::Closure callback() { return cancelable_closure_.callback(); } |
+ |
+ private: |
+ void RunCallback() { issuers_callback_.Run(this); } |
+ |
+ base::CancelableClosure cancelable_closure_; |
+ IssuerCallback issuers_callback_; |
+ ParsedCertificateList issuers_; |
+ ParsedCertificateList::iterator issuers_iter_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(StaticAsyncRequest); |
+ }; |
+ |
+ ~AsyncCertIssuerSourceStatic() override {} |
+ |
+ void AddCert(scoped_refptr<ParsedCertificate> cert) { |
+ static_cert_issuer_source_.AddCert(std::move(cert)); |
+ } |
+ |
+ void SyncGetIssuersOf(const ParsedCertificate* cert, |
+ ParsedCertificateList* issuers) override {} |
+ void AsyncGetIssuersOf(const ParsedCertificate* cert, |
+ const IssuerCallback& issuers_callback, |
+ std::unique_ptr<Request>* out_req) override { |
+ num_async_gets_++; |
+ ParsedCertificateList issuers; |
+ static_cert_issuer_source_.SyncGetIssuersOf(cert, &issuers); |
+ std::unique_ptr<StaticAsyncRequest> req( |
+ new StaticAsyncRequest(issuers_callback, std::move(issuers))); |
+ base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, req->callback()); |
+ *out_req = std::move(req); |
+ } |
+ int num_async_gets() const { return num_async_gets_; } |
+ |
+ private: |
+ CertIssuerSourceStatic static_cert_issuer_source_; |
+ |
+ int num_async_gets_ = 0; |
+}; |
+ |
+// Reads a data file from the unit-test data. |
+std::string ReadTestFileToString(const std::string& file_name) { |
+ // Compute the full path, relative to the src/ directory. |
+ base::FilePath src_root; |
+ PathService::Get(base::DIR_SOURCE_ROOT, &src_root); |
+ base::FilePath filepath = src_root.AppendASCII(file_name); |
+ |
+ // Read the full contents of the file. |
+ std::string file_data; |
+ if (!base::ReadFileToString(filepath, &file_data)) { |
+ ADD_FAILURE() << "Couldn't read file: " << filepath.value(); |
+ return std::string(); |
+ } |
+ |
+ return file_data; |
+} |
+ |
+// Reads a verify_certificate_chain_unittest-style test case from |file_name|. |
+// Test cases are comprised of a certificate chain, trust store, a timestamp to |
+// validate at, and the expected result of verification (though the expected |
+// result is ignored here). |
+void ReadVerifyCertChainTestFromFile(const std::string& file_name, |
+ std::vector<std::string>* chain, |
+ scoped_refptr<ParsedCertificate>* root, |
+ der::GeneralizedTime* time) { |
+ chain->clear(); |
+ |
+ std::string file_data = ReadTestFileToString(file_name); |
+ |
+ std::vector<std::string> pem_headers; |
+ |
+ const char kCertificateHeader[] = "CERTIFICATE"; |
+ const char kTrustedCertificateHeader[] = "TRUSTED_CERTIFICATE"; |
+ const char kTimeHeader[] = "TIME"; |
+ |
+ pem_headers.push_back(kCertificateHeader); |
+ pem_headers.push_back(kTrustedCertificateHeader); |
+ pem_headers.push_back(kTimeHeader); |
+ |
+ bool has_time = false; |
+ |
+ PEMTokenizer pem_tokenizer(file_data, pem_headers); |
+ while (pem_tokenizer.GetNext()) { |
+ const std::string& block_type = pem_tokenizer.block_type(); |
+ const std::string& block_data = pem_tokenizer.data(); |
+ |
+ if (block_type == kCertificateHeader) { |
+ chain->push_back(block_data); |
+ } else if (block_type == kTrustedCertificateHeader) { |
+ *root = ParsedCertificate::CreateFromCertificateCopy(block_data, {}); |
+ ASSERT_TRUE(*root); |
+ } else if (block_type == kTimeHeader) { |
+ ASSERT_FALSE(has_time) << "Duplicate " << kTimeHeader; |
+ has_time = true; |
+ ASSERT_TRUE(der::ParseUTCTime(der::Input(&block_data), time)); |
+ } |
+ } |
+ |
+ ASSERT_TRUE(has_time); |
+} |
+ |
+::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, |
+ scoped_refptr<ParsedCertificate>* result) { |
+ std::string der; |
+ ::testing::AssertionResult r = ReadTestPem( |
+ "net/data/ssl/certificates/" + file_name, "CERTIFICATE", &der); |
+ if (!r) |
+ return r; |
+ *result = ParsedCertificate::CreateFromCertificateCopy(der, {}); |
+ if (!*result) |
+ return ::testing::AssertionFailure() << "CreateFromCertificateCopy failed"; |
+ return ::testing::AssertionSuccess(); |
+} |
+ |
+// Run the path builder, and wait for async completion if necessary. The return |
+// value signifies whether the path builder completed synchronously or |
+// asynchronously, not that RunPathBuilder itself is asynchronous. |
+CompletionStatus RunPathBuilder(CertPathBuilder* path_builder) { |
+ TestClosure callback; |
+ CompletionStatus rv = path_builder->Run(callback.closure()); |
+ |
+ if (rv == CompletionStatus::ASYNC) { |
+ DVLOG(1) << "waiting for async completion..."; |
+ callback.WaitForResult(); |
+ DVLOG(1) << "async completed."; |
+ } |
+ return rv; |
+} |
+ |
+class PathBuilderMultiRootTest : public ::testing::Test { |
+ public: |
+ PathBuilderMultiRootTest() : signature_policy_(1024) {} |
+ |
+ 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: |
+ scoped_refptr<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_; |
+ |
+ SimpleSignaturePolicy signature_policy_; |
+ der::GeneralizedTime time_ = {2016, 4, 11, 0, 0, 0}; |
+}; |
+ |
+// If the target cert is a trust anchor, it should verify and should not include |
+// anything else in the path. |
+TEST_F(PathBuilderMultiRootTest, TargetIsTrustAnchor) { |
+ TrustStore trust_store; |
+ trust_store.AddTrustedCertificate(a_by_b_); |
+ trust_store.AddTrustedCertificate(b_by_f_); |
+ |
+ CertPathBuilder::Result result; |
+ CertPathBuilder path_builder(a_by_b_, &trust_store, &signature_policy_, time_, |
+ &result); |
+ |
+ EXPECT_EQ(CompletionStatus::SYNC, RunPathBuilder(&path_builder)); |
+ |
+ EXPECT_EQ(OK, result.error()); |
+ EXPECT_EQ(1U, result.paths[result.best_result_index]->path.size()); |
+ EXPECT_EQ(a_by_b_, result.paths[result.best_result_index]->path[0]); |
+} |
+ |
+// If the target cert is directly issued by a trust anchor, it should verify |
+// without any intermediate certs being provided. |
+TEST_F(PathBuilderMultiRootTest, TargetDirectlySignedByTrustAnchor) { |
+ TrustStore trust_store; |
+ trust_store.AddTrustedCertificate(b_by_f_); |
+ |
+ CertPathBuilder::Result result; |
+ CertPathBuilder path_builder(a_by_b_, &trust_store, &signature_policy_, time_, |
+ &result); |
+ |
+ EXPECT_EQ(CompletionStatus::SYNC, RunPathBuilder(&path_builder)); |
+ |
+ EXPECT_EQ(OK, result.error()); |
+ EXPECT_EQ(2U, result.paths[result.best_result_index]->path.size()); |
+ EXPECT_EQ(a_by_b_, result.paths[result.best_result_index]->path[0]); |
+ EXPECT_EQ(b_by_f_, result.paths[result.best_result_index]->path[1]); |
+} |
+ |
+// Test that async cert queries are not made if the path can be successfully |
+// built with synchronously available certs. |
+TEST_F(PathBuilderMultiRootTest, TriesSyncFirst) { |
+ TrustStore trust_store; |
+ trust_store.AddTrustedCertificate(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::Result result; |
+ CertPathBuilder path_builder(a_by_b_, &trust_store, &signature_policy_, time_, |
+ &result); |
+ path_builder.AddCertIssuerSource(&async_certs); |
+ path_builder.AddCertIssuerSource(&sync_certs); |
+ |
+ EXPECT_EQ(CompletionStatus::SYNC, RunPathBuilder(&path_builder)); |
+ |
+ EXPECT_EQ(OK, result.error()); |
+ EXPECT_EQ(0, async_certs.num_async_gets()); |
+} |
+ |
+// Test that async cert queries are not made if no callback is provided. |
+TEST_F(PathBuilderMultiRootTest, SychronousOnlyMode) { |
+ TrustStore trust_store; |
+ trust_store.AddTrustedCertificate(e_by_e_); |
+ |
+ CertIssuerSourceStatic sync_certs; |
+ sync_certs.AddCert(f_by_e_); |
+ |
+ AsyncCertIssuerSourceStatic async_certs; |
+ async_certs.AddCert(b_by_f_); |
+ |
+ CertPathBuilder::Result result; |
+ CertPathBuilder path_builder(a_by_b_, &trust_store, &signature_policy_, time_, |
+ &result); |
+ path_builder.AddCertIssuerSource(&async_certs); |
+ path_builder.AddCertIssuerSource(&sync_certs); |
+ |
+ EXPECT_EQ(CompletionStatus::SYNC, path_builder.Run(base::Closure())); |
+ |
+ EXPECT_EQ(ERR_CERT_AUTHORITY_INVALID, result.error()); |
+ EXPECT_EQ(0, async_certs.num_async_gets()); |
+} |
+ |
+// If async queries are needed, all async sources will be queried |
+// simultaneously. |
+TEST_F(PathBuilderMultiRootTest, TestAsyncSimultaneous) { |
+ TrustStore trust_store; |
+ trust_store.AddTrustedCertificate(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::Result result; |
+ CertPathBuilder path_builder(a_by_b_, &trust_store, &signature_policy_, time_, |
+ &result); |
+ path_builder.AddCertIssuerSource(&async_certs1); |
+ path_builder.AddCertIssuerSource(&async_certs2); |
+ path_builder.AddCertIssuerSource(&sync_certs); |
+ |
+ EXPECT_EQ(CompletionStatus::ASYNC, RunPathBuilder(&path_builder)); |
+ |
+ EXPECT_EQ(OK, result.error()); |
+ EXPECT_EQ(1, async_certs1.num_async_gets()); |
+ EXPECT_EQ(1, async_certs2.num_async_gets()); |
+} |
+ |
+// 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. |
+ TrustStore trust_store; |
+ trust_store.AddTrustedCertificate(d_by_d_); |
+ trust_store.AddTrustedCertificate(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::Result result; |
+ CertPathBuilder path_builder(a_by_b_, &trust_store, &signature_policy_, time_, |
+ &result); |
+ path_builder.AddCertIssuerSource(&sync_certs); |
+ |
+ EXPECT_EQ(CompletionStatus::SYNC, RunPathBuilder(&path_builder)); |
+ |
+ EXPECT_EQ(OK, result.error()); |
+ |
+ // 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.paths[result.best_result_index]->path.size()); |
+} |
+ |
+// 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. |
+ TrustStore trust_store; |
+ trust_store.AddTrustedCertificate(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::Result result; |
+ CertPathBuilder path_builder(a_by_b_, &trust_store, &signature_policy_, time_, |
+ &result); |
+ path_builder.AddCertIssuerSource(&sync_certs); |
+ path_builder.AddCertIssuerSource(&async_certs); |
+ |
+ EXPECT_EQ(CompletionStatus::ASYNC, RunPathBuilder(&path_builder)); |
+ |
+ EXPECT_EQ(OK, result.error()); |
+ |
+ // The result path should be A(B) <- B(C) <- C(D) <- D(D) |
+ ASSERT_EQ(4U, result.paths[result.best_result_index]->path.size()); |
+ EXPECT_EQ(a_by_b_, result.paths[result.best_result_index]->path[0]); |
+ EXPECT_EQ(b_by_c_, result.paths[result.best_result_index]->path[1]); |
+ EXPECT_EQ(c_by_d_, result.paths[result.best_result_index]->path[2]); |
+ EXPECT_EQ(d_by_d_, result.paths[result.best_result_index]->path[3]); |
+} |
+ |
+// Test that whichever order CertIssuerSource returns the issuers, the path |
+// building still succeeds. |
+TEST_F(PathBuilderMultiRootTest, TestCertIssuerOrdering) { |
+ // Only D(D) is a trusted root. |
+ TrustStore trust_store; |
+ trust_store.AddTrustedCertificate(d_by_d_); |
+ |
+ for (bool reverse_order : {false, true}) { |
+ SCOPED_TRACE(reverse_order); |
+ std::vector<scoped_refptr<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::Result result; |
+ CertPathBuilder path_builder(a_by_b_, &trust_store, &signature_policy_, |
+ time_, &result); |
+ path_builder.AddCertIssuerSource(&sync_certs); |
+ |
+ EXPECT_EQ(CompletionStatus::SYNC, RunPathBuilder(&path_builder)); |
+ |
+ EXPECT_EQ(OK, result.error()); |
+ |
+ // The result path should be A(B) <- B(C) <- C(D) <- D(D) |
+ ASSERT_EQ(4U, result.paths[result.best_result_index]->path.size()); |
+ EXPECT_EQ(a_by_b_, result.paths[result.best_result_index]->path[0]); |
+ EXPECT_EQ(b_by_c_, result.paths[result.best_result_index]->path[1]); |
+ EXPECT_EQ(c_by_d_, result.paths[result.best_result_index]->path[2]); |
+ EXPECT_EQ(d_by_d_, result.paths[result.best_result_index]->path[3]); |
+ } |
+} |
+ |
+class PathBuilderKeyRolloverTest : public ::testing::Test { |
+ public: |
+ PathBuilderKeyRolloverTest() : signature_policy_(1024) {} |
+ |
+ void SetUp() override { |
+ std::vector<std::string> path; |
+ |
+ ReadVerifyCertChainTestFromFile( |
+ "net/data/verify_certificate_chain_unittest/key-rollover-oldchain.pem", |
+ &path, &oldroot_, &time_); |
+ ASSERT_EQ(2U, path.size()); |
+ target_ = ParsedCertificate::CreateFromCertificateCopy(path[0], {}); |
+ oldintermediate_ = |
+ ParsedCertificate::CreateFromCertificateCopy(path[1], {}); |
+ ASSERT_TRUE(target_); |
+ ASSERT_TRUE(oldintermediate_); |
+ |
+ ReadVerifyCertChainTestFromFile( |
+ "net/data/verify_certificate_chain_unittest/" |
+ "key-rollover-longrolloverchain.pem", |
+ &path, &oldroot_, &time_); |
+ ASSERT_EQ(4U, path.size()); |
+ newintermediate_ = |
+ ParsedCertificate::CreateFromCertificateCopy(path[1], {}); |
+ newroot_ = ParsedCertificate::CreateFromCertificateCopy(path[2], {}); |
+ newrootrollover_ = |
+ ParsedCertificate::CreateFromCertificateCopy(path[3], {}); |
+ ASSERT_TRUE(newintermediate_); |
+ ASSERT_TRUE(newroot_); |
+ ASSERT_TRUE(newrootrollover_); |
+ } |
+ |
+ protected: |
+ // oldroot-------->newrootrollover newroot |
+ // | | | |
+ // v v v |
+ // oldintermediate newintermediate |
+ // | | |
+ // +------------+-------------+ |
+ // | |
+ // v |
+ // target |
+ scoped_refptr<ParsedCertificate> target_; |
+ scoped_refptr<ParsedCertificate> oldintermediate_; |
+ scoped_refptr<ParsedCertificate> newintermediate_; |
+ scoped_refptr<ParsedCertificate> oldroot_; |
+ scoped_refptr<ParsedCertificate> newroot_; |
+ scoped_refptr<ParsedCertificate> newrootrollover_; |
+ |
+ SimpleSignaturePolicy signature_policy_; |
+ der::GeneralizedTime time_; |
+}; |
+ |
+// 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. |
+ TrustStore trust_store; |
+ trust_store.AddTrustedCertificate(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::Result result; |
+ CertPathBuilder path_builder(target_, &trust_store, &signature_policy_, time_, |
+ &result); |
+ path_builder.AddCertIssuerSource(&sync_certs); |
+ |
+ EXPECT_EQ(CompletionStatus::SYNC, RunPathBuilder(&path_builder)); |
+ |
+ EXPECT_EQ(OK, result.error()); |
+ |
+ // Path builder will first attempt: target <- newintermediate <- oldroot |
+ // but it will fail since newintermediate is signed by newroot. |
+ ASSERT_EQ(2U, result.paths.size()); |
+ EXPECT_EQ(ERR_CERT_AUTHORITY_INVALID, result.paths[0]->error); |
+ ASSERT_EQ(3U, result.paths[0]->path.size()); |
+ EXPECT_EQ(target_, result.paths[0]->path[0]); |
+ EXPECT_EQ(newintermediate_, result.paths[0]->path[1]); |
+ EXPECT_EQ(oldroot_, result.paths[0]->path[2]); |
+ |
+ // Path builder will next attempt: |
+ // target <- newintermediate <- newrootrollover <- oldroot |
+ // which will succeed. |
+ EXPECT_EQ(1U, result.best_result_index); |
+ EXPECT_EQ(OK, result.paths[1]->error); |
+ ASSERT_EQ(4U, result.paths[1]->path.size()); |
+ EXPECT_EQ(target_, result.paths[1]->path[0]); |
+ EXPECT_EQ(newintermediate_, result.paths[1]->path[1]); |
+ EXPECT_EQ(newrootrollover_, result.paths[1]->path[2]); |
+ EXPECT_EQ(oldroot_, result.paths[1]->path[3]); |
+} |
+ |
+// Tests that if both old and new roots are trusted it can build a path through |
+// either. |
+// TODO(mattm): Once prioritization is implemented, it should test that it |
+// always builds the path through the new intermediate and new root. |
+TEST_F(PathBuilderKeyRolloverTest, TestRolloverBothRootsTrusted) { |
+ // Both oldroot and newroot are trusted. |
+ TrustStore trust_store; |
+ trust_store.AddTrustedCertificate(oldroot_); |
+ trust_store.AddTrustedCertificate(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::Result result; |
+ CertPathBuilder path_builder(target_, &trust_store, &signature_policy_, time_, |
+ &result); |
+ path_builder.AddCertIssuerSource(&sync_certs); |
+ |
+ EXPECT_EQ(CompletionStatus::SYNC, RunPathBuilder(&path_builder)); |
+ |
+ EXPECT_EQ(OK, result.error()); |
+ |
+ // Path builder willattempt one of: |
+ // target <- oldintermediate <- oldroot |
+ // target <- newintermediate <- newroot |
+ // either will succeed. |
+ ASSERT_EQ(1U, result.paths.size()); |
+ EXPECT_EQ(OK, result.paths[0]->error); |
+ ASSERT_EQ(3U, result.paths[0]->path.size()); |
+ EXPECT_EQ(target_, result.paths[0]->path[0]); |
+ if (result.paths[0]->path[1] != newintermediate_) { |
+ DVLOG(1) << "USED OLD"; |
+ EXPECT_EQ(oldintermediate_, result.paths[0]->path[1]); |
+ EXPECT_EQ(oldroot_, result.paths[0]->path[2]); |
+ } else { |
+ DVLOG(1) << "USED NEW"; |
+ EXPECT_EQ(newintermediate_, result.paths[0]->path[1]); |
+ EXPECT_EQ(newroot_, result.paths[0]->path[2]); |
+ } |
+} |
+ |
+// 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) { |
+ // Both newroot and oldroot are trusted. |
+ TrustStore trust_store; |
+ // Note: The test assumes newroot will be tried before oldroot. |
+ // Currently this depends on the order the roots are added. |
+ trust_store.AddTrustedCertificate(newroot_); |
+ trust_store.AddTrustedCertificate(oldroot_); |
+ |
+ // Only oldintermediate is supplied, so the path with newroot should fail, |
+ // oldroot should succeed. |
+ CertIssuerSourceStatic sync_certs; |
+ sync_certs.AddCert(oldintermediate_); |
+ |
+ CertPathBuilder::Result result; |
+ CertPathBuilder path_builder(target_, &trust_store, &signature_policy_, time_, |
+ &result); |
+ path_builder.AddCertIssuerSource(&sync_certs); |
+ |
+ EXPECT_EQ(CompletionStatus::SYNC, RunPathBuilder(&path_builder)); |
+ |
+ EXPECT_EQ(OK, result.error()); |
+ 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_EQ(ERR_CERT_AUTHORITY_INVALID, result.paths[0]->error); |
+ ASSERT_EQ(3U, result.paths[0]->path.size()); |
+ EXPECT_EQ(target_, result.paths[0]->path[0]); |
+ EXPECT_EQ(oldintermediate_, result.paths[0]->path[1]); |
+ EXPECT_EQ(newroot_, result.paths[0]->path[2]); |
+ |
+ // Path builder will next attempt: |
+ // target <- old intermediate <- oldroot |
+ // which should succeed. |
+ EXPECT_EQ(OK, result.paths[1]->error); |
+ ASSERT_EQ(3U, result.paths[1]->path.size()); |
+ EXPECT_EQ(target_, result.paths[1]->path[0]); |
+ EXPECT_EQ(oldintermediate_, result.paths[1]->path[1]); |
+ EXPECT_EQ(oldroot_, result.paths[1]->path[2]); |
+} |
+ |
+// Tests that the path builder doesn't build longer than necessary paths. |
+TEST_F(PathBuilderKeyRolloverTest, TestRolloverLongChain) { |
+ // Only oldroot is trusted. |
+ TrustStore trust_store; |
+ trust_store.AddTrustedCertificate(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::Result result; |
+ CertPathBuilder path_builder(target_, &trust_store, &signature_policy_, time_, |
+ &result); |
+ path_builder.AddCertIssuerSource(&sync_certs); |
+ path_builder.AddCertIssuerSource(&async_certs); |
+ |
+ EXPECT_EQ(CompletionStatus::ASYNC, RunPathBuilder(&path_builder)); |
+ |
+ EXPECT_EQ(OK, result.error()); |
+ ASSERT_EQ(3U, result.paths.size()); |
+ |
+ // Path builder will first attempt: target <- newintermediate <- oldroot |
+ // but it will fail since newintermediate is signed by newroot. |
+ EXPECT_EQ(ERR_CERT_AUTHORITY_INVALID, result.paths[0]->error); |
+ ASSERT_EQ(3U, result.paths[0]->path.size()); |
+ EXPECT_EQ(target_, result.paths[0]->path[0]); |
+ EXPECT_EQ(newintermediate_, result.paths[0]->path[1]); |
+ EXPECT_EQ(oldroot_, result.paths[0]->path[2]); |
+ |
+ // Path builder will next attempt: |
+ // target <- newintermediate <- newroot <- oldroot |
+ // but it will fail since newroot is self-signed. |
+ EXPECT_EQ(ERR_CERT_AUTHORITY_INVALID, result.paths[1]->error); |
+ ASSERT_EQ(4U, result.paths[1]->path.size()); |
+ EXPECT_EQ(target_, result.paths[1]->path[0]); |
+ EXPECT_EQ(newintermediate_, result.paths[1]->path[1]); |
+ EXPECT_EQ(newroot_, result.paths[1]->path[2]); |
+ EXPECT_EQ(oldroot_, result.paths[1]->path[3]); |
+ |
+ // 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_EQ(OK, result.paths[2]->error); |
+ ASSERT_EQ(4U, result.paths[2]->path.size()); |
+ EXPECT_EQ(target_, result.paths[2]->path[0]); |
+ EXPECT_EQ(newintermediate_, result.paths[2]->path[1]); |
+ EXPECT_EQ(newrootrollover_, result.paths[2]->path[2]); |
+ EXPECT_EQ(oldroot_, result.paths[2]->path[3]); |
+} |
+ |
+// If the target cert is a trust root, that alone is a valid path. |
+TEST_F(PathBuilderKeyRolloverTest, TestEndEntityIsTrustRoot) { |
+ // Trust newintermediate. |
+ TrustStore trust_store; |
+ trust_store.AddTrustedCertificate(newintermediate_); |
+ |
+ CertPathBuilder::Result result; |
+ // Newintermediate is also the target cert. |
+ CertPathBuilder path_builder(newintermediate_, &trust_store, |
+ &signature_policy_, time_, &result); |
+ |
+ EXPECT_EQ(CompletionStatus::SYNC, RunPathBuilder(&path_builder)); |
+ |
+ EXPECT_EQ(OK, result.error()); |
+ |
+ ASSERT_EQ(1U, result.paths.size()); |
+ EXPECT_EQ(OK, result.paths[0]->error); |
+ ASSERT_EQ(1U, result.paths[0]->path.size()); |
+ EXPECT_EQ(newintermediate_, result.paths[0]->path[0]); |
+} |
+ |
+// 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. |
+ TrustStore trust_store; |
+ trust_store.AddTrustedCertificate(oldroot_); |
+ |
+ // New root rollover is provided synchronously. |
+ CertIssuerSourceStatic sync_certs; |
+ sync_certs.AddCert(newrootrollover_); |
+ |
+ CertPathBuilder::Result result; |
+ // Newroot is the target cert. |
+ CertPathBuilder path_builder(newroot_, &trust_store, &signature_policy_, |
+ time_, &result); |
+ path_builder.AddCertIssuerSource(&sync_certs); |
+ |
+ EXPECT_EQ(CompletionStatus::SYNC, RunPathBuilder(&path_builder)); |
+ |
+ // This could actually be OK, but CertPathBuilder does not build the |
+ // newroot <- newrootrollover <- oldroot path. |
+ EXPECT_EQ(ERR_CERT_AUTHORITY_INVALID, result.error()); |
+} |
+ |
+// 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. |
+ TrustStore trust_store; |
+ trust_store.AddTrustedCertificate(newrootrollover_); |
+ |
+ CertPathBuilder::Result result; |
+ // Newroot is the target cert. |
+ CertPathBuilder path_builder(newroot_, &trust_store, &signature_policy_, |
+ time_, &result); |
+ |
+ EXPECT_EQ(CompletionStatus::SYNC, RunPathBuilder(&path_builder)); |
+ |
+ EXPECT_EQ(OK, result.error()); |
+ |
+ ASSERT_FALSE(result.paths.empty()); |
+ const CertPathBuilder::ResultPath* best_result = |
+ result.paths[result.best_result_index].get(); |
+ |
+ // Newroot has same name+SPKI as newrootrollover, thus the path is valid and |
+ // only contains newroot. |
+ EXPECT_EQ(OK, best_result->error); |
+ ASSERT_EQ(1U, best_result->path.size()); |
+ EXPECT_EQ(newroot_, best_result->path[0]); |
+} |
+ |
+// 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. |
+ scoped_refptr<ParsedCertificate> oldintermediate_dupe( |
+ ParsedCertificate::CreateFromCertificateCopy( |
+ oldintermediate_->der_cert().AsStringPiece(), {})); |
+ |
+ // Only newroot is a trusted root. |
+ TrustStore trust_store; |
+ trust_store.AddTrustedCertificate(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::Result result; |
+ CertPathBuilder path_builder(target_, &trust_store, &signature_policy_, time_, |
+ &result); |
+ path_builder.AddCertIssuerSource(&sync_certs1); |
+ path_builder.AddCertIssuerSource(&sync_certs2); |
+ path_builder.AddCertIssuerSource(&async_certs); |
+ |
+ EXPECT_EQ(CompletionStatus::ASYNC, RunPathBuilder(&path_builder)); |
+ |
+ EXPECT_EQ(OK, result.error()); |
+ 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_EQ(ERR_CERT_AUTHORITY_INVALID, result.paths[0]->error); |
+ ASSERT_EQ(3U, result.paths[0]->path.size()); |
+ EXPECT_EQ(target_, result.paths[0]->path[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(), result.paths[0]->path[1]->der_cert()); |
+ EXPECT_EQ(newroot_, result.paths[0]->path[2]); |
+ |
+ // Path builder will next attempt: target <- newintermediate <- newroot |
+ // which will succeed. |
+ EXPECT_EQ(1U, result.best_result_index); |
+ EXPECT_EQ(OK, result.paths[1]->error); |
+ ASSERT_EQ(3U, result.paths[1]->path.size()); |
+ EXPECT_EQ(target_, result.paths[1]->path[0]); |
+ EXPECT_EQ(newintermediate_, result.paths[1]->path[1]); |
+ EXPECT_EQ(newroot_, result.paths[1]->path[2]); |
+} |
+ |
+// Test that PathBuilder will not try the same path twice if the same cert is |
+// presented via a CertIssuerSources and a TrustAnchor. |
+TEST_F(PathBuilderKeyRolloverTest, TestDuplicateIntermediateAndRoot) { |
+ // Create a separate copy of newroot. |
+ scoped_refptr<ParsedCertificate> newroot_dupe( |
+ ParsedCertificate::CreateFromCertificateCopy( |
+ newroot_->der_cert().AsStringPiece(), {})); |
+ |
+ // Only newroot is a trusted root. |
+ TrustStore trust_store; |
+ trust_store.AddTrustedCertificate(newroot_); |
+ |
+ // The oldintermediate and newroot are supplied synchronously by |sync_certs|. |
+ CertIssuerSourceStatic sync_certs; |
+ sync_certs.AddCert(oldintermediate_); |
+ sync_certs.AddCert(newroot_dupe); |
+ |
+ CertPathBuilder::Result result; |
+ CertPathBuilder path_builder(target_, &trust_store, &signature_policy_, time_, |
+ &result); |
+ path_builder.AddCertIssuerSource(&sync_certs); |
+ |
+ EXPECT_EQ(CompletionStatus::SYNC, RunPathBuilder(&path_builder)); |
+ |
+ EXPECT_EQ(ERR_CERT_AUTHORITY_INVALID, result.error()); |
+ ASSERT_EQ(1U, result.paths.size()); |
+ |
+ // Path builder attempt: target <- oldintermediate <- newroot |
+ // but it will fail since oldintermediate is signed by oldroot. |
+ EXPECT_EQ(ERR_CERT_AUTHORITY_INVALID, result.paths[0]->error); |
+ ASSERT_EQ(3U, result.paths[0]->path.size()); |
+ EXPECT_EQ(target_, result.paths[0]->path[0]); |
+ EXPECT_EQ(oldintermediate_, result.paths[0]->path[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(), result.paths[0]->path[2]->der_cert()); |
+} |
+ |
+class MockCertIssuerSourceRequest : public CertIssuerSource::Request { |
+ public: |
+ MOCK_METHOD1(GetNext, CompletionStatus(scoped_refptr<ParsedCertificate>*)); |
+}; |
+ |
+class MockCertIssuerSource : public CertIssuerSource { |
+ public: |
+ MOCK_METHOD2(SyncGetIssuersOf, |
+ void(const ParsedCertificate*, ParsedCertificateList*)); |
+ MOCK_METHOD3(AsyncGetIssuersOf, |
+ void(const ParsedCertificate*, |
+ const IssuerCallback&, |
+ 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: |
+ CertIssuerSourceRequestMover(std::unique_ptr<CertIssuerSource::Request> req) |
+ : request_(std::move(req)) {} |
+ void MoveIt(const ParsedCertificate* cert, |
+ const CertIssuerSource::IssuerCallback& issuers_callback, |
+ std::unique_ptr<CertIssuerSource::Request>* out_req) { |
+ *out_req = std::move(request_); |
+ } |
+ |
+ private: |
+ std::unique_ptr<CertIssuerSource::Request> request_; |
+}; |
+ |
+// 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, TestMultipleAsyncCallbacksFromSingleSource) { |
+ StrictMock<MockCertIssuerSource> cert_issuer_source; |
+ |
+ // Only newroot is a trusted root. |
+ TrustStore trust_store; |
+ trust_store.AddTrustedCertificate(newroot_); |
+ |
+ CertPathBuilder::Result result; |
+ CertPathBuilder path_builder(target_, &trust_store, &signature_policy_, time_, |
+ &result); |
+ path_builder.AddCertIssuerSource(&cert_issuer_source); |
+ |
+ CertIssuerSource::IssuerCallback target_issuers_callback; |
+ // Create the mock CertIssuerSource::Request... |
+ std::unique_ptr<StrictMock<MockCertIssuerSourceRequest>> |
+ target_issuers_req_owner(new 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( |
+ DoAll(SaveArg<1>(&target_issuers_callback), |
+ Invoke(&req_mover, &CertIssuerSourceRequestMover::MoveIt))); |
+ } |
+ |
+ TestClosure callback; |
+ CompletionStatus rv = path_builder.Run(callback.closure()); |
+ ASSERT_EQ(CompletionStatus::ASYNC, rv); |
+ |
+ ASSERT_FALSE(target_issuers_callback.is_null()); |
+ |
+ ::testing::Mock::VerifyAndClearExpectations(&cert_issuer_source); |
+ |
+ // First async batch: return oldintermediate_. |
+ EXPECT_CALL(*target_issuers_req, GetNext(_)) |
+ .WillOnce(DoAll(SetArgPointee<0>(oldintermediate_), |
+ Return(CompletionStatus::SYNC))) |
+ .WillOnce( |
+ DoAll(SetArgPointee<0>(nullptr), Return(CompletionStatus::ASYNC))); |
+ { |
+ ::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(), _, _)); |
+ } |
+ target_issuers_callback.Run(target_issuers_req); |
+ ::testing::Mock::VerifyAndClearExpectations(target_issuers_req); |
+ ::testing::Mock::VerifyAndClearExpectations(&cert_issuer_source); |
+ |
+ // Second async batch: return newintermediate_. |
+ EXPECT_CALL(*target_issuers_req, GetNext(_)) |
+ .WillOnce(DoAll(SetArgPointee<0>(newintermediate_), |
+ Return(CompletionStatus::SYNC))) |
+ .WillOnce( |
+ DoAll(SetArgPointee<0>(nullptr), Return(CompletionStatus::ASYNC))); |
+ // 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(), _)); |
+ target_issuers_callback.Run(target_issuers_req); |
+ // 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); |
+ |
+ // Ensure pathbuilder finished and filled result. |
+ callback.WaitForResult(); |
+ |
+ EXPECT_EQ(OK, result.error()); |
+ ASSERT_EQ(2U, result.paths.size()); |
+ |
+ // Path builder first attempts: target <- oldintermediate <- newroot |
+ // but it will fail since oldintermediate is signed by oldroot. |
+ EXPECT_EQ(ERR_CERT_AUTHORITY_INVALID, result.paths[0]->error); |
+ ASSERT_EQ(3U, result.paths[0]->path.size()); |
+ EXPECT_EQ(target_, result.paths[0]->path[0]); |
+ EXPECT_EQ(oldintermediate_, result.paths[0]->path[1]); |
+ EXPECT_EQ(newroot_, result.paths[0]->path[2]); |
+ |
+ // After the second batch of async results, path builder will attempt: |
+ // target <- newintermediate <- newroot which will succeed. |
+ EXPECT_EQ(OK, result.paths[1]->error); |
+ ASSERT_EQ(3U, result.paths[1]->path.size()); |
+ EXPECT_EQ(target_, result.paths[1]->path[0]); |
+ EXPECT_EQ(newintermediate_, result.paths[1]->path[1]); |
+ EXPECT_EQ(newroot_, result.paths[1]->path[2]); |
+} |
+ |
+// 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. |
+ TrustStore trust_store; |
+ trust_store.AddTrustedCertificate(newroot_); |
+ |
+ CertPathBuilder::Result result; |
+ CertPathBuilder path_builder(target_, &trust_store, &signature_policy_, time_, |
+ &result); |
+ path_builder.AddCertIssuerSource(&cert_issuer_source); |
+ |
+ CertIssuerSource::IssuerCallback target_issuers_callback; |
+ // Create the mock CertIssuerSource::Request... |
+ std::unique_ptr<StrictMock<MockCertIssuerSourceRequest>> |
+ target_issuers_req_owner(new 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( |
+ DoAll(SaveArg<1>(&target_issuers_callback), |
+ Invoke(&req_mover, &CertIssuerSourceRequestMover::MoveIt))); |
+ } |
+ |
+ TestClosure callback; |
+ CompletionStatus rv = path_builder.Run(callback.closure()); |
+ ASSERT_EQ(CompletionStatus::ASYNC, rv); |
+ |
+ ASSERT_FALSE(target_issuers_callback.is_null()); |
+ |
+ ::testing::Mock::VerifyAndClearExpectations(&cert_issuer_source); |
+ |
+ // First async batch: return oldintermediate_. |
+ EXPECT_CALL(*target_issuers_req, GetNext(_)) |
+ .WillOnce(DoAll(SetArgPointee<0>(oldintermediate_), |
+ Return(CompletionStatus::SYNC))) |
+ .WillOnce( |
+ DoAll(SetArgPointee<0>(nullptr), Return(CompletionStatus::ASYNC))); |
+ { |
+ ::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(), _, _)); |
+ } |
+ target_issuers_callback.Run(target_issuers_req); |
+ ::testing::Mock::VerifyAndClearExpectations(target_issuers_req); |
+ ::testing::Mock::VerifyAndClearExpectations(&cert_issuer_source); |
+ |
+ // Second async batch: return a different copy of oldintermediate_ again. |
+ scoped_refptr<ParsedCertificate> oldintermediate_dupe( |
+ ParsedCertificate::CreateFromCertificateCopy( |
+ oldintermediate_->der_cert().AsStringPiece(), {})); |
+ EXPECT_CALL(*target_issuers_req, GetNext(_)) |
+ .WillOnce(DoAll(SetArgPointee<0>(oldintermediate_dupe), |
+ Return(CompletionStatus::SYNC))) |
+ .WillOnce( |
+ DoAll(SetArgPointee<0>(nullptr), Return(CompletionStatus::ASYNC))); |
+ target_issuers_callback.Run(target_issuers_req); |
+ // oldintermediate was already processed above, it should not generate any |
+ // more requests. |
+ ::testing::Mock::VerifyAndClearExpectations(target_issuers_req); |
+ ::testing::Mock::VerifyAndClearExpectations(&cert_issuer_source); |
+ |
+ // Third async batch: return newintermediate_. |
+ EXPECT_CALL(*target_issuers_req, GetNext(_)) |
+ .WillOnce(DoAll(SetArgPointee<0>(newintermediate_), |
+ Return(CompletionStatus::SYNC))) |
+ .WillOnce( |
+ DoAll(SetArgPointee<0>(nullptr), Return(CompletionStatus::ASYNC))); |
+ // 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(), _)); |
+ target_issuers_callback.Run(target_issuers_req); |
+ // 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); |
+ |
+ // Ensure pathbuilder finished and filled result. |
+ callback.WaitForResult(); |
+ |
+ EXPECT_EQ(OK, result.error()); |
+ ASSERT_EQ(2U, result.paths.size()); |
+ |
+ // Path builder first attempts: target <- oldintermediate <- newroot |
+ // but it will fail since oldintermediate is signed by oldroot. |
+ EXPECT_EQ(ERR_CERT_AUTHORITY_INVALID, result.paths[0]->error); |
+ ASSERT_EQ(3U, result.paths[0]->path.size()); |
+ EXPECT_EQ(target_, result.paths[0]->path[0]); |
+ EXPECT_EQ(oldintermediate_, result.paths[0]->path[1]); |
+ EXPECT_EQ(newroot_, result.paths[0]->path[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_EQ(OK, result.paths[1]->error); |
+ ASSERT_EQ(3U, result.paths[1]->path.size()); |
+ EXPECT_EQ(target_, result.paths[1]->path[0]); |
+ EXPECT_EQ(newintermediate_, result.paths[1]->path[1]); |
+ EXPECT_EQ(newroot_, result.paths[1]->path[2]); |
+} |
+ |
+} // namespace |
+ |
+} // namespace net |