Split CrOS-specific TPM, etc., code into a separate file

It's still in //crypto, but now it's a bit more self-contained.

Bug: 125848
Change-Id: I8596aa9f66630625234206986636edcb29853c73
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1857046
Commit-Queue: David Benjamin <[email protected]>
Reviewed-by: Matt Mueller <[email protected]>
Cr-Commit-Position: refs/heads/master@{#706202}
diff --git a/crypto/BUILD.gn b/crypto/BUILD.gn
index c27c7cf..0eab491 100644
--- a/crypto/BUILD.gn
+++ b/crypto/BUILD.gn
@@ -116,6 +116,10 @@
     ]
   }
 
+  if (is_chromeos) {
+    sources += [ "nss_util_chromeos.cc" ]
+  }
+
   defines = [ "CRYPTO_IMPLEMENTATION" ]
 
   if (is_nacl) {
diff --git a/crypto/nss_util.cc b/crypto/nss_util.cc
index f3489604b..d369529 100644
--- a/crypto/nss_util.cc
+++ b/crypto/nss_util.cc
@@ -12,64 +12,34 @@
 #include <prtime.h>
 #include <secmod.h>
 
-#include <map>
 #include <memory>
 #include <utility>
-#include <vector>
 
 #include "base/base_paths.h"
-#include "base/bind.h"
 #include "base/debug/alias.h"
-#include "base/debug/stack_trace.h"
 #include "base/files/file_path.h"
 #include "base/files/file_util.h"
 #include "base/lazy_instance.h"
-#include "base/location.h"
 #include "base/logging.h"
-#include "base/memory/ptr_util.h"
 #include "base/path_service.h"
 #include "base/strings/stringprintf.h"
 #include "base/task/post_task.h"
 #include "base/threading/scoped_blocking_call.h"
-#include "base/threading/thread_checker.h"
 #include "base/threading/thread_restrictions.h"
-#include "base/threading/thread_task_runner_handle.h"
 #include "build/build_config.h"
 #include "crypto/nss_crypto_module_delegate.h"
 #include "crypto/nss_util_internal.h"
 
-#if defined(OS_CHROMEOS)
-#include <dlfcn.h>
-#endif
-
 namespace crypto {
 
 namespace {
 
 #if defined(OS_CHROMEOS)
-const char kUserNSSDatabaseName[] = "UserNSSDB";
-
-// Constants for loading the Chrome OS TPM-backed PKCS #11 library.
-const char kChapsModuleName[] = "Chaps";
-const char kChapsPath[] = "libchaps.so";
-
 // Fake certificate authority database used for testing.
 static const base::FilePath::CharType kReadOnlyCertDB[] =
     FILE_PATH_LITERAL("/etc/fake_root_ca/nssdb");
 #endif  // defined(OS_CHROMEOS)
 
-std::string GetNSSErrorMessage() {
-  std::string result;
-  if (PR_GetErrorTextLength()) {
-    std::unique_ptr<char[]> error_text(new char[PR_GetErrorTextLength() + 1]);
-    PRInt32 copied = PR_GetErrorText(error_text.get());
-    result = std::string(error_text.get(), copied);
-  } else {
-    result = base::StringPrintf("NSS error code: %d", PR_GetError());
-  }
-  return result;
-}
-
 #if !defined(OS_CHROMEOS)
 base::FilePath GetDefaultConfigDirectory() {
   base::FilePath dir;
@@ -156,478 +126,7 @@
   LOG(FATAL) << "nss_error=" << nss_error << ", os_error=" << os_error;
 }
 
-#if defined(OS_CHROMEOS)
-class ChromeOSUserData {
- public:
-  explicit ChromeOSUserData(ScopedPK11Slot public_slot)
-      : public_slot_(std::move(public_slot)),
-        private_slot_initialization_started_(false) {}
-  ~ChromeOSUserData() {
-    if (public_slot_) {
-      SECStatus status = SECMOD_CloseUserDB(public_slot_.get());
-      if (status != SECSuccess)
-        PLOG(ERROR) << "SECMOD_CloseUserDB failed: " << PORT_GetError();
-    }
-  }
-
-  ScopedPK11Slot GetPublicSlot() {
-    return ScopedPK11Slot(public_slot_ ? PK11_ReferenceSlot(public_slot_.get())
-                                       : nullptr);
-  }
-
-  ScopedPK11Slot GetPrivateSlot(
-      base::OnceCallback<void(ScopedPK11Slot)> callback) {
-    if (private_slot_)
-      return ScopedPK11Slot(PK11_ReferenceSlot(private_slot_.get()));
-    if (!callback.is_null())
-      tpm_ready_callback_list_.push_back(std::move(callback));
-    return ScopedPK11Slot();
-  }
-
-  void SetPrivateSlot(ScopedPK11Slot private_slot) {
-    DCHECK(!private_slot_);
-    private_slot_ = std::move(private_slot);
-
-    SlotReadyCallbackList callback_list;
-    callback_list.swap(tpm_ready_callback_list_);
-    for (SlotReadyCallbackList::iterator i = callback_list.begin();
-         i != callback_list.end();
-         ++i) {
-      std::move(*i).Run(
-          ScopedPK11Slot(PK11_ReferenceSlot(private_slot_.get())));
-    }
-  }
-
-  bool private_slot_initialization_started() const {
-      return private_slot_initialization_started_;
-  }
-
-  void set_private_slot_initialization_started() {
-      private_slot_initialization_started_ = true;
-  }
-
- private:
-  ScopedPK11Slot public_slot_;
-  ScopedPK11Slot private_slot_;
-
-  bool private_slot_initialization_started_;
-
-  typedef std::vector<base::OnceCallback<void(ScopedPK11Slot)>>
-      SlotReadyCallbackList;
-  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() {
-  // LoadModule() will have taken a 2nd reference.
-  if (chaps_handle_)
-    dlclose(chaps_handle_);
-}
-
-#else
-
-ScopedChapsLoadFixup::ScopedChapsLoadFixup() {}
-ScopedChapsLoadFixup::~ScopedChapsLoadFixup() {}
-
-#endif  // defined(COMPONENT_BUILD)
-#endif  // defined(OS_CHROMEOS)
-
 class NSSInitSingleton {
- public:
-#if defined(OS_CHROMEOS)
-  // Used with PostTaskAndReply to pass handles to worker thread and back.
-  struct TPMModuleAndSlot {
-    explicit TPMModuleAndSlot(SECMODModule* init_chaps_module)
-        : chaps_module(init_chaps_module) {}
-    SECMODModule* chaps_module;
-    crypto::ScopedPK11Slot tpm_slot;
-  };
-
-  ScopedPK11Slot OpenPersistentNSSDBForPath(const std::string& db_name,
-                                            const base::FilePath& path) {
-    DCHECK(thread_checker_.CalledOnValidThread());
-    // NSS is allowed to do IO on the current thread since dispatching
-    // to a dedicated thread would still have the affect of blocking
-    // the current thread, due to NSS's internal locking requirements
-    base::ThreadRestrictions::ScopedAllowIO allow_io;
-
-    base::FilePath nssdb_path = path.AppendASCII(".pki").AppendASCII("nssdb");
-    if (!base::CreateDirectory(nssdb_path)) {
-      LOG(ERROR) << "Failed to create " << nssdb_path.value() << " directory.";
-      return ScopedPK11Slot();
-    }
-    return OpenSoftwareNSSDB(nssdb_path, db_name);
-  }
-
-  void EnableTPMTokenForNSS() {
-    DCHECK(thread_checker_.CalledOnValidThread());
-
-    // If this gets set, then we'll use the TPM for certs with
-    // private keys, otherwise we'll fall back to the software
-    // implementation.
-    tpm_token_enabled_for_nss_ = true;
-  }
-
-  bool IsTPMTokenEnabledForNSS() {
-    DCHECK(thread_checker_.CalledOnValidThread());
-    return tpm_token_enabled_for_nss_;
-  }
-
-  void InitializeTPMTokenAndSystemSlot(
-      int system_slot_id,
-      base::OnceCallback<void(bool)> callback) {
-    DCHECK(thread_checker_.CalledOnValidThread());
-    // Should not be called while there is already an initialization in
-    // progress.
-    DCHECK(!initializing_tpm_token_);
-    // If EnableTPMTokenForNSS hasn't been called, return false.
-    if (!tpm_token_enabled_for_nss_) {
-      base::ThreadTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE, base::BindOnce(std::move(callback), false));
-      return;
-    }
-
-    // If everything is already initialized, then return true.
-    // Note that only |tpm_slot_| is checked, since |chaps_module_| could be
-    // nullptr in tests while |tpm_slot_| has been set to the test DB.
-    if (tpm_slot_) {
-      base::ThreadTaskRunnerHandle::Get()->PostTask(
-          FROM_HERE, base::BindOnce(std::move(callback), true));
-      return;
-    }
-
-    // Note that a reference is not taken to chaps_module_. This is safe since
-    // NSSInitSingleton is Leaky, so the reference it holds is never released.
-    std::unique_ptr<TPMModuleAndSlot> tpm_args(
-        new TPMModuleAndSlot(chaps_module_));
-    TPMModuleAndSlot* tpm_args_ptr = tpm_args.get();
-    base::PostTaskAndReply(
-        FROM_HERE,
-        {base::ThreadPool(), base::MayBlock(),
-         base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
-        base::BindOnce(&NSSInitSingleton::InitializeTPMTokenInThreadPool,
-                       system_slot_id, tpm_args_ptr),
-        base::BindOnce(&NSSInitSingleton::OnInitializedTPMTokenAndSystemSlot,
-                       base::Unretained(this),  // NSSInitSingleton is leaky
-                       std::move(callback), std::move(tpm_args)));
-    initializing_tpm_token_ = true;
-  }
-
-  static void InitializeTPMTokenInThreadPool(CK_SLOT_ID token_slot_id,
-                                             TPMModuleAndSlot* tpm_args) {
-    // 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);
-
-    if (!tpm_args->chaps_module) {
-      ScopedChapsLoadFixup chaps_loader;
-
-      DVLOG(3) << "Loading chaps...";
-      tpm_args->chaps_module = LoadModule(
-          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})\"");
-    }
-    if (tpm_args->chaps_module) {
-      tpm_args->tpm_slot =
-          GetTPMSlotForIdInThreadPool(tpm_args->chaps_module, token_slot_id);
-    }
-  }
-
-  void OnInitializedTPMTokenAndSystemSlot(
-      base::OnceCallback<void(bool)> callback,
-      std::unique_ptr<TPMModuleAndSlot> tpm_args) {
-    DCHECK(thread_checker_.CalledOnValidThread());
-    DVLOG(2) << "Loaded chaps: " << !!tpm_args->chaps_module
-             << ", got tpm slot: " << !!tpm_args->tpm_slot;
-
-    chaps_module_ = tpm_args->chaps_module;
-    tpm_slot_ = std::move(tpm_args->tpm_slot);
-    if (!chaps_module_ && test_system_slot_) {
-      // chromeos_unittests try to test the TPM initialization process. If we
-      // have a test DB open, pretend that it is the TPM slot.
-      tpm_slot_.reset(PK11_ReferenceSlot(test_system_slot_.get()));
-    }
-    initializing_tpm_token_ = false;
-
-    if (tpm_slot_)
-      RunAndClearTPMReadyCallbackList();
-
-    std::move(callback).Run(!!tpm_slot_);
-  }
-
-  void RunAndClearTPMReadyCallbackList() {
-    TPMReadyCallbackList callback_list;
-    callback_list.swap(tpm_ready_callback_list_);
-    for (TPMReadyCallbackList::iterator i = callback_list.begin();
-         i != callback_list.end();
-         ++i) {
-      std::move(*i).Run();
-    }
-  }
-
-  bool IsTPMTokenReady(base::OnceClosure callback) {
-    if (!callback.is_null()) {
-      // Cannot DCHECK in the general case yet, but since the callback is
-      // a new addition to the API, DCHECK to make sure at least the new uses
-      // don't regress.
-      DCHECK(thread_checker_.CalledOnValidThread());
-    } else if (!thread_checker_.CalledOnValidThread()) {
-      // TODO(mattm): Change to DCHECK when callers have been fixed.
-      DVLOG(1) << "Called on wrong thread.\n"
-               << base::debug::StackTrace().ToString();
-    }
-
-    if (tpm_slot_)
-      return true;
-
-    if (!callback.is_null())
-      tpm_ready_callback_list_.push_back(std::move(callback));
-
-    return false;
-  }
-
-  // Note that CK_SLOT_ID is an unsigned long, but cryptohome gives us the slot
-  // id as an int. This should be safe since this is only used with chaps, which
-  // we also control.
-  static crypto::ScopedPK11Slot GetTPMSlotForIdInThreadPool(
-      SECMODModule* chaps_module,
-      CK_SLOT_ID slot_id) {
-    DCHECK(chaps_module);
-
-    DVLOG(3) << "Poking chaps module.";
-    SECStatus rv = SECMOD_UpdateSlotList(chaps_module);
-    if (rv != SECSuccess)
-      PLOG(ERROR) << "SECMOD_UpdateSlotList failed: " << PORT_GetError();
-
-    PK11SlotInfo* slot = SECMOD_LookupSlot(chaps_module->moduleID, slot_id);
-    if (!slot)
-      LOG(ERROR) << "TPM slot " << slot_id << " not found.";
-    return crypto::ScopedPK11Slot(slot);
-  }
-
-  bool InitializeNSSForChromeOSUser(const std::string& username_hash,
-                                    const base::FilePath& path) {
-    DCHECK(thread_checker_.CalledOnValidThread());
-    if (chromeos_user_map_.find(username_hash) != chromeos_user_map_.end()) {
-      // This user already exists in our mapping.
-      DVLOG(2) << username_hash << " already initialized.";
-      return false;
-    }
-
-    DVLOG(2) << "Opening NSS DB " << path.value();
-    std::string db_name = base::StringPrintf(
-        "%s %s", kUserNSSDatabaseName, username_hash.c_str());
-    ScopedPK11Slot public_slot(OpenPersistentNSSDBForPath(db_name, path));
-    chromeos_user_map_[username_hash] =
-        std::make_unique<ChromeOSUserData>(std::move(public_slot));
-    return true;
-  }
-
-  bool ShouldInitializeTPMForChromeOSUser(const std::string& username_hash) {
-    DCHECK(thread_checker_.CalledOnValidThread());
-    DCHECK(chromeos_user_map_.find(username_hash) != chromeos_user_map_.end());
-
-    return !chromeos_user_map_[username_hash]
-                ->private_slot_initialization_started();
-  }
-
-  void WillInitializeTPMForChromeOSUser(const std::string& username_hash) {
-    DCHECK(thread_checker_.CalledOnValidThread());
-    DCHECK(chromeos_user_map_.find(username_hash) != chromeos_user_map_.end());
-
-    chromeos_user_map_[username_hash]
-        ->set_private_slot_initialization_started();
-  }
-
-  void InitializeTPMForChromeOSUser(const std::string& username_hash,
-                                    CK_SLOT_ID slot_id) {
-    DCHECK(thread_checker_.CalledOnValidThread());
-    DCHECK(chromeos_user_map_.find(username_hash) != chromeos_user_map_.end());
-    DCHECK(chromeos_user_map_[username_hash]->
-               private_slot_initialization_started());
-
-    if (!chaps_module_)
-      return;
-
-    // Note that a reference is not taken to chaps_module_. This is safe since
-    // NSSInitSingleton is Leaky, so the reference it holds is never released.
-    std::unique_ptr<TPMModuleAndSlot> tpm_args(
-        new TPMModuleAndSlot(chaps_module_));
-    TPMModuleAndSlot* tpm_args_ptr = tpm_args.get();
-    base::PostTaskAndReply(
-        FROM_HERE,
-        {base::ThreadPool(), base::MayBlock(),
-         base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
-        base::BindOnce(&NSSInitSingleton::InitializeTPMTokenInThreadPool,
-                       slot_id, tpm_args_ptr),
-        base::BindOnce(&NSSInitSingleton::OnInitializedTPMForChromeOSUser,
-                       base::Unretained(this),  // NSSInitSingleton is leaky
-                       username_hash, std::move(tpm_args)));
-  }
-
-  void OnInitializedTPMForChromeOSUser(
-      const std::string& username_hash,
-      std::unique_ptr<TPMModuleAndSlot> tpm_args) {
-    DCHECK(thread_checker_.CalledOnValidThread());
-    DVLOG(2) << "Got tpm slot for " << username_hash << " "
-             << !!tpm_args->tpm_slot;
-    chromeos_user_map_[username_hash]->SetPrivateSlot(
-        std::move(tpm_args->tpm_slot));
-  }
-
-  void InitializePrivateSoftwareSlotForChromeOSUser(
-      const std::string& username_hash) {
-    DCHECK(thread_checker_.CalledOnValidThread());
-    VLOG(1) << "using software private slot for " << username_hash;
-    DCHECK(chromeos_user_map_.find(username_hash) != chromeos_user_map_.end());
-    DCHECK(chromeos_user_map_[username_hash]->
-               private_slot_initialization_started());
-
-    if (prepared_test_private_slot_) {
-      chromeos_user_map_[username_hash]->SetPrivateSlot(
-          std::move(prepared_test_private_slot_));
-      return;
-    }
-
-    chromeos_user_map_[username_hash]->SetPrivateSlot(
-        chromeos_user_map_[username_hash]->GetPublicSlot());
-  }
-
-  ScopedPK11Slot GetPublicSlotForChromeOSUser(
-      const std::string& username_hash) {
-    DCHECK(thread_checker_.CalledOnValidThread());
-
-    if (username_hash.empty()) {
-      DVLOG(2) << "empty username_hash";
-      return ScopedPK11Slot();
-    }
-
-    if (chromeos_user_map_.find(username_hash) == chromeos_user_map_.end()) {
-      LOG(ERROR) << username_hash << " not initialized.";
-      return ScopedPK11Slot();
-    }
-    return chromeos_user_map_[username_hash]->GetPublicSlot();
-  }
-
-  ScopedPK11Slot GetPrivateSlotForChromeOSUser(
-      const std::string& username_hash,
-      base::OnceCallback<void(ScopedPK11Slot)> callback) {
-    DCHECK(thread_checker_.CalledOnValidThread());
-
-    if (username_hash.empty()) {
-      DVLOG(2) << "empty username_hash";
-      if (!callback.is_null()) {
-        base::ThreadTaskRunnerHandle::Get()->PostTask(
-            FROM_HERE, base::BindOnce(std::move(callback), ScopedPK11Slot()));
-      }
-      return ScopedPK11Slot();
-    }
-
-    DCHECK(chromeos_user_map_.find(username_hash) != chromeos_user_map_.end());
-
-    return chromeos_user_map_[username_hash]->GetPrivateSlot(
-        std::move(callback));
-  }
-
-  void CloseChromeOSUserForTesting(const std::string& username_hash) {
-    DCHECK(thread_checker_.CalledOnValidThread());
-    auto i = chromeos_user_map_.find(username_hash);
-    DCHECK(i != chromeos_user_map_.end());
-    chromeos_user_map_.erase(i);
-  }
-
-  void SetSystemKeySlotForTesting(ScopedPK11Slot slot) {
-    DCHECK(thread_checker_.CalledOnValidThread());
-
-    // Ensure that a previous value of test_system_slot_ is not overwritten.
-    // Unsetting, i.e. setting a nullptr, however is allowed.
-    DCHECK(!slot || !test_system_slot_);
-    test_system_slot_ = std::move(slot);
-    if (test_system_slot_) {
-      tpm_slot_.reset(PK11_ReferenceSlot(test_system_slot_.get()));
-      RunAndClearTPMReadyCallbackList();
-    } else {
-      tpm_slot_.reset();
-    }
-  }
-
-  void SetSystemKeySlotWithoutInitializingTPMForTesting(ScopedPK11Slot slot) {
-    DCHECK(thread_checker_.CalledOnValidThread());
-
-    // Ensure that a previous value of test_system_slot_ is not overwritten.
-    // Unsetting, i.e. setting a nullptr, however is allowed.
-    DCHECK(!slot || !test_system_slot_);
-    if (tpm_slot_ && tpm_slot_ == test_system_slot_) {
-      // Unset |tpm_slot_| if it was initialized from |test_system_slot_|.
-      tpm_slot_.reset();
-    }
-    test_system_slot_ = std::move(slot);
-  }
-
-  void SetPrivateSoftwareSlotForChromeOSUserForTesting(ScopedPK11Slot slot) {
-    DCHECK(thread_checker_.CalledOnValidThread());
-
-    // Ensure that a previous value of prepared_test_private_slot_ is not
-    // overwritten. Unsetting, i.e. setting a nullptr, however is allowed.
-    DCHECK(!slot || !prepared_test_private_slot_);
-    prepared_test_private_slot_ = std::move(slot);
-  }
-
-  void GetSystemNSSKeySlotCallback(
-      base::OnceCallback<void(ScopedPK11Slot)> callback) {
-    std::move(callback).Run(
-        ScopedPK11Slot(PK11_ReferenceSlot(tpm_slot_.get())));
-  }
-
-  ScopedPK11Slot GetSystemNSSKeySlot(
-      base::OnceCallback<void(ScopedPK11Slot)> callback) {
-    DCHECK(thread_checker_.CalledOnValidThread());
-    // TODO(mattm): chromeos::TPMTokenloader always calls
-    // InitializeTPMTokenAndSystemSlot with slot 0.  If the system slot is
-    // disabled, tpm_slot_ will be the first user's slot instead. Can that be
-    // detected and return nullptr instead?
-
-    base::OnceClosure wrapped_callback;
-    if (!callback.is_null()) {
-      wrapped_callback = base::BindOnce(
-          &NSSInitSingleton::GetSystemNSSKeySlotCallback,
-          base::Unretained(this) /* singleton is leaky */, std::move(callback));
-    }
-    if (IsTPMTokenReady(std::move(wrapped_callback)))
-      return ScopedPK11Slot(PK11_ReferenceSlot(tpm_slot_.get()));
-    return ScopedPK11Slot();
-  }
-#endif
-
  private:
   friend struct base::LazyInstanceTraitsBase<NSSInitSingleton>;
 
@@ -637,10 +136,6 @@
     //   http://code.google.com/p/chromium/issues/detail?id=59847
     base::ThreadRestrictions::ScopedAllowIO allow_io;
 
-    // It's safe to construct on any thread, since LazyInstance will prevent any
-    // other threads from accessing until the constructor is done.
-    thread_checker_.DetachFromThread();
-
     EnsureNSPRInit();
 
     // We *must* have NSS >= 3.26 at compile time.
@@ -700,7 +195,7 @@
     //
     // TODO(mattm): DCHECK this succeeded when crbug.com/310972 is fixed.
     // Failing to load root certs will it hard to talk to anybody via https.
-    LoadModule("Root Certs", "libnssckbi.so", nullptr);
+    LoadNSSModule("Root Certs", "libnssckbi.so", nullptr);
 
     // Disable MD5 certificate signatures. (They are disabled by default in
     // NSS 3.14.)
@@ -713,48 +208,6 @@
   // to prevent non-joinable threads from using NSS after it's already been
   // shut down.
   ~NSSInitSingleton() = delete;
-
-  // Load the given module for this NSS session.
-  static SECMODModule* LoadModule(const char* name,
-                                  const char* library_path,
-                                  const char* params) {
-    std::string modparams = base::StringPrintf(
-        "name=\"%s\" library=\"%s\" %s",
-        name, library_path, params ? params : "");
-
-    // Shouldn't need to const_cast here, but SECMOD doesn't properly
-    // declare input string arguments as const.  Bug
-    // https://bugzilla.mozilla.org/show_bug.cgi?id=642546 was filed
-    // on NSS codebase to address this.
-    SECMODModule* module = SECMOD_LoadUserModule(
-        const_cast<char*>(modparams.c_str()), nullptr, PR_FALSE);
-    if (!module) {
-      LOG(ERROR) << "Error loading " << name << " module into NSS: "
-                 << GetNSSErrorMessage();
-      return nullptr;
-    }
-    if (!module->loaded) {
-      LOG(ERROR) << "After loading " << name << ", loaded==false: "
-                 << GetNSSErrorMessage();
-      SECMOD_DestroyModule(module);
-      return nullptr;
-    }
-    return module;
-  }
-
-#if defined(OS_CHROMEOS)
-  bool tpm_token_enabled_for_nss_ = false;
-  bool initializing_tpm_token_ = false;
-  using TPMReadyCallbackList = std::vector<base::OnceClosure>;
-  TPMReadyCallbackList tpm_ready_callback_list_;
-  SECMODModule* chaps_module_ = nullptr;
-  crypto::ScopedPK11Slot tpm_slot_;
-  std::map<std::string, std::unique_ptr<ChromeOSUserData>> chromeos_user_map_;
-  ScopedPK11Slot test_system_slot_;
-  ScopedPK11Slot prepared_test_private_slot_;
-#endif
-
-  base::ThreadChecker thread_checker_;
 };
 
 base::LazyInstance<NSSInitSingleton>::Leaky
@@ -799,87 +252,6 @@
   SECMOD_ReleaseReadLock(lock_);
 }
 
-#if defined(OS_CHROMEOS)
-ScopedPK11Slot GetSystemNSSKeySlot(
-    base::OnceCallback<void(ScopedPK11Slot)> callback) {
-  return g_nss_singleton.Get().GetSystemNSSKeySlot(std::move(callback));
-}
-
-void SetSystemKeySlotForTesting(ScopedPK11Slot slot) {
-  g_nss_singleton.Get().SetSystemKeySlotForTesting(std::move(slot));
-}
-
-void SetSystemKeySlotWithoutInitializingTPMForTesting(ScopedPK11Slot slot) {
-  g_nss_singleton.Get().SetSystemKeySlotWithoutInitializingTPMForTesting(
-      std::move(slot));
-}
-
-void EnableTPMTokenForNSS() {
-  g_nss_singleton.Get().EnableTPMTokenForNSS();
-}
-
-bool IsTPMTokenEnabledForNSS() {
-  return g_nss_singleton.Get().IsTPMTokenEnabledForNSS();
-}
-
-bool IsTPMTokenReady(base::OnceClosure callback) {
-  return g_nss_singleton.Get().IsTPMTokenReady(std::move(callback));
-}
-
-void InitializeTPMTokenAndSystemSlot(int token_slot_id,
-                                     base::OnceCallback<void(bool)> callback) {
-  g_nss_singleton.Get().InitializeTPMTokenAndSystemSlot(token_slot_id,
-                                                        std::move(callback));
-}
-
-bool InitializeNSSForChromeOSUser(const std::string& username_hash,
-                                  const base::FilePath& path) {
-  return g_nss_singleton.Get().InitializeNSSForChromeOSUser(username_hash,
-                                                            path);
-}
-
-bool ShouldInitializeTPMForChromeOSUser(const std::string& username_hash) {
-  return g_nss_singleton.Get().ShouldInitializeTPMForChromeOSUser(
-      username_hash);
-}
-
-void WillInitializeTPMForChromeOSUser(const std::string& username_hash) {
-  g_nss_singleton.Get().WillInitializeTPMForChromeOSUser(username_hash);
-}
-
-void InitializeTPMForChromeOSUser(
-    const std::string& username_hash,
-    CK_SLOT_ID slot_id) {
-  g_nss_singleton.Get().InitializeTPMForChromeOSUser(username_hash, slot_id);
-}
-
-void InitializePrivateSoftwareSlotForChromeOSUser(
-    const std::string& username_hash) {
-  g_nss_singleton.Get().InitializePrivateSoftwareSlotForChromeOSUser(
-      username_hash);
-}
-
-ScopedPK11Slot GetPublicSlotForChromeOSUser(const std::string& username_hash) {
-  return g_nss_singleton.Get().GetPublicSlotForChromeOSUser(username_hash);
-}
-
-ScopedPK11Slot GetPrivateSlotForChromeOSUser(
-    const std::string& username_hash,
-    base::OnceCallback<void(ScopedPK11Slot)> callback) {
-  return g_nss_singleton.Get().GetPrivateSlotForChromeOSUser(
-      username_hash, std::move(callback));
-}
-
-void CloseChromeOSUserForTesting(const std::string& username_hash) {
-  g_nss_singleton.Get().CloseChromeOSUserForTesting(username_hash);
-}
-
-void SetPrivateSoftwareSlotForChromeOSUserForTesting(ScopedPK11Slot slot) {
-  g_nss_singleton.Get().SetPrivateSoftwareSlotForChromeOSUserForTesting(
-      std::move(slot));
-}
-#endif  // defined(OS_CHROMEOS)
-
 base::Time PRTimeToBaseTime(PRTime prtime) {
   return base::Time::FromInternalValue(
       prtime + base::Time::UnixEpoch().ToInternalValue());
@@ -889,4 +261,43 @@
   return time.ToInternalValue() - base::Time::UnixEpoch().ToInternalValue();
 }
 
+SECMODModule* LoadNSSModule(const char* name,
+                            const char* library_path,
+                            const char* params) {
+  std::string modparams =
+      base::StringPrintf("name=\"%s\" library=\"%s\" %s", name, library_path,
+                         params ? params : "");
+
+  // Shouldn't need to const_cast here, but SECMOD doesn't properly declare
+  // input string arguments as const.  Bug
+  // https://bugzilla.mozilla.org/show_bug.cgi?id=642546 was filed on NSS
+  // codebase to address this.
+  SECMODModule* module = SECMOD_LoadUserModule(
+      const_cast<char*>(modparams.c_str()), nullptr, PR_FALSE);
+  if (!module) {
+    LOG(ERROR) << "Error loading " << name
+               << " module into NSS: " << GetNSSErrorMessage();
+    return nullptr;
+  }
+  if (!module->loaded) {
+    LOG(ERROR) << "After loading " << name
+               << ", loaded==false: " << GetNSSErrorMessage();
+    SECMOD_DestroyModule(module);
+    return nullptr;
+  }
+  return module;
+}
+
+std::string GetNSSErrorMessage() {
+  std::string result;
+  if (PR_GetErrorTextLength()) {
+    std::unique_ptr<char[]> error_text(new char[PR_GetErrorTextLength() + 1]);
+    PRInt32 copied = PR_GetErrorText(error_text.get());
+    result = std::string(error_text.get(), copied);
+  } else {
+    result = base::StringPrintf("NSS error code: %d", PR_GetError());
+  }
+  return result;
+}
+
 }  // namespace crypto
diff --git a/crypto/nss_util_chromeos.cc b/crypto/nss_util_chromeos.cc
new file mode 100644
index 0000000..4011f36b
--- /dev/null
+++ b/crypto/nss_util_chromeos.cc
@@ -0,0 +1,611 @@
+// Copyright (c) 2012 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/nss_util.h"
+
+#include <dlfcn.h>
+#include <nss.h>
+#include <pk11pub.h>
+#include <plarena.h>
+#include <prerror.h>
+#include <prinit.h>
+#include <prtime.h>
+#include <secmod.h>
+
+#include <map>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/debug/stack_trace.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/lazy_instance.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/strings/stringprintf.h"
+#include "base/task/post_task.h"
+#include "base/threading/scoped_blocking_call.h"
+#include "base/threading/thread_checker.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "crypto/nss_util_internal.h"
+
+namespace crypto {
+
+namespace {
+
+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:
+  explicit ChromeOSUserData(ScopedPK11Slot public_slot)
+      : public_slot_(std::move(public_slot)),
+        private_slot_initialization_started_(false) {}
+  ~ChromeOSUserData() {
+    if (public_slot_) {
+      SECStatus status = SECMOD_CloseUserDB(public_slot_.get());
+      if (status != SECSuccess)
+        PLOG(ERROR) << "SECMOD_CloseUserDB failed: " << PORT_GetError();
+    }
+  }
+
+  ScopedPK11Slot GetPublicSlot() {
+    return ScopedPK11Slot(public_slot_ ? PK11_ReferenceSlot(public_slot_.get())
+                                       : nullptr);
+  }
+
+  ScopedPK11Slot GetPrivateSlot(
+      base::OnceCallback<void(ScopedPK11Slot)> callback) {
+    if (private_slot_)
+      return ScopedPK11Slot(PK11_ReferenceSlot(private_slot_.get()));
+    if (!callback.is_null())
+      tpm_ready_callback_list_.push_back(std::move(callback));
+    return ScopedPK11Slot();
+  }
+
+  void SetPrivateSlot(ScopedPK11Slot private_slot) {
+    DCHECK(!private_slot_);
+    private_slot_ = std::move(private_slot);
+
+    SlotReadyCallbackList callback_list;
+    callback_list.swap(tpm_ready_callback_list_);
+    for (SlotReadyCallbackList::iterator i = callback_list.begin();
+         i != callback_list.end(); ++i) {
+      std::move(*i).Run(
+          ScopedPK11Slot(PK11_ReferenceSlot(private_slot_.get())));
+    }
+  }
+
+  bool private_slot_initialization_started() const {
+    return private_slot_initialization_started_;
+  }
+
+  void set_private_slot_initialization_started() {
+    private_slot_initialization_started_ = true;
+  }
+
+ private:
+  ScopedPK11Slot public_slot_;
+  ScopedPK11Slot private_slot_;
+
+  bool private_slot_initialization_started_;
+
+  typedef std::vector<base::OnceCallback<void(ScopedPK11Slot)>>
+      SlotReadyCallbackList;
+  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.
+  struct TPMModuleAndSlot {
+    explicit TPMModuleAndSlot(SECMODModule* init_chaps_module)
+        : chaps_module(init_chaps_module) {}
+    SECMODModule* chaps_module;
+    crypto::ScopedPK11Slot tpm_slot;
+  };
+
+  ScopedPK11Slot OpenPersistentNSSDBForPath(const std::string& db_name,
+                                            const base::FilePath& path) {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    // NSS is allowed to do IO on the current thread since dispatching
+    // to a dedicated thread would still have the affect of blocking
+    // the current thread, due to NSS's internal locking requirements
+    base::ThreadRestrictions::ScopedAllowIO allow_io;
+
+    base::FilePath nssdb_path = path.AppendASCII(".pki").AppendASCII("nssdb");
+    if (!base::CreateDirectory(nssdb_path)) {
+      LOG(ERROR) << "Failed to create " << nssdb_path.value() << " directory.";
+      return ScopedPK11Slot();
+    }
+    return OpenSoftwareNSSDB(nssdb_path, db_name);
+  }
+
+  void EnableTPMTokenForNSS() {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+    // If this gets set, then we'll use the TPM for certs with
+    // private keys, otherwise we'll fall back to the software
+    // implementation.
+    tpm_token_enabled_for_nss_ = true;
+  }
+
+  bool IsTPMTokenEnabledForNSS() {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    return tpm_token_enabled_for_nss_;
+  }
+
+  void InitializeTPMTokenAndSystemSlot(
+      int system_slot_id,
+      base::OnceCallback<void(bool)> callback) {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    // Should not be called while there is already an initialization in
+    // progress.
+    DCHECK(!initializing_tpm_token_);
+    // If EnableTPMTokenForNSS hasn't been called, return false.
+    if (!tpm_token_enabled_for_nss_) {
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(std::move(callback), false));
+      return;
+    }
+
+    // If everything is already initialized, then return true.
+    // Note that only |tpm_slot_| is checked, since |chaps_module_| could be
+    // nullptr in tests while |tpm_slot_| has been set to the test DB.
+    if (tpm_slot_) {
+      base::ThreadTaskRunnerHandle::Get()->PostTask(
+          FROM_HERE, base::BindOnce(std::move(callback), true));
+      return;
+    }
+
+    // Note that a reference is not taken to chaps_module_. This is safe since
+    // ChromeOSTokenManager is Leaky, so the reference it holds is never
+    // released.
+    std::unique_ptr<TPMModuleAndSlot> tpm_args(
+        new TPMModuleAndSlot(chaps_module_));
+    TPMModuleAndSlot* tpm_args_ptr = tpm_args.get();
+    base::PostTaskAndReply(
+        FROM_HERE,
+        {base::ThreadPool(), base::MayBlock(),
+         base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
+        base::BindOnce(&ChromeOSTokenManager::InitializeTPMTokenInThreadPool,
+                       system_slot_id, tpm_args_ptr),
+        base::BindOnce(
+            &ChromeOSTokenManager::OnInitializedTPMTokenAndSystemSlot,
+            base::Unretained(this),  // ChromeOSTokenManager is leaky
+            std::move(callback), std::move(tpm_args)));
+    initializing_tpm_token_ = true;
+  }
+
+  static void InitializeTPMTokenInThreadPool(CK_SLOT_ID token_slot_id,
+                                             TPMModuleAndSlot* tpm_args) {
+    // 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);
+
+    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})\"");
+    }
+    if (tpm_args->chaps_module) {
+      tpm_args->tpm_slot =
+          GetTPMSlotForIdInThreadPool(tpm_args->chaps_module, token_slot_id);
+    }
+  }
+
+  void OnInitializedTPMTokenAndSystemSlot(
+      base::OnceCallback<void(bool)> callback,
+      std::unique_ptr<TPMModuleAndSlot> tpm_args) {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    DVLOG(2) << "Loaded chaps: " << !!tpm_args->chaps_module
+             << ", got tpm slot: " << !!tpm_args->tpm_slot;
+
+    chaps_module_ = tpm_args->chaps_module;
+    tpm_slot_ = std::move(tpm_args->tpm_slot);
+    if (!chaps_module_ && test_system_slot_) {
+      // chromeos_unittests try to test the TPM initialization process. If we
+      // have a test DB open, pretend that it is the TPM slot.
+      tpm_slot_.reset(PK11_ReferenceSlot(test_system_slot_.get()));
+    }
+    initializing_tpm_token_ = false;
+
+    if (tpm_slot_)
+      RunAndClearTPMReadyCallbackList();
+
+    std::move(callback).Run(!!tpm_slot_);
+  }
+
+  void RunAndClearTPMReadyCallbackList() {
+    TPMReadyCallbackList callback_list;
+    callback_list.swap(tpm_ready_callback_list_);
+    for (TPMReadyCallbackList::iterator i = callback_list.begin();
+         i != callback_list.end(); ++i) {
+      std::move(*i).Run();
+    }
+  }
+
+  bool IsTPMTokenReady(base::OnceClosure callback) {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    if (tpm_slot_)
+      return true;
+
+    if (!callback.is_null())
+      tpm_ready_callback_list_.push_back(std::move(callback));
+
+    return false;
+  }
+
+  // Note that CK_SLOT_ID is an unsigned long, but cryptohome gives us the slot
+  // id as an int. This should be safe since this is only used with chaps, which
+  // we also control.
+  static crypto::ScopedPK11Slot GetTPMSlotForIdInThreadPool(
+      SECMODModule* chaps_module,
+      CK_SLOT_ID slot_id) {
+    DCHECK(chaps_module);
+
+    DVLOG(3) << "Poking chaps module.";
+    SECStatus rv = SECMOD_UpdateSlotList(chaps_module);
+    if (rv != SECSuccess)
+      PLOG(ERROR) << "SECMOD_UpdateSlotList failed: " << PORT_GetError();
+
+    PK11SlotInfo* slot = SECMOD_LookupSlot(chaps_module->moduleID, slot_id);
+    if (!slot)
+      LOG(ERROR) << "TPM slot " << slot_id << " not found.";
+    return crypto::ScopedPK11Slot(slot);
+  }
+
+  bool InitializeNSSForChromeOSUser(const std::string& username_hash,
+                                    const base::FilePath& path) {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    if (chromeos_user_map_.find(username_hash) != chromeos_user_map_.end()) {
+      // This user already exists in our mapping.
+      DVLOG(2) << username_hash << " already initialized.";
+      return false;
+    }
+
+    DVLOG(2) << "Opening NSS DB " << path.value();
+    std::string db_name = base::StringPrintf("%s %s", kUserNSSDatabaseName,
+                                             username_hash.c_str());
+    ScopedPK11Slot public_slot(OpenPersistentNSSDBForPath(db_name, path));
+    chromeos_user_map_[username_hash] =
+        std::make_unique<ChromeOSUserData>(std::move(public_slot));
+    return true;
+  }
+
+  bool ShouldInitializeTPMForChromeOSUser(const std::string& username_hash) {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    DCHECK(chromeos_user_map_.find(username_hash) != chromeos_user_map_.end());
+
+    return !chromeos_user_map_[username_hash]
+                ->private_slot_initialization_started();
+  }
+
+  void WillInitializeTPMForChromeOSUser(const std::string& username_hash) {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    DCHECK(chromeos_user_map_.find(username_hash) != chromeos_user_map_.end());
+
+    chromeos_user_map_[username_hash]
+        ->set_private_slot_initialization_started();
+  }
+
+  void InitializeTPMForChromeOSUser(const std::string& username_hash,
+                                    CK_SLOT_ID slot_id) {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    DCHECK(chromeos_user_map_.find(username_hash) != chromeos_user_map_.end());
+    DCHECK(chromeos_user_map_[username_hash]
+               ->private_slot_initialization_started());
+
+    if (!chaps_module_)
+      return;
+
+    // Note that a reference is not taken to chaps_module_. This is safe since
+    // ChromeOSTokenManager is Leaky, so the reference it holds is never
+    // released.
+    std::unique_ptr<TPMModuleAndSlot> tpm_args(
+        new TPMModuleAndSlot(chaps_module_));
+    TPMModuleAndSlot* tpm_args_ptr = tpm_args.get();
+    base::PostTaskAndReply(
+        FROM_HERE,
+        {base::ThreadPool(), base::MayBlock(),
+         base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
+        base::BindOnce(&ChromeOSTokenManager::InitializeTPMTokenInThreadPool,
+                       slot_id, tpm_args_ptr),
+        base::BindOnce(&ChromeOSTokenManager::OnInitializedTPMForChromeOSUser,
+                       base::Unretained(this),  // ChromeOSTokenManager is leaky
+                       username_hash, std::move(tpm_args)));
+  }
+
+  void OnInitializedTPMForChromeOSUser(
+      const std::string& username_hash,
+      std::unique_ptr<TPMModuleAndSlot> tpm_args) {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    DVLOG(2) << "Got tpm slot for " << username_hash << " "
+             << !!tpm_args->tpm_slot;
+    chromeos_user_map_[username_hash]->SetPrivateSlot(
+        std::move(tpm_args->tpm_slot));
+  }
+
+  void InitializePrivateSoftwareSlotForChromeOSUser(
+      const std::string& username_hash) {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    VLOG(1) << "using software private slot for " << username_hash;
+    DCHECK(chromeos_user_map_.find(username_hash) != chromeos_user_map_.end());
+    DCHECK(chromeos_user_map_[username_hash]
+               ->private_slot_initialization_started());
+
+    if (prepared_test_private_slot_) {
+      chromeos_user_map_[username_hash]->SetPrivateSlot(
+          std::move(prepared_test_private_slot_));
+      return;
+    }
+
+    chromeos_user_map_[username_hash]->SetPrivateSlot(
+        chromeos_user_map_[username_hash]->GetPublicSlot());
+  }
+
+  ScopedPK11Slot GetPublicSlotForChromeOSUser(
+      const std::string& username_hash) {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+    if (username_hash.empty()) {
+      DVLOG(2) << "empty username_hash";
+      return ScopedPK11Slot();
+    }
+
+    if (chromeos_user_map_.find(username_hash) == chromeos_user_map_.end()) {
+      LOG(ERROR) << username_hash << " not initialized.";
+      return ScopedPK11Slot();
+    }
+    return chromeos_user_map_[username_hash]->GetPublicSlot();
+  }
+
+  ScopedPK11Slot GetPrivateSlotForChromeOSUser(
+      const std::string& username_hash,
+      base::OnceCallback<void(ScopedPK11Slot)> callback) {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+    if (username_hash.empty()) {
+      DVLOG(2) << "empty username_hash";
+      if (!callback.is_null()) {
+        base::ThreadTaskRunnerHandle::Get()->PostTask(
+            FROM_HERE, base::BindOnce(std::move(callback), ScopedPK11Slot()));
+      }
+      return ScopedPK11Slot();
+    }
+
+    DCHECK(chromeos_user_map_.find(username_hash) != chromeos_user_map_.end());
+
+    return chromeos_user_map_[username_hash]->GetPrivateSlot(
+        std::move(callback));
+  }
+
+  void CloseChromeOSUserForTesting(const std::string& username_hash) {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    auto i = chromeos_user_map_.find(username_hash);
+    DCHECK(i != chromeos_user_map_.end());
+    chromeos_user_map_.erase(i);
+  }
+
+  void SetSystemKeySlotForTesting(ScopedPK11Slot slot) {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+    // Ensure that a previous value of test_system_slot_ is not overwritten.
+    // Unsetting, i.e. setting a nullptr, however is allowed.
+    DCHECK(!slot || !test_system_slot_);
+    test_system_slot_ = std::move(slot);
+    if (test_system_slot_) {
+      tpm_slot_.reset(PK11_ReferenceSlot(test_system_slot_.get()));
+      RunAndClearTPMReadyCallbackList();
+    } else {
+      tpm_slot_.reset();
+    }
+  }
+
+  void SetSystemKeySlotWithoutInitializingTPMForTesting(ScopedPK11Slot slot) {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+    // Ensure that a previous value of test_system_slot_ is not overwritten.
+    // Unsetting, i.e. setting a nullptr, however is allowed.
+    DCHECK(!slot || !test_system_slot_);
+    if (tpm_slot_ && tpm_slot_ == test_system_slot_) {
+      // Unset |tpm_slot_| if it was initialized from |test_system_slot_|.
+      tpm_slot_.reset();
+    }
+    test_system_slot_ = std::move(slot);
+  }
+
+  void SetPrivateSoftwareSlotForChromeOSUserForTesting(ScopedPK11Slot slot) {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+    // Ensure that a previous value of prepared_test_private_slot_ is not
+    // overwritten. Unsetting, i.e. setting a nullptr, however is allowed.
+    DCHECK(!slot || !prepared_test_private_slot_);
+    prepared_test_private_slot_ = std::move(slot);
+  }
+
+  void GetSystemNSSKeySlotCallback(
+      base::OnceCallback<void(ScopedPK11Slot)> callback) {
+    std::move(callback).Run(
+        ScopedPK11Slot(PK11_ReferenceSlot(tpm_slot_.get())));
+  }
+
+  ScopedPK11Slot GetSystemNSSKeySlot(
+      base::OnceCallback<void(ScopedPK11Slot)> callback) {
+    DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+    // TODO(mattm): chromeos::TPMTokenloader always calls
+    // InitializeTPMTokenAndSystemSlot with slot 0.  If the system slot is
+    // disabled, tpm_slot_ will be the first user's slot instead. Can that be
+    // detected and return nullptr instead?
+
+    base::OnceClosure wrapped_callback;
+    if (!callback.is_null()) {
+      wrapped_callback = base::BindOnce(
+          &ChromeOSTokenManager::GetSystemNSSKeySlotCallback,
+          base::Unretained(this) /* singleton is leaky */, std::move(callback));
+    }
+    if (IsTPMTokenReady(std::move(wrapped_callback)))
+      return ScopedPK11Slot(PK11_ReferenceSlot(tpm_slot_.get()));
+    return ScopedPK11Slot();
+  }
+
+ private:
+  friend struct base::LazyInstanceTraitsBase<ChromeOSTokenManager>;
+
+  ChromeOSTokenManager() { EnsureNSSInit(); }
+
+  // NOTE(willchan): We don't actually cleanup on destruction since we leak NSS
+  // to prevent non-joinable threads from using NSS after it's already been
+  // shut down.
+  ~ChromeOSTokenManager() = delete;
+
+  bool tpm_token_enabled_for_nss_ = false;
+  bool initializing_tpm_token_ = false;
+  using TPMReadyCallbackList = std::vector<base::OnceClosure>;
+  TPMReadyCallbackList tpm_ready_callback_list_;
+  SECMODModule* chaps_module_ = nullptr;
+  crypto::ScopedPK11Slot tpm_slot_;
+  std::map<std::string, std::unique_ptr<ChromeOSUserData>> chromeos_user_map_;
+  ScopedPK11Slot test_system_slot_;
+  ScopedPK11Slot prepared_test_private_slot_;
+
+  THREAD_CHECKER(thread_checker_);
+};
+
+base::LazyInstance<ChromeOSTokenManager>::Leaky g_token_manager =
+    LAZY_INSTANCE_INITIALIZER;
+}  // namespace
+
+ScopedPK11Slot GetSystemNSSKeySlot(
+    base::OnceCallback<void(ScopedPK11Slot)> callback) {
+  return g_token_manager.Get().GetSystemNSSKeySlot(std::move(callback));
+}
+
+void SetSystemKeySlotForTesting(ScopedPK11Slot slot) {
+  g_token_manager.Get().SetSystemKeySlotForTesting(std::move(slot));
+}
+
+void SetSystemKeySlotWithoutInitializingTPMForTesting(ScopedPK11Slot slot) {
+  g_token_manager.Get().SetSystemKeySlotWithoutInitializingTPMForTesting(
+      std::move(slot));
+}
+
+void EnableTPMTokenForNSS() {
+  g_token_manager.Get().EnableTPMTokenForNSS();
+}
+
+bool IsTPMTokenEnabledForNSS() {
+  return g_token_manager.Get().IsTPMTokenEnabledForNSS();
+}
+
+bool IsTPMTokenReady(base::OnceClosure callback) {
+  return g_token_manager.Get().IsTPMTokenReady(std::move(callback));
+}
+
+void InitializeTPMTokenAndSystemSlot(int token_slot_id,
+                                     base::OnceCallback<void(bool)> callback) {
+  g_token_manager.Get().InitializeTPMTokenAndSystemSlot(token_slot_id,
+                                                        std::move(callback));
+}
+
+bool InitializeNSSForChromeOSUser(const std::string& username_hash,
+                                  const base::FilePath& path) {
+  return g_token_manager.Get().InitializeNSSForChromeOSUser(username_hash,
+                                                            path);
+}
+
+bool ShouldInitializeTPMForChromeOSUser(const std::string& username_hash) {
+  return g_token_manager.Get().ShouldInitializeTPMForChromeOSUser(
+      username_hash);
+}
+
+void WillInitializeTPMForChromeOSUser(const std::string& username_hash) {
+  g_token_manager.Get().WillInitializeTPMForChromeOSUser(username_hash);
+}
+
+void InitializeTPMForChromeOSUser(const std::string& username_hash,
+                                  CK_SLOT_ID slot_id) {
+  g_token_manager.Get().InitializeTPMForChromeOSUser(username_hash, slot_id);
+}
+
+void InitializePrivateSoftwareSlotForChromeOSUser(
+    const std::string& username_hash) {
+  g_token_manager.Get().InitializePrivateSoftwareSlotForChromeOSUser(
+      username_hash);
+}
+
+ScopedPK11Slot GetPublicSlotForChromeOSUser(const std::string& username_hash) {
+  return g_token_manager.Get().GetPublicSlotForChromeOSUser(username_hash);
+}
+
+ScopedPK11Slot GetPrivateSlotForChromeOSUser(
+    const std::string& username_hash,
+    base::OnceCallback<void(ScopedPK11Slot)> callback) {
+  return g_token_manager.Get().GetPrivateSlotForChromeOSUser(
+      username_hash, std::move(callback));
+}
+
+void CloseChromeOSUserForTesting(const std::string& username_hash) {
+  g_token_manager.Get().CloseChromeOSUserForTesting(username_hash);
+}
+
+void SetPrivateSoftwareSlotForChromeOSUserForTesting(ScopedPK11Slot slot) {
+  g_token_manager.Get().SetPrivateSoftwareSlotForChromeOSUserForTesting(
+      std::move(slot));
+}
+
+}  // namespace crypto
diff --git a/crypto/nss_util_internal.h b/crypto/nss_util_internal.h
index e1ef6c3..eb95afe4 100644
--- a/crypto/nss_util_internal.h
+++ b/crypto/nss_util_internal.h
@@ -123,6 +123,14 @@
 
 #endif  // defined(OS_CHROMEOS)
 
+// Loads the given module for this NSS session.
+SECMODModule* LoadNSSModule(const char* name,
+                            const char* library_path,
+                            const char* params);
+
+// Returns the current NSS error message.
+std::string GetNSSErrorMessage();
+
 }  // namespace crypto
 
 #endif  // CRYPTO_NSS_UTIL_INTERNAL_H_