chromium/chrome/browser/ash/net/client_cert_store_ash_unittest.cc

// Copyright 2013 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/ash/net/client_cert_store_ash.h"

#include <memory>
#include <string>

#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/run_loop.h"
#include "chrome/browser/certificate_provider/certificate_provider.h"
#include "content/public/test/browser_task_environment.h"
#include "crypto/nss_util.h"
#include "crypto/nss_util_internal.h"
#include "crypto/scoped_nss_types.h"
#include "crypto/scoped_test_nss_chromeos_user.h"
#include "crypto/scoped_test_nss_db.h"
#include "crypto/scoped_test_system_nss_key_slot.h"
#include "net/cert/x509_certificate.h"
#include "net/cert/x509_util_nss.h"
#include "net/ssl/client_cert_identity_test_util.h"
#include "net/ssl/ssl_cert_request_info.h"
#include "net/test/cert_test_util.h"
#include "net/test/test_data_directory.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace ash {

namespace {

// "CN=B CA" - DER encoded DN of the issuer of client_1.pem
const unsigned char kAuthority1DN[] = {0x30, 0x0f, 0x31, 0x0d, 0x30, 0x0b,
                                       0x06, 0x03, 0x55, 0x04, 0x03, 0x0c,
                                       0x04, 0x42, 0x20, 0x43, 0x41};

void SaveIdentitiesAndQuitCallback(net::ClientCertIdentityList* out_identities,
                                   base::OnceClosure quit_closure,
                                   net::ClientCertIdentityList in_identities) {
  *out_identities = std::move(in_identities);
  std::move(quit_closure).Run();
}

}  // namespace

class ClientCertStoreAshTest : public ::testing::Test {
 public:
  ClientCertStoreAshTest() {}

  void SetUp() override {
    ASSERT_TRUE(user1_.constructed_successfully());
    ASSERT_TRUE(user2_.constructed_successfully());
    ASSERT_TRUE(system_slot_.ConstructedSuccessfully());
  }

  scoped_refptr<net::X509Certificate> ImportCertToSlot(
      const std::string& cert_filename,
      const std::string& key_filename,
      PK11SlotInfo* slot) {
    return net::ImportClientCertAndKeyFromFile(
        net::GetTestCertsDirectory(), cert_filename, key_filename, slot);
  }

 protected:
  crypto::ScopedTestNSSChromeOSUser user1_{"user1"};
  crypto::ScopedTestNSSChromeOSUser user2_{"user2"};
  crypto::ScopedTestSystemNSSKeySlot system_slot_{
      /*simulate_token_loader=*/true};
  content::BrowserTaskEnvironment task_environment_;
};

// Ensure that cert requests, that are started before the filter is initialized,
// will wait for the initialization and succeed afterwards.
TEST_F(ClientCertStoreAshTest, RequestWaitsForNSSInitAndSucceeds) {
  ClientCertStoreAsh store(nullptr /* no additional provider */,
                           /*use_system_slot=*/false, user1_.username_hash(),
                           ClientCertStoreAsh::PasswordDelegateFactory());

  scoped_refptr<net::X509Certificate> cert_1(ImportCertToSlot(
      "client_1.pem", "client_1.pk8",
      crypto::GetPublicSlotForChromeOSUser(user1_.username_hash()).get()));
  ASSERT_TRUE(cert_1);

  // Request any client certificate, which is expected to match client_1.
  auto request_all = base::MakeRefCounted<net::SSLCertRequestInfo>();

  net::ClientCertIdentityList selected_identities;
  base::RunLoop run_loop;
  store.GetClientCerts(
      request_all,
      base::BindOnce(SaveIdentitiesAndQuitCallback, &selected_identities,
                     run_loop.QuitClosure()));

  {
    base::RunLoop run_loop_inner;
    run_loop_inner.RunUntilIdle();
    // GetClientCerts should wait for the initialization of the filter to
    // finish.
    ASSERT_EQ(0u, selected_identities.size());
  }

  user1_.FinishInit();
  run_loop.Run();

  ASSERT_EQ(1u, selected_identities.size());
}

// Ensure that cert requests, that are started after the filter was initialized,
// will succeed.
TEST_F(ClientCertStoreAshTest, RequestsAfterNSSInitSucceed) {
  user1_.FinishInit();

  ClientCertStoreAsh store(nullptr /* no additional provider */,
                           /*use_system_slot=*/false, user1_.username_hash(),
                           ClientCertStoreAsh::PasswordDelegateFactory());

  scoped_refptr<net::X509Certificate> cert_1(ImportCertToSlot(
      "client_1.pem", "client_1.pk8",
      crypto::GetPublicSlotForChromeOSUser(user1_.username_hash()).get()));
  ASSERT_TRUE(cert_1);

  auto request_all = base::MakeRefCounted<net::SSLCertRequestInfo>();

  base::RunLoop run_loop;
  net::ClientCertIdentityList selected_identities;
  store.GetClientCerts(
      request_all,
      base::BindOnce(SaveIdentitiesAndQuitCallback, &selected_identities,
                     run_loop.QuitClosure()));
  run_loop.Run();

  ASSERT_EQ(1u, selected_identities.size());
}

TEST_F(ClientCertStoreAshTest, Filter) {
  user1_.FinishInit();
  user2_.FinishInit();

  crypto::ScopedPK11Slot slot_public1 =
      crypto::GetPublicSlotForChromeOSUser(user1_.username_hash());
  crypto::ScopedPK11Slot slot_public2 =
      crypto::GetPublicSlotForChromeOSUser(user2_.username_hash());
  crypto::ScopedPK11Slot slot_private1 = crypto::GetPrivateSlotForChromeOSUser(
      user1_.username_hash(), base::DoNothing());
  crypto::ScopedPK11Slot slot_private2 = crypto::GetPrivateSlotForChromeOSUser(
      user2_.username_hash(), base::DoNothing());
  // crypto::GetPrivateSlotForChromeOSUser() may return nullptr if the slot is
  // not initialized yet, but this should not happen.
  ASSERT_TRUE(slot_private1);
  ASSERT_TRUE(slot_private2);

  // Import a certificate into each slot.
  scoped_refptr<net::X509Certificate> cert_public1(
      ImportCertToSlot("client_1.pem", "client_1.pk8", slot_public1.get()));
  ASSERT_TRUE(cert_public1);
  scoped_refptr<net::X509Certificate> cert_private1(
      ImportCertToSlot("client_2.pem", "client_2.pk8", slot_private1.get()));
  ASSERT_TRUE(cert_private1);
  scoped_refptr<net::X509Certificate> cert_public2(
      ImportCertToSlot("client_3.pem", "client_3.pk8", slot_public2.get()));
  ASSERT_TRUE(cert_public2);
  scoped_refptr<net::X509Certificate> cert_private2(
      ImportCertToSlot("client_4.pem", "client_4.pk8", slot_private2.get()));
  ASSERT_TRUE(cert_private2);
  scoped_refptr<net::X509Certificate> cert_system(
      ImportCertToSlot("client_5.pem", "client_5.pk8", system_slot_.slot()));
  ASSERT_TRUE(cert_system);

  const struct FilterTest {
    bool use_system_slot;
    std::string username_hash;
    std::vector<raw_ptr<net::X509Certificate, VectorExperimental>> results;
  } kTests[] = {
      {false,
       user1_.username_hash(),
       {cert_public1.get(), cert_private1.get()}},
      {true,
       user1_.username_hash(),
       {cert_public1.get(), cert_private1.get(), cert_system.get()}},
      {false,
       user2_.username_hash(),
       {cert_public2.get(), cert_private2.get()}},
      {true,
       user2_.username_hash(),
       {cert_public2.get(), cert_private2.get(), cert_system.get()}},
  };

  for (const auto& test : kTests) {
    SCOPED_TRACE(test.use_system_slot);
    SCOPED_TRACE(test.username_hash);

    ClientCertStoreAsh store(nullptr /* no additional provider */,
                             test.use_system_slot, test.username_hash,
                             ClientCertStoreAsh::PasswordDelegateFactory());

    auto request_all = base::MakeRefCounted<net::SSLCertRequestInfo>();

    base::RunLoop run_loop;
    net::ClientCertIdentityList selected_identities;
    store.GetClientCerts(
        request_all,
        base::BindOnce(SaveIdentitiesAndQuitCallback, &selected_identities,
                       run_loop.QuitClosure()));
    run_loop.Run();

    ASSERT_EQ(test.results.size(), selected_identities.size());
    for (size_t i = 0; i < test.results.size(); i++) {
      bool found_cert = false;
      for (const auto& identity : selected_identities) {
        if (test.results[i]->EqualsExcludingChain(identity->certificate())) {
          found_cert = true;
          break;
        }
      }
      EXPECT_TRUE(found_cert) << "Could not find certificate " << i;
    }
  }
}

// Ensure that the delegation of the request matching to the base class is
// functional.
TEST_F(ClientCertStoreAshTest, CertRequestMatching) {
  user1_.FinishInit();

  ClientCertStoreAsh store(nullptr,  // no additional provider
                           /*use_system_slot=*/false, user1_.username_hash(),
                           ClientCertStoreAsh::PasswordDelegateFactory());

  crypto::ScopedPK11Slot slot =
      crypto::GetPublicSlotForChromeOSUser(user1_.username_hash());
  scoped_refptr<net::X509Certificate> cert_1(
      ImportCertToSlot("client_1.pem", "client_1.pk8", slot.get()));
  ASSERT_TRUE(cert_1);
  scoped_refptr<net::X509Certificate> cert_2(
      ImportCertToSlot("client_2.pem", "client_2.pk8", slot.get()));
  ASSERT_TRUE(cert_2.get());

  std::vector<std::string> authority_1 = {
      std::string(std::begin(kAuthority1DN), std::end(kAuthority1DN))};
  auto request = base::MakeRefCounted<net::SSLCertRequestInfo>();
  request->cert_authorities = authority_1;

  base::RunLoop run_loop;
  net::ClientCertIdentityList selected_identities;
  store.GetClientCerts(
      request, base::BindOnce(SaveIdentitiesAndQuitCallback,
                              &selected_identities, run_loop.QuitClosure()));
  run_loop.Run();

  ASSERT_EQ(1u, selected_identities.size());
  EXPECT_TRUE(
      cert_1->EqualsExcludingChain(selected_identities[0]->certificate()));
}

}  // namespace ash