361 lines
14 KiB
C++
361 lines
14 KiB
C++
// Copyright 2020 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "net/cert/cert_and_ct_verifier.h"
|
|
|
|
#include <memory>
|
|
|
|
#include "base/files/file_path.h"
|
|
#include "base/memory/ref_counted.h"
|
|
#include "net/base/completion_once_callback.h"
|
|
#include "net/base/net_errors.h"
|
|
#include "net/base/test_completion_callback.h"
|
|
#include "net/cert/cert_verifier.h"
|
|
#include "net/cert/cert_verify_result.h"
|
|
#include "net/cert/ct_verifier.h"
|
|
#include "net/cert/mock_cert_verifier.h"
|
|
#include "net/cert/sct_status_flags.h"
|
|
#include "net/cert/signed_certificate_timestamp_and_status.h"
|
|
#include "net/cert/x509_certificate.h"
|
|
#include "net/cert/x509_util.h"
|
|
#include "net/log/net_log_with_source.h"
|
|
#include "net/test/cert_test_util.h"
|
|
#include "net/test/ct_test_util.h"
|
|
#include "net/test/gtest_util.h"
|
|
#include "net/test/test_data_directory.h"
|
|
#include "net/test/test_with_task_environment.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
|
|
using net::test::IsError;
|
|
using net::test::IsOk;
|
|
|
|
namespace net {
|
|
|
|
namespace {
|
|
|
|
// Callback that allows a test to check that the callback was canceled. The
|
|
// FailTest() callback can own the CallbackHelper while the test keeps a WeakPtr
|
|
// to check whether it has been deleted. The callback itself will fail the test
|
|
// if it is run.
|
|
struct CallbackHelper {
|
|
base::WeakPtrFactory<CallbackHelper> factory{this};
|
|
};
|
|
void FailTest(std::unique_ptr<CallbackHelper> helper, int result) {
|
|
FAIL();
|
|
}
|
|
|
|
class FakeCTVerifier : public CTVerifier {
|
|
public:
|
|
// CTVerifier implementation:
|
|
void Verify(base::StringPiece hostname,
|
|
X509Certificate* cert,
|
|
base::StringPiece stapled_ocsp_response,
|
|
base::StringPiece sct_list_from_tls_extension,
|
|
SignedCertificateTimestampAndStatusList* output_scts,
|
|
const NetLogWithSource& net_log) override {
|
|
*output_scts = scts_;
|
|
}
|
|
|
|
// Test setup interface:
|
|
void set_scts(const SignedCertificateTimestampAndStatusList& scts) {
|
|
scts_ = scts;
|
|
}
|
|
|
|
private:
|
|
SignedCertificateTimestampAndStatusList scts_;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
class CertAndCTVerifierTest : public TestWithTaskEnvironment {
|
|
public:
|
|
CertAndCTVerifierTest() = default;
|
|
~CertAndCTVerifierTest() override = default;
|
|
};
|
|
|
|
// Tests that both certificate and certificate transparency details in the
|
|
// CertVerifyResult are filled by the CertAndCTVerifier, when completion occurs
|
|
// synchronously.
|
|
TEST_F(CertAndCTVerifierTest, CertAndCTDetailsFilled_Sync) {
|
|
base::FilePath certs_dir = GetTestCertsDirectory();
|
|
scoped_refptr<X509Certificate> test_cert(
|
|
ImportCertFromFile(certs_dir, "ok_cert.pem"));
|
|
ASSERT_TRUE(test_cert.get());
|
|
|
|
// Mock the cert verification and CT verification results.
|
|
CertVerifyResult mock_result;
|
|
mock_result.cert_status = OK;
|
|
mock_result.verified_cert = test_cert;
|
|
auto cert_verifier = std::make_unique<MockCertVerifier>();
|
|
cert_verifier->AddResultForCert(test_cert, mock_result, OK);
|
|
cert_verifier->set_async(false);
|
|
|
|
scoped_refptr<ct::SignedCertificateTimestamp> sct;
|
|
ct::GetX509CertSCT(&sct);
|
|
SignedCertificateTimestampAndStatus sct_and_status(sct, ct::SCT_STATUS_OK);
|
|
SignedCertificateTimestampAndStatusList sct_list{sct_and_status};
|
|
auto ct_verifier = std::make_unique<FakeCTVerifier>();
|
|
ct_verifier->set_scts(sct_list);
|
|
|
|
CertAndCTVerifier cert_and_ct_verifier(std::move(cert_verifier),
|
|
std::move(ct_verifier));
|
|
|
|
CertVerifyResult verify_result;
|
|
TestCompletionCallback callback;
|
|
std::unique_ptr<CertVerifier::Request> request;
|
|
|
|
int result = callback.GetResult(cert_and_ct_verifier.Verify(
|
|
CertVerifier::RequestParams(test_cert, "www.example.com", 0,
|
|
/*ocsp_response=*/std::string(),
|
|
/*sct_list=*/std::string()),
|
|
&verify_result, callback.callback(), &request, NetLogWithSource()));
|
|
EXPECT_THAT(result, IsOk());
|
|
ASSERT_EQ(1u, verify_result.scts.size());
|
|
EXPECT_EQ(ct::SCT_STATUS_OK, verify_result.scts[0].status);
|
|
}
|
|
|
|
// Tests that both certificate and certificate transparency details in the
|
|
// CertVerifyResult are filled by the CertAndCTVerifier, when completion occurs
|
|
// asynchronously.
|
|
TEST_F(CertAndCTVerifierTest, CertAndCTDetailsFilled_Async) {
|
|
base::FilePath certs_dir = GetTestCertsDirectory();
|
|
scoped_refptr<X509Certificate> test_cert(
|
|
ImportCertFromFile(certs_dir, "ok_cert.pem"));
|
|
ASSERT_TRUE(test_cert.get());
|
|
|
|
// Mock the cert verification and CT verification results.
|
|
CertVerifyResult mock_result;
|
|
mock_result.cert_status = OK;
|
|
mock_result.verified_cert = test_cert;
|
|
auto cert_verifier = std::make_unique<MockCertVerifier>();
|
|
cert_verifier->AddResultForCert(test_cert, mock_result, OK);
|
|
cert_verifier->set_async(true);
|
|
|
|
scoped_refptr<ct::SignedCertificateTimestamp> sct;
|
|
ct::GetX509CertSCT(&sct);
|
|
SignedCertificateTimestampAndStatus sct_and_status(sct, ct::SCT_STATUS_OK);
|
|
SignedCertificateTimestampAndStatusList sct_list{sct_and_status};
|
|
auto ct_verifier = std::make_unique<FakeCTVerifier>();
|
|
ct_verifier->set_scts(sct_list);
|
|
|
|
CertAndCTVerifier cert_and_ct_verifier(std::move(cert_verifier),
|
|
std::move(ct_verifier));
|
|
|
|
CertVerifyResult verify_result;
|
|
TestCompletionCallback callback;
|
|
std::unique_ptr<CertVerifier::Request> request;
|
|
|
|
int result = callback.GetResult(cert_and_ct_verifier.Verify(
|
|
CertVerifier::RequestParams(test_cert, "www.example.com", 0,
|
|
/*ocsp_response=*/std::string(),
|
|
/*sct_list=*/std::string()),
|
|
&verify_result, callback.callback(), &request, NetLogWithSource()));
|
|
EXPECT_THAT(result, IsOk());
|
|
ASSERT_EQ(1u, verify_result.scts.size());
|
|
EXPECT_EQ(ct::SCT_STATUS_OK, verify_result.scts[0].status);
|
|
}
|
|
|
|
// Tests that the callback of a canceled request is never run.
|
|
TEST_F(CertAndCTVerifierTest, CancelRequest) {
|
|
base::FilePath certs_dir = GetTestCertsDirectory();
|
|
scoped_refptr<X509Certificate> test_cert(
|
|
ImportCertFromFile(certs_dir, "ok_cert.pem"));
|
|
ASSERT_TRUE(test_cert.get());
|
|
|
|
// Mock the cert verification and CT verification results.
|
|
CertVerifyResult mock_result;
|
|
mock_result.cert_status = OK;
|
|
mock_result.verified_cert = test_cert;
|
|
auto cert_verifier = std::make_unique<MockCertVerifier>();
|
|
cert_verifier->AddResultForCert(test_cert, mock_result, OK);
|
|
cert_verifier->set_async(true);
|
|
|
|
scoped_refptr<ct::SignedCertificateTimestamp> sct;
|
|
ct::GetX509CertSCT(&sct);
|
|
SignedCertificateTimestampAndStatus sct_and_status(sct, ct::SCT_STATUS_OK);
|
|
SignedCertificateTimestampAndStatusList sct_list{sct_and_status};
|
|
auto ct_verifier = std::make_unique<FakeCTVerifier>();
|
|
ct_verifier->set_scts(sct_list);
|
|
|
|
CertAndCTVerifier cert_and_ct_verifier(std::move(cert_verifier),
|
|
std::move(ct_verifier));
|
|
|
|
CertVerifyResult verify_result;
|
|
std::unique_ptr<CertVerifier::Request> request;
|
|
|
|
auto helper = std::make_unique<CallbackHelper>();
|
|
base::WeakPtr<CallbackHelper> weak_helper = helper->factory.GetWeakPtr();
|
|
|
|
int result = cert_and_ct_verifier.Verify(
|
|
CertVerifier::RequestParams(test_cert, "www.example.com", 0,
|
|
/*ocsp_response=*/std::string(),
|
|
/*sct_list=*/std::string()),
|
|
&verify_result, base::BindOnce(&FailTest, std::move(helper)), &request,
|
|
NetLogWithSource());
|
|
ASSERT_THAT(result, IsError(ERR_IO_PENDING));
|
|
ASSERT_TRUE(request);
|
|
request.reset();
|
|
|
|
// Check that the callback was reset when the request was reset.
|
|
ASSERT_TRUE(weak_helper.WasInvalidated());
|
|
|
|
RunUntilIdle();
|
|
}
|
|
|
|
// Tests that the callback of a request is never run if the CertAndCTVerifier is
|
|
// deleted.
|
|
TEST_F(CertAndCTVerifierTest, DeleteVerifier) {
|
|
base::FilePath certs_dir = GetTestCertsDirectory();
|
|
scoped_refptr<X509Certificate> test_cert(
|
|
ImportCertFromFile(certs_dir, "ok_cert.pem"));
|
|
ASSERT_TRUE(test_cert.get());
|
|
|
|
// Mock the cert verification and CT verification results.
|
|
CertVerifyResult mock_result;
|
|
mock_result.cert_status = OK;
|
|
mock_result.verified_cert = test_cert;
|
|
auto cert_verifier = std::make_unique<MockCertVerifier>();
|
|
cert_verifier->AddResultForCert(test_cert, mock_result, OK);
|
|
cert_verifier->set_async(true);
|
|
|
|
scoped_refptr<ct::SignedCertificateTimestamp> sct;
|
|
ct::GetX509CertSCT(&sct);
|
|
SignedCertificateTimestampAndStatus sct_and_status(sct, ct::SCT_STATUS_OK);
|
|
SignedCertificateTimestampAndStatusList sct_list{sct_and_status};
|
|
auto ct_verifier = std::make_unique<FakeCTVerifier>();
|
|
ct_verifier->set_scts(sct_list);
|
|
|
|
auto cert_and_ct_verifier = std::make_unique<CertAndCTVerifier>(
|
|
std::move(cert_verifier), std::move(ct_verifier));
|
|
|
|
CertVerifyResult verify_result;
|
|
std::unique_ptr<CertVerifier::Request> request;
|
|
|
|
auto helper = std::make_unique<CallbackHelper>();
|
|
base::WeakPtr<CallbackHelper> weak_helper = helper->factory.GetWeakPtr();
|
|
|
|
int result = cert_and_ct_verifier->Verify(
|
|
CertVerifier::RequestParams(test_cert, "www.example.com", 0,
|
|
/*ocsp_response=*/std::string(),
|
|
/*sct_list=*/std::string()),
|
|
&verify_result, base::BindOnce(&FailTest, std::move(helper)), &request,
|
|
NetLogWithSource());
|
|
ASSERT_THAT(result, IsError(ERR_IO_PENDING));
|
|
ASSERT_TRUE(request);
|
|
cert_and_ct_verifier.reset();
|
|
|
|
// Check that the callback was reset when the verifier was deleted.
|
|
ASSERT_TRUE(weak_helper.WasInvalidated());
|
|
|
|
RunUntilIdle();
|
|
}
|
|
|
|
// Tests that cancelling the request and stopping without ever running anything
|
|
// works as expected.
|
|
TEST_F(CertAndCTVerifierTest, CancelRequestThenQuit) {
|
|
base::FilePath certs_dir = GetTestCertsDirectory();
|
|
scoped_refptr<X509Certificate> test_cert(
|
|
ImportCertFromFile(certs_dir, "ok_cert.pem"));
|
|
ASSERT_TRUE(test_cert.get());
|
|
|
|
// Mock the cert verification and CT verification results.
|
|
CertVerifyResult mock_result;
|
|
mock_result.cert_status = OK;
|
|
mock_result.verified_cert = test_cert;
|
|
auto cert_verifier = std::make_unique<MockCertVerifier>();
|
|
cert_verifier->AddResultForCert(test_cert, mock_result, OK);
|
|
cert_verifier->set_async(true);
|
|
|
|
scoped_refptr<ct::SignedCertificateTimestamp> sct;
|
|
ct::GetX509CertSCT(&sct);
|
|
SignedCertificateTimestampAndStatus sct_and_status(sct, ct::SCT_STATUS_OK);
|
|
SignedCertificateTimestampAndStatusList sct_list{sct_and_status};
|
|
auto ct_verifier = std::make_unique<FakeCTVerifier>();
|
|
ct_verifier->set_scts(sct_list);
|
|
|
|
auto cert_and_ct_verifier = std::make_unique<CertAndCTVerifier>(
|
|
std::move(cert_verifier), std::move(ct_verifier));
|
|
|
|
CertVerifyResult verify_result;
|
|
std::unique_ptr<CertVerifier::Request> request;
|
|
|
|
auto helper = std::make_unique<CallbackHelper>();
|
|
base::WeakPtr<CallbackHelper> weak_helper = helper->factory.GetWeakPtr();
|
|
|
|
int result = cert_and_ct_verifier->Verify(
|
|
CertVerifier::RequestParams(test_cert, "www.example.com", 0,
|
|
/*ocsp_response=*/std::string(),
|
|
/*sct_list=*/std::string()),
|
|
&verify_result, base::BindOnce(&FailTest, std::move(helper)), &request,
|
|
NetLogWithSource());
|
|
ASSERT_THAT(result, IsError(ERR_IO_PENDING));
|
|
EXPECT_TRUE(request);
|
|
request.reset();
|
|
|
|
// Check that the callback was reset when the request was reset.
|
|
ASSERT_TRUE(weak_helper.WasInvalidated());
|
|
|
|
// Destroy |cert_and_ct_verifier| by going out of scope.
|
|
}
|
|
|
|
// Regression test for crbug.com/1153484: If the CertVerifier aborts, the
|
|
// CT verification step should be skipped.
|
|
TEST_F(CertAndCTVerifierTest, CertVerifierErrorShouldSkipCT) {
|
|
base::FilePath certs_dir = GetTestCertsDirectory();
|
|
scoped_refptr<X509Certificate> test_cert(
|
|
ImportCertFromFile(certs_dir, "ok_cert.pem"));
|
|
ASSERT_TRUE(test_cert.get());
|
|
|
|
// Mock the cert verification result as aborted (like what happens if the
|
|
// MojoCertVerifier gets disconnected).
|
|
CertVerifyResult mock_result;
|
|
mock_result.cert_status = CERT_STATUS_INVALID;
|
|
mock_result.verified_cert = test_cert;
|
|
auto cert_verifier = std::make_unique<MockCertVerifier>();
|
|
cert_verifier->AddResultForCert(test_cert, mock_result, ERR_ABORTED);
|
|
cert_verifier->set_async(true);
|
|
|
|
// Mock valid SCTs.
|
|
scoped_refptr<ct::SignedCertificateTimestamp> sct;
|
|
ct::GetX509CertSCT(&sct);
|
|
SignedCertificateTimestampAndStatus sct_and_status(sct, ct::SCT_STATUS_OK);
|
|
SignedCertificateTimestampAndStatusList sct_list{sct_and_status};
|
|
auto ct_verifier = std::make_unique<FakeCTVerifier>();
|
|
ct_verifier->set_scts(sct_list);
|
|
|
|
CertAndCTVerifier cert_and_ct_verifier(std::move(cert_verifier),
|
|
std::move(ct_verifier));
|
|
|
|
CertVerifyResult verify_result;
|
|
TestCompletionCallback callback;
|
|
std::unique_ptr<CertVerifier::Request> request;
|
|
|
|
int result = callback.GetResult(cert_and_ct_verifier.Verify(
|
|
CertVerifier::RequestParams(test_cert, "www.example.com", 0,
|
|
/*ocsp_response=*/std::string(),
|
|
/*sct_list=*/std::string()),
|
|
&verify_result, callback.callback(), &request, NetLogWithSource()));
|
|
EXPECT_THAT(result, IsError(ERR_ABORTED));
|
|
// SCTs should not be filled in because CTVerifier::Verify() should not be
|
|
// called.
|
|
ASSERT_EQ(0u, verify_result.scts.size());
|
|
}
|
|
|
|
TEST_F(CertAndCTVerifierTest, ObserverIsForwarded) {
|
|
auto mock_cert_verifier_owner = std::make_unique<MockCertVerifier>();
|
|
MockCertVerifier* mock_cert_verifier = mock_cert_verifier_owner.get();
|
|
|
|
CertAndCTVerifier cert_and_ct_verifier(std::move(mock_cert_verifier_owner),
|
|
std::make_unique<FakeCTVerifier>());
|
|
|
|
CertVerifierObserverCounter observer(&cert_and_ct_verifier);
|
|
EXPECT_EQ(observer.change_count(), 0u);
|
|
// A CertVerifierChanged event on the wrapped verifier should be forwarded to
|
|
// observers registered on CertAndCTVerifier.
|
|
mock_cert_verifier->SimulateOnCertVerifierChanged();
|
|
EXPECT_EQ(observer.change_count(), 1u);
|
|
}
|
|
|
|
} // namespace net
|