| // Copyright 2019 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/cert_verify_proc_builtin.h" |
| |
| #include "base/numerics/safe_conversions.h" |
| #include "base/run_loop.h" |
| #include "base/task/post_task.h" |
| #include "base/task/thread_pool.h" |
| #include "base/test/task_environment.h" |
| #include "net/base/test_completion_callback.h" |
| #include "net/cert/cert_verify_proc.h" |
| #include "net/cert/crl_set.h" |
| #include "net/cert/ev_root_ca_metadata.h" |
| #include "net/cert/internal/system_trust_store.h" |
| #include "net/cert_net/cert_net_fetcher_url_request.h" |
| #include "net/der/encode_values.h" |
| #include "net/log/net_log_with_source.h" |
| #include "net/log/test_net_log.h" |
| #include "net/test/cert_builder.h" |
| #include "net/test/cert_test_util.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "net/test/embedded_test_server/http_request.h" |
| #include "net/test/embedded_test_server/http_response.h" |
| #include "net/test/embedded_test_server/request_handler_util.h" |
| #include "net/test/gtest_util.h" |
| #include "net/url_request/url_request_test_util.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using net::test::IsError; |
| using net::test::IsOk; |
| |
| namespace net { |
| |
| namespace { |
| |
| class DummySystemTrustStoreProvider : public SystemTrustStoreProvider { |
| public: |
| std::unique_ptr<SystemTrustStore> CreateSystemTrustStore() override { |
| return CreateEmptySystemTrustStore(); |
| } |
| }; |
| |
| std::unique_ptr<test_server::HttpResponse> HangRequestAndCallback( |
| base::OnceClosure callback, |
| const test_server::HttpRequest& request) { |
| std::move(callback).Run(); |
| return std::make_unique<test_server::HungResponse>(); |
| } |
| |
| void FailTest(const std::string& message) { |
| ADD_FAILURE() << message; |
| } |
| |
| std::unique_ptr<test_server::HttpResponse> FailRequestAndFailTest( |
| const std::string& message, |
| scoped_refptr<base::TaskRunner> main_task_runner, |
| const test_server::HttpRequest& request) { |
| main_task_runner->PostTask(FROM_HERE, base::BindOnce(FailTest, message)); |
| auto response = std::make_unique<test_server::BasicHttpResponse>(); |
| response->set_code(HTTP_NOT_ACCEPTABLE); |
| return response; |
| } |
| |
| int VerifyOnWorkerThread(const scoped_refptr<CertVerifyProc>& verify_proc, |
| scoped_refptr<X509Certificate> cert, |
| const std::string& hostname, |
| int flags, |
| const CertificateList& additional_trust_anchors, |
| CertVerifyResult* verify_result, |
| NetLogSource* out_source) { |
| base::ScopedAllowBaseSyncPrimitivesForTesting scoped_allow_blocking; |
| scoped_refptr<CRLSet> crl_set = CRLSet::EmptyCRLSetForTesting(); |
| NetLogWithSource net_log(NetLogWithSource::Make( |
| net::NetLog::Get(), net::NetLogSourceType::CERT_VERIFIER_TASK)); |
| int error = |
| verify_proc->Verify(cert.get(), hostname, |
| /*ocsp_response=*/std::string(), |
| /*sct_list=*/std::string(), flags, crl_set.get(), |
| additional_trust_anchors, verify_result, net_log); |
| verify_result->DetachFromSequence(); |
| *out_source = net_log.source(); |
| return error; |
| } |
| |
| } // namespace |
| |
| class CertVerifyProcBuiltinTest : public ::testing::Test { |
| public: |
| // CertVerifyProcBuiltinTest() {} |
| |
| void SetUp() override { |
| cert_net_fetcher_ = base::MakeRefCounted<CertNetFetcherURLRequest>(); |
| verify_proc_ = CreateCertVerifyProcBuiltin( |
| cert_net_fetcher_, std::make_unique<DummySystemTrustStoreProvider>()); |
| |
| context_ = std::make_unique<net::TestURLRequestContext>(); |
| |
| cert_net_fetcher_->SetURLRequestContext(context_.get()); |
| } |
| |
| void TearDown() override { cert_net_fetcher_->Shutdown(); } |
| |
| void Verify(scoped_refptr<X509Certificate> cert, |
| const std::string& hostname, |
| int flags, |
| const CertificateList& additional_trust_anchors, |
| CertVerifyResult* verify_result, |
| NetLogSource* out_source, |
| CompletionOnceCallback callback) { |
| verify_result->DetachFromSequence(); |
| base::ThreadPool::PostTaskAndReplyWithResult( |
| FROM_HERE, |
| {base::MayBlock(), base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}, |
| base::BindOnce(&VerifyOnWorkerThread, verify_proc_, std::move(cert), |
| hostname, flags, additional_trust_anchors, verify_result, |
| out_source), |
| std::move(callback)); |
| } |
| |
| base::test::TaskEnvironment& task_environment() { return task_environment_; } |
| |
| void CreateChain(std::unique_ptr<CertBuilder>* out_leaf, |
| std::unique_ptr<CertBuilder>* out_intermediate, |
| std::unique_ptr<CertBuilder>* out_root) { |
| CertBuilder::CreateSimpleChain(out_leaf, out_intermediate, out_root); |
| ASSERT_TRUE(*out_leaf && *out_intermediate && *out_root); |
| // This test uses MOCK_TIME, so need to set the cert validity dates based |
| // on whatever the mock time happens to start at. |
| base::Time not_before = base::Time::Now() - base::TimeDelta::FromDays(1); |
| base::Time not_after = base::Time::Now() + base::TimeDelta::FromDays(10); |
| (*out_leaf)->SetValidity(not_before, not_after); |
| (*out_intermediate)->SetValidity(not_before, not_after); |
| (*out_root)->SetValidity(not_before, not_after); |
| } |
| |
| private: |
| base::test::TaskEnvironment task_environment_{ |
| base::test::TaskEnvironment::TimeSource::MOCK_TIME, |
| base::test::TaskEnvironment::MainThreadType::IO, |
| }; |
| |
| CertVerifier::Config config_; |
| std::unique_ptr<net::TestURLRequestContext> context_; |
| scoped_refptr<CertVerifyProc> verify_proc_; |
| scoped_refptr<CertNetFetcherURLRequest> cert_net_fetcher_; |
| }; |
| |
| TEST_F(CertVerifyProcBuiltinTest, SimpleSuccess) { |
| std::unique_ptr<CertBuilder> leaf, intermediate, root; |
| CreateChain(&leaf, &intermediate, &root); |
| ASSERT_TRUE(leaf && intermediate && root); |
| |
| scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain(); |
| ASSERT_TRUE(chain.get()); |
| |
| CertVerifyResult verify_result; |
| NetLogSource verify_net_log_source; |
| TestCompletionCallback callback; |
| Verify(chain.get(), "www.example.com", /*flags=*/0, |
| /*additional_trust_anchors=*/{root->GetX509Certificate()}, |
| &verify_result, &verify_net_log_source, callback.callback()); |
| |
| int error = callback.WaitForResult(); |
| EXPECT_THAT(error, IsOk()); |
| } |
| |
| // Tests that if the verification deadline is exceeded during revocation |
| // checking, additional CRL fetches will not be attempted. |
| TEST_F(CertVerifyProcBuiltinTest, RevocationCheckDeadlineCRL) { |
| std::unique_ptr<CertBuilder> leaf, intermediate, root; |
| CreateChain(&leaf, &intermediate, &root); |
| ASSERT_TRUE(leaf && intermediate && root); |
| |
| const base::TimeDelta timeout_increment = |
| CertNetFetcherURLRequest::GetDefaultTimeoutForTesting() + |
| base::TimeDelta::FromMilliseconds(1); |
| const int expected_request_count = |
| base::ClampFloor(GetCertVerifyProcBuiltinTimeLimitForTesting() / |
| timeout_increment) + |
| 1; |
| |
| EmbeddedTestServer test_server(EmbeddedTestServer::TYPE_HTTP); |
| ASSERT_TRUE(test_server.InitializeAndListen()); |
| |
| // Set up the test cert to have enough crlDistributionPoint urls that if the |
| // first N-1 requests hang the deadline will be exceeded before the Nth |
| // request is made. |
| std::vector<GURL> crl_urls; |
| std::vector<base::RunLoop> runloops(expected_request_count); |
| for (int i = 0; i < expected_request_count; ++i) { |
| std::string path = base::StringPrintf("/hung/%i", i); |
| crl_urls.emplace_back(test_server.GetURL(path)); |
| test_server.RegisterRequestHandler( |
| base::BindRepeating(&test_server::HandlePrefixedRequest, path, |
| base::BindRepeating(&HangRequestAndCallback, |
| runloops[i].QuitClosure()))); |
| } |
| // Add CRL URLs and handlers that will add test failures if requested. |
| for (int i = expected_request_count; i < expected_request_count + 1; ++i) { |
| std::string path = base::StringPrintf("/failtest/%i", i); |
| crl_urls.emplace_back(test_server.GetURL(path)); |
| test_server.RegisterRequestHandler(base::BindRepeating( |
| &test_server::HandlePrefixedRequest, path, |
| base::BindRepeating(FailRequestAndFailTest, |
| "additional request made after deadline exceeded", |
| base::SequencedTaskRunnerHandle::Get()))); |
| } |
| leaf->SetCrlDistributionPointUrls(crl_urls); |
| |
| test_server.StartAcceptingConnections(); |
| |
| scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain(); |
| ASSERT_TRUE(chain.get()); |
| |
| CertVerifyResult verify_result; |
| NetLogSource verify_net_log_source; |
| TestCompletionCallback verify_callback; |
| Verify(chain.get(), "www.example.com", |
| CertVerifyProc::VERIFY_REV_CHECKING_ENABLED, |
| /*additional_trust_anchors=*/{root->GetX509Certificate()}, |
| &verify_result, &verify_net_log_source, verify_callback.callback()); |
| |
| for (int i = 0; i < expected_request_count; i++) { |
| // Wait for request #|i| to be made. |
| runloops[i].Run(); |
| // Advance virtual time to cause the timeout task to become runnable. |
| task_environment().AdvanceClock(timeout_increment); |
| } |
| |
| // Once |expected_request_count| requests have been made and timed out, the |
| // overall deadline should be reached, and no more requests should have been |
| // made. (If they were, the test will fail due to the ADD_FAILURE callback in |
| // the request handlers.) |
| int error = verify_callback.WaitForResult(); |
| // Soft-fail revocation checking was used, therefore verification result |
| // should be OK even though none of the CRLs could be retrieved. |
| EXPECT_THAT(error, IsOk()); |
| } |
| |
| // Tests that if the verification deadline is exceeded during revocation |
| // checking, additional OCSP fetches will not be attempted. |
| TEST_F(CertVerifyProcBuiltinTest, RevocationCheckDeadlineOCSP) { |
| std::unique_ptr<CertBuilder> leaf, intermediate, root; |
| CreateChain(&leaf, &intermediate, &root); |
| ASSERT_TRUE(leaf && intermediate && root); |
| |
| const base::TimeDelta timeout_increment = |
| CertNetFetcherURLRequest::GetDefaultTimeoutForTesting() + |
| base::TimeDelta::FromMilliseconds(1); |
| const int expected_request_count = |
| base::ClampFloor(GetCertVerifyProcBuiltinTimeLimitForTesting() / |
| timeout_increment) + |
| 1; |
| |
| EmbeddedTestServer test_server(EmbeddedTestServer::TYPE_HTTP); |
| ASSERT_TRUE(test_server.InitializeAndListen()); |
| |
| // Set up the test cert to have enough OCSP urls that if the |
| // first N-1 requests hang the deadline will be exceeded before the Nth |
| // request is made. |
| std::vector<GURL> ocsp_urls; |
| std::vector<base::RunLoop> runloops(expected_request_count); |
| for (int i = 0; i < expected_request_count; ++i) { |
| std::string path = base::StringPrintf("/hung/%i", i); |
| ocsp_urls.emplace_back(test_server.GetURL(path)); |
| test_server.RegisterRequestHandler( |
| base::BindRepeating(&test_server::HandlePrefixedRequest, path, |
| base::BindRepeating(&HangRequestAndCallback, |
| runloops[i].QuitClosure()))); |
| } |
| // Add OCSP URLs and handlers that will add test failures if requested. |
| for (int i = expected_request_count; i < expected_request_count + 1; ++i) { |
| std::string path = base::StringPrintf("/failtest/%i", i); |
| ocsp_urls.emplace_back(test_server.GetURL(path)); |
| test_server.RegisterRequestHandler(base::BindRepeating( |
| &test_server::HandlePrefixedRequest, path, |
| base::BindRepeating(FailRequestAndFailTest, |
| "additional request made after deadline exceeded", |
| base::SequencedTaskRunnerHandle::Get()))); |
| } |
| leaf->SetCaIssuersAndOCSPUrls({}, ocsp_urls); |
| |
| test_server.StartAcceptingConnections(); |
| |
| scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain(); |
| ASSERT_TRUE(chain.get()); |
| |
| CertVerifyResult verify_result; |
| NetLogSource verify_net_log_source; |
| TestCompletionCallback verify_callback; |
| Verify(chain.get(), "www.example.com", |
| CertVerifyProc::VERIFY_REV_CHECKING_ENABLED, |
| /*additional_trust_anchors=*/{root->GetX509Certificate()}, |
| &verify_result, &verify_net_log_source, verify_callback.callback()); |
| |
| for (int i = 0; i < expected_request_count; i++) { |
| // Wait for request #|i| to be made. |
| runloops[i].Run(); |
| // Advance virtual time to cause the timeout task to become runnable. |
| task_environment().AdvanceClock(timeout_increment); |
| } |
| |
| // Once |expected_request_count| requests have been made and timed out, the |
| // overall deadline should be reached, and no more requests should have been |
| // made. (If they were, the test will fail due to the ADD_FAILURE callback in |
| // the request handlers.) |
| int error = verify_callback.WaitForResult(); |
| // Soft-fail revocation checking was used, therefore verification result |
| // should be OK even though none of the OCSP responses could be retrieved. |
| EXPECT_THAT(error, IsOk()); |
| } |
| |
| #if defined(PLATFORM_USES_CHROMIUM_EV_METADATA) |
| // Tests that if the verification deadline is exceeded during EV revocation |
| // checking, the certificate is verified as non-EV. |
| TEST_F(CertVerifyProcBuiltinTest, EVRevocationCheckDeadline) { |
| std::unique_ptr<CertBuilder> leaf, intermediate, root; |
| CreateChain(&leaf, &intermediate, &root); |
| ASSERT_TRUE(leaf && intermediate && root); |
| |
| // Add test EV policy to leaf and intermediate. |
| static const char kEVTestCertPolicy[] = "1.2.3.4"; |
| leaf->SetCertificatePolicies({kEVTestCertPolicy}); |
| intermediate->SetCertificatePolicies({kEVTestCertPolicy}); |
| |
| const base::TimeDelta timeout_increment = |
| CertNetFetcherURLRequest::GetDefaultTimeoutForTesting() + |
| base::TimeDelta::FromMilliseconds(1); |
| const int expected_request_count = |
| base::ClampFloor(GetCertVerifyProcBuiltinTimeLimitForTesting() / |
| timeout_increment) + |
| 1; |
| |
| EmbeddedTestServer test_server(EmbeddedTestServer::TYPE_HTTP); |
| ASSERT_TRUE(test_server.InitializeAndListen()); |
| |
| // Set up the test intermediate to have enough OCSP urls that if all the |
| // requests hang the deadline will be exceeded. |
| std::vector<GURL> ocsp_urls; |
| std::vector<base::RunLoop> runloops(expected_request_count); |
| for (int i = 0; i < expected_request_count; ++i) { |
| std::string path = base::StringPrintf("/hung/%i", i); |
| ocsp_urls.emplace_back(test_server.GetURL(path)); |
| test_server.RegisterRequestHandler( |
| base::BindRepeating(&test_server::HandlePrefixedRequest, path, |
| base::BindRepeating(&HangRequestAndCallback, |
| runloops[i].QuitClosure()))); |
| } |
| intermediate->SetCaIssuersAndOCSPUrls({}, ocsp_urls); |
| |
| test_server.StartAcceptingConnections(); |
| |
| // Consider the root of the test chain a valid EV root for the test policy. |
| ScopedTestEVPolicy scoped_test_ev_policy( |
| EVRootCAMetadata::GetInstance(), |
| X509Certificate::CalculateFingerprint256(root->GetCertBuffer()), |
| kEVTestCertPolicy); |
| |
| scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain(); |
| ASSERT_TRUE(chain.get()); |
| |
| RecordingNetLogObserver net_log_observer(NetLogCaptureMode::kDefault); |
| CertVerifyResult verify_result; |
| NetLogSource verify_net_log_source; |
| TestCompletionCallback verify_callback; |
| Verify(chain.get(), "www.example.com", |
| /*flags=*/0, |
| /*additional_trust_anchors=*/{root->GetX509Certificate()}, |
| &verify_result, &verify_net_log_source, verify_callback.callback()); |
| |
| for (int i = 0; i < expected_request_count; i++) { |
| // Wait for request #|i| to be made. |
| runloops[i].Run(); |
| // Advance virtual time to cause the timeout task to become runnable. |
| task_environment().AdvanceClock(timeout_increment); |
| } |
| |
| // Once |expected_request_count| requests have been made and timed out, the |
| // overall deadline should be reached, causing the EV verification attempt to |
| // fail. |
| int error = verify_callback.WaitForResult(); |
| // EV uses soft-fail revocation checking, therefore verification result |
| // should be OK but not EV. |
| EXPECT_THAT(error, IsOk()); |
| EXPECT_FALSE(verify_result.cert_status & CERT_STATUS_IS_EV); |
| EXPECT_TRUE(verify_result.cert_status & CERT_STATUS_REV_CHECKING_ENABLED); |
| |
| auto events = net_log_observer.GetEntriesForSource(verify_net_log_source); |
| |
| auto event = std::find_if(events.begin(), events.end(), [](const auto& e) { |
| return e.type == NetLogEventType::CERT_VERIFY_PROC_PATH_BUILD_ATTEMPT; |
| }); |
| ASSERT_NE(event, events.end()); |
| EXPECT_EQ(net::NetLogEventPhase::BEGIN, event->phase); |
| ASSERT_TRUE(event->params.is_dict()); |
| EXPECT_EQ(true, event->params.FindBoolKey("is_ev_attempt")); |
| |
| event = std::find_if(++event, events.end(), [](const auto& e) { |
| return e.type == NetLogEventType::CERT_VERIFY_PROC_PATH_BUILT; |
| }); |
| ASSERT_NE(event, events.end()); |
| EXPECT_EQ(net::NetLogEventPhase::NONE, event->phase); |
| ASSERT_TRUE(event->params.is_dict()); |
| const std::string* errors = event->params.FindStringKey("errors"); |
| ASSERT_TRUE(errors); |
| EXPECT_EQ("----- Certificate i=1 (CN=" + |
| intermediate->GetX509Certificate()->subject().common_name + |
| ") -----\nERROR: Unable to check revocation\n\n", |
| *errors); |
| |
| event = std::find_if(++event, events.end(), [](const auto& e) { |
| return e.type == NetLogEventType::CERT_VERIFY_PROC_PATH_BUILD_ATTEMPT; |
| }); |
| ASSERT_NE(event, events.end()); |
| EXPECT_EQ(net::NetLogEventPhase::END, event->phase); |
| ASSERT_TRUE(event->params.is_dict()); |
| EXPECT_EQ(false, event->params.FindBoolKey("has_valid_path")); |
| |
| event = std::find_if(++event, events.end(), [](const auto& e) { |
| return e.type == NetLogEventType::CERT_VERIFY_PROC_PATH_BUILD_ATTEMPT; |
| }); |
| ASSERT_NE(event, events.end()); |
| EXPECT_EQ(net::NetLogEventPhase::BEGIN, event->phase); |
| ASSERT_TRUE(event->params.is_dict()); |
| EXPECT_EQ(base::nullopt, event->params.FindBoolKey("is_ev_attempt")); |
| |
| event = std::find_if(++event, events.end(), [](const auto& e) { |
| return e.type == NetLogEventType::CERT_VERIFY_PROC_PATH_BUILT; |
| }); |
| ASSERT_NE(event, events.end()); |
| EXPECT_EQ(net::NetLogEventPhase::NONE, event->phase); |
| ASSERT_TRUE(event->params.is_dict()); |
| EXPECT_FALSE(event->params.FindStringKey("errors")); |
| |
| event = std::find_if(++event, events.end(), [](const auto& e) { |
| return e.type == NetLogEventType::CERT_VERIFY_PROC_PATH_BUILD_ATTEMPT; |
| }); |
| ASSERT_NE(event, events.end()); |
| EXPECT_EQ(net::NetLogEventPhase::END, event->phase); |
| ASSERT_TRUE(event->params.is_dict()); |
| EXPECT_EQ(true, event->params.FindBoolKey("has_valid_path")); |
| } |
| #endif // defined(PLATFORM_USES_CHROMIUM_EV_METADATA) |
| |
| TEST_F(CertVerifyProcBuiltinTest, DebugData) { |
| std::unique_ptr<CertBuilder> leaf, intermediate, root; |
| CreateChain(&leaf, &intermediate, &root); |
| ASSERT_TRUE(leaf && intermediate && root); |
| |
| scoped_refptr<X509Certificate> chain = leaf->GetX509CertificateChain(); |
| ASSERT_TRUE(chain.get()); |
| |
| base::Time time = base::Time::Now(); |
| |
| CertVerifyResult verify_result; |
| NetLogSource verify_net_log_source; |
| TestCompletionCallback callback; |
| Verify(chain.get(), "www.example.com", /*flags=*/0, |
| /*additional_trust_anchors=*/{root->GetX509Certificate()}, |
| &verify_result, &verify_net_log_source, callback.callback()); |
| |
| int error = callback.WaitForResult(); |
| EXPECT_THAT(error, IsOk()); |
| |
| auto* debug_data = CertVerifyProcBuiltinResultDebugData::Get(&verify_result); |
| ASSERT_TRUE(debug_data); |
| // No delayed tasks involved, so the mock time should not have advanced. |
| EXPECT_EQ(time, debug_data->verification_time()); |
| |
| base::Time der_verification_time_converted_back_to_base_time; |
| EXPECT_TRUE(net::der::GeneralizedTimeToTime( |
| debug_data->der_verification_time(), |
| &der_verification_time_converted_back_to_base_time)); |
| // GeneralizedTime only has seconds precision. |
| EXPECT_EQ( |
| 0, |
| (time - der_verification_time_converted_back_to_base_time).InSeconds()); |
| } |
| |
| } // namespace net |