blob: bf7de6d7651554d0cf095c495b5d547bd684f29a [file] [log] [blame]
// 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.get());
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.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;
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.get());
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.get());
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.
#if !defined(LEAK_SANITIZER)
#define MAYBE_CancelRequestThenQuit CancelRequestThenQuit
#else
// See PR303886. LeakSanitizer flags a leak here.
#define MAYBE_CancelRequestThenQuit DISABLED_CancelRequestThenQuit
#endif
TEST_F(MultiThreadedCertVerifierTest, MAYBE_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;
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