#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
// 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 {
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";
}
#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
// 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) {
// 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)
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();
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:
// 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() {
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.
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_);
}
// 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 : "");
}
#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
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())
#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;
} else {
LOG(ERROR) << "Failed to load NSS libraries.";
}
-#endif
+#endif // defined(USE_NSS)
}
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();
}
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)
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