| // 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 "chrome/browser/lacros/client_cert_store_lacros.h" |
| |
| #include <memory> |
| |
| #include "base/test/bind.h" |
| #include "base/test/gmock_callback_support.h" |
| #include "base/threading/sequenced_task_runner_handle.h" |
| #include "chrome/browser/lacros/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; |
| |
| namespace { |
| class MockClientCertStore : public net::ClientCertStore { |
| public: |
| MOCK_METHOD(void, |
| GetClientCerts, |
| (const net::SSLCertRequestInfo& cert_request_info, |
| ClientCertListCallback callback)); |
| }; |
| |
| class MockCertDbInitializer : public CertDbInitializer { |
| public: |
| MOCK_METHOD(base::CallbackListSubscription, |
| WaitUntilReady, |
| (ReadyCallback callback)); |
| }; |
| |
| 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( |
| const net::SSLCertRequestInfo& cert_request_info, |
| ClientCertStoreLacros::ClientCertListCallback callback) { |
| base::SequencedTaskRunnerHandle::Get()->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( |
| CertDbInitializer::ReadyCallback cb) { |
| callback = std::move(cb); |
| return {}; |
| } |
| CertDbInitializer::ReadyCallback 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_; |
| }; |
| |
| // It is reasonable to compare SSLCertRequestInfo by its address because it is a |
| // non-copyable RefCounted object. `ClientCertStoreLacros` is expected to |
| // forward the same object (with the same address) to its underlying store. |
| MATCHER_P(AddressEq, expected_ptr, "") { |
| return (&arg == expected_ptr); |
| } |
| |
| // 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>( |
| &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(AddressEq(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(/*is_success=*/false); |
| 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>( |
| &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(/*is_success=*/true); |
| |
| EXPECT_CALL(*underlying_store, |
| GetClientCerts(AddressEq(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>( |
| &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(AddressEq(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(AddressEq(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(AddressEq(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(/*is_success=*/true); |
| |
| 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>( |
| &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((AddressEq(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(/*is_success=*/true); |
| |
| 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>( |
| &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(AddressEq(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(/*is_success=*/true); |
| |
| // Verify that both callbacks are called. |
| get_certs_callback_observer_1.WaitUntilGotCerts(); |
| get_certs_callback_observer_2.WaitUntilGotCerts(); |
| } |
| |
| } // namespace |