|  | // 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<X509Certificate> test_cert( | 
|  | ImportCertFromFile(certs_dir, "ok_cert.pem")); | 
|  | ASSERT_NE(static_cast<X509Certificate*>(NULL), test_cert.get()); | 
|  |  | 
|  | int error; | 
|  | CertVerifyResult verify_result; | 
|  | TestCompletionCallback callback; | 
|  | scoped_ptr<CertVerifier::Request> 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<X509Certificate> server_cert = | 
|  | ImportCertFromFile(certs_dir, "salesforce_com_test.pem"); | 
|  | ASSERT_NE(static_cast<X509Certificate*>(NULL), server_cert.get()); | 
|  |  | 
|  | scoped_refptr<X509Certificate> intermediate_cert1 = | 
|  | ImportCertFromFile(certs_dir, "verisign_intermediate_ca_2011.pem"); | 
|  | ASSERT_NE(static_cast<X509Certificate*>(NULL), intermediate_cert1.get()); | 
|  |  | 
|  | scoped_refptr<X509Certificate> intermediate_cert2 = | 
|  | ImportCertFromFile(certs_dir, "verisign_intermediate_ca_2016.pem"); | 
|  | ASSERT_NE(static_cast<X509Certificate*>(NULL), intermediate_cert2.get()); | 
|  |  | 
|  | X509Certificate::OSCertHandles intermediates; | 
|  | intermediates.push_back(intermediate_cert1->os_cert_handle()); | 
|  | scoped_refptr<X509Certificate> cert_chain1 = | 
|  | X509Certificate::CreateFromHandle(server_cert->os_cert_handle(), | 
|  | intermediates); | 
|  |  | 
|  | intermediates.clear(); | 
|  | intermediates.push_back(intermediate_cert2->os_cert_handle()); | 
|  | scoped_refptr<X509Certificate> cert_chain2 = | 
|  | X509Certificate::CreateFromHandle(server_cert->os_cert_handle(), | 
|  | intermediates); | 
|  |  | 
|  | int error; | 
|  | CertVerifyResult verify_result; | 
|  | TestCompletionCallback callback; | 
|  | scoped_ptr<CertVerifier::Request> 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<X509Certificate> test_cert( | 
|  | ImportCertFromFile(certs_dir, "ok_cert.pem")); | 
|  | ASSERT_NE(static_cast<X509Certificate*>(NULL), test_cert.get()); | 
|  |  | 
|  | int error; | 
|  | CertVerifyResult verify_result; | 
|  | TestCompletionCallback callback; | 
|  | scoped_ptr<CertVerifier::Request> request; | 
|  | CertVerifyResult verify_result2; | 
|  | TestCompletionCallback callback2; | 
|  | scoped_ptr<CertVerifier::Request> 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<X509Certificate> test_cert( | 
|  | ImportCertFromFile(certs_dir, "ok_cert.pem")); | 
|  | ASSERT_NE(static_cast<X509Certificate*>(NULL), test_cert.get()); | 
|  |  | 
|  | int error; | 
|  | CertVerifyResult verify_result; | 
|  | scoped_ptr<CertVerifier::Request> 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<X509Certificate> test_cert( | 
|  | ImportCertFromFile(certs_dir, "ok_cert.pem")); | 
|  | ASSERT_NE(static_cast<X509Certificate*>(NULL), test_cert.get()); | 
|  |  | 
|  | int error; | 
|  | CertVerifyResult verify_result; | 
|  | TestCompletionCallback callback; | 
|  | scoped_ptr<CertVerifier::Request> 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<X509Certificate> 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<CertVerifier::Request> 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<X509Certificate> test_cert( | 
|  | ImportCertFromFile(certs_dir, "ok_cert.pem")); | 
|  | ASSERT_NE(static_cast<X509Certificate*>(nullptr), test_cert.get()); | 
|  |  | 
|  | int error; | 
|  | CertVerifyResult verify_result1; | 
|  | TestCompletionCallback callback1; | 
|  | scoped_ptr<CertVerifier::Request> request1; | 
|  | CertVerifyResult verify_result2; | 
|  | TestCompletionCallback callback2; | 
|  | scoped_ptr<CertVerifier::Request> request2; | 
|  | CertVerifyResult verify_result3; | 
|  | TestCompletionCallback callback3; | 
|  | scoped_ptr<CertVerifier::Request> request3; | 
|  | CertVerifyResult verify_result4; | 
|  | TestCompletionCallback callback4; | 
|  | scoped_ptr<CertVerifier::Request> request4; | 
|  | CertVerifyResult verify_result5; | 
|  | TestCompletionCallback callback5; | 
|  | scoped_ptr<CertVerifier::Request> 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 |