chromium/chrome/browser/lacros/cert/client_cert_store_lacros_unittest.cc

// 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 "chrome/browser/lacros/cert/client_cert_store_lacros.h"

#include <memory>

#include "base/task/sequenced_task_runner.h"
#include "base/test/bind.h"
#include "base/test/gmock_callback_support.h"
#include "chrome/browser/certificate_provider/certificate_provider.h"
#include "chrome/browser/lacros/cert/cert_db_initializer.h"
#include "content/public/test/browser_task_environment.h"
#include "net/ssl/ssl_cert_request_info.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using base::test::RunOnceCallback;
using testing::_;
using testing::Invoke;
using testing::Pointer;

namespace {
class MockClientCertStore : public net::ClientCertStore {
 public:
  MOCK_METHOD(void,
              GetClientCerts,
              (scoped_refptr<const net::SSLCertRequestInfo> cert_request_info,
               ClientCertListCallback callback));
};

class MockCertDbInitializer : public CertDbInitializer {
 public:
  MOCK_METHOD(base::CallbackListSubscription,
              WaitUntilReady,
              (base::OnceClosure callback));
  MOCK_METHOD(NssCertDatabaseGetter,
              CreateNssCertDatabaseGetterForIOThread,
              ());
};

class ClientCertStoreLacrosTest : public ::testing::Test {
 public:
  ClientCertStoreLacrosTest() {
    cert_request_ = base::MakeRefCounted<net::SSLCertRequestInfo>();
  }

  std::unique_ptr<MockClientCertStore> CreateMockStore(
      MockClientCertStore** non_owning_pointer) {
    auto store = std::make_unique<MockClientCertStore>();
    *non_owning_pointer = store.get();
    return store;
  }

  void FakeGetClientCerts(
      scoped_refptr<const net::SSLCertRequestInfo> cert_request_info,
      ClientCertStoreLacros::ClientCertListCallback callback) {
    base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE,
        base::BindOnce(std::move(callback), net::ClientCertIdentityList()));
  }

 protected:
  content::BrowserTaskEnvironment task_environment_{
      base::test::TaskEnvironment::TimeSource::MOCK_TIME};

  MockCertDbInitializer cert_db_initializer_;
  scoped_refptr<net::SSLCertRequestInfo> cert_request_;
};

// Captures callback from `CertDbInitializer::WaitUntilReady(...)` and allows
// to imitate the "ready" notification by calling the `callback`.
struct DbInitCallbackHolder {
  base::CallbackListSubscription SaveCallback(base::OnceClosure cb) {
    callback = std::move(cb);
    return {};
  }
  base::OnceClosure callback;
};

// Provides callback for `ClientCertStore::GetClientCerts(...)` and allows to
// set expectations on it.
class GetCertsCallbackObserver {
 public:
  void GotClientCerts(net::ClientCertIdentityList) { loop_.Quit(); }

  auto GetCallback() {
    return base::BindOnce(&GetCertsCallbackObserver::GotClientCerts,
                          base::Unretained(this));
  }

  void WaitUntilGotCerts() { loop_.Run(); }

 private:
  base::RunLoop loop_;
};

// Test that if CertDbInitializing is not initially ready,
// ClientCertStoreLacros will wait for it.
TEST_F(ClientCertStoreLacrosTest, WaitsForInitialization) {
  DbInitCallbackHolder db_init_callback_holder;
  EXPECT_CALL(cert_db_initializer_, WaitUntilReady)
      .WillOnce(Invoke(&db_init_callback_holder,
                       &DbInitCallbackHolder::SaveCallback));

  // Create ClientCertStoreLacros.
  MockClientCertStore* underlying_store = nullptr;
  auto cert_store_lacros = std::make_unique<ClientCertStoreLacros>(
      nullptr, &cert_db_initializer_, CreateMockStore(&underlying_store));

  // Request client certs.
  GetCertsCallbackObserver get_certs_callback_observer;
  cert_store_lacros->GetClientCerts(cert_request_,
                                    get_certs_callback_observer.GetCallback());

  // The request should be forwarded to the underlying store, when executed.
  EXPECT_CALL(*underlying_store,
              GetClientCerts(Pointer(cert_request_.get()), /*callback=*/_))
      .WillOnce(Invoke(this, &ClientCertStoreLacrosTest::FakeGetClientCerts));

  // Imitate signal from cert_db_initializer_ that the initialization is done.
  // Even if it failed, the cert store should try to continue.
  std::move(db_init_callback_holder.callback).Run();
  get_certs_callback_observer.WaitUntilGotCerts();
}

// Test that if CertDbInitializing is initially ready, ClientCertStoreLacros
// will properly forward the `GetClientCerts` request to the underlying store.
TEST_F(ClientCertStoreLacrosTest, RunsImmediatelyIfReady) {
  DbInitCallbackHolder db_init_callback_holder;
  EXPECT_CALL(cert_db_initializer_, WaitUntilReady)
      .WillOnce(Invoke(&db_init_callback_holder,
                       &DbInitCallbackHolder::SaveCallback));

  // Create ClientCertStoreLacros.
  MockClientCertStore* underlying_store = nullptr;
  auto cert_store_lacros = std::make_unique<ClientCertStoreLacros>(
      nullptr, &cert_db_initializer_, CreateMockStore(&underlying_store));

  // Imitate signal from cert_db_initializer_ that the initialization is
  // done before calling `GetClientCerts`.
  std::move(db_init_callback_holder.callback).Run();

  EXPECT_CALL(*underlying_store,
              GetClientCerts(Pointer(cert_request_.get()), /*callback=*/_))
      .WillOnce(Invoke(this, &ClientCertStoreLacrosTest::FakeGetClientCerts));

  GetCertsCallbackObserver get_certs_callback_observer;
  // Because the cert db is already initialized, the callback should be called
  // immediately.
  cert_store_lacros->GetClientCerts(cert_request_,
                                    get_certs_callback_observer.GetCallback());
  get_certs_callback_observer.WaitUntilGotCerts();
}

// Test that ClientCertStoreLacros can queue multiple requests.
TEST_F(ClientCertStoreLacrosTest, QueueMultupleRequests) {
  DbInitCallbackHolder db_init_callback_holder;
  EXPECT_CALL(cert_db_initializer_, WaitUntilReady)
      .WillOnce(Invoke(&db_init_callback_holder,
                       &DbInitCallbackHolder::SaveCallback));

  // Create a lot of different requests.
  auto cert_request_1 = base::MakeRefCounted<net::SSLCertRequestInfo>();
  auto cert_request_2 = base::MakeRefCounted<net::SSLCertRequestInfo>();
  auto cert_request_3 = base::MakeRefCounted<net::SSLCertRequestInfo>();

  // Create ClientCertStoreLacros.
  MockClientCertStore* underlying_store = nullptr;
  auto cert_store_lacros = std::make_unique<ClientCertStoreLacros>(
      nullptr, &cert_db_initializer_, CreateMockStore(&underlying_store));

  // Request client certs for every cert request.

  GetCertsCallbackObserver get_certs_callback_observer_1;
  cert_store_lacros->GetClientCerts(
      cert_request_1, get_certs_callback_observer_1.GetCallback());
  EXPECT_CALL(*underlying_store,
              GetClientCerts(Pointer(cert_request_1.get()), /*callback=*/_))
      .WillOnce(Invoke(this, &ClientCertStoreLacrosTest::FakeGetClientCerts));

  GetCertsCallbackObserver get_certs_callback_observer_2;
  cert_store_lacros->GetClientCerts(
      cert_request_2, get_certs_callback_observer_2.GetCallback());
  EXPECT_CALL(*underlying_store,
              GetClientCerts(Pointer(cert_request_2.get()), /*callback=*/_))
      .WillOnce(Invoke(this, &ClientCertStoreLacrosTest::FakeGetClientCerts));

  GetCertsCallbackObserver get_certs_callback_observer_3;
  cert_store_lacros->GetClientCerts(
      cert_request_3, get_certs_callback_observer_3.GetCallback());
  EXPECT_CALL(*underlying_store,
              GetClientCerts(Pointer(cert_request_3.get()), /*callback=*/_))
      .WillOnce(Invoke(this, &ClientCertStoreLacrosTest::FakeGetClientCerts));

  // Imitate signal from cert_db_initializer_ that the initialization is done.
  std::move(db_init_callback_holder.callback).Run();

  get_certs_callback_observer_1.WaitUntilGotCerts();
  get_certs_callback_observer_2.WaitUntilGotCerts();
  get_certs_callback_observer_3.WaitUntilGotCerts();
}

// Test that ClientCertStoreLacros can be deleted from the last callback.
// (Deleting from a non-last one is prohibited by the API.)
TEST_F(ClientCertStoreLacrosTest, DeletedFromLastCallback) {
  DbInitCallbackHolder db_init_callback_holder;
  EXPECT_CALL(cert_db_initializer_, WaitUntilReady)
      .WillOnce(Invoke(&db_init_callback_holder,
                       &DbInitCallbackHolder::SaveCallback));

  // Create ClientCertStoreLacros.
  MockClientCertStore* underlying_store = nullptr;
  auto cert_store_lacros = std::make_unique<ClientCertStoreLacros>(
      nullptr, &cert_db_initializer_, CreateMockStore(&underlying_store));

  // Request client certs a couple of times.
  GetCertsCallbackObserver get_certs_callback_observer_1;
  cert_store_lacros->GetClientCerts(
      cert_request_, get_certs_callback_observer_1.GetCallback());

  GetCertsCallbackObserver get_certs_callback_observer_2;
  cert_store_lacros->GetClientCerts(
      cert_request_, get_certs_callback_observer_2.GetCallback());

  // Create a callback that will delete `cert_store_lacros` when executed and
  // pass it into the cert store. This code relies on the current implementation
  // of ClientCertStoreLacros and assumes that it will be executed last. If the
  // implementation changes, it is ok to move it around.
  GetCertsCallbackObserver get_certs_callback_observer_3;
  auto deleting_callback = [&](net::ClientCertIdentityList list) {
    get_certs_callback_observer_3.GotClientCerts(std::move(list));
    cert_store_lacros.reset();
  };
  cert_store_lacros->GetClientCerts(
      cert_request_, base::BindLambdaForTesting(std::move(deleting_callback)));

  // All 3 requests should be forwarded to the underlying store.
  EXPECT_CALL(*underlying_store,
              GetClientCerts((Pointer(cert_request_)), /*callback=*/_))
      .Times(3)
      .WillRepeatedly(
          Invoke(this, &ClientCertStoreLacrosTest::FakeGetClientCerts));

  // Imitate signal from cert_db_initializer_ that the initialization is
  // done.
  std::move(db_init_callback_holder.callback).Run();

  get_certs_callback_observer_1.WaitUntilGotCerts();
  get_certs_callback_observer_2.WaitUntilGotCerts();
  get_certs_callback_observer_3.WaitUntilGotCerts();

  // The third request should be able to delete the cert store without anything
  // crashing.
  EXPECT_FALSE(cert_store_lacros);
}

// Test that ClientCertStoreLacros can handle new cert requests during execution
// of another request (i.e. reentering).
TEST_F(ClientCertStoreLacrosTest, HandlesReentrancy) {
  DbInitCallbackHolder db_init_callback_holder;
  EXPECT_CALL(cert_db_initializer_, WaitUntilReady)
      .WillOnce(Invoke(&db_init_callback_holder,
                       &DbInitCallbackHolder::SaveCallback));

  // Create ClientCertStoreLacros.
  MockClientCertStore* underlying_store = nullptr;
  auto cert_store_lacros = std::make_unique<ClientCertStoreLacros>(
      nullptr, &cert_db_initializer_, CreateMockStore(&underlying_store));

  GetCertsCallbackObserver get_certs_callback_observer_1;
  GetCertsCallbackObserver get_certs_callback_observer_2;

  auto reentering_callback = [&](net::ClientCertIdentityList list) {
    get_certs_callback_observer_1.GotClientCerts(std::move(list));
    cert_store_lacros->GetClientCerts(
        cert_request_, get_certs_callback_observer_2.GetCallback());
  };

  // Request client certs with the reentering callback.
  cert_store_lacros->GetClientCerts(
      cert_request_,
      base::BindLambdaForTesting(std::move(reentering_callback)));

  EXPECT_CALL(*underlying_store,
              GetClientCerts(Pointer(cert_request_.get()), /*callback=*/_))
      .Times(2)
      .WillRepeatedly(
          Invoke(this, &ClientCertStoreLacrosTest::FakeGetClientCerts));

  // Imitate signal from cert_db_initializer_ that the initialization is done.
  std::move(db_init_callback_holder.callback).Run();

  // Verify that both callbacks are called.
  get_certs_callback_observer_1.WaitUntilGotCerts();
  get_certs_callback_observer_2.WaitUntilGotCerts();
}

}  // namespace