[go: nahoru, domu]

Certificates for Lacros (MVP)

This allows Lacros-Chrome access to the system-wide TPM slot,
the per-user TPM slot, and per-user storage for certificates and
trust settings, for enterprise-managed devices where the logged-in
user is an affiliate of that enterprise.

Rather than Lacros-Chrome directly interacting with the login services,
it requests that Ash-Chrome notify it when the TPM and Cryptohome are
available, receiving the path to the per-user storage and the result
of TPM initialization. Lacros-Chrome can then initialize initialize a
connection to the TPM (via a PKCS#11 module) and initialize storage at
the path provided by Ash-Chrome.

For users that are not affiliated, Chrome today enables access to both
the per-user TPM and per-user storage. However, for Lacros-Chrome, this
is a known issue and not yet supported (crbug.com/1146430). This is
because loading the TPM unconditionally loads both the system and user
slot today, while non-affiliated users should not have access to the
system slot. The per-user storage will be loaded for all users with
this CL.

Similarly, using the system slot on the login screen is not yet
supported, and will be addressed in a follow-up (crbug.com/1146430).

Bug: 1145946
Change-Id: I2edc6e04c2de09f4c8a4d3bdca9c9ba5a9dee3fa
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2435642
Commit-Queue: Michael Ershov <miersh@google.com>
Reviewed-by: David Roger <droger@chromium.org>
Reviewed-by: Roland Bock <rbock@google.com>
Reviewed-by: Pavol Marko <pmarko@chromium.org>
Reviewed-by: Ryan Sleevi <rsleevi@chromium.org>
Reviewed-by: Erik Chen <erikchen@chromium.org>
Cr-Commit-Position: refs/heads/master@{#832960}
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 6a61f55c..9d9aab0 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -4559,6 +4559,10 @@
       "lacros/account_manager_facade_lacros.h",
       "lacros/account_manager_util.cc",
       "lacros/account_manager_util.h",
+      "lacros/cert_db_initializer.cc",
+      "lacros/cert_db_initializer.h",
+      "lacros/cert_db_initializer_factory.cc",
+      "lacros/cert_db_initializer_factory.h",
       "lacros/feedback_util.cc",
       "lacros/feedback_util.h",
       "lacros/immersive_context_lacros.cc",
diff --git a/chrome/browser/chromeos/BUILD.gn b/chrome/browser/chromeos/BUILD.gn
index 8a9627d..2a3e550 100644
--- a/chrome/browser/chromeos/BUILD.gn
+++ b/chrome/browser/chromeos/BUILD.gn
@@ -1041,6 +1041,8 @@
     "crosapi/browser_manager_observer.h",
     "crosapi/browser_util.cc",
     "crosapi/browser_util.h",
+    "crosapi/cert_database_ash.cc",
+    "crosapi/cert_database_ash.h",
     "crosapi/environment_provider.cc",
     "crosapi/environment_provider.h",
     "crosapi/fake_browser_manager.cc",
diff --git a/chrome/browser/chromeos/crosapi/ash_chrome_service_impl.cc b/chrome/browser/chromeos/crosapi/ash_chrome_service_impl.cc
index 2a051c1..4e5accf 100644
--- a/chrome/browser/chromeos/crosapi/ash_chrome_service_impl.cc
+++ b/chrome/browser/chromeos/crosapi/ash_chrome_service_impl.cc
@@ -13,6 +13,7 @@
 #include "chrome/browser/browser_process_platform_part.h"
 #include "chrome/browser/chromeos/crosapi/account_manager_ash.h"
 #include "chrome/browser/chromeos/crosapi/browser_manager.h"
+#include "chrome/browser/chromeos/crosapi/cert_database_ash.h"
 #include "chrome/browser/chromeos/crosapi/feedback_ash.h"
 #include "chrome/browser/chromeos/crosapi/file_manager_ash.h"
 #include "chrome/browser/chromeos/crosapi/keystore_service_ash.h"
@@ -39,7 +40,8 @@
 AshChromeServiceImpl::AshChromeServiceImpl(
     mojo::PendingReceiver<mojom::AshChromeService> pending_receiver)
     : receiver_(this, std::move(pending_receiver)),
-      screen_manager_ash_(std::make_unique<ScreenManagerAsh>()) {
+      screen_manager_ash_(std::make_unique<ScreenManagerAsh>()),
+      cert_database_ash_(std::make_unique<CertDatabaseAsh>()) {
   // TODO(hidehiko): Remove non-critical log from here.
   // Currently this is the signal that the connection is established.
   LOG(WARNING) << "AshChromeService connected.";
@@ -146,6 +148,11 @@
       std::move(receiver));
 }
 
+void AshChromeServiceImpl::BindCertDatabase(
+    mojo::PendingReceiver<mojom::CertDatabase> receiver) {
+  cert_database_ash_->BindReceiver(std::move(receiver));
+}
+
 void AshChromeServiceImpl::OnLacrosStartup(mojom::LacrosInfoPtr lacros_info) {
   BrowserManager::Get()->set_lacros_version(lacros_info->lacros_version);
 }
diff --git a/chrome/browser/chromeos/crosapi/ash_chrome_service_impl.h b/chrome/browser/chromeos/crosapi/ash_chrome_service_impl.h
index 972fbeb5..9fb6e4e 100644
--- a/chrome/browser/chromeos/crosapi/ash_chrome_service_impl.h
+++ b/chrome/browser/chromeos/crosapi/ash_chrome_service_impl.h
@@ -14,6 +14,7 @@
 namespace crosapi {
 
 class AccountManagerAsh;
+class CertDatabaseAsh;
 class FeedbackAsh;
 class FileManagerAsh;
 class KeystoreServiceAsh;
@@ -45,6 +46,8 @@
   void BindHidManager(
       mojo::PendingReceiver<device::mojom::HidManager> receiver) override;
   void BindFeedback(mojo::PendingReceiver<mojom::Feedback> receiver) override;
+  void BindCertDatabase(
+      mojo::PendingReceiver<mojom::CertDatabase> receiver) override;
   void OnLacrosStartup(mojom::LacrosInfoPtr lacros_info) override;
   void BindMediaSessionController(
       mojo::PendingReceiver<media_session::mojom::MediaControllerManager>
@@ -66,6 +69,7 @@
   std::unique_ptr<ScreenManagerAsh> screen_manager_ash_;
   std::unique_ptr<SelectFileAsh> select_file_ash_;
   std::unique_ptr<FeedbackAsh> feedback_ash_;
+  std::unique_ptr<CertDatabaseAsh> cert_database_ash_;
 };
 
 }  // namespace crosapi
diff --git a/chrome/browser/chromeos/crosapi/browser_util.cc b/chrome/browser/chromeos/crosapi/browser_util.cc
index f89b81dc..a7e77d5 100644
--- a/chrome/browser/chromeos/crosapi/browser_util.cc
+++ b/chrome/browser/chromeos/crosapi/browser_util.cc
@@ -23,6 +23,7 @@
 #include "chrome/common/pref_names.h"
 #include "chromeos/constants/chromeos_features.h"
 #include "chromeos/crosapi/cpp/crosapi_constants.h"
+#include "chromeos/crosapi/mojom/cert_database.mojom.h"
 #include "chromeos/crosapi/mojom/crosapi.mojom.h"
 #include "components/exo/shell_surface_util.h"
 #include "components/metrics/metrics_pref_names.h"
@@ -156,11 +157,12 @@
 
 base::flat_map<base::Token, uint32_t> GetInterfaceVersions() {
   static_assert(
-      crosapi::mojom::AshChromeService::Version_ == 6,
+      crosapi::mojom::AshChromeService::Version_ == 7,
       "if you add a new crosapi, please add it to the version map here");
   InterfaceVersions versions;
   AddVersion<crosapi::mojom::AccountManager>(&versions);
   AddVersion<crosapi::mojom::AshChromeService>(&versions);
+  AddVersion<crosapi::mojom::CertDatabase>(&versions);
   AddVersion<crosapi::mojom::Feedback>(&versions);
   AddVersion<crosapi::mojom::FileManager>(&versions);
   AddVersion<crosapi::mojom::KeystoreService>(&versions);
diff --git a/chrome/browser/chromeos/crosapi/cert_database_ash.cc b/chrome/browser/chromeos/crosapi/cert_database_ash.cc
new file mode 100644
index 0000000..1e2f8e1
--- /dev/null
+++ b/chrome/browser/chromeos/crosapi/cert_database_ash.cc
@@ -0,0 +1,115 @@
+// 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/chromeos/crosapi/cert_database_ash.h"
+
+#include "chrome/browser/chromeos/profiles/profile_helper.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chromeos/crosapi/mojom/cert_database.mojom.h"
+#include "chromeos/login/login_state/login_state.h"
+
+#include "chromeos/tpm/tpm_token_info_getter.h"
+#include "components/account_id/account_id.h"
+#include "components/user_manager/user.h"
+#include "content/public/browser/browser_thread.h"
+#include "crypto/nss_util_internal.h"
+
+namespace crosapi {
+
+CertDatabaseAsh::CertDatabaseAsh() {
+  DCHECK(chromeos::LoginState::IsInitialized());
+  chromeos::LoginState::Get()->AddObserver(this);
+}
+
+CertDatabaseAsh::~CertDatabaseAsh() {
+  chromeos::LoginState::Get()->RemoveObserver(this);
+}
+
+void CertDatabaseAsh::BindReceiver(
+    mojo::PendingReceiver<mojom::CertDatabase> pending_receiver) {
+  receivers_.Add(this, std::move(pending_receiver));
+}
+
+void CertDatabaseAsh::GetCertDatabaseInfo(
+    GetCertDatabaseInfoCallback callback) {
+  // TODO(crbug.com/1146430): For now Lacros-Chrome will initialize certificate
+  // database only in session. Revisit later to decide what to do on the login
+  // screen.
+  if (!chromeos::LoginState::Get()->IsUserLoggedIn()) {
+    LOG(ERROR) << "Not implemented";
+    std::move(callback).Run(nullptr);
+    return;
+  }
+
+  // If this is the first attempt to load the TPM, begin the async load.
+  if (!is_tpm_token_ready_.has_value()) {
+    WaitForTpmTokenReady(std::move(callback));
+    return;
+  }
+
+  const user_manager::User* user =
+      user_manager::UserManager::Get()->GetPrimaryUser();
+
+  // If user is not available or the TPM was previously attempted to be loaded,
+  // and failed, don't retry, just return an empty result that indicates error.
+  if (!user || !is_tpm_token_ready_.value()) {
+    std::move(callback).Run(nullptr);
+    return;
+  }
+
+  // Otherwise, if the TPM was already loaded previously, let the
+  // caller know.
+  // TODO(crbug.com/1146430) For now Lacros-Chrome loads chaps and has access to
+  // TPM operations only for affiliated users, because it gives access to
+  // system token. Find a way to give unaffiliated users access only to user TPM
+  // token.
+  mojom::GetCertDatabaseInfoResultPtr result =
+      mojom::GetCertDatabaseInfoResult::New();
+  result->should_load_chaps = user->IsAffiliated();
+  result->software_nss_db_path =
+      crypto::GetSoftwareNSSDBPath(
+          ProfileManager::GetPrimaryUserProfile()->GetPath())
+          .value();
+  std::move(callback).Run(std::move(result));
+}
+
+void CertDatabaseAsh::WaitForTpmTokenReady(
+    GetCertDatabaseInfoCallback callback) {
+  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
+
+  Profile* profile = ProfileManager::GetPrimaryUserProfile();
+  AccountId account_id =
+      chromeos::ProfileHelper::Get()->GetUserByProfile(profile)->GetAccountId();
+
+  std::unique_ptr<chromeos::TPMTokenInfoGetter> scoped_token_info_getter =
+      chromeos::TPMTokenInfoGetter::CreateForUserToken(
+          account_id, chromeos::CryptohomeClient::Get(),
+          base::ThreadTaskRunnerHandle::Get());
+  chromeos::TPMTokenInfoGetter* token_info_getter =
+      scoped_token_info_getter.get();
+
+  token_info_getter->Start(base::BindOnce(
+      &CertDatabaseAsh::OnTpmTokenReady, weak_factory_.GetWeakPtr(),
+      std::move(scoped_token_info_getter), std::move(callback)));
+}
+
+void CertDatabaseAsh::OnTpmTokenReady(
+    std::unique_ptr<chromeos::TPMTokenInfoGetter> token_getter,
+    GetCertDatabaseInfoCallback callback,
+    base::Optional<chromeos::CryptohomeClient::TpmTokenInfo> token_info) {
+  is_tpm_token_ready_ = token_info.has_value();
+
+  // Calling the initial method again. Since |is_tpm_token_ready_| is not empty
+  // this time, it will return some result via mojo.
+  GetCertDatabaseInfo(std::move(callback));
+}
+
+void CertDatabaseAsh::LoggedInStateChanged() {
+  // Cached result is valid only within one session and should be reset on
+  // sign out. Currently it is not necessary to reset it on sign in, but doesn't
+  // hurt.
+  is_tpm_token_ready_.reset();
+}
+
+}  // namespace crosapi
diff --git a/chrome/browser/chromeos/crosapi/cert_database_ash.h b/chrome/browser/chromeos/crosapi/cert_database_ash.h
new file mode 100644
index 0000000..6ac4f997
--- /dev/null
+++ b/chrome/browser/chromeos/crosapi/cert_database_ash.h
@@ -0,0 +1,70 @@
+// 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.
+
+#ifndef CHROME_BROWSER_CHROMEOS_CROSAPI_CERT_DATABASE_ASH_H_
+#define CHROME_BROWSER_CHROMEOS_CROSAPI_CERT_DATABASE_ASH_H_
+
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "chromeos/crosapi/mojom/cert_database.mojom.h"
+#include "chromeos/dbus/cryptohome/cryptohome_client.h"
+#include "chromeos/login/login_state/login_state.h"
+#include "mojo/public/cpp/bindings/pending_receiver.h"
+#include "mojo/public/cpp/bindings/receiver_set.h"
+
+namespace chromeos {
+class TPMTokenInfoGetter;
+}  // namespace chromeos
+
+namespace crosapi {
+
+// Implements the crosapi interface for certificate database. Lives in
+// Ash-Chrome on the UI thread.
+//
+// It is expected that during Lacros-Chrome initialization when it creates the
+// main profile (that contains device account), it will call GetCertDatabaseInfo
+// mojo API. If the ChromeOS user session was just started, it can take time for
+// Ash-Chrome to initialize TPM and certificate database. When it is done, the
+// API call will be resolved. If Lacros-Chrome is restarted, it will call
+// GetCertDatabaseInfo again and receive a cached result from the first call.
+// The cached result is reset on login state change (i.e. sign in / sign out).
+class CertDatabaseAsh : public mojom::CertDatabase,
+                        chromeos::LoginState::Observer {
+ public:
+  CertDatabaseAsh();
+  CertDatabaseAsh(const CertDatabaseAsh&) = delete;
+  CertDatabaseAsh& operator=(const CertDatabaseAsh&) = delete;
+  ~CertDatabaseAsh() override;
+
+  void BindReceiver(mojo::PendingReceiver<mojom::CertDatabase> receiver);
+
+  // Returns to Lacros-Chrome all necessary data to initialize certificate
+  // database when it is ready. Caches the result of first call for all
+  // subsequent calls during current user session.
+  void GetCertDatabaseInfo(GetCertDatabaseInfoCallback callback) override;
+
+ private:
+  // chromeos::LoginState::Observer
+  void LoggedInStateChanged() override;
+
+  // The fact that TpmTokenInfo can be retrieved is used as a signal that
+  // certificate database is ready to be initialized in Lacros-Chrome.
+  void WaitForTpmTokenReady(GetCertDatabaseInfoCallback callback);
+  void OnTpmTokenReady(
+      std::unique_ptr<chromeos::TPMTokenInfoGetter> token_getter,
+      GetCertDatabaseInfoCallback callback,
+      base::Optional<chromeos::CryptohomeClient::TpmTokenInfo> token_info);
+
+  base::Optional<bool> is_tpm_token_ready_;
+
+  // This class supports any number of connections. This allows the client to
+  // have multiple, potentially thread-affine, remotes.
+  mojo::ReceiverSet<mojom::CertDatabase> receivers_;
+
+  base::WeakPtrFactory<CertDatabaseAsh> weak_factory_{this};
+};
+
+}  // namespace crosapi
+
+#endif  // CHROME_BROWSER_CHROMEOS_CROSAPI_CERT_DATABASE_ASH_H_
diff --git a/chrome/browser/lacros/cert_db_initializer.cc b/chrome/browser/lacros/cert_db_initializer.cc
new file mode 100644
index 0000000..8fb5fbc
--- /dev/null
+++ b/chrome/browser/lacros/cert_db_initializer.cc
@@ -0,0 +1,117 @@
+// 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/cert_db_initializer.h"
+
+#include "base/callback_forward.h"
+#include "base/check.h"
+#include "base/files/file_path.h"
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chromeos/crosapi/cpp/crosapi_constants.h"
+#include "chromeos/crosapi/mojom/cert_database.mojom.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
+#include "crypto/chaps_support.h"
+#include "crypto/nss_util.h"
+#include "crypto/nss_util_internal.h"
+#include "mojo/public/cpp/bindings/remote.h"
+
+class IdentityManagerObserver : public signin::IdentityManager::Observer {
+ public:
+  explicit IdentityManagerObserver(signin::IdentityManager* identity_manager)
+      : identity_manager_(identity_manager) {
+    DCHECK(identity_manager_);
+    identity_manager_->AddObserver(this);
+  }
+
+  IdentityManagerObserver(const IdentityManagerObserver&) = delete;
+  IdentityManagerObserver& operator==(const IdentityManagerObserver&) = delete;
+
+  ~IdentityManagerObserver() override {
+    identity_manager_->RemoveObserver(this);
+  }
+
+  void WaitForRefreshTokensLoaded(base::OnceClosure callback) {
+    DCHECK(callback_.is_null());
+    callback_ = std::move(callback);
+  }
+
+ private:
+  void OnRefreshTokensLoaded() override {
+    if (!callback_) {
+      return;
+    }
+    std::move(callback_).Run();  // NOTE: may delete `this`.
+  }
+
+  signin::IdentityManager* identity_manager_ = nullptr;
+  base::OnceClosure callback_;
+};
+
+// =============================================================================
+
+CertDbInitializer::CertDbInitializer(
+    mojo::Remote<crosapi::mojom::CertDatabase>& cert_database_remote,
+    Profile* profile)
+    : cert_database_remote_(cert_database_remote), profile_(profile) {}
+
+CertDbInitializer::~CertDbInitializer() = default;
+
+void CertDbInitializer::Start(signin::IdentityManager* identity_manager) {
+  DCHECK(identity_manager);
+  // TODO(crbug.com/1148300): This is temporary. Until ~2021
+  // `Profile::IsMainProfile()` method can return a false negative response if
+  // called before refresh tokens are loaded. This should be removed together
+  // with the entire usage of `IdentityManager` in this class when it is not the
+  // case anymore.
+  if (!identity_manager->AreRefreshTokensLoaded()) {
+    identity_manager_observer_ =
+        std::make_unique<IdentityManagerObserver>(identity_manager);
+    identity_manager_observer_->WaitForRefreshTokensLoaded(base::BindOnce(
+        &CertDbInitializer::OnRefreshTokensLoaded, weak_factory_.GetWeakPtr()));
+    return;
+  }
+  WaitForCertDbReady();
+}
+
+void CertDbInitializer::OnRefreshTokensLoaded() {
+  identity_manager_observer_.reset();
+  WaitForCertDbReady();
+}
+
+void CertDbInitializer::WaitForCertDbReady() {
+  if (!profile_->IsMainProfile()) {
+    return;
+  }
+
+  cert_database_remote_->GetCertDatabaseInfo(base::BindOnce(
+      &CertDbInitializer::OnCertDbInfoReceived, weak_factory_.GetWeakPtr()));
+}
+
+void CertDbInitializer::OnCertDbInfoReceived(
+    crosapi::mojom::GetCertDatabaseInfoResultPtr cert_db_info) {
+  if (!cert_db_info) {
+    LOG(WARNING) << "Certificate database is not accesible";
+    return;
+  }
+
+  crypto::EnsureNSSInit();
+
+  // There's no need to save the result of `LoadChaps()`, chaps will stay loaded
+  // and can be used anyway. Using the result only to determine success/failure.
+  if (cert_db_info->should_load_chaps && !crypto::LoadChaps()) {
+    LOG(ERROR) << "Failed to load chaps.";
+    return;
+  }
+
+  // The slot doesn't need to be saved either. `description` doesn't affect
+  // anything. `software_nss_db_path` file path should be already created by
+  // Ash-Chrome.
+  base::FilePath software_nss_db_path(cert_db_info->software_nss_db_path);
+  auto slot = crypto::OpenSoftwareNSSDB(software_nss_db_path,
+                                        /*description=*/"cert_db");
+  if (!slot) {
+    LOG(ERROR) << "Failed to open user certificate database";
+  }
+}
diff --git a/chrome/browser/lacros/cert_db_initializer.h b/chrome/browser/lacros/cert_db_initializer.h
new file mode 100644
index 0000000..62fc56d4
--- /dev/null
+++ b/chrome/browser/lacros/cert_db_initializer.h
@@ -0,0 +1,47 @@
+// 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.
+
+#ifndef CHROME_BROWSER_LACROS_CERT_DB_INITIALIZER_H_
+#define CHROME_BROWSER_LACROS_CERT_DB_INITIALIZER_H_
+
+#include "base/memory/weak_ptr.h"
+#include "chromeos/crosapi/mojom/cert_database.mojom.h"
+#include "components/keyed_service/core/keyed_service.h"
+#include "components/signin/public/identity_manager/identity_manager.h"
+#include "mojo/public/cpp/bindings/remote.h"
+
+class Profile;
+class IdentityManagerObserver;
+
+// Initializes certificate database in Lacros-Chrome.
+class CertDbInitializer : public KeyedService {
+ public:
+  CertDbInitializer(
+      mojo::Remote<crosapi::mojom::CertDatabase>& cert_database_remote,
+      Profile* profile);
+  ~CertDbInitializer() override;
+
+  void Start(signin::IdentityManager* identity_manager);
+
+ private:
+  // Starts the initialization. The first step is to wait for IdentityManager.
+
+  void OnRefreshTokensLoaded();
+
+  // Checks that the current profile is the main profile and, if yes, makes a
+  // mojo request to Ash-Chrome to get information about certificate database.
+  void WaitForCertDbReady();
+
+  // Checks from the result that the certificate database should be initialized.
+  // If yes, loads Chaps and opens user's certificate database.
+  void OnCertDbInfoReceived(
+      crosapi::mojom::GetCertDatabaseInfoResultPtr result);
+
+  mojo::Remote<crosapi::mojom::CertDatabase>& cert_database_remote_;
+  Profile* profile_ = nullptr;
+  std::unique_ptr<IdentityManagerObserver> identity_manager_observer_;
+  base::WeakPtrFactory<CertDbInitializer> weak_factory_{this};
+};
+
+#endif  // CHROME_BROWSER_LACROS_CERT_DB_INITIALIZER_H_
diff --git a/chrome/browser/lacros/cert_db_initializer_factory.cc b/chrome/browser/lacros/cert_db_initializer_factory.cc
new file mode 100644
index 0000000..292c86f2
--- /dev/null
+++ b/chrome/browser/lacros/cert_db_initializer_factory.cc
@@ -0,0 +1,49 @@
+// 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/cert_db_initializer_factory.h"
+
+#include "chrome/browser/lacros/cert_db_initializer.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/signin/identity_manager_factory.h"
+#include "chromeos/lacros/lacros_chrome_service_impl.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+
+class Profile;
+
+// static
+CertDbInitializerFactory* CertDbInitializerFactory::GetInstance() {
+  static base::NoDestructor<CertDbInitializerFactory> factory;
+  return factory.get();
+}
+
+CertDbInitializerFactory::CertDbInitializerFactory()
+    : BrowserContextKeyedServiceFactory(
+          "CertDbInitializerFactory",
+          BrowserContextDependencyManager::GetInstance()) {
+  DependsOn(IdentityManagerFactory::GetInstance());
+}
+
+bool CertDbInitializerFactory::ServiceIsCreatedWithBrowserContext() const {
+  return true;
+}
+
+KeyedService* CertDbInitializerFactory::BuildServiceInstanceFor(
+    content::BrowserContext* context) const {
+  Profile* profile = Profile::FromBrowserContext(context);
+
+  if (!chromeos::LacrosChromeServiceImpl::Get()->IsCertDbAvailable()) {
+    return nullptr;
+  }
+
+  CertDbInitializer* result = new CertDbInitializer(
+      chromeos::LacrosChromeServiceImpl::Get()->cert_database_remote(),
+      profile);
+  // TODO(crbug.com/1145946): Enable certificate database initialization when
+  // the policy stack is ready (expected to happen before Feb 2021).
+  if (/* DISABLES CODE */ (false)) {
+    result->Start(IdentityManagerFactory::GetForProfile(profile));
+  }
+  return result;
+}
diff --git a/chrome/browser/lacros/cert_db_initializer_factory.h b/chrome/browser/lacros/cert_db_initializer_factory.h
new file mode 100644
index 0000000..f3dcb40
--- /dev/null
+++ b/chrome/browser/lacros/cert_db_initializer_factory.h
@@ -0,0 +1,27 @@
+// 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.
+
+#ifndef CHROME_BROWSER_LACROS_CERT_DB_INITIALIZER_FACTORY_H_
+#define CHROME_BROWSER_LACROS_CERT_DB_INITIALIZER_FACTORY_H_
+
+#include "base/no_destructor.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+class CertDbInitializerFactory : public BrowserContextKeyedServiceFactory {
+ public:
+  static CertDbInitializerFactory* GetInstance();
+
+ private:
+  friend class base::NoDestructor<CertDbInitializerFactory>;
+
+  CertDbInitializerFactory();
+  ~CertDbInitializerFactory() override = default;
+
+  // BrowserStateKeyedServiceFactory
+  bool ServiceIsCreatedWithBrowserContext() const override;
+  KeyedService* BuildServiceInstanceFor(
+      content::BrowserContext* context) const override;
+};
+
+#endif  // CHROME_BROWSER_LACROS_CERT_DB_INITIALIZER_FACTORY_H_
diff --git a/chrome/browser/net/nss_context_linux.cc b/chrome/browser/net/nss_context_linux.cc
index ce90909..2de6025 100644
--- a/chrome/browser/net/nss_context_linux.cc
+++ b/chrome/browser/net/nss_context_linux.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/net/nss_context.h"
 
+#include "build/chromeos_buildflags.h"
 #include "content/public/browser/browser_thread.h"
 #include "crypto/nss_util.h"
 #include "net/cert/nss_cert_database.h"
@@ -18,6 +19,13 @@
   // This initialization is not thread safe. This CHECK ensures that this code
   // is only run on a single thread.
   CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
+
+#if BUILDFLAG(IS_LACROS)
+  // TODO(crbug.com/1147032): remove the CHECK after the certificates settings
+  // page is updated.
+  CHECK(false) << "Currently disabled for Lacros-Chrome.";
+#endif
+
   if (!g_nss_cert_database) {
     // Linux has only a single persistent slot compared to ChromeOS's separate
     // public and private slot.
diff --git a/chrome/browser/net/profile_network_context_service.cc b/chrome/browser/net/profile_network_context_service.cc
index 0b849227..0cc13c4 100644
--- a/chrome/browser/net/profile_network_context_service.cc
+++ b/chrome/browser/net/profile_network_context_service.cc
@@ -576,6 +576,15 @@
       base::BindRepeating(&CreateCryptoModuleBlockingPasswordDelegate,
                           kCryptoModulePasswordClientAuth));
 #elif defined(USE_NSS_CERTS)
+
+#if BUILDFLAG(IS_LACROS)
+  if (!profile_->IsMainProfile()) {
+    // TODO(crbug.com/1148298): return some cert store for secondary profiles in
+    // Lacros-Chrome.
+    return nullptr;
+  }
+#endif  // BUILDFLAG(IS_LACROS)
+
   return std::make_unique<net::ClientCertStoreNSS>(
       base::BindRepeating(&CreateCryptoModuleBlockingPasswordDelegate,
                           kCryptoModulePasswordClientAuth));
diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
index 26e1cdd..172c27d 100644
--- a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
+++ b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
@@ -189,6 +189,10 @@
 #include "chrome/browser/nearby_sharing/nearby_sharing_service_factory.h"
 #endif
 
+#if BUILDFLAG(IS_LACROS)
+#include "chrome/browser/lacros/cert_db_initializer_factory.h"
+#endif
+
 namespace chrome {
 
 void AddProfilesExtraParts(ChromeBrowserMainParts* main_parts) {
@@ -432,6 +436,10 @@
 #endif
   WebDataServiceFactory::GetInstance();
   webrtc_event_logging::WebRtcEventLogManagerKeyedServiceFactory::GetInstance();
+
+#if BUILDFLAG(IS_LACROS)
+  CertDbInitializerFactory::GetInstance();
+#endif
 }
 
 void ChromeBrowserMainExtraPartsProfiles::PreProfileInit() {
diff --git a/chrome/browser/resources/settings/route.js b/chrome/browser/resources/settings/route.js
index 0e3a27e..661c989 100644
--- a/chrome/browser/resources/settings/route.js
+++ b/chrome/browser/resources/settings/route.js
@@ -18,7 +18,9 @@
   r.COOKIES = r.PRIVACY.createChild('/cookies');
   r.SECURITY = r.PRIVACY.createChild('/security');
 
-  // <if expr="use_nss_certs">
+  // TODO(crbug.com/1147032): The certificates settings page is temporarily
+  // disabled for Lacros-Chrome until a better solution is found.
+  // <if expr="use_nss_certs and not lacros">
   r.CERTIFICATES = r.SECURITY.createChild('/certificates');
   // </if>
 
diff --git a/chrome/browser/ssl/ssl_browsertest.cc b/chrome/browser/ssl/ssl_browsertest.cc
index c8b27efb..0cd324e 100644
--- a/chrome/browser/ssl/ssl_browsertest.cc
+++ b/chrome/browser/ssl/ssl_browsertest.cc
@@ -1994,6 +1994,11 @@
   EXPECT_EQ(security_state::NONE, helper->GetSecurityLevel());
 }
 
+// TODO(crbug.com/1148302): This class directly calls
+// `GetNSSCertDatabaseForProfile()` that causes crash at the moment and is never
+// called from Lacros-Chrome. This should be revisited when there is a solution
+// for the client certificates settings page on Lacros-Chrome.
+#if !BUILDFLAG(IS_LACROS)
 #if defined(USE_NSS_CERTS)
 class SSLUITestWithClientCert : public SSLUITestBase {
  public:
@@ -2084,6 +2089,7 @@
   EXPECT_TRUE(base::LowerCaseEqualsASCII(result, "pass"));
 }
 #endif  // defined(USE_NSS_CERTS)
+#endif  // !BUILDFLAG(IS_LACROS)
 
 // A stub ClientCertStore that returns a FakeClientCertIdentity.
 class ClientCertStoreStub : public net::ClientCertStore {
diff --git a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
index 9033c2c4..7118b2d 100644
--- a/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
+++ b/chrome/browser/ui/webui/chrome_web_ui_controller_factory.cc
@@ -701,8 +701,12 @@
     return &NewWebUI<chromeos::BluetoothPairingDialogUI>;
   if (url.host_piece() == chrome::kChromeUICellularSetupHost)
     return &NewWebUI<chromeos::cellular_setup::CellularSetupDialogUI>;
+// TODO(crbug.com/1147032): The certificates settings page is temporarily
+// disabled for Lacros-Chrome until a better solution is found.
+#if !BUILDFLAG(IS_LACROS)
   if (url.host_piece() == chrome::kChromeUICertificateManagerHost)
     return &NewWebUI<chromeos::CertificateManagerDialogUI>;
+#endif  // !BUILDFLAG(IS_LACROS)
   if (base::FeatureList::IsEnabled(
           chromeos::features::kConnectivityDiagnosticsWebUi) &&
       url.host_piece() == chromeos::kChromeUIConnectivityDiagnosticsHost) {
diff --git a/chrome/browser/ui/webui/settings/settings_ui.cc b/chrome/browser/ui/webui/settings/settings_ui.cc
index b9e4325b..10e8bc6d 100644
--- a/chrome/browser/ui/webui/settings/settings_ui.cc
+++ b/chrome/browser/ui/webui/settings/settings_ui.cc
@@ -166,6 +166,9 @@
 
   AddSettingsPageUIHandler(std::make_unique<AppearanceHandler>(web_ui));
 
+// TODO(crbug.com/1147032): The certificates settings page is temporarily
+// disabled for Lacros-Chrome until a better solution is found.
+#if !BUILDFLAG(IS_LACROS)
 #if defined(USE_NSS_CERTS)
   AddSettingsPageUIHandler(
       std::make_unique<certificate_manager::CertificatesHandler>());
@@ -176,7 +179,8 @@
   AddSettingsPageUIHandler(
       chromeos::cert_provisioning::CertificateProvisioningUiHandler::
           CreateForProfile(profile));
-#endif
+#endif  // defined(OS_CHROMEOS)
+#endif  // !BUILDFLAG(IS_LACROS)
 
   AddSettingsPageUIHandler(std::make_unique<AccessibilityMainHandler>());
   AddSettingsPageUIHandler(std::make_unique<BrowserLifetimeHandler>());
diff --git a/chrome/test/data/webui/settings/security_page_test.js b/chrome/test/data/webui/settings/security_page_test.js
index e5fc850..696fc3b8 100644
--- a/chrome/test/data/webui/settings/security_page_test.js
+++ b/chrome/test/data/webui/settings/security_page_test.js
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 // clang-format off
-import {isMac, isWindows} from 'chrome://resources/js/cr.m.js';
+import {isLacros, isMac, isWindows} from 'chrome://resources/js/cr.m.js';
 import {loadTimeData} from 'chrome://resources/js/load_time_data.m.js';
 import {flush} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js';
 import {SafeBrowsingSetting} from 'chrome://settings/lazy_load.js';
@@ -87,12 +87,18 @@
     assertTrue(page.$$('#safeBrowsingStandard').expanded);
   });
 
-  test('LogManageCerfificatesClick', async function() {
-    page.$$('#manageCertificates').click();
-    const result =
-        await testMetricsBrowserProxy.whenCalled('recordSettingsPageHistogram');
-    assertEquals(PrivacyElementInteractions.MANAGE_CERTIFICATES, result);
-  });
+  // TODO(crbug.com/1148302): This class directly calls
+  // `GetNSSCertDatabaseForProfile()` that causes crash at the moment and is
+  // never called from Lacros-Chrome. This should be revisited when there is a
+  // solution for the client certificates settings page on Lacros-Chrome.
+  if (!isLacros) {
+    test('LogManageCerfificatesClick', async function() {
+      page.$$('#manageCertificates').click();
+      const result = await testMetricsBrowserProxy.whenCalled(
+          'recordSettingsPageHistogram');
+      assertEquals(PrivacyElementInteractions.MANAGE_CERTIFICATES, result);
+    });
+  }
 
   test('ManageSecurityKeysSubpageVisible', function() {
     assertTrue(isChildVisible(page, '#security-keys-subpage-trigger'));
diff --git a/chromeos/crosapi/mojom/BUILD.gn b/chromeos/crosapi/mojom/BUILD.gn
index d1f9ee8..d0105e4 100644
--- a/chromeos/crosapi/mojom/BUILD.gn
+++ b/chromeos/crosapi/mojom/BUILD.gn
@@ -8,6 +8,7 @@
   sources = [
     "account_manager.mojom",
     "bitmap.mojom",
+    "cert_database.mojom",
     "crosapi.mojom",
     "feedback.mojom",
     "file_manager.mojom",
diff --git a/chromeos/crosapi/mojom/cert_database.mojom b/chromeos/crosapi/mojom/cert_database.mojom
new file mode 100644
index 0000000..c33f84fe
--- /dev/null
+++ b/chromeos/crosapi/mojom/cert_database.mojom
@@ -0,0 +1,19 @@
+// 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.
+
+module crosapi.mojom;
+
+[Stable, Extensible]
+struct GetCertDatabaseInfoResult {
+  string software_nss_db_path@0;
+  bool should_load_chaps@1;
+};
+
+// This interface is implemented by Ash-Chrome.
+[Stable, Uuid="e7f924bf-0e10-4aef-98d3-6e2f216dc914"]
+interface CertDatabase {
+  // Waits until Ash-Chrome finishes certificate database initialization and
+  // returns necessary data for Lacros-Chrome to connect to it.
+  GetCertDatabaseInfo@0() => (GetCertDatabaseInfoResult? result);
+};
diff --git a/chromeos/crosapi/mojom/crosapi.mojom b/chromeos/crosapi/mojom/crosapi.mojom
index 299610ca..e0af8d3 100644
--- a/chromeos/crosapi/mojom/crosapi.mojom
+++ b/chromeos/crosapi/mojom/crosapi.mojom
@@ -5,6 +5,7 @@
 module crosapi.mojom;
 
 import "chromeos/crosapi/mojom/account_manager.mojom";
+import "chromeos/crosapi/mojom/cert_database.mojom";
 import "chromeos/crosapi/mojom/feedback.mojom";
 import "chromeos/crosapi/mojom/file_manager.mojom";
 import "chromeos/crosapi/mojom/keystore_service.mojom";
@@ -39,8 +40,8 @@
 // milestone when you added it, to help us reason about compatibility between
 // lacros-chrome and older ash-chrome binaries.
 //
-// Next version: 7
-// Next method id: 12
+// Next version: 8
+// Next method id: 13
 [Stable, Uuid="8b79c34f-2bf8-4499-979a-b17cac522c1e"]
 interface AshChromeService {
   // Binds Chrome OS Account Manager for Identity management.
@@ -96,6 +97,11 @@
   [MinVersion=6] BindMediaSessionAudioFocusDebug@11(
       pending_receiver<media_session.mojom.AudioFocusManagerDebug> receiver);
 
+  // Binds the CertDatabase interface for initializing certificate database in
+  // Lacros-Chrome.
+  // Added in M89.
+  [MinVersion=7] BindCertDatabase@12(pending_receiver<CertDatabase> receiver);
+
   // Passes generic lacros information such as lacros version, etc into ash
   // in |lacros_info| during startup.
   // Added in M87.
diff --git a/chromeos/lacros/lacros_chrome_service_impl.cc b/chromeos/lacros/lacros_chrome_service_impl.cc
index e32e3103..ce91c97 100644
--- a/chromeos/lacros/lacros_chrome_service_impl.cc
+++ b/chromeos/lacros/lacros_chrome_service_impl.cc
@@ -10,6 +10,7 @@
 #include "base/logging.h"
 #include "base/task/task_traits.h"
 #include "base/task/thread_pool.h"
+#include "chromeos/crosapi/mojom/crosapi.mojom.h"
 #include "chromeos/lacros/lacros_chrome_service_delegate.h"
 #include "mojo/public/cpp/bindings/pending_receiver.h"
 #include "url/gurl.h"
@@ -164,6 +165,12 @@
     ash_chrome_service_->BindFeedback(std::move(pending_receiver));
   }
 
+  void BindCertDbReceiver(
+      mojo::PendingReceiver<crosapi::mojom::CertDatabase> pending_receiver) {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+    ash_chrome_service_->BindCertDatabase(std::move(pending_receiver));
+  }
+
   void OnLacrosStartup(crosapi::mojom::LacrosInfoPtr lacros_info) {
     DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
     ash_chrome_service_->OnLacrosStartup(std::move(lacros_info));
@@ -355,6 +362,15 @@
             feedback_remote_.BindNewPipeAndPassReceiver()));
   }
 
+  if (IsCertDbAvailable()) {
+    never_blocking_sequence_->PostTask(
+        FROM_HERE,
+        base::BindOnce(
+            &LacrosChromeServiceNeverBlockingState::BindCertDbReceiver,
+            weak_sequenced_state_,
+            cert_database_remote_.BindNewPipeAndPassReceiver()));
+  }
+
   if (IsOnLacrosStartupAvailable()) {
     never_blocking_sequence_->PostTask(
         FROM_HERE,
@@ -500,6 +516,13 @@
                                 weak_sequenced_state_, std::move(remote)));
 }
 
+bool LacrosChromeServiceImpl::IsCertDbAvailable() {
+  base::Optional<uint32_t> version = AshChromeServiceVersion();
+  return version &&
+         version.value() >=
+             AshChromeService::MethodMinVersions::kBindCertDatabaseMinVersion;
+}
+
 bool LacrosChromeServiceImpl::IsOnLacrosStartupAvailable() {
   base::Optional<uint32_t> version = AshChromeServiceVersion();
   return version &&
diff --git a/chromeos/lacros/lacros_chrome_service_impl.h b/chromeos/lacros/lacros_chrome_service_impl.h
index de5f71b..b949e812 100644
--- a/chromeos/lacros/lacros_chrome_service_impl.h
+++ b/chromeos/lacros/lacros_chrome_service_impl.h
@@ -14,6 +14,7 @@
 #include "base/sequence_checker.h"
 #include "base/sequenced_task_runner.h"
 #include "chromeos/crosapi/mojom/account_manager.mojom.h"
+#include "chromeos/crosapi/mojom/cert_database.mojom.h"
 #include "chromeos/crosapi/mojom/crosapi.mojom.h"
 #include "chromeos/crosapi/mojom/feedback.mojom.h"
 #include "chromeos/crosapi/mojom/keystore_service.mojom.h"
@@ -161,6 +162,15 @@
   void BindMediaControllerManager(
       mojo::PendingReceiver<media_session::mojom::MediaControllerManager>
           remote);
+  // cert_database_remote() can only be used when this method returns true;
+  bool IsCertDbAvailable();
+
+  // This must be called on the affine sequence.
+  mojo::Remote<crosapi::mojom::CertDatabase>& cert_database_remote() {
+    DCHECK_CALLED_ON_VALID_SEQUENCE(affine_sequence_checker_);
+    DCHECK(IsCertDbAvailable());
+    return cert_database_remote_;
+  }
 
   // file_manager_remote() can only be used if this method returns true.
   bool IsFileManagerAvailable();
@@ -251,6 +261,7 @@
   mojo::Remote<crosapi::mojom::SelectFile> select_file_remote_;
   mojo::Remote<device::mojom::HidManager> hid_manager_remote_;
   mojo::Remote<crosapi::mojom::Feedback> feedback_remote_;
+  mojo::Remote<crosapi::mojom::CertDatabase> cert_database_remote_;
   mojo::Remote<crosapi::mojom::KeystoreService> keystore_service_remote_;
   mojo::Remote<crosapi::mojom::FileManager> file_manager_remote_;
 
diff --git a/crypto/BUILD.gn b/crypto/BUILD.gn
index c5d6af8..54ca26c6 100644
--- a/crypto/BUILD.gn
+++ b/crypto/BUILD.gn
@@ -123,6 +123,13 @@
     sources += [ "nss_util_chromeos.cc" ]
   }
 
+  if (is_chromeos || is_lacros) {
+    sources += [
+      "chaps_support.cc",
+      "chaps_support.h",
+    ]
+  }
+
   defines = [ "CRYPTO_IMPLEMENTATION" ]
 
   if (is_nacl) {
diff --git a/crypto/chaps_support.cc b/crypto/chaps_support.cc
new file mode 100644
index 0000000..b68c3fb
--- /dev/null
+++ b/crypto/chaps_support.cc
@@ -0,0 +1,88 @@
+// 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 "crypto/chaps_support.h"
+
+#include <dlfcn.h>
+#include <secmodt.h>
+
+#include "base/logging.h"
+#include "base/threading/scoped_blocking_call.h"
+#include "nss_util_internal.h"
+
+namespace crypto {
+
+namespace {
+
+// Constants for loading the Chrome OS TPM-backed PKCS #11 library.
+const char kChapsModuleName[] = "Chaps";
+const char kChapsPath[] = "libchaps.so";
+
+class ScopedChapsLoadFixup {
+ public:
+  ScopedChapsLoadFixup();
+  ~ScopedChapsLoadFixup();
+
+ private:
+#if defined(COMPONENT_BUILD)
+  void* chaps_handle_;
+#endif
+};
+
+#if defined(COMPONENT_BUILD)
+
+ScopedChapsLoadFixup::ScopedChapsLoadFixup() {
+  // HACK: libchaps links the system protobuf and there are symbol conflicts
+  // with the bundled copy. Load chaps with RTLD_DEEPBIND to workaround.
+  chaps_handle_ = dlopen(kChapsPath, RTLD_LOCAL | RTLD_NOW | RTLD_DEEPBIND);
+}
+
+ScopedChapsLoadFixup::~ScopedChapsLoadFixup() {
+  // LoadNSSModule() will have taken a 2nd reference.
+  if (chaps_handle_)
+    dlclose(chaps_handle_);
+}
+
+#else
+
+ScopedChapsLoadFixup::ScopedChapsLoadFixup() = default;
+ScopedChapsLoadFixup::~ScopedChapsLoadFixup() = default;
+
+#endif  // defined(COMPONENT_BUILD)
+
+}  // namespace
+
+SECMODModule* LoadChaps() {
+  // NSS functions may reenter //net via extension hooks. If the reentered
+  // code needs to synchronously wait for a task to run but the thread pool in
+  // which that task must run doesn't have enough threads to schedule it, a
+  // deadlock occurs. To prevent that, the base::ScopedBlockingCall below
+  // increments the thread pool capacity for the duration of the TPM
+  // initialization.
+  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
+                                                base::BlockingType::WILL_BLOCK);
+
+  ScopedChapsLoadFixup chaps_loader;
+
+  DVLOG(3) << "Loading chaps...";
+  return LoadNSSModule(
+      kChapsModuleName, kChapsPath,
+      // For more details on these parameters, see:
+      // https://developer.mozilla.org/en/PKCS11_Module_Specs
+      // slotFlags=[PublicCerts] -- Certificates and public keys can be
+      //   read from this slot without requiring a call to C_Login.
+      // askpw=only -- Only authenticate to the token when necessary.
+      "NSS=\"slotParams=(0={slotFlags=[PublicCerts] askpw=only})\"");
+}
+
+bool IsSlotProvidedByChaps(PK11SlotInfo* slot) {
+  if (!slot)
+    return false;
+
+  SECMODModule* pk11_module = PK11_GetModule(slot);
+  return pk11_module && base::StringPiece(pk11_module->commonName) ==
+                            base::StringPiece(kChapsModuleName);
+}
+
+}  // namespace crypto
diff --git a/crypto/chaps_support.h b/crypto/chaps_support.h
new file mode 100644
index 0000000..6b44dcb
--- /dev/null
+++ b/crypto/chaps_support.h
@@ -0,0 +1,22 @@
+// 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.
+
+#ifndef CRYPTO_CHAPS_SUPPORT_H_
+#define CRYPTO_CHAPS_SUPPORT_H_
+
+#include <secmodt.h>
+
+#include "crypto/crypto_export.h"
+
+namespace crypto {
+
+// Loads chaps module for this NSS session.
+CRYPTO_EXPORT SECMODModule* LoadChaps();
+
+// Returns true if chaps is the module to which |slot| is attached.
+CRYPTO_EXPORT bool IsSlotProvidedByChaps(PK11SlotInfo* slot);
+
+}  // namespace crypto
+
+#endif  // CRYPTO_CHAPS_SUPPORT_H_
diff --git a/crypto/nss_util.cc b/crypto/nss_util.cc
index 0f124c2..f7f57dc 100644
--- a/crypto/nss_util.cc
+++ b/crypto/nss_util.cc
@@ -35,13 +35,14 @@
 
 namespace {
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_LACROS)
+
 // Fake certificate authority database used for testing.
 static const base::FilePath::CharType kReadOnlyCertDB[] =
     FILE_PATH_LITERAL("/etc/fake_root_ca/nssdb");
-#endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
-#if !BUILDFLAG(IS_CHROMEOS_ASH)
+#else
+
 base::FilePath GetDefaultConfigDirectory() {
   base::FilePath dir;
   base::PathService::Get(base::DIR_HOME, &dir);
@@ -57,7 +58,8 @@
   DVLOG(2) << "DefaultConfigDirectory: " << dir.value();
   return dir;
 }
-#endif  // !BUILDFLAG(IS_CHROMEOS_ASH)
+
+#endif  // BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_LACROS)
 
 // On non-Chrome OS platforms, return the default config directory. On Chrome OS
 // test images, return a read-only directory with fake root CA certs (which are
@@ -65,7 +67,7 @@
 // code). On Chrome OS non-test images (where the read-only directory doesn't
 // exist), return an empty path.
 base::FilePath GetInitialConfigDirectory() {
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_LACROS)
   base::FilePath database_dir = base::FilePath(kReadOnlyCertDB);
   if (!base::PathExists(database_dir))
     database_dir.clear();
@@ -158,7 +160,7 @@
       // Use "sql:" which can be shared by multiple processes safely.
       std::string nss_config_dir =
           base::StringPrintf("sql:%s", database_dir.value().c_str());
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_LACROS)
       status = NSS_Init(nss_config_dir.c_str());
 #else
       status = NSS_InitReadWrite(nss_config_dir.c_str());
diff --git a/crypto/nss_util_chromeos.cc b/crypto/nss_util_chromeos.cc
index 1b52f16..d9fd3271 100644
--- a/crypto/nss_util_chromeos.cc
+++ b/crypto/nss_util_chromeos.cc
@@ -4,7 +4,6 @@
 
 #include "crypto/nss_util.h"
 
-#include <dlfcn.h>
 #include <nss.h>
 #include <pk11pub.h>
 #include <plarena.h>
@@ -35,6 +34,7 @@
 #include "base/threading/thread_restrictions.h"
 #include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
+#include "crypto/chaps_support.h"
 #include "crypto/nss_util_internal.h"
 
 namespace crypto {
@@ -43,10 +43,6 @@
 
 const char kUserNSSDatabaseName[] = "UserNSSDB";
 
-// Constants for loading the Chrome OS TPM-backed PKCS #11 library.
-const char kChapsModuleName[] = "Chaps";
-const char kChapsPath[] = "libchaps.so";
-
 class ChromeOSUserData {
  public:
   using SlotReadyCallback = base::OnceCallback<void(ScopedPK11Slot)>;
@@ -110,38 +106,6 @@
   SlotReadyCallbackList tpm_ready_callback_list_;
 };
 
-class ScopedChapsLoadFixup {
- public:
-  ScopedChapsLoadFixup();
-  ~ScopedChapsLoadFixup();
-
- private:
-#if defined(COMPONENT_BUILD)
-  void* chaps_handle_;
-#endif
-};
-
-#if defined(COMPONENT_BUILD)
-
-ScopedChapsLoadFixup::ScopedChapsLoadFixup() {
-  // HACK: libchaps links the system protobuf and there are symbol conflicts
-  // with the bundled copy. Load chaps with RTLD_DEEPBIND to workaround.
-  chaps_handle_ = dlopen(kChapsPath, RTLD_LOCAL | RTLD_NOW | RTLD_DEEPBIND);
-}
-
-ScopedChapsLoadFixup::~ScopedChapsLoadFixup() {
-  // LoadNSSModule() will have taken a 2nd reference.
-  if (chaps_handle_)
-    dlclose(chaps_handle_);
-}
-
-#else
-
-ScopedChapsLoadFixup::ScopedChapsLoadFixup() {}
-ScopedChapsLoadFixup::~ScopedChapsLoadFixup() {}
-
-#endif  // defined(COMPONENT_BUILD)
-
 class ChromeOSTokenManager {
  public:
   // Used with PostTaskAndReply to pass handles to worker thread and back.
@@ -160,7 +124,7 @@
     // the current thread, due to NSS's internal locking requirements
     base::ThreadRestrictions::ScopedAllowIO allow_io;
 
-    base::FilePath nssdb_path = path.AppendASCII(".pki").AppendASCII("nssdb");
+    base::FilePath nssdb_path = GetSoftwareNSSDBPath(path);
     if (!base::CreateDirectory(nssdb_path)) {
       LOG(ERROR) << "Failed to create " << nssdb_path.value() << " directory.";
       return ScopedPK11Slot();
@@ -235,17 +199,7 @@
         FROM_HERE, base::BlockingType::WILL_BLOCK);
 
     if (!tpm_args->chaps_module) {
-      ScopedChapsLoadFixup chaps_loader;
-
-      DVLOG(3) << "Loading chaps...";
-      tpm_args->chaps_module = LoadNSSModule(
-          kChapsModuleName, kChapsPath,
-          // For more details on these parameters, see:
-          // https://developer.mozilla.org/en/PKCS11_Module_Specs
-          // slotFlags=[PublicCerts] -- Certificates and public keys can be
-          //   read from this slot without requiring a call to C_Login.
-          // askpw=only -- Only authenticate to the token when necessary.
-          "NSS=\"slotParams=(0={slotFlags=[PublicCerts] askpw=only})\"");
+      tpm_args->chaps_module = LoadChaps();
     }
     if (tpm_args->chaps_module) {
       tpm_args->tpm_slot =
@@ -527,6 +481,11 @@
     LAZY_INSTANCE_INITIALIZER;
 }  // namespace
 
+base::FilePath GetSoftwareNSSDBPath(
+    const base::FilePath& profile_directory_path) {
+  return profile_directory_path.AppendASCII(".pki").AppendASCII("nssdb");
+}
+
 ScopedPK11Slot GetSystemNSSKeySlot(
     base::OnceCallback<void(ScopedPK11Slot)> callback) {
   return g_token_manager.Get().GetSystemNSSKeySlot(std::move(callback));
@@ -605,13 +564,4 @@
       std::move(slot));
 }
 
-bool IsSlotProvidedByChaps(PK11SlotInfo* slot) {
-  if (!slot)
-    return false;
-
-  SECMODModule* pk11_module = PK11_GetModule(slot);
-  return pk11_module && base::StringPiece(pk11_module->commonName) ==
-                            base::StringPiece(kChapsModuleName);
-}
-
 }  // namespace crypto
diff --git a/crypto/nss_util_internal.h b/crypto/nss_util_internal.h
index 7221375..99fbb10 100644
--- a/crypto/nss_util_internal.h
+++ b/crypto/nss_util_internal.h
@@ -44,6 +44,11 @@
 };
 
 #if BUILDFLAG(IS_CHROMEOS_ASH)
+// Returns path to the NSS database file in the provided profile
+// directory.
+CRYPTO_EXPORT base::FilePath GetSoftwareNSSDBPath(
+    const base::FilePath& profile_directory_path);
+
 // Returns a reference to the system-wide TPM slot if it is loaded. If it is not
 // loaded and |callback| is non-null, the |callback| will be run once the slot
 // is loaded.
@@ -122,9 +127,6 @@
 CRYPTO_EXPORT void SetPrivateSoftwareSlotForChromeOSUserForTesting(
     ScopedPK11Slot slot);
 
-// Returns true if chaps is the module to which |slot| is attached.
-CRYPTO_EXPORT bool IsSlotProvidedByChaps(PK11SlotInfo* slot);
-
 #endif  // BUILDFLAG(IS_CHROMEOS_ASH)
 
 // Loads the given module for this NSS session.
diff --git a/net/cert/nss_cert_database.cc b/net/cert/nss_cert_database.cc
index e582b57..e880b1ab 100644
--- a/net/cert/nss_cert_database.cc
+++ b/net/cert/nss_cert_database.cc
@@ -32,6 +32,10 @@
 #include "net/third_party/mozilla_security_manager/nsNSSCertificateDB.h"
 #include "net/third_party/mozilla_security_manager/nsPKCS12Blob.h"
 
+#if defined(OS_CHROMEOS) || BUILDFLAG(IS_LACROS)
+#include "crypto/chaps_support.h"
+#endif
+
 // PSM = Mozilla's Personal Security Manager.
 namespace psm = mozilla_security_manager;
 
@@ -436,7 +440,7 @@
   if (!slot || !PK11_IsHW(slot))
     return false;
 
-#if BUILDFLAG(IS_CHROMEOS_ASH)
+#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_LACROS)
   // Chaps announces PK11_IsHW(slot) for all slots. However, it is possible for
   // a key in chaps to be not truly hardware-backed, either because it has been
   // requested to be software-backed, or because the TPM does not support the