blob: 1f5adbd6018650e37afe44d91e4b7254bb9ab8e5 [file] [log] [blame]
// Copyright 2020 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 "services/cert_verifier/cert_verifier_service.h"
#include <iterator>
#include <memory>
#include <string>
#include <tuple>
#include "base/callback_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/memory/scoped_refptr.h"
#include "base/no_destructor.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/bind.h"
#include "base/test/task_environment.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/base/net_errors.h"
#include "net/cert/cert_status_flags.h"
#include "net/cert/cert_verifier.h"
#include "net/cert/cert_verify_result.h"
#include "net/cert/x509_certificate.h"
#include "net/test/cert_test_util.h"
#include "net/test/test_data_directory.h"
#include "services/cert_verifier/cert_verifier_service_factory.h"
#include "services/network/public/mojom/cert_verifier_service.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
namespace cert_verifier {
namespace {
const int kExpectedNetError = net::ERR_CERT_INVALID;
const unsigned int kExpectedCertStatus = net::CERT_STATUS_INVALID;
scoped_refptr<net::X509Certificate> GetTestCert() {
return net::ImportCertFromFile(net::GetTestCertsDirectory(), "ok_cert.pem");
}
class DummyCertVerifier : public net::CertVerifier {
public:
struct DummyRequest : public net::CertVerifier::Request {
~DummyRequest() override {
if (cancel_cb)
std::move(cancel_cb).Run();
}
net::CertVerifyResult* verify_result;
net::CompletionOnceCallback callback;
base::OnceClosure cancel_cb;
base::WeakPtrFactory<DummyRequest> weak_ptr_factory_{this};
};
~DummyCertVerifier() override {
for (base::WeakPtr<DummyRequest>& dummy_req : dummy_request_ptrs) {
if (dummy_req)
dummy_req->callback.Reset();
}
}
// CertVerifier implementation
int Verify(const net::CertVerifier::RequestParams& params,
net::CertVerifyResult* verify_result,
net::CompletionOnceCallback callback,
std::unique_ptr<Request>* out_req,
const net::NetLogWithSource& net_log) override {
if (sync_response_params_.find(params) != sync_response_params_.end()) {
verify_result->cert_status = kExpectedCertStatus;
return kExpectedNetError;
}
// Don't currently handle requests with same params for simplicity.
EXPECT_TRUE(dummy_requests_.find(params) == dummy_requests_.end());
auto dummy_req = std::make_unique<DummyRequest>();
dummy_request_ptrs.push_back(dummy_req->weak_ptr_factory_.GetWeakPtr());
dummy_req->verify_result = verify_result;
dummy_req->callback = std::move(callback);
dummy_req->cancel_cb = base::BindLambdaForTesting([this, params]() {
ASSERT_TRUE(cancelled_requests_.find(params) ==
cancelled_requests_.end());
cancelled_requests_.insert(std::move(params));
});
dummy_requests_[params] = dummy_req.get();
*out_req = std::move(dummy_req);
return net::ERR_IO_PENDING;
}
void SetConfig(const Config& config) override { config_ = config; }
void RespondToRequest(const net::CertVerifier::RequestParams& params) {
auto it = dummy_requests_.find(params);
ASSERT_FALSE(it == dummy_requests_.end());
DummyRequest* req = it->second;
dummy_requests_.erase(it);
req->cancel_cb.Reset();
req->verify_result->cert_status = kExpectedCertStatus;
std::move(req->callback).Run(kExpectedNetError);
}
void ShouldRespondSynchronouslyToParams(
const net::CertVerifier::RequestParams& params) {
sync_response_params_.insert(params);
}
bool WasRequestCancelled(const net::CertVerifier::RequestParams& params) {
return cancelled_requests_.find(params) != cancelled_requests_.end();
}
const net::CertVerifier::Config* config() const { return &config_; }
private:
std::map<net::CertVerifier::RequestParams, DummyRequest*> dummy_requests_;
std::set<net::CertVerifier::RequestParams> sync_response_params_;
std::set<net::CertVerifier::RequestParams> cancelled_requests_;
// Keep WeakPtr's to all the DummyRequests in case we need to reset the
// callbacks if |this| is destructed;
std::vector<base::WeakPtr<DummyRequest>> dummy_request_ptrs;
net::CertVerifier::Config config_;
};
struct DummyCVServiceRequest : public mojom::CertVerifierRequest {
void Complete(const net::CertVerifyResult& result_param,
int32_t net_error_param) override {
is_completed = true;
result = result_param;
net_error = net_error_param;
}
bool is_completed = false;
net::CertVerifyResult result;
int net_error;
};
class CertVerifierServiceTest : public PlatformTest {
public:
struct RequestInfo {
RequestInfo(net::CertVerifier::RequestParams request_params_p,
std::unique_ptr<DummyCVServiceRequest> dummy_cv_request_p,
std::unique_ptr<mojo::Receiver<mojom::CertVerifierRequest>>
cv_request_receiver_p)
: request_params(std::move(request_params_p)),
dummy_cv_request(std::move(dummy_cv_request_p)),
cv_request_receiver(std::move(cv_request_receiver_p)) {}
net::CertVerifier::RequestParams request_params;
std::unique_ptr<DummyCVServiceRequest> dummy_cv_request;
std::unique_ptr<mojo::Receiver<mojom::CertVerifierRequest>>
cv_request_receiver;
};
// Wraps a DummyCertVerifier with a CertVerifierServiceImpl.
// NOTE: CertVerifierServiceImpl is self-deleting.
CertVerifierServiceTest()
: dummy_cv_(new DummyCertVerifier),
cv_service_(new internal::CertVerifierServiceImpl(
base::WrapUnique(dummy_cv_),
cv_service_remote_.BindNewPipeAndPassReceiver(),
/*cert_net_fetcher=*/nullptr)) {}
void SetUp() override { ASSERT_TRUE(GetTestCert()); }
void TestCompletions(int num_simultaneous, bool sync) {
std::vector<RequestInfo> request_infos;
for (int i = 0; i < num_simultaneous; i++) {
std::string hostname = "www" + base::NumberToString(i) + ".example.com";
net::CertVerifier::RequestParams dummy_params(
GetTestCert(), hostname, 0,
/*ocsp_response=*/std::string(),
/*sct_list=*/std::string());
if (sync) {
dummy_cv_->ShouldRespondSynchronouslyToParams(dummy_params);
}
// Perform a verification request using the Remote<CertVerifierService>,
// which forwards to the CertVerifierServiceImpl.
auto cv_service_req = std::make_unique<DummyCVServiceRequest>();
auto cv_request_receiver =
std::make_unique<mojo::Receiver<mojom::CertVerifierRequest>>(
cv_service_req.get());
request_infos.emplace_back(std::move(dummy_params),
std::move(cv_service_req),
std::move(cv_request_receiver));
}
for (RequestInfo& info : request_infos) {
cv_service_remote_->Verify(
info.request_params,
info.cv_request_receiver->BindNewPipeAndPassRemote());
}
// Handle async Mojo request.
task_environment_.RunUntilIdle();
if (!sync) { // For fun, complete the requests in reverse order.
for (auto it = request_infos.rbegin(); it != request_infos.rend(); ++it) {
RequestInfo& info = *it;
ASSERT_FALSE(info.dummy_cv_request->is_completed);
dummy_cv()->RespondToRequest(info.request_params);
}
// FlushForTesting() so the CertVerifierServiceImpl Mojo response is
// handled.
cv_service_remote().FlushForTesting();
}
for (RequestInfo& info : request_infos) {
// Check the CertVerifierService finished the request.
ASSERT_TRUE(info.dummy_cv_request->is_completed);
ASSERT_EQ(info.dummy_cv_request->net_error, kExpectedNetError);
ASSERT_EQ(info.dummy_cv_request->result.cert_status, kExpectedCertStatus);
}
}
base::test::TaskEnvironment* task_environment() { return &task_environment_; }
mojo::Remote<mojom::CertVerifierService>& cv_service_remote() {
return cv_service_remote_;
}
DummyCertVerifier* dummy_cv() { return dummy_cv_; }
private:
base::test::TaskEnvironment task_environment_;
mojo::Remote<mojom::CertVerifierService> cv_service_remote_;
DummyCertVerifier* dummy_cv_;
internal::CertVerifierServiceImpl* cv_service_;
};
} // namespace
TEST_F(CertVerifierServiceTest, TestSingleCompletion) {
TestCompletions(1, false);
}
TEST_F(CertVerifierServiceTest, TestMultipleSimultaneousCompletions) {
TestCompletions(5, false);
}
TEST_F(CertVerifierServiceTest, TestSingleSyncCompletion) {
TestCompletions(1, true);
}
TEST_F(CertVerifierServiceTest, TestMultipleSimultaneousSyncCompletions) {
TestCompletions(5, true);
}
TEST_F(CertVerifierServiceTest, TestRequestDisconnectionCancelsCVRequest) {
net::CertVerifier::RequestParams dummy_params(GetTestCert(), "example.com", 0,
/*ocsp_response=*/std::string(),
/*sct_list=*/std::string());
// Perform a verification request using the Remote<CertVerifierService>,
// which forwards to the CertVerifierServiceImpl.
DummyCVServiceRequest cv_service_req;
mojo::Receiver<mojom::CertVerifierRequest> cv_request_receiver(
&cv_service_req);
cv_service_remote()->Verify(dummy_params,
cv_request_receiver.BindNewPipeAndPassRemote());
// Handle async Mojo request.
cv_service_remote().FlushForTesting();
ASSERT_FALSE(dummy_cv()->WasRequestCancelled(dummy_params));
// Disconnect our receiver.
cv_request_receiver.reset();
// Run mojo disconnection request.
task_environment()->RunUntilIdle();
ASSERT_TRUE(dummy_cv()->WasRequestCancelled(dummy_params));
}
TEST_F(CertVerifierServiceTest, TestCVServiceDisconnection) {
net::CertVerifier::RequestParams dummy_params(GetTestCert(), "example.com", 0,
/*ocsp_response=*/std::string(),
/*sct_list=*/std::string());
bool disconnected = false;
// Perform a verification request using the Remote<CertVerifierService>,
// which forwards to the CertVerifierServiceImpl.
DummyCVServiceRequest cv_service_req;
mojo::Receiver<mojom::CertVerifierRequest> cv_request_receiver(
&cv_service_req);
cv_service_remote()->Verify(dummy_params,
cv_request_receiver.BindNewPipeAndPassRemote());
// Make sure we observe disconnection.
cv_request_receiver.set_disconnect_handler(
base::BindLambdaForTesting([&]() { disconnected = true; }));
// Disconnect our receiver.
cv_service_remote().reset();
task_environment()->RunUntilIdle();
ASSERT_FALSE(cv_service_req.is_completed);
ASSERT_TRUE(disconnected);
}
// Check that calling SetConfig() on the Mojo interface results in a SetConfig
// call to the underlying net::CertVerifier.
TEST_F(CertVerifierServiceTest, StoresConfig) {
ASSERT_FALSE(dummy_cv()->config()->disable_symantec_enforcement);
net::CertVerifier::Config config;
config.disable_symantec_enforcement = true;
cv_service_remote()->SetConfig(config);
cv_service_remote().FlushForTesting();
ASSERT_TRUE(dummy_cv()->config()->disable_symantec_enforcement);
}
} // namespace cert_verifier