summaryrefslogtreecommitdiff
path: root/chromium/net/cert/multi_threaded_cert_verifier_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/net/cert/multi_threaded_cert_verifier_unittest.cc')
-rw-r--r--chromium/net/cert/multi_threaded_cert_verifier_unittest.cc479
1 files changed, 479 insertions, 0 deletions
diff --git a/chromium/net/cert/multi_threaded_cert_verifier_unittest.cc b/chromium/net/cert/multi_threaded_cert_verifier_unittest.cc
new file mode 100644
index 00000000000..17d23d34995
--- /dev/null
+++ b/chromium/net/cert/multi_threaded_cert_verifier_unittest.cc
@@ -0,0 +1,479 @@
+// 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/files/file_path.h"
+#include "base/format_macros.h"
+#include "base/strings/stringprintf.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.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/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:
+ virtual ~MockCertVerifyProc() {}
+
+ // CertVerifyProc implementation
+ virtual bool SupportsAdditionalTrustAnchors() const OVERRIDE {
+ return false;
+ }
+
+ virtual int VerifyInternal(X509Certificate* cert,
+ const std::string& hostname,
+ 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()) {}
+ virtual ~MultiThreadedCertVerifierTest() {}
+
+ 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);
+
+ int error;
+ CertVerifyResult verify_result;
+ TestCompletionCallback callback;
+ CertVerifier::RequestHandle request_handle;
+
+ error = verifier_.Verify(test_cert.get(),
+ "www.example.com",
+ 0,
+ NULL,
+ &verify_result,
+ callback.callback(),
+ &request_handle,
+ BoundNetLog());
+ ASSERT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle);
+ 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",
+ 0,
+ NULL,
+ &verify_result,
+ callback.callback(),
+ &request_handle,
+ BoundNetLog());
+ // Synchronous completion.
+ ASSERT_NE(ERR_IO_PENDING, error);
+ ASSERT_TRUE(IsCertificateError(error));
+ ASSERT_TRUE(request_handle == NULL);
+ 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);
+
+ scoped_refptr<X509Certificate> intermediate_cert1 =
+ ImportCertFromFile(certs_dir, "verisign_intermediate_ca_2011.pem");
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), intermediate_cert1);
+
+ scoped_refptr<X509Certificate> intermediate_cert2 =
+ ImportCertFromFile(certs_dir, "verisign_intermediate_ca_2016.pem");
+ ASSERT_NE(static_cast<X509Certificate*>(NULL), intermediate_cert2);
+
+ 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;
+ CertVerifier::RequestHandle request_handle;
+
+ error = verifier_.Verify(cert_chain1.get(),
+ "www.example.com",
+ 0,
+ NULL,
+ &verify_result,
+ callback.callback(),
+ &request_handle,
+ BoundNetLog());
+ ASSERT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle);
+ 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",
+ 0,
+ NULL,
+ &verify_result,
+ callback.callback(),
+ &request_handle,
+ BoundNetLog());
+ ASSERT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle);
+ 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);
+
+ int error;
+ CertVerifyResult verify_result;
+ TestCompletionCallback callback;
+ CertVerifier::RequestHandle request_handle;
+ CertVerifyResult verify_result2;
+ TestCompletionCallback callback2;
+ CertVerifier::RequestHandle request_handle2;
+
+ error = verifier_.Verify(test_cert.get(),
+ "www.example.com",
+ 0,
+ NULL,
+ &verify_result,
+ callback.callback(),
+ &request_handle,
+ BoundNetLog());
+ ASSERT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle);
+ error = verifier_.Verify(test_cert.get(),
+ "www.example.com",
+ 0,
+ NULL,
+ &verify_result2,
+ callback2.callback(),
+ &request_handle2,
+ BoundNetLog());
+ EXPECT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle2 != NULL);
+ 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);
+
+ int error;
+ CertVerifyResult verify_result;
+ CertVerifier::RequestHandle request_handle;
+
+ error = verifier_.Verify(test_cert.get(),
+ "www.example.com",
+ 0,
+ NULL,
+ &verify_result,
+ base::Bind(&FailTest),
+ &request_handle,
+ BoundNetLog());
+ ASSERT_EQ(ERR_IO_PENDING, error);
+ ASSERT_TRUE(request_handle != NULL);
+ verifier_.CancelRequest(request_handle);
+
+ // 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",
+ 0,
+ NULL,
+ &verify_result,
+ callback.callback(),
+ &request_handle,
+ BoundNetLog());
+ ASSERT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle);
+ 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);
+
+ int error;
+ CertVerifyResult verify_result;
+ TestCompletionCallback callback;
+ CertVerifier::RequestHandle request_handle;
+
+ error = verifier_.Verify(test_cert.get(),
+ "www.example.com",
+ 0,
+ NULL,
+ &verify_result,
+ callback.callback(),
+ &request_handle,
+ BoundNetLog());
+ ASSERT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle);
+ verifier_.CancelRequest(request_handle);
+ // 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",
+ 0, test_list),
+ MultiThreadedCertVerifier::RequestParams(a_key, a_key, "www.example.test",
+ 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",
+ 0, test_list),
+ MultiThreadedCertVerifier::RequestParams(z_key, a_key, "www.example.test",
+ 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",
+ 0, test_list),
+ MultiThreadedCertVerifier::RequestParams(a_key, a_key, "www.example.test",
+ 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", 0,
+ test_list),
+ MultiThreadedCertVerifier::RequestParams(a_key, a_key,
+ "www2.example.test", 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",
+ CertVerifier::VERIFY_EV_CERT,
+ test_list),
+ MultiThreadedCertVerifier::RequestParams(a_key, a_key, "www.example.test",
+ 0, test_list),
+ 1,
+ },
+ { // Different additional_trust_anchors.
+ MultiThreadedCertVerifier::RequestParams(a_key, a_key, "www.example.test",
+ 0, empty_list),
+ MultiThreadedCertVerifier::RequestParams(a_key, a_key, "www.example.test",
+ 0, test_list),
+ -1,
+ },
+ };
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(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;
+ CertVerifier::RequestHandle request_handle;
+ EXPECT_CALL(trust_provider, GetAdditionalTrustAnchors())
+ .WillOnce(ReturnRef(empty_cert_list));
+ error = verifier_.Verify(test_cert.get(),
+ "www.example.com",
+ 0,
+ NULL,
+ &verify_result,
+ callback.callback(),
+ &request_handle,
+ BoundNetLog());
+ Mock::VerifyAndClearExpectations(&trust_provider);
+ ASSERT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle);
+ 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",
+ 0,
+ NULL,
+ &verify_result,
+ callback.callback(),
+ &request_handle,
+ BoundNetLog());
+ Mock::VerifyAndClearExpectations(&trust_provider);
+ EXPECT_EQ(ERR_CERT_COMMON_NAME_INVALID, error);
+ EXPECT_FALSE(request_handle);
+ 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",
+ 0,
+ NULL,
+ &verify_result,
+ callback.callback(),
+ &request_handle,
+ BoundNetLog());
+ Mock::VerifyAndClearExpectations(&trust_provider);
+ ASSERT_EQ(ERR_IO_PENDING, error);
+ EXPECT_TRUE(request_handle);
+ error = callback.WaitForResult();
+ EXPECT_EQ(ERR_CERT_COMMON_NAME_INVALID, error);
+ ASSERT_EQ(3u, verifier_.requests());
+ ASSERT_EQ(1u, verifier_.cache_hits());
+}
+
+} // namespace net