blob: fc3a9c958c66617c0c9cb28d8b3e84e1af713b81 [file] [log] [blame]
// Copyright 2019 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/coalescing_cert_verifier.h"
#include <memory>
#include "base/functional/bind.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "net/base/net_errors.h"
#include "net/base/test_completion_callback.h"
#include "net/cert/mock_cert_verifier.h"
#include "net/cert/x509_certificate.h"
#include "net/log/net_log_with_source.h"
#include "net/test/cert_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/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using net::test::IsError;
using net::test::IsOk;
namespace net {
using CoalescingCertVerifierTest = TestWithTaskEnvironment;
// Tests that synchronous completion does not cause any issues.
TEST_F(CoalescingCertVerifierTest, SyncCompletion) {
scoped_refptr<X509Certificate> test_cert(
ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"));
ASSERT_TRUE(test_cert);
CertVerifyResult fake_result;
fake_result.verified_cert = test_cert;
std::unique_ptr<MockCertVerifier> mock_verifier_owner =
std::make_unique<MockCertVerifier>();
MockCertVerifier* mock_verifier = mock_verifier_owner.get();
mock_verifier->set_async(false); // Force sync completion.
mock_verifier->AddResultForCert(test_cert, fake_result, OK);
CoalescingCertVerifier verifier(std::move(mock_verifier_owner));
CertVerifier::RequestParams request_params(test_cert, "www.example.com", 0,
/*ocsp_response=*/std::string(),
/*sct_list=*/std::string());
CertVerifyResult result1, result2;
TestCompletionCallback callback1, callback2;
std::unique_ptr<CertVerifier::Request> request1, request2;
// Start an (asynchronous) initial request.
int error = verifier.Verify(request_params, &result1, callback1.callback(),
&request1, NetLogWithSource());
ASSERT_THAT(error, IsOk());
ASSERT_FALSE(request1);
ASSERT_TRUE(result1.verified_cert);
}
// Test that requests with identical parameters only result in a single
// underlying verification; that is, the second Request is joined to the
// in-progress first Request.
TEST_F(CoalescingCertVerifierTest, InflightJoin) {
scoped_refptr<X509Certificate> test_cert(
ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"));
ASSERT_TRUE(test_cert);
base::HistogramTester histograms;
CertVerifyResult fake_result;
fake_result.verified_cert = test_cert;
std::unique_ptr<MockCertVerifier> mock_verifier_owner =
std::make_unique<MockCertVerifier>();
MockCertVerifier* mock_verifier = mock_verifier_owner.get();
mock_verifier->set_async(true); // Always complete via PostTask
mock_verifier->AddResultForCert(test_cert, fake_result, OK);
CoalescingCertVerifier verifier(std::move(mock_verifier_owner));
CertVerifier::RequestParams request_params(test_cert, "www.example.com", 0,
/*ocsp_response=*/std::string(),
/*sct_list=*/std::string());
CertVerifyResult result1, result2;
TestCompletionCallback callback1, callback2;
std::unique_ptr<CertVerifier::Request> request1, request2;
// Start an (asynchronous) initial request.
int error = verifier.Verify(request_params, &result1, callback1.callback(),
&request1, NetLogWithSource());
ASSERT_THAT(error, IsError(ERR_IO_PENDING));
EXPECT_TRUE(request1);
// Simulate the underlying verifier returning different results if another
// verification is done.
mock_verifier->ClearRules();
mock_verifier->AddResultForCert(test_cert, fake_result, ERR_CERT_REVOKED);
// Start a second request; this should join the first request.
error = verifier.Verify(request_params, &result2, callback2.callback(),
&request2, NetLogWithSource());
ASSERT_THAT(error, IsError(ERR_IO_PENDING));
EXPECT_TRUE(request2);
// Ensure only one request was ever started.
EXPECT_EQ(2u, verifier.requests_for_testing());
EXPECT_EQ(1u, verifier.inflight_joins_for_testing());
// Make sure both results completed.
EXPECT_THAT(callback1.WaitForResult(), IsOk());
EXPECT_THAT(callback2.WaitForResult(), IsOk());
// There should only have been one Job started.
histograms.ExpectTotalCount("Net.CertVerifier_Job_Latency", 1);
histograms.ExpectTotalCount("Net.CertVerifier_First_Job_Latency", 1);
}
// Test that changing configurations between Requests prevents the second
// Request from being attached to the first Request. There should be two
// Requests to the underlying CertVerifier, and the correct results should be
// received by each.
TEST_F(CoalescingCertVerifierTest, DoesNotJoinAfterConfigChange) {
scoped_refptr<X509Certificate> test_cert(
ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"));
ASSERT_TRUE(test_cert);
base::HistogramTester histograms;
CertVerifyResult fake_result;
fake_result.verified_cert = test_cert;
std::unique_ptr<MockCertVerifier> mock_verifier_owner =
std::make_unique<MockCertVerifier>();
MockCertVerifier* mock_verifier = mock_verifier_owner.get();
mock_verifier->set_async(true); // Always complete via PostTask
mock_verifier->AddResultForCert(test_cert, fake_result, OK);
CoalescingCertVerifier verifier(std::move(mock_verifier_owner));
CertVerifier::Config config1;
verifier.SetConfig(config1);
CertVerifier::RequestParams request_params(test_cert, "www.example.com", 0,
/*ocsp_response=*/std::string(),
/*sct_list=*/std::string());
CertVerifyResult result1, result2;
TestCompletionCallback callback1, callback2;
std::unique_ptr<CertVerifier::Request> request1, request2;
// Start an (asynchronous) initial request.
int error = verifier.Verify(request_params, &result1, callback1.callback(),
&request1, NetLogWithSource());
ASSERT_THAT(error, IsError(ERR_IO_PENDING));
EXPECT_TRUE(request1);
// Change the configuration, and change the result to to simulate the
// configuration change affecting behavior.
CertVerifier::Config config2;
config2.enable_rev_checking = !config1.enable_rev_checking;
verifier.SetConfig(config2);
mock_verifier->ClearRules();
mock_verifier->AddResultForCert(test_cert, fake_result, ERR_CERT_REVOKED);
// Start a second request; this should not join the first request, as the
// config is different.
error = verifier.Verify(request_params, &result2, callback2.callback(),
&request2, NetLogWithSource());
ASSERT_THAT(error, IsError(ERR_IO_PENDING));
EXPECT_TRUE(request2);
// Ensure a total of two requests were started, and neither were joined.
EXPECT_EQ(2u, verifier.requests_for_testing());
EXPECT_EQ(0u, verifier.inflight_joins_for_testing());
// Make sure both results completed.
EXPECT_THAT(callback1.WaitForResult(), IsOk());
EXPECT_THAT(callback2.WaitForResult(), IsError(ERR_CERT_REVOKED));
// There should have been two separate Jobs.
histograms.ExpectTotalCount("Net.CertVerifier_Job_Latency", 2);
histograms.ExpectTotalCount("Net.CertVerifier_First_Job_Latency", 1);
}
// Test that the underlying CertVerifier changing configurations and triggering
// an OnCertVerifierChanged notification between Requests prevents the second
// Request from being attached to the first Request. There should be two
// Requests to the underlying CertVerifier, and the correct results should be
// received by each.
TEST_F(CoalescingCertVerifierTest, DoesNotJoinAfterUnderlyingVerifierChange) {
scoped_refptr<X509Certificate> test_cert(
ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"));
ASSERT_TRUE(test_cert);
base::HistogramTester histograms;
CertVerifyResult fake_result;
fake_result.verified_cert = test_cert;
std::unique_ptr<MockCertVerifier> mock_verifier_owner =
std::make_unique<MockCertVerifier>();
MockCertVerifier* mock_verifier = mock_verifier_owner.get();
mock_verifier->set_async(true); // Always complete via PostTask
mock_verifier->AddResultForCert(test_cert, fake_result, OK);
CoalescingCertVerifier verifier(std::move(mock_verifier_owner));
mock_verifier->SimulateOnCertVerifierChanged();
CertVerifier::RequestParams request_params(test_cert, "www.example.com", 0,
/*ocsp_response=*/std::string(),
/*sct_list=*/std::string());
CertVerifyResult result1, result2;
TestCompletionCallback callback1, callback2;
std::unique_ptr<CertVerifier::Request> request1, request2;
// Start an (asynchronous) initial request.
int error = verifier.Verify(request_params, &result1, callback1.callback(),
&request1, NetLogWithSource());
ASSERT_THAT(error, IsError(ERR_IO_PENDING));
EXPECT_TRUE(request1);
// Change the configuration, and change the result to to simulate the
// configuration change affecting behavior.
mock_verifier->SimulateOnCertVerifierChanged();
mock_verifier->ClearRules();
mock_verifier->AddResultForCert(test_cert, fake_result, ERR_CERT_REVOKED);
// Start a second request; this should not join the first request, as the
// config is different.
error = verifier.Verify(request_params, &result2, callback2.callback(),
&request2, NetLogWithSource());
ASSERT_THAT(error, IsError(ERR_IO_PENDING));
EXPECT_TRUE(request2);
// Ensure a total of two requests were started, and neither were joined.
EXPECT_EQ(2u, verifier.requests_for_testing());
EXPECT_EQ(0u, verifier.inflight_joins_for_testing());
// Make sure both results completed.
EXPECT_THAT(callback1.WaitForResult(), IsOk());
EXPECT_THAT(callback2.WaitForResult(), IsError(ERR_CERT_REVOKED));
// There should have been two separate Jobs.
histograms.ExpectTotalCount("Net.CertVerifier_Job_Latency", 2);
histograms.ExpectTotalCount("Net.CertVerifier_First_Job_Latency", 1);
}
TEST_F(CoalescingCertVerifierTest, ObserverIsForwarded) {
auto mock_cert_verifier_owner = std::make_unique<MockCertVerifier>();
MockCertVerifier* mock_cert_verifier = mock_cert_verifier_owner.get();
CoalescingCertVerifier verifier(std::move(mock_cert_verifier_owner));
CertVerifierObserverCounter observer_(&verifier);
EXPECT_EQ(observer_.change_count(), 0u);
// A CertVerifierChanged event on the wrapped verifier should be forwarded to
// observers registered on CoalescingCertVerifier.
mock_cert_verifier->SimulateOnCertVerifierChanged();
EXPECT_EQ(observer_.change_count(), 1u);
}
// Test that when two Requests are attached to the same Job, it's safe to
// delete the second Request while processing the response to the first. The
// second Request should not cause the second callback to be called.
TEST_F(CoalescingCertVerifierTest, DeleteSecondRequestDuringFirstCompletion) {
scoped_refptr<X509Certificate> test_cert(
ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"));
ASSERT_TRUE(test_cert);
CertVerifyResult fake_result;
fake_result.verified_cert = test_cert;
std::unique_ptr<MockCertVerifier> mock_verifier_owner =
std::make_unique<MockCertVerifier>();
MockCertVerifier* mock_verifier = mock_verifier_owner.get();
mock_verifier->set_async(true); // Always complete via PostTask
mock_verifier->AddResultForCert(test_cert, fake_result, OK);
CoalescingCertVerifier verifier(std::move(mock_verifier_owner));
CertVerifier::RequestParams request_params(test_cert, "www.example.com", 0,
/*ocsp_response=*/std::string(),
/*sct_list=*/std::string());
CertVerifyResult result1, result2;
TestCompletionCallback callback1, callback2;
std::unique_ptr<CertVerifier::Request> request1, request2;
// Start an (asynchronous) initial request. When this request is completed,
// it will delete (reset) |request2|, which should prevent it from being
// called.
int error = verifier.Verify(
request_params, &result1,
base::BindLambdaForTesting([&callback1, &request2](int result) {
request2.reset();
callback1.callback().Run(result);
}),
&request1, NetLogWithSource());
ASSERT_THAT(error, IsError(ERR_IO_PENDING));
EXPECT_TRUE(request1);
// Start a second request; this should join the first request.
error = verifier.Verify(request_params, &result2, callback2.callback(),
&request2, NetLogWithSource());
ASSERT_THAT(error, IsError(ERR_IO_PENDING));
EXPECT_TRUE(request2);
// Ensure only one underlying verification was started.
ASSERT_EQ(2u, verifier.requests_for_testing());
ASSERT_EQ(1u, verifier.inflight_joins_for_testing());
// Make sure that only the first callback is invoked; because the second
// CertVerifier::Request was deleted during processing the first's callback,
// the second callback should not be invoked.
EXPECT_THAT(callback1.WaitForResult(), IsOk());
ASSERT_FALSE(callback2.have_result());
ASSERT_FALSE(request2);
// While CoalescingCertVerifier doesn't use PostTask, make sure to flush the
// tasks as well, in case the implementation changes in the future.
RunUntilIdle();
ASSERT_FALSE(callback2.have_result());
ASSERT_FALSE(request2);
}
// Test that it's safe to delete the CoalescingCertVerifier during completion,
// even when there are outstanding Requests to be processed. The additional
// Requests should not invoke the user callback once the
// CoalescingCertVerifier is deleted.
TEST_F(CoalescingCertVerifierTest, DeleteVerifierDuringCompletion) {
scoped_refptr<X509Certificate> test_cert(
ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"));
ASSERT_TRUE(test_cert);
CertVerifyResult fake_result;
fake_result.verified_cert = test_cert;
std::unique_ptr<MockCertVerifier> mock_verifier_owner =
std::make_unique<MockCertVerifier>();
MockCertVerifier* mock_verifier = mock_verifier_owner.get();
mock_verifier->set_async(true); // Always complete via PostTask
mock_verifier->AddResultForCert(test_cert, fake_result, OK);
auto verifier =
std::make_unique<CoalescingCertVerifier>(std::move(mock_verifier_owner));
CertVerifier::RequestParams request_params(test_cert, "www.example.com", 0,
/*ocsp_response=*/std::string(),
/*sct_list=*/std::string());
CertVerifyResult result1, result2;
TestCompletionCallback callback1, callback2;
std::unique_ptr<CertVerifier::Request> request1, request2;
// Start an (asynchronous) initial request. When this request is completed,
// it will delete (reset) |request2|, which should prevent it from being
// called.
int error = verifier->Verify(
request_params, &result1,
base::BindLambdaForTesting([&callback1, &verifier](int result) {
verifier.reset();
callback1.callback().Run(result);
}),
&request1, NetLogWithSource());
ASSERT_THAT(error, IsError(ERR_IO_PENDING));
EXPECT_TRUE(request1);
// Start a second request; this should join the first request.
error = verifier->Verify(request_params, &result2, callback2.callback(),
&request2, NetLogWithSource());
ASSERT_THAT(error, IsError(ERR_IO_PENDING));
EXPECT_TRUE(request2);
// Ensure only one underlying verification was started.
ASSERT_EQ(2u, verifier->requests_for_testing());
ASSERT_EQ(1u, verifier->inflight_joins_for_testing());
// Make sure that only the first callback is invoked. This will delete the
// underlying CoalescingCertVerifier, which should prevent the second
// request's callback from being invoked.
EXPECT_THAT(callback1.WaitForResult(), IsOk());
ASSERT_FALSE(callback2.have_result());
ASSERT_TRUE(request2);
// While CoalescingCertVerifier doesn't use PostTask, make sure to flush the
// tasks as well, in case the implementation changes in the future.
RunUntilIdle();
ASSERT_FALSE(callback2.have_result());
ASSERT_TRUE(request2);
}
// Test that it's safe to delete a Request before the underlying verifier has
// completed. This is a guard against memory safety (e.g. when this Request
// is the last/only Request remaining).
TEST_F(CoalescingCertVerifierTest, DeleteRequestBeforeCompletion) {
scoped_refptr<X509Certificate> test_cert(
ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"));
ASSERT_TRUE(test_cert);
CertVerifyResult fake_result;
fake_result.verified_cert = test_cert;
std::unique_ptr<MockCertVerifier> mock_verifier_owner =
std::make_unique<MockCertVerifier>();
MockCertVerifier* mock_verifier = mock_verifier_owner.get();
mock_verifier->set_async(true); // Always complete via PostTask
mock_verifier->AddResultForCert(test_cert, fake_result, OK);
CoalescingCertVerifier verifier(std::move(mock_verifier_owner));
CertVerifier::RequestParams request_params(test_cert, "www.example.com", 0,
/*ocsp_response=*/std::string(),
/*sct_list=*/std::string());
CertVerifyResult result1;
TestCompletionCallback callback1;
std::unique_ptr<CertVerifier::Request> request1;
// Start an (asynchronous) initial request.
int error = verifier.Verify(request_params, &result1, callback1.callback(),
&request1, NetLogWithSource());
ASSERT_THAT(error, IsError(ERR_IO_PENDING));
EXPECT_TRUE(request1);
// Abandon the request before it's completed.
request1.reset();
EXPECT_FALSE(callback1.have_result());
// Make sure the request never completes / the callback is never invoked.
RunUntilIdle();
EXPECT_FALSE(callback1.have_result());
}
// Test that it's safe to delete a Request before the underlying verifier has
// completed. This is a correctness test, to ensure that other Requests are
// still notified.
TEST_F(CoalescingCertVerifierTest,
DeleteFirstRequestBeforeCompletionStillCompletesSecondRequest) {
scoped_refptr<X509Certificate> test_cert(
ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"));
ASSERT_TRUE(test_cert);
CertVerifyResult fake_result;
fake_result.verified_cert = test_cert;
std::unique_ptr<MockCertVerifier> mock_verifier_owner =
std::make_unique<MockCertVerifier>();
MockCertVerifier* mock_verifier = mock_verifier_owner.get();
mock_verifier->set_async(true); // Always complete via PostTask
mock_verifier->AddResultForCert(test_cert, fake_result, OK);
CoalescingCertVerifier verifier(std::move(mock_verifier_owner));
CertVerifier::RequestParams request_params(test_cert, "www.example.com", 0,
/*ocsp_response=*/std::string(),
/*sct_list=*/std::string());
CertVerifyResult result1, result2;
TestCompletionCallback callback1, callback2;
std::unique_ptr<CertVerifier::Request> request1, request2;
// Start an (asynchronous) initial request.
int error = verifier.Verify(request_params, &result1, callback1.callback(),
&request1, NetLogWithSource());
ASSERT_THAT(error, IsError(ERR_IO_PENDING));
EXPECT_TRUE(request1);
// Start a second request; this should join the first request.
error = verifier.Verify(request_params, &result2, callback2.callback(),
&request2, NetLogWithSource());
ASSERT_THAT(error, IsError(ERR_IO_PENDING));
EXPECT_TRUE(request2);
// Ensure only one underlying verification was started.
ASSERT_EQ(2u, verifier.requests_for_testing());
ASSERT_EQ(1u, verifier.inflight_joins_for_testing());
// Abandon the first request before it's completed.
request1.reset();
// Make sure the first request never completes / the callback is never
// invoked, while the second request completes normally.
EXPECT_THAT(callback2.WaitForResult(), IsOk());
EXPECT_FALSE(callback1.have_result());
// Simulate the second request going away during processing.
request2.reset();
// Flush any events, although there should not be any.
RunUntilIdle();
EXPECT_FALSE(callback1.have_result());
}
TEST_F(CoalescingCertVerifierTest, DeleteRequestDuringCompletion) {
scoped_refptr<X509Certificate> test_cert(
ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"));
ASSERT_TRUE(test_cert);
CertVerifyResult fake_result;
fake_result.verified_cert = test_cert;
std::unique_ptr<MockCertVerifier> mock_verifier_owner =
std::make_unique<MockCertVerifier>();
MockCertVerifier* mock_verifier = mock_verifier_owner.get();
mock_verifier->set_async(true); // Always complete via PostTask
mock_verifier->AddResultForCert(test_cert, fake_result, OK);
CoalescingCertVerifier verifier(std::move(mock_verifier_owner));
CertVerifier::RequestParams request_params(test_cert, "www.example.com", 0,
/*ocsp_response=*/std::string(),
/*sct_list=*/std::string());
CertVerifyResult result1;
TestCompletionCallback callback1;
std::unique_ptr<CertVerifier::Request> request1;
// Start an (asynchronous) initial request.
int error = verifier.Verify(
request_params, &result1,
base::BindLambdaForTesting([&callback1, &request1](int result) {
// Delete the Request during the completion callback. This should be
// perfectly safe, and not cause any memory trouble, because the
// Request was already detached from the Job prior to being invoked.
request1.reset();
callback1.callback().Run(result);
}),
&request1, NetLogWithSource());
ASSERT_THAT(error, IsError(ERR_IO_PENDING));
EXPECT_TRUE(request1);
// The result should be available, even though the request is deleted
// during the result processing. This should not cause any memory errors.
EXPECT_THAT(callback1.WaitForResult(), IsOk());
}
TEST_F(CoalescingCertVerifierTest, DeleteVerifierBeforeRequest) {
scoped_refptr<X509Certificate> test_cert(
ImportCertFromFile(GetTestCertsDirectory(), "ok_cert.pem"));
ASSERT_TRUE(test_cert);
base::HistogramTester histograms;
CertVerifyResult fake_result;
fake_result.verified_cert = test_cert;
std::unique_ptr<MockCertVerifier> mock_verifier_owner =
std::make_unique<MockCertVerifier>();
MockCertVerifier* mock_verifier = mock_verifier_owner.get();
mock_verifier->set_async(true); // Always complete via PostTask
mock_verifier->AddResultForCert(test_cert, fake_result, OK);
auto verifier =
std::make_unique<CoalescingCertVerifier>(std::move(mock_verifier_owner));
CertVerifier::RequestParams request_params(test_cert, "www.example.com", 0,
/*ocsp_response=*/std::string(),
/*sct_list=*/std::string());
CertVerifyResult result1;
TestCompletionCallback callback1;
std::unique_ptr<CertVerifier::Request> request1;
// Start an (asynchronous) initial request.
int error = verifier->Verify(request_params, &result1, callback1.callback(),
&request1, NetLogWithSource());
ASSERT_THAT(error, IsError(ERR_IO_PENDING));
EXPECT_TRUE(request1);
// Delete the CoalescingCertVerifier first. This should orphan all
// outstanding Requests and delete all associated Jobs.
verifier.reset();
// Flush any pending tasks; there should not be any, at this point, but use
// it in case the implementation changes.
RunUntilIdle();
// Make sure the callback was never called.
EXPECT_FALSE(callback1.have_result());
// Delete the Request. This should be a no-op as the Request was orphaned
// when the CoalescingCertVerifier was deleted.
request1.reset();
// There should not have been any histograms logged.
histograms.ExpectTotalCount("Net.CertVerifier_Job_Latency", 0);
histograms.ExpectTotalCount("Net.CertVerifier_First_Job_Latency", 0);
}
} // namespace net