// Copyright (c) 2012 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/multi_threaded_cert_verifier.h" #include "base/bind.h" #include "base/debug/leak_annotations.h" #include "base/files/file_path.h" #include "base/format_macros.h" #include "base/strings/stringprintf.h" #include "net/base/net_errors.h" #include "net/base/test_completion_callback.h" #include "net/base/test_data_directory.h" #include "net/cert/cert_trust_anchor_provider.h" #include "net/cert/cert_verify_proc.h" #include "net/cert/cert_verify_result.h" #include "net/cert/x509_certificate.h" #include "net/log/net_log.h" #include "net/test/cert_test_util.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" using testing::Mock; using testing::ReturnRef; namespace net { namespace { void FailTest(int /* result */) { FAIL(); } class MockCertVerifyProc : public CertVerifyProc { public: MockCertVerifyProc() {} private: ~MockCertVerifyProc() override {} // CertVerifyProc implementation bool SupportsAdditionalTrustAnchors() const override { return false; } bool SupportsOCSPStapling() const override { return false; } int VerifyInternal(X509Certificate* cert, const std::string& hostname, const std::string& ocsp_response, int flags, CRLSet* crl_set, const CertificateList& additional_trust_anchors, CertVerifyResult* verify_result) override { verify_result->Reset(); verify_result->verified_cert = cert; verify_result->cert_status = CERT_STATUS_COMMON_NAME_INVALID; return ERR_CERT_COMMON_NAME_INVALID; } }; class MockCertTrustAnchorProvider : public CertTrustAnchorProvider { public: MockCertTrustAnchorProvider() {} virtual ~MockCertTrustAnchorProvider() {} MOCK_METHOD0(GetAdditionalTrustAnchors, const CertificateList&()); }; } // namespace class MultiThreadedCertVerifierTest : public ::testing::Test { public: MultiThreadedCertVerifierTest() : verifier_(new MockCertVerifyProc()) {} ~MultiThreadedCertVerifierTest() override {} protected: MultiThreadedCertVerifier verifier_; }; TEST_F(MultiThreadedCertVerifierTest, CacheHit) { base::FilePath certs_dir = GetTestCertsDirectory(); scoped_refptr test_cert( ImportCertFromFile(certs_dir, "ok_cert.pem")); ASSERT_NE(static_cast(NULL), test_cert.get()); int error; CertVerifyResult verify_result; TestCompletionCallback callback; scoped_ptr request; error = verifier_.Verify(test_cert.get(), "www.example.com", std::string(), 0, NULL, &verify_result, callback.callback(), &request, BoundNetLog()); ASSERT_EQ(ERR_IO_PENDING, error); EXPECT_TRUE(request); error = callback.WaitForResult(); ASSERT_TRUE(IsCertificateError(error)); ASSERT_EQ(1u, verifier_.requests()); ASSERT_EQ(0u, verifier_.cache_hits()); ASSERT_EQ(0u, verifier_.inflight_joins()); ASSERT_EQ(1u, verifier_.GetCacheSize()); error = verifier_.Verify(test_cert.get(), "www.example.com", std::string(), 0, NULL, &verify_result, callback.callback(), &request, BoundNetLog()); // Synchronous completion. ASSERT_NE(ERR_IO_PENDING, error); ASSERT_TRUE(IsCertificateError(error)); ASSERT_FALSE(request); ASSERT_EQ(2u, verifier_.requests()); ASSERT_EQ(1u, verifier_.cache_hits()); ASSERT_EQ(0u, verifier_.inflight_joins()); ASSERT_EQ(1u, verifier_.GetCacheSize()); } // Tests the same server certificate with different intermediate CA // certificates. These should be treated as different certificate chains even // though the two X509Certificate objects contain the same server certificate. TEST_F(MultiThreadedCertVerifierTest, DifferentCACerts) { base::FilePath certs_dir = GetTestCertsDirectory(); scoped_refptr server_cert = ImportCertFromFile(certs_dir, "salesforce_com_test.pem"); ASSERT_NE(static_cast(NULL), server_cert.get()); scoped_refptr intermediate_cert1 = ImportCertFromFile(certs_dir, "verisign_intermediate_ca_2011.pem"); ASSERT_NE(static_cast(NULL), intermediate_cert1.get()); scoped_refptr intermediate_cert2 = ImportCertFromFile(certs_dir, "verisign_intermediate_ca_2016.pem"); ASSERT_NE(static_cast(NULL), intermediate_cert2.get()); X509Certificate::OSCertHandles intermediates; intermediates.push_back(intermediate_cert1->os_cert_handle()); scoped_refptr cert_chain1 = X509Certificate::CreateFromHandle(server_cert->os_cert_handle(), intermediates); intermediates.clear(); intermediates.push_back(intermediate_cert2->os_cert_handle()); scoped_refptr cert_chain2 = X509Certificate::CreateFromHandle(server_cert->os_cert_handle(), intermediates); int error; CertVerifyResult verify_result; TestCompletionCallback callback; scoped_ptr request; error = verifier_.Verify(cert_chain1.get(), "www.example.com", std::string(), 0, NULL, &verify_result, callback.callback(), &request, BoundNetLog()); ASSERT_EQ(ERR_IO_PENDING, error); EXPECT_TRUE(request); error = callback.WaitForResult(); ASSERT_TRUE(IsCertificateError(error)); ASSERT_EQ(1u, verifier_.requests()); ASSERT_EQ(0u, verifier_.cache_hits()); ASSERT_EQ(0u, verifier_.inflight_joins()); ASSERT_EQ(1u, verifier_.GetCacheSize()); error = verifier_.Verify(cert_chain2.get(), "www.example.com", std::string(), 0, NULL, &verify_result, callback.callback(), &request, BoundNetLog()); ASSERT_EQ(ERR_IO_PENDING, error); EXPECT_TRUE(request); error = callback.WaitForResult(); ASSERT_TRUE(IsCertificateError(error)); ASSERT_EQ(2u, verifier_.requests()); ASSERT_EQ(0u, verifier_.cache_hits()); ASSERT_EQ(0u, verifier_.inflight_joins()); ASSERT_EQ(2u, verifier_.GetCacheSize()); } // Tests an inflight join. TEST_F(MultiThreadedCertVerifierTest, InflightJoin) { base::FilePath certs_dir = GetTestCertsDirectory(); scoped_refptr test_cert( ImportCertFromFile(certs_dir, "ok_cert.pem")); ASSERT_NE(static_cast(NULL), test_cert.get()); int error; CertVerifyResult verify_result; TestCompletionCallback callback; scoped_ptr request; CertVerifyResult verify_result2; TestCompletionCallback callback2; scoped_ptr request2; error = verifier_.Verify(test_cert.get(), "www.example.com", std::string(), 0, NULL, &verify_result, callback.callback(), &request, BoundNetLog()); ASSERT_EQ(ERR_IO_PENDING, error); EXPECT_TRUE(request); error = verifier_.Verify(test_cert.get(), "www.example.com", std::string(), 0, NULL, &verify_result2, callback2.callback(), &request2, BoundNetLog()); EXPECT_EQ(ERR_IO_PENDING, error); EXPECT_TRUE(request2); error = callback.WaitForResult(); EXPECT_TRUE(IsCertificateError(error)); error = callback2.WaitForResult(); ASSERT_TRUE(IsCertificateError(error)); ASSERT_EQ(2u, verifier_.requests()); ASSERT_EQ(0u, verifier_.cache_hits()); ASSERT_EQ(1u, verifier_.inflight_joins()); } // Tests that the callback of a canceled request is never made. TEST_F(MultiThreadedCertVerifierTest, CancelRequest) { base::FilePath certs_dir = GetTestCertsDirectory(); scoped_refptr test_cert( ImportCertFromFile(certs_dir, "ok_cert.pem")); ASSERT_NE(static_cast(NULL), test_cert.get()); int error; CertVerifyResult verify_result; scoped_ptr request; error = verifier_.Verify(test_cert.get(), "www.example.com", std::string(), 0, NULL, &verify_result, base::Bind(&FailTest), &request, BoundNetLog()); ASSERT_EQ(ERR_IO_PENDING, error); ASSERT_TRUE(request); request.reset(); // Issue a few more requests to the worker pool and wait for their // completion, so that the task of the canceled request (which runs on a // worker thread) is likely to complete by the end of this test. TestCompletionCallback callback; for (int i = 0; i < 5; ++i) { error = verifier_.Verify(test_cert.get(), "www2.example.com", std::string(), 0, NULL, &verify_result, callback.callback(), &request, BoundNetLog()); ASSERT_EQ(ERR_IO_PENDING, error); EXPECT_TRUE(request); error = callback.WaitForResult(); verifier_.ClearCache(); } } // Tests that a canceled request is not leaked. TEST_F(MultiThreadedCertVerifierTest, CancelRequestThenQuit) { base::FilePath certs_dir = GetTestCertsDirectory(); scoped_refptr test_cert( ImportCertFromFile(certs_dir, "ok_cert.pem")); ASSERT_NE(static_cast(NULL), test_cert.get()); int error; CertVerifyResult verify_result; TestCompletionCallback callback; scoped_ptr request; { // Because shutdown intentionally doesn't join worker threads, memory may // be leaked if the main thread shuts down before the worker thread // completes. In particular MultiThreadedCertVerifier calls // base::WorkerPool::PostTaskAndReply(), which leaks its "relay" when it // can't post the reply back to the origin thread. See // https://crbug.com/522514 ANNOTATE_SCOPED_MEMORY_LEAK; error = verifier_.Verify(test_cert.get(), "www.example.com", std::string(), 0, NULL, &verify_result, callback.callback(), &request, BoundNetLog()); } ASSERT_EQ(ERR_IO_PENDING, error); EXPECT_TRUE(request); request.reset(); // Destroy |verifier| by going out of scope. } TEST_F(MultiThreadedCertVerifierTest, RequestParamsComparators) { SHA1HashValue a_key; memset(a_key.data, 'a', sizeof(a_key.data)); SHA1HashValue z_key; memset(z_key.data, 'z', sizeof(z_key.data)); const CertificateList empty_list; CertificateList test_list; test_list.push_back( ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem")); struct { // Keys to test MultiThreadedCertVerifier::RequestParams key1; MultiThreadedCertVerifier::RequestParams key2; // Expectation: // -1 means key1 is less than key2 // 0 means key1 equals key2 // 1 means key1 is greater than key2 int expected_result; } tests[] = { { // Test for basic equivalence. MultiThreadedCertVerifier::RequestParams( a_key, a_key, "www.example.test", std::string(), 0, test_list), MultiThreadedCertVerifier::RequestParams( a_key, a_key, "www.example.test", std::string(), 0, test_list), 0, }, { // Test that different certificates but with the same CA and for // the same host are different validation keys. MultiThreadedCertVerifier::RequestParams( a_key, a_key, "www.example.test", std::string(), 0, test_list), MultiThreadedCertVerifier::RequestParams( z_key, a_key, "www.example.test", std::string(), 0, test_list), -1, }, { // Test that the same EE certificate for the same host, but with // different chains are different validation keys. MultiThreadedCertVerifier::RequestParams( a_key, z_key, "www.example.test", std::string(), 0, test_list), MultiThreadedCertVerifier::RequestParams( a_key, a_key, "www.example.test", std::string(), 0, test_list), 1, }, { // The same certificate, with the same chain, but for different // hosts are different validation keys. MultiThreadedCertVerifier::RequestParams( a_key, a_key, "www1.example.test", std::string(), 0, test_list), MultiThreadedCertVerifier::RequestParams( a_key, a_key, "www2.example.test", std::string(), 0, test_list), -1, }, { // The same certificate, chain, and host, but with different flags // are different validation keys. MultiThreadedCertVerifier::RequestParams( a_key, a_key, "www.example.test", std::string(), CertVerifier::VERIFY_EV_CERT, test_list), MultiThreadedCertVerifier::RequestParams( a_key, a_key, "www.example.test", std::string(), 0, test_list), 1, }, { // Different additional_trust_anchors. MultiThreadedCertVerifier::RequestParams( a_key, a_key, "www.example.test", std::string(), 0, empty_list), MultiThreadedCertVerifier::RequestParams( a_key, a_key, "www.example.test", std::string(), 0, test_list), -1, }, { // Different OCSP responses. MultiThreadedCertVerifier::RequestParams( a_key, a_key, "www.example.test", "ocsp response", 0, test_list), MultiThreadedCertVerifier::RequestParams( a_key, a_key, "www.example.test", std::string(), 0, test_list), -1, }, }; for (size_t i = 0; i < arraysize(tests); ++i) { SCOPED_TRACE(base::StringPrintf("Test[%" PRIuS "]", i)); const MultiThreadedCertVerifier::RequestParams& key1 = tests[i].key1; const MultiThreadedCertVerifier::RequestParams& key2 = tests[i].key2; switch (tests[i].expected_result) { case -1: EXPECT_TRUE(key1 < key2); EXPECT_FALSE(key2 < key1); break; case 0: EXPECT_FALSE(key1 < key2); EXPECT_FALSE(key2 < key1); break; case 1: EXPECT_FALSE(key1 < key2); EXPECT_TRUE(key2 < key1); break; default: FAIL() << "Invalid expectation. Can be only -1, 0, 1"; } } } TEST_F(MultiThreadedCertVerifierTest, CertTrustAnchorProvider) { MockCertTrustAnchorProvider trust_provider; verifier_.SetCertTrustAnchorProvider(&trust_provider); scoped_refptr test_cert( ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem")); ASSERT_TRUE(test_cert.get()); const CertificateList empty_cert_list; CertificateList cert_list; cert_list.push_back(test_cert); // Check that Verify() asks the |trust_provider| for the current list of // additional trust anchors. int error; CertVerifyResult verify_result; TestCompletionCallback callback; scoped_ptr request; EXPECT_CALL(trust_provider, GetAdditionalTrustAnchors()) .WillOnce(ReturnRef(empty_cert_list)); error = verifier_.Verify(test_cert.get(), "www.example.com", std::string(), 0, NULL, &verify_result, callback.callback(), &request, BoundNetLog()); Mock::VerifyAndClearExpectations(&trust_provider); ASSERT_EQ(ERR_IO_PENDING, error); EXPECT_TRUE(request); error = callback.WaitForResult(); EXPECT_EQ(ERR_CERT_COMMON_NAME_INVALID, error); ASSERT_EQ(1u, verifier_.requests()); ASSERT_EQ(0u, verifier_.cache_hits()); // The next Verify() uses the cached result. EXPECT_CALL(trust_provider, GetAdditionalTrustAnchors()) .WillOnce(ReturnRef(empty_cert_list)); error = verifier_.Verify(test_cert.get(), "www.example.com", std::string(), 0, NULL, &verify_result, callback.callback(), &request, BoundNetLog()); Mock::VerifyAndClearExpectations(&trust_provider); EXPECT_EQ(ERR_CERT_COMMON_NAME_INVALID, error); EXPECT_FALSE(request); ASSERT_EQ(2u, verifier_.requests()); ASSERT_EQ(1u, verifier_.cache_hits()); // Another Verify() for the same certificate but with a different list of // trust anchors will not reuse the cache. EXPECT_CALL(trust_provider, GetAdditionalTrustAnchors()) .WillOnce(ReturnRef(cert_list)); error = verifier_.Verify(test_cert.get(), "www.example.com", std::string(), 0, NULL, &verify_result, callback.callback(), &request, BoundNetLog()); Mock::VerifyAndClearExpectations(&trust_provider); ASSERT_EQ(ERR_IO_PENDING, error); EXPECT_TRUE(request); error = callback.WaitForResult(); EXPECT_EQ(ERR_CERT_COMMON_NAME_INVALID, error); ASSERT_EQ(3u, verifier_.requests()); ASSERT_EQ(1u, verifier_.cache_hits()); } // Tests de-duplication of requests. // Starts up 5 requests, of which 3 are unique. TEST_F(MultiThreadedCertVerifierTest, MultipleInflightJoin) { base::FilePath certs_dir = GetTestCertsDirectory(); scoped_refptr test_cert( ImportCertFromFile(certs_dir, "ok_cert.pem")); ASSERT_NE(static_cast(nullptr), test_cert.get()); int error; CertVerifyResult verify_result1; TestCompletionCallback callback1; scoped_ptr request1; CertVerifyResult verify_result2; TestCompletionCallback callback2; scoped_ptr request2; CertVerifyResult verify_result3; TestCompletionCallback callback3; scoped_ptr request3; CertVerifyResult verify_result4; TestCompletionCallback callback4; scoped_ptr request4; CertVerifyResult verify_result5; TestCompletionCallback callback5; scoped_ptr request5; const char domain1[] = "www.example1.com"; const char domain2[] = "www.exampleB.com"; const char domain3[] = "www.example3.com"; // Start 3 unique requests. error = verifier_.Verify(test_cert.get(), domain2, std::string(), 0, nullptr, &verify_result1, callback1.callback(), &request1, BoundNetLog()); ASSERT_EQ(ERR_IO_PENDING, error); EXPECT_TRUE(request1); error = verifier_.Verify(test_cert.get(), domain2, std::string(), 0, nullptr, &verify_result2, callback2.callback(), &request2, BoundNetLog()); EXPECT_EQ(ERR_IO_PENDING, error); EXPECT_TRUE(request2); error = verifier_.Verify(test_cert.get(), domain3, std::string(), 0, nullptr, &verify_result3, callback3.callback(), &request3, BoundNetLog()); EXPECT_EQ(ERR_IO_PENDING, error); EXPECT_TRUE(request3); // Start duplicate requests (which should join to existing jobs). error = verifier_.Verify(test_cert.get(), domain1, std::string(), 0, nullptr, &verify_result4, callback4.callback(), &request4, BoundNetLog()); EXPECT_EQ(ERR_IO_PENDING, error); EXPECT_TRUE(request4); error = verifier_.Verify(test_cert.get(), domain2, std::string(), 0, nullptr, &verify_result5, callback5.callback(), &request5, BoundNetLog()); EXPECT_EQ(ERR_IO_PENDING, error); EXPECT_TRUE(request5); error = callback1.WaitForResult(); EXPECT_TRUE(IsCertificateError(error)); error = callback2.WaitForResult(); ASSERT_TRUE(IsCertificateError(error)); error = callback4.WaitForResult(); ASSERT_TRUE(IsCertificateError(error)); // Let the other requests automatically cancel. ASSERT_EQ(5u, verifier_.requests()); ASSERT_EQ(0u, verifier_.cache_hits()); ASSERT_EQ(2u, verifier_.inflight_joins()); } } // namespace net