Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / crypto / nss_util.cc
index 04080ed..8f9865a 100644 (file)
 #include <prtime.h>
 #include <secmod.h>
 
-#if defined(OS_LINUX)
-#include <linux/nfs_fs.h>
-#include <sys/vfs.h>
-#elif defined(OS_OPENBSD)
+#if defined(OS_OPENBSD)
 #include <sys/mount.h>
 #include <sys/param.h>
 #endif
 
+#include <map>
 #include <vector>
 
+#include "base/base_paths.h"
+#include "base/bind.h"
+#include "base/cpu.h"
 #include "base/debug/alias.h"
+#include "base/debug/stack_trace.h"
 #include "base/environment.h"
-#include "base/file_util.h"
 #include "base/files/file_path.h"
-#include "base/files/scoped_temp_dir.h"
+#include "base/files/file_util.h"
 #include "base/lazy_instance.h"
 #include "base/logging.h"
 #include "base/memory/scoped_ptr.h"
+#include "base/message_loop/message_loop.h"
 #include "base/metrics/histogram.h"
 #include "base/native_library.h"
+#include "base/path_service.h"
+#include "base/stl_util.h"
 #include "base/strings/stringprintf.h"
+#include "base/threading/thread_checker.h"
 #include "base/threading/thread_restrictions.h"
+#include "base/threading/worker_pool.h"
 #include "build/build_config.h"
 
 // USE_NSS means we use NSS for everything crypto-related.  If USE_NSS is not
@@ -43,7 +49,7 @@
 // certificate and key databases.
 #if defined(USE_NSS)
 #include "base/synchronization/lock.h"
-#include "crypto/crypto_module_blocking_password_delegate.h"
+#include "crypto/nss_crypto_module_delegate.h"
 #endif  // defined(USE_NSS)
 
 namespace crypto {
@@ -51,7 +57,7 @@ namespace crypto {
 namespace {
 
 #if defined(OS_CHROMEOS)
-const char kNSSDatabaseName[] = "Real NSS database";
+const char kUserNSSDatabaseName[] = "UserNSSDB";
 
 // Constants for loading the Chrome OS TPM-backed PKCS #11 library.
 const char kChapsModuleName[] = "Chaps";
@@ -75,19 +81,23 @@ std::string GetNSSErrorMessage() {
 }
 
 #if defined(USE_NSS)
+#if !defined(OS_CHROMEOS)
 base::FilePath GetDefaultConfigDirectory() {
-  base::FilePath dir = file_util::GetHomeDir();
+  base::FilePath dir;
+  PathService::Get(base::DIR_HOME, &dir);
   if (dir.empty()) {
     LOG(ERROR) << "Failed to get home directory.";
     return dir;
   }
   dir = dir.AppendASCII(".pki").AppendASCII("nssdb");
-  if (!file_util::CreateDirectory(dir)) {
+  if (!base::CreateDirectory(dir)) {
     LOG(ERROR) << "Failed to create " << dir.value() << " directory.";
     dir.clear();
   }
+  DVLOG(2) << "DefaultConfigDirectory: " << dir.value();
   return dir;
 }
+#endif  // !defined(IS_CHROMEOS)
 
 // 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
@@ -108,18 +118,6 @@ base::FilePath GetInitialConfigDirectory() {
 // This callback for NSS forwards all requests to a caller-specified
 // CryptoModuleBlockingPasswordDelegate object.
 char* PKCS11PasswordFunc(PK11SlotInfo* slot, PRBool retry, void* arg) {
-#if defined(OS_CHROMEOS)
-  // If we get asked for a password for the TPM, then return the
-  // well known password we use, as long as the TPM slot has been
-  // initialized.
-  if (crypto::IsTPMTokenReady()) {
-    std::string token_name;
-    std::string user_pin;
-    crypto::GetTPMTokenInfo(&token_name, &user_pin);
-    if (PK11_GetTokenName(slot) == token_name)
-      return PORT_Strdup(user_pin.c_str());
-  }
-#endif
   crypto::CryptoModuleBlockingPasswordDelegate* delegate =
       reinterpret_cast<crypto::CryptoModuleBlockingPasswordDelegate*>(arg);
   if (delegate) {
@@ -151,21 +149,25 @@ char* PKCS11PasswordFunc(PK11SlotInfo* slot, PRBool retry, void* arg) {
 // Because this function sets an environment variable it must be run before we
 // go multi-threaded.
 void UseLocalCacheOfNSSDatabaseIfNFS(const base::FilePath& database_dir) {
-#if defined(OS_LINUX) || defined(OS_OPENBSD)
-  struct statfs buf;
-  if (statfs(database_dir.value().c_str(), &buf) == 0) {
+  bool db_on_nfs = false;
 #if defined(OS_LINUX)
-    if (buf.f_type == NFS_SUPER_MAGIC) {
+  base::FileSystemType fs_type = base::FILE_SYSTEM_UNKNOWN;
+  if (base::GetFileSystemType(database_dir, &fs_type))
+    db_on_nfs = (fs_type == base::FILE_SYSTEM_NFS);
 #elif defined(OS_OPENBSD)
-    if (strcmp(buf.f_fstypename, MOUNT_NFS) == 0) {
+  struct statfs buf;
+  if (statfs(database_dir.value().c_str(), &buf) == 0)
+    db_on_nfs = (strcmp(buf.f_fstypename, MOUNT_NFS) == 0);
+#else
+  NOTIMPLEMENTED();
 #endif
-      scoped_ptr<base::Environment> env(base::Environment::Create());
-      const char* use_cache_env_var = "NSS_SDB_USE_CACHE";
-      if (!env->HasVar(use_cache_env_var))
-        env->SetVar(use_cache_env_var, "yes");
-    }
+
+  if (db_on_nfs) {
+    scoped_ptr<base::Environment> env(base::Environment::Create());
+    static const char kUseCacheEnvVar[] = "NSS_SDB_USE_CACHE";
+    if (!env->HasVar(kUseCacheEnvVar))
+      env->SetVar(kUseCacheEnvVar, "yes");
   }
-#endif  // defined(OS_LINUX) || defined(OS_OPENBSD)
 }
 
 #endif  // defined(USE_NSS)
@@ -196,12 +198,6 @@ class NSPRInitSingleton {
 base::LazyInstance<NSPRInitSingleton>::Leaky
     g_nspr_singleton = LAZY_INSTANCE_INITIALIZER;
 
-// This is a LazyInstance so that it will be deleted automatically when the
-// unittest exits.  NSSInitSingleton is a LeakySingleton, so it would not be
-// deleted if it were a regular member.
-base::LazyInstance<base::ScopedTempDir> g_test_nss_db_dir =
-    LAZY_INSTANCE_INITIALIZER;
-
 // Force a crash with error info on NSS_NoDB_Init failure.
 void CrashOnNSSInitFailure() {
   int nss_error = PR_GetError();
@@ -213,50 +209,160 @@ void CrashOnNSSInitFailure() {
   LOG(FATAL) << "nss_error=" << nss_error << ", os_error=" << os_error;
 }
 
+#if defined(OS_CHROMEOS)
+class ChromeOSUserData {
+ public:
+  explicit ChromeOSUserData(ScopedPK11Slot public_slot)
+      : public_slot_(public_slot.Pass()),
+        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()) : NULL);
+  }
+
+  ScopedPK11Slot GetPrivateSlot(
+      const base::Callback<void(ScopedPK11Slot)>& callback) {
+    if (private_slot_)
+      return ScopedPK11Slot(PK11_ReferenceSlot(private_slot_.get()));
+    if (!callback.is_null())
+      tpm_ready_callback_list_.push_back(callback);
+    return ScopedPK11Slot();
+  }
+
+  void SetPrivateSlot(ScopedPK11Slot private_slot) {
+    DCHECK(!private_slot_);
+    private_slot_ = private_slot.Pass();
+
+    SlotReadyCallbackList callback_list;
+    callback_list.swap(tpm_ready_callback_list_);
+    for (SlotReadyCallbackList::iterator i = callback_list.begin();
+         i != callback_list.end();
+         ++i) {
+      (*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::Callback<void(ScopedPK11Slot)> >
+      SlotReadyCallbackList;
+  SlotReadyCallbackList tpm_ready_callback_list_;
+};
+#endif  // defined(OS_CHROMEOS)
+
 class NSSInitSingleton {
  public:
 #if defined(OS_CHROMEOS)
-  void OpenPersistentNSSDB() {
-    if (!chromeos_user_logged_in_) {
-      // GetDefaultConfigDirectory causes us to do blocking IO on UI thread.
-      // Temporarily allow it until we fix http://crbug.com/70119
-      base::ThreadRestrictions::ScopedAllowIO allow_io;
-      chromeos_user_logged_in_ = true;
-
-      // This creates another DB slot in NSS that is read/write, unlike
-      // the fake root CA cert DB and the "default" crypto key
-      // provider, which are still read-only (because we initialized
-      // NSS before we had a cryptohome mounted).
-      software_slot_ = OpenUserDB(GetDefaultConfigDirectory(),
-                                  kNSSDatabaseName);
+  // 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 InitializeTPMToken(const std::string& token_name,
-                          int token_slot_id,
-                          const std::string& user_pin) {
+  bool IsTPMTokenEnabledForNSS() {
+    DCHECK(thread_checker_.CalledOnValidThread());
+    return tpm_token_enabled_for_nss_;
+  }
+
+  void InitializeTPMTokenAndSystemSlot(
+      int system_slot_id,
+      const base::Callback<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_)
-      return false;
+    if (!tpm_token_enabled_for_nss_) {
+      base::MessageLoop::current()->PostTask(FROM_HERE,
+                                             base::Bind(callback, false));
+      return;
+    }
 
     // If everything is already initialized, then return true.
-    if (chaps_module_ && tpm_slot_)
-      return true;
+    // Note that only |tpm_slot_| is checked, since |chaps_module_| could be
+    // NULL in tests while |tpm_slot_| has been set to the test DB.
+    if (tpm_slot_) {
+      base::MessageLoop::current()->PostTask(FROM_HERE,
+                                             base::Bind(callback, true));
+      return;
+    }
 
-    tpm_token_name_ = token_name;
-    tpm_user_pin_ = user_pin;
+    // 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.
+    scoped_ptr<TPMModuleAndSlot> tpm_args(new TPMModuleAndSlot(chaps_module_));
+    TPMModuleAndSlot* tpm_args_ptr = tpm_args.get();
+    if (base::WorkerPool::PostTaskAndReply(
+            FROM_HERE,
+            base::Bind(&NSSInitSingleton::InitializeTPMTokenOnWorkerThread,
+                       system_slot_id,
+                       tpm_args_ptr),
+            base::Bind(&NSSInitSingleton::OnInitializedTPMTokenAndSystemSlot,
+                       base::Unretained(this),  // NSSInitSingleton is leaky
+                       callback,
+                       base::Passed(&tpm_args)),
+            true /* task_is_slow */
+            )) {
+      initializing_tpm_token_ = true;
+    } else {
+      base::MessageLoop::current()->PostTask(FROM_HERE,
+                                             base::Bind(callback, false));
+    }
+  }
 
+  static void InitializeTPMTokenOnWorkerThread(CK_SLOT_ID token_slot_id,
+                                               TPMModuleAndSlot* tpm_args) {
     // This tries to load the Chaps module so NSS can talk to the hardware
     // TPM.
-    if (!chaps_module_) {
-      chaps_module_ = LoadModule(
+    if (!tpm_args->chaps_module) {
+      DVLOG(3) << "Loading chaps...";
+      tpm_args->chaps_module = LoadModule(
           kChapsModuleName,
           kChapsPath,
           // For more details on these parameters, see:
@@ -265,105 +371,262 @@ class NSSInitSingleton {
           //   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 (!chaps_module_ && test_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_ = PK11_ReferenceSlot(test_slot_);
-        return true;
-      }
     }
-    if (chaps_module_){
-      tpm_slot_ = GetTPMSlotForId(token_slot_id);
+    if (tpm_args->chaps_module) {
+      tpm_args->tpm_slot =
+          GetTPMSlotForIdOnWorkerThread(tpm_args->chaps_module, token_slot_id);
+    }
+  }
 
-      return tpm_slot_ != NULL;
+  void OnInitializedTPMTokenAndSystemSlot(
+      const base::Callback<void(bool)>& callback,
+      scoped_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_ = tpm_args->tpm_slot.Pass();
+    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()));
     }
-    return false;
+    initializing_tpm_token_ = false;
+
+    if (tpm_slot_)
+      RunAndClearTPMReadyCallbackList();
+
+    callback.Run(!!tpm_slot_);
   }
 
-  void GetTPMTokenInfo(std::string* token_name, std::string* user_pin) {
-    if (!tpm_token_enabled_for_nss_) {
-      LOG(ERROR) << "GetTPMTokenInfo called before TPM Token is ready.";
-      return;
+  void RunAndClearTPMReadyCallbackList() {
+    TPMReadyCallbackList callback_list;
+    callback_list.swap(tpm_ready_callback_list_);
+    for (TPMReadyCallbackList::iterator i = callback_list.begin();
+         i != callback_list.end();
+         ++i) {
+      i->Run();
     }
-    if (token_name)
-      *token_name = tpm_token_name_;
-    if (user_pin)
-      *user_pin = tpm_user_pin_;
   }
 
-  bool IsTPMTokenReady() {
-    return tpm_slot_ != NULL;
+  bool IsTPMTokenReady(const base::Closure& 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(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.
-  PK11SlotInfo* GetTPMSlotForId(CK_SLOT_ID slot_id) {
-    if (!chaps_module_)
-      return NULL;
+  static crypto::ScopedPK11Slot GetTPMSlotForIdOnWorkerThread(
+      SECMODModule* chaps_module,
+      CK_SLOT_ID slot_id) {
+    DCHECK(chaps_module);
 
-    VLOG(1) << "Poking chaps module.";
-    SECStatus rv = SECMOD_UpdateSlotList(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);
+    PK11SlotInfo* slot = SECMOD_LookupSlot(chaps_module->moduleID, slot_id);
     if (!slot)
       LOG(ERROR) << "TPM slot " << slot_id << " not found.";
-    return slot;
+    return crypto::ScopedPK11Slot(slot);
   }
-#endif  // defined(OS_CHROMEOS)
-
 
-  bool OpenTestNSSDB() {
-    if (test_slot_)
-      return true;
-    if (!g_test_nss_db_dir.Get().CreateUniqueTempDir())
+  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;
-    test_slot_ = OpenUserDB(g_test_nss_db_dir.Get().path(), kTestTPMTokenName);
-    return !!test_slot_;
+    }
+
+    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] =
+        new ChromeOSUserData(public_slot.Pass());
+    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 CloseTestNSSDB() {
-    if (!test_slot_)
+  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;
-    SECStatus status = SECMOD_CloseUserDB(test_slot_);
-    if (status != SECSuccess)
-      PLOG(ERROR) << "SECMOD_CloseUserDB failed: " << PORT_GetError();
-    PK11_FreeSlot(test_slot_);
-    test_slot_ = NULL;
-    ignore_result(g_test_nss_db_dir.Get().Delete());
-  }
-
-  PK11SlotInfo* GetPublicNSSKeySlot() {
-    if (test_slot_)
-      return PK11_ReferenceSlot(test_slot_);
-    if (software_slot_)
-      return PK11_ReferenceSlot(software_slot_);
-    return PK11_GetInternalKeySlot();
+
+    // 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.
+    scoped_ptr<TPMModuleAndSlot> tpm_args(new TPMModuleAndSlot(chaps_module_));
+    TPMModuleAndSlot* tpm_args_ptr = tpm_args.get();
+    base::WorkerPool::PostTaskAndReply(
+        FROM_HERE,
+        base::Bind(&NSSInitSingleton::InitializeTPMTokenOnWorkerThread,
+                   slot_id,
+                   tpm_args_ptr),
+        base::Bind(&NSSInitSingleton::OnInitializedTPMForChromeOSUser,
+                   base::Unretained(this),  // NSSInitSingleton is leaky
+                   username_hash,
+                   base::Passed(&tpm_args)),
+        true /* task_is_slow */
+        );
   }
 
-  PK11SlotInfo* GetPrivateNSSKeySlot() {
-    if (test_slot_)
-      return PK11_ReferenceSlot(test_slot_);
+  void OnInitializedTPMForChromeOSUser(const std::string& username_hash,
+                                       scoped_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(
+        tpm_args->tpm_slot.Pass());
+  }
 
-#if defined(OS_CHROMEOS)
-    if (tpm_token_enabled_for_nss_) {
-      if (IsTPMTokenReady()) {
-        return PK11_ReferenceSlot(tpm_slot_);
-      } else {
-        // If we were supposed to get the hardware token, but were
-        // unable to, return NULL rather than fall back to sofware.
-        return NULL;
+  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());
+
+    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,
+      const base::Callback<void(ScopedPK11Slot)>& callback) {
+    DCHECK(thread_checker_.CalledOnValidThread());
+
+    if (username_hash.empty()) {
+      DVLOG(2) << "empty username_hash";
+      if (!callback.is_null()) {
+        base::MessageLoop::current()->PostTask(
+            FROM_HERE, base::Bind(callback, base::Passed(ScopedPK11Slot())));
       }
+      return ScopedPK11Slot();
     }
-#endif
-    // If we weren't supposed to enable the TPM for NSS, then return
-    // the software slot.
-    if (software_slot_)
-      return PK11_ReferenceSlot(software_slot_);
+
+    DCHECK(chromeos_user_map_.find(username_hash) != chromeos_user_map_.end());
+
+    return chromeos_user_map_[username_hash]->GetPrivateSlot(callback);
+  }
+
+  void CloseChromeOSUserForTesting(const std::string& username_hash) {
+    DCHECK(thread_checker_.CalledOnValidThread());
+    ChromeOSUserMap::iterator i = chromeos_user_map_.find(username_hash);
+    DCHECK(i != chromeos_user_map_.end());
+    delete i->second;
+    chromeos_user_map_.erase(i);
+  }
+
+  void SetSystemKeySlotForTesting(ScopedPK11Slot slot) {
+    // Ensure that a previous value of test_system_slot_ is not overwritten.
+    // Unsetting, i.e. setting a NULL, however is allowed.
+    DCHECK(!slot || !test_system_slot_);
+    test_system_slot_ = slot.Pass();
+    if (test_system_slot_) {
+      tpm_slot_.reset(PK11_ReferenceSlot(test_system_slot_.get()));
+      RunAndClearTPMReadyCallbackList();
+    } else {
+      tpm_slot_.reset();
+    }
+  }
+#endif  // defined(OS_CHROMEOS)
+
+#if !defined(OS_CHROMEOS)
+  PK11SlotInfo* GetPersistentNSSKeySlot() {
+    // TODO(mattm): Change to DCHECK when callers have been fixed.
+    if (!thread_checker_.CalledOnValidThread()) {
+      DVLOG(1) << "Called on wrong thread.\n"
+               << base::debug::StackTrace().ToString();
+    }
+
     return PK11_GetInternalKeySlot();
   }
+#endif
+
+#if defined(OS_CHROMEOS)
+  void GetSystemNSSKeySlotCallback(
+      const base::Callback<void(ScopedPK11Slot)>& callback) {
+    callback.Run(ScopedPK11Slot(PK11_ReferenceSlot(tpm_slot_.get())));
+  }
+
+  ScopedPK11Slot GetSystemNSSKeySlot(
+      const base::Callback<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 NULL instead?
+
+    base::Closure wrapped_callback;
+    if (!callback.is_null()) {
+      wrapped_callback =
+          base::Bind(&NSSInitSingleton::GetSystemNSSKeySlotCallback,
+                     base::Unretained(this) /* singleton is leaky */,
+                     callback);
+    }
+    if (IsTPMTokenReady(wrapped_callback))
+      return ScopedPK11Slot(PK11_ReferenceSlot(tpm_slot_.get()));
+    return ScopedPK11Slot();
+  }
+#endif
 
 #if defined(USE_NSS)
   base::Lock* write_lock() {
@@ -382,13 +645,17 @@ class NSSInitSingleton {
 
   NSSInitSingleton()
       : tpm_token_enabled_for_nss_(false),
+        initializing_tpm_token_(false),
         chaps_module_(NULL),
-        software_slot_(NULL),
-        test_slot_(NULL),
-        tpm_slot_(NULL),
-        root_(NULL),
-        chromeos_user_logged_in_(false) {
+        root_(NULL) {
     base::TimeTicks start_time = base::TimeTicks::Now();
+
+    // 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();
+
+    DisableAESNIIfNeeded();
+
     EnsureNSPRInit();
 
     // We *must* have NSS >= 3.14.3.
@@ -481,28 +748,22 @@ class NSSInitSingleton {
                            0, NSS_USE_ALG_IN_CERT_SIGNATURE);
 
     // The UMA bit is conditionally set for this histogram in
-    // chrome/common/startup_metric_utils.cc .
-    HISTOGRAM_CUSTOM_TIMES("Startup.SlowStartupNSSInit",
-                           base::TimeTicks::Now() - start_time,
-                           base::TimeDelta::FromMilliseconds(10),
-                           base::TimeDelta::FromHours(1),
-                           50);
+    // components/startup_metric_utils.cc .
+    LOCAL_HISTOGRAM_CUSTOM_TIMES("Startup.SlowStartupNSSInit",
+                                 base::TimeTicks::Now() - start_time,
+                                 base::TimeDelta::FromMilliseconds(10),
+                                 base::TimeDelta::FromHours(1),
+                                 50);
   }
 
   // NOTE(willchan): We don't actually execute this code since we leak NSS to
   // prevent non-joinable threads from using NSS after it's already been shut
   // down.
   ~NSSInitSingleton() {
-    if (tpm_slot_) {
-      PK11_FreeSlot(tpm_slot_);
-      tpm_slot_ = NULL;
-    }
-    if (software_slot_) {
-      SECMOD_CloseUserDB(software_slot_);
-      PK11_FreeSlot(software_slot_);
-      software_slot_ = NULL;
-    }
-    CloseTestNSSDB();
+#if defined(OS_CHROMEOS)
+    STLDeleteValues(&chromeos_user_map_);
+#endif
+    tpm_slot_.reset();
     if (root_) {
       SECMOD_UnloadUserModule(root_);
       SECMOD_DestroyModule(root_);
@@ -536,9 +797,9 @@ class NSSInitSingleton {
   }
 
   // Load the given module for this NSS session.
-  SECMODModule* LoadModule(const char* name,
-                           const char* library_path,
-                           const char* params) {
+  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 : "");
@@ -564,40 +825,43 @@ class NSSInitSingleton {
   }
 #endif
 
-  static PK11SlotInfo* OpenUserDB(const base::FilePath& path,
-                                  const char* description) {
-    const std::string modspec =
-        base::StringPrintf("configDir='sql:%s' tokenDescription='%s'",
-                           path.value().c_str(), description);
-    PK11SlotInfo* db_slot = SECMOD_OpenUserDB(modspec.c_str());
-    if (db_slot) {
-      if (PK11_NeedUserInit(db_slot))
-        PK11_InitPin(db_slot, NULL, NULL);
-    }
-    else {
-      LOG(ERROR) << "Error opening persistent database (" << modspec
-                 << "): " << GetNSSErrorMessage();
+  static void DisableAESNIIfNeeded() {
+    if (NSS_VersionCheck("3.15") && !NSS_VersionCheck("3.15.4")) {
+      // Some versions of NSS have a bug that causes AVX instructions to be
+      // used without testing whether XSAVE is enabled by the operating system.
+      // In order to work around this, we disable AES-NI in NSS when we find
+      // that |has_avx()| is false (which includes the XSAVE test). See
+      // https://bugzilla.mozilla.org/show_bug.cgi?id=940794
+      base::CPU cpu;
+
+      if (cpu.has_avx_hardware() && !cpu.has_avx()) {
+        base::Environment::Create()->SetVar("NSS_DISABLE_HW_AES", "1");
+      }
     }
-    return db_slot;
   }
 
   // If this is set to true NSS is forced to be initialized without a DB.
   static bool force_nodb_init_;
 
   bool tpm_token_enabled_for_nss_;
-  std::string tpm_token_name_;
-  std::string tpm_user_pin_;
+  bool initializing_tpm_token_;
+  typedef std::vector<base::Closure> TPMReadyCallbackList;
+  TPMReadyCallbackList tpm_ready_callback_list_;
   SECMODModule* chaps_module_;
-  PK11SlotInfo* software_slot_;
-  PK11SlotInfo* test_slot_;
-  PK11SlotInfo* tpm_slot_;
+  crypto::ScopedPK11Slot tpm_slot_;
   SECMODModule* root_;
-  bool chromeos_user_logged_in_;
+#if defined(OS_CHROMEOS)
+  typedef std::map<std::string, ChromeOSUserData*> ChromeOSUserMap;
+  ChromeOSUserMap chromeos_user_map_;
+  ScopedPK11Slot test_system_slot_;
+#endif
 #if defined(USE_NSS)
   // TODO(davidben): When https://bugzilla.mozilla.org/show_bug.cgi?id=564011
   // is fixed, we will no longer need the lock.
   base::Lock write_lock_;
 #endif  // defined(USE_NSS)
+
+  base::ThreadChecker thread_checker_;
 };
 
 // static
@@ -607,9 +871,24 @@ base::LazyInstance<NSSInitSingleton>::Leaky
     g_nss_singleton = LAZY_INSTANCE_INITIALIZER;
 }  // namespace
 
-const char kTestTPMTokenName[] = "Test DB";
-
 #if defined(USE_NSS)
+ScopedPK11Slot OpenSoftwareNSSDB(const base::FilePath& path,
+                                 const std::string& description) {
+  const std::string modspec =
+      base::StringPrintf("configDir='sql:%s' tokenDescription='%s'",
+                         path.value().c_str(),
+                         description.c_str());
+  PK11SlotInfo* db_slot = SECMOD_OpenUserDB(modspec.c_str());
+  if (db_slot) {
+    if (PK11_NeedUserInit(db_slot))
+      PK11_InitPin(db_slot, NULL, NULL);
+  } else {
+    LOG(ERROR) << "Error opening persistent database (" << modspec
+               << "): " << GetNSSErrorMessage();
+  }
+  return ScopedPK11Slot(db_slot);
+}
+
 void EarlySetupForNSSInit() {
   base::FilePath database_dir = GetInitialConfigDirectory();
   if (!database_dir.empty())
@@ -667,10 +946,14 @@ void LoadNSSLibraries() {
 #elif defined(ARCH_CPU_X86)
   paths.push_back(base::FilePath("/usr/lib/i386-linux-gnu/nss"));
 #elif defined(ARCH_CPU_ARMEL)
+#if defined(__ARM_PCS_VFP)
+  paths.push_back(base::FilePath("/usr/lib/arm-linux-gnueabihf/nss"));
+#else
   paths.push_back(base::FilePath("/usr/lib/arm-linux-gnueabi/nss"));
+#endif  // defined(__ARM_PCS_VFP)
 #elif defined(ARCH_CPU_MIPSEL)
   paths.push_back(base::FilePath("/usr/lib/mipsel-linux-gnu/nss"));
-#endif
+#endif  // defined(ARCH_CPU_X86_64)
 
   // A list of library files to load.
   std::vector<std::string> libs;
@@ -696,7 +979,7 @@ void LoadNSSLibraries() {
   } else {
     LOG(ERROR) << "Failed to load NSS libraries.";
   }
-#endif
+#endif  // defined(USE_NSS)
 }
 
 bool CheckNSSVersion(const char* version) {
@@ -704,19 +987,6 @@ bool CheckNSSVersion(const char* version) {
 }
 
 #if defined(USE_NSS)
-ScopedTestNSSDB::ScopedTestNSSDB()
-  : is_open_(g_nss_singleton.Get().OpenTestNSSDB()) {
-}
-
-ScopedTestNSSDB::~ScopedTestNSSDB() {
-  // Don't close when NSS is < 3.15.1, because it would require an additional
-  // sleep for 1 second after closing the database, due to
-  // http://bugzil.la/875601.
-  if (NSS_VersionCheck("3.15.1")) {
-    g_nss_singleton.Get().CloseTestNSSDB();
-  }
-}
-
 base::Lock* GetNSSWriteLock() {
   return g_nss_singleton.Get().write_lock();
 }
@@ -742,31 +1012,77 @@ AutoSECMODListReadLock::AutoSECMODListReadLock()
 AutoSECMODListReadLock::~AutoSECMODListReadLock() {
   SECMOD_ReleaseReadLock(lock_);
 }
-
 #endif  // defined(USE_NSS)
 
 #if defined(OS_CHROMEOS)
-void OpenPersistentNSSDB() {
-  g_nss_singleton.Get().OpenPersistentNSSDB();
+ScopedPK11Slot GetSystemNSSKeySlot(
+    const base::Callback<void(ScopedPK11Slot)>& callback) {
+  return g_nss_singleton.Get().GetSystemNSSKeySlot(callback);
+}
+
+void SetSystemKeySlotForTesting(ScopedPK11Slot slot) {
+  g_nss_singleton.Get().SetSystemKeySlotForTesting(slot.Pass());
 }
 
 void EnableTPMTokenForNSS() {
   g_nss_singleton.Get().EnableTPMTokenForNSS();
 }
 
-void GetTPMTokenInfo(std::string* token_name, std::string* user_pin) {
-  g_nss_singleton.Get().GetTPMTokenInfo(token_name, user_pin);
+bool IsTPMTokenEnabledForNSS() {
+  return g_nss_singleton.Get().IsTPMTokenEnabledForNSS();
+}
+
+bool IsTPMTokenReady(const base::Closure& callback) {
+  return g_nss_singleton.Get().IsTPMTokenReady(callback);
+}
+
+void InitializeTPMTokenAndSystemSlot(
+    int token_slot_id,
+    const base::Callback<void(bool)>& callback) {
+  g_nss_singleton.Get().InitializeTPMTokenAndSystemSlot(token_slot_id,
+                                                        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);
 }
 
-bool IsTPMTokenReady() {
-  return g_nss_singleton.Get().IsTPMTokenReady();
+ScopedPK11Slot GetPublicSlotForChromeOSUser(const std::string& username_hash) {
+  return g_nss_singleton.Get().GetPublicSlotForChromeOSUser(username_hash);
 }
 
-bool InitializeTPMToken(const std::string& token_name,
-                        int token_slot_id,
-                        const std::string& user_pin) {
-  return g_nss_singleton.Get().InitializeTPMToken(
-      token_name, token_slot_id, user_pin);
+ScopedPK11Slot GetPrivateSlotForChromeOSUser(
+    const std::string& username_hash,
+    const base::Callback<void(ScopedPK11Slot)>& callback) {
+  return g_nss_singleton.Get().GetPrivateSlotForChromeOSUser(username_hash,
+                                                             callback);
+}
+
+void CloseChromeOSUserForTesting(const std::string& username_hash) {
+  g_nss_singleton.Get().CloseChromeOSUserForTesting(username_hash);
 }
 #endif  // defined(OS_CHROMEOS)
 
@@ -779,12 +1095,10 @@ PRTime BaseTimeToPRTime(base::Time time) {
   return time.ToInternalValue() - base::Time::UnixEpoch().ToInternalValue();
 }
 
-PK11SlotInfo* GetPublicNSSKeySlot() {
-  return g_nss_singleton.Get().GetPublicNSSKeySlot();
-}
-
-PK11SlotInfo* GetPrivateNSSKeySlot() {
-  return g_nss_singleton.Get().GetPrivateNSSKeySlot();
+#if !defined(OS_CHROMEOS)
+PK11SlotInfo* GetPersistentNSSKeySlot() {
+  return g_nss_singleton.Get().GetPersistentNSSKeySlot();
 }
+#endif
 
 }  // namespace crypto