diff options
Diffstat (limited to 'crypto/nss_util.cc')
-rw-r--r-- | crypto/nss_util.cc | 1131 |
1 files changed, 1131 insertions, 0 deletions
diff --git a/crypto/nss_util.cc b/crypto/nss_util.cc new file mode 100644 index 0000000000..125591c73f --- /dev/null +++ b/crypto/nss_util.cc @@ -0,0 +1,1131 @@ +// 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 "crypto/nss_util_internal.h" + +#include <nss.h> +#include <pk11pub.h> +#include <plarena.h> +#include <prerror.h> +#include <prinit.h> +#include <prtime.h> +#include <secmod.h> + +#if defined(OS_OPENBSD) +#include <sys/mount.h> +#include <sys/param.h> +#endif + +#if defined(OS_CHROMEOS) +#include <dlfcn.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/files/file_path.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/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_CERTS means NSS is used for certificates and platform integration. +// This requires additional support to manage the platform certificate and key +// stores. +#if defined(USE_NSS_CERTS) +#include "base/synchronization/lock.h" +#include "crypto/nss_crypto_module_delegate.h" +#endif // defined(USE_NSS_CERTS) + +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()) { + scoped_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(USE_NSS_CERTS) +#if !defined(OS_CHROMEOS) +base::FilePath GetDefaultConfigDirectory() { + 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 (!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 +// used by the local Google Accounts server mock we use when testing our login +// code). On Chrome OS non-test images (where the read-only directory doesn't +// exist), return an empty path. +base::FilePath GetInitialConfigDirectory() { +#if defined(OS_CHROMEOS) + base::FilePath database_dir = base::FilePath(kReadOnlyCertDB); + if (!base::PathExists(database_dir)) + database_dir.clear(); + return database_dir; +#else + return GetDefaultConfigDirectory(); +#endif // defined(OS_CHROMEOS) +} + +// This callback for NSS forwards all requests to a caller-specified +// CryptoModuleBlockingPasswordDelegate object. +char* PKCS11PasswordFunc(PK11SlotInfo* slot, PRBool retry, void* arg) { + crypto::CryptoModuleBlockingPasswordDelegate* delegate = + reinterpret_cast<crypto::CryptoModuleBlockingPasswordDelegate*>(arg); + if (delegate) { + bool cancelled = false; + std::string password = delegate->RequestPassword(PK11_GetTokenName(slot), + retry != PR_FALSE, + &cancelled); + if (cancelled) + return NULL; + char* result = PORT_Strdup(password.c_str()); + password.replace(0, password.size(), password.size(), 0); + return result; + } + DLOG(ERROR) << "PK11 password requested with NULL arg"; + return NULL; +} + +// NSS creates a local cache of the sqlite database if it detects that the +// filesystem the database is on is much slower than the local disk. The +// detection doesn't work with the latest versions of sqlite, such as 3.6.22 +// (NSS bug https://bugzilla.mozilla.org/show_bug.cgi?id=578561). So we set +// the NSS environment variable NSS_SDB_USE_CACHE to "yes" to override NSS's +// detection when database_dir is on NFS. See http://crbug.com/48585. +// +// TODO(wtc): port this function to other USE_NSS_CERTS platforms. It is +// defined only for OS_LINUX and OS_OPENBSD simply because the statfs structure +// is OS-specific. +// +// Because this function sets an environment variable it must be run before we +// go multi-threaded. +void UseLocalCacheOfNSSDatabaseIfNFS(const base::FilePath& database_dir) { + bool db_on_nfs = false; +#if defined(OS_LINUX) + 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) + 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 + + 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(USE_NSS_CERTS) + +// A singleton to initialize/deinitialize NSPR. +// Separate from the NSS singleton because we initialize NSPR on the UI thread. +// Now that we're leaking the singleton, we could merge back with the NSS +// singleton. +class NSPRInitSingleton { + private: + friend struct base::DefaultLazyInstanceTraits<NSPRInitSingleton>; + + NSPRInitSingleton() { + PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0); + } + + // 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. + ~NSPRInitSingleton() { + PL_ArenaFinish(); + PRStatus prstatus = PR_Cleanup(); + if (prstatus != PR_SUCCESS) + LOG(ERROR) << "PR_Cleanup failed; was NSPR initialized on wrong thread?"; + } +}; + +base::LazyInstance<NSPRInitSingleton>::Leaky + g_nspr_singleton = LAZY_INSTANCE_INITIALIZER; + +// Force a crash with error info on NSS_NoDB_Init failure. +void CrashOnNSSInitFailure() { + int nss_error = PR_GetError(); + int os_error = PR_GetOSError(); + base::debug::Alias(&nss_error); + base::debug::Alias(&os_error); + LOG(ERROR) << "Error initializing NSS without a persistent database: " + << GetNSSErrorMessage(); + 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_; +}; + +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, + 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_) { + base::MessageLoop::current()->PostTask(FROM_HERE, + base::Bind(callback, false)); + return; + } + + // If everything is already initialized, then 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; + } + + // 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 (!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 = + GetTPMSlotForIdOnWorkerThread(tpm_args->chaps_module, token_slot_id); + } + } + + 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())); + } + initializing_tpm_token_ = false; + + if (tpm_slot_) + RunAndClearTPMReadyCallbackList(); + + 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) { + i->Run(); + } + } + + 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. + static crypto::ScopedPK11Slot GetTPMSlotForIdOnWorkerThread( + 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] = + 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 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. + 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 */ + ); + } + + 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()); + } + + 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(); + } + + 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_CERTS) + base::Lock* write_lock() { + return &write_lock_; + } +#endif // defined(USE_NSS_CERTS) + + // This method is used to force NSS to be initialized without a DB. + // Call this method before NSSInitSingleton() is constructed. + static void ForceNoDBInit() { + force_nodb_init_ = true; + } + + private: + friend struct base::DefaultLazyInstanceTraits<NSSInitSingleton>; + + NSSInitSingleton() + : tpm_token_enabled_for_nss_(false), + initializing_tpm_token_(false), + chaps_module_(NULL), + root_(NULL) { + // 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. + static_assert( + (NSS_VMAJOR == 3 && NSS_VMINOR == 14 && NSS_VPATCH >= 3) || + (NSS_VMAJOR == 3 && NSS_VMINOR > 14) || + (NSS_VMAJOR > 3), + "nss version check failed"); + // Also check the run-time NSS version. + // NSS_VersionCheck is a >= check, not strict equality. + if (!NSS_VersionCheck("3.14.3")) { + LOG(FATAL) << "NSS_VersionCheck(\"3.14.3\") failed. NSS >= 3.14.3 is " + "required. Please upgrade to the latest NSS, and if you " + "still get this error, contact your distribution " + "maintainer."; + } + + SECStatus status = SECFailure; + bool nodb_init = force_nodb_init_; + +#if !defined(USE_NSS_CERTS) + // Use the system certificate store, so initialize NSS without database. + nodb_init = true; +#endif + + if (nodb_init) { + status = NSS_NoDB_Init(NULL); + if (status != SECSuccess) { + CrashOnNSSInitFailure(); + return; + } +#if defined(OS_IOS) + root_ = InitDefaultRootCerts(); +#endif // defined(OS_IOS) + } else { +#if defined(USE_NSS_CERTS) + base::FilePath database_dir = GetInitialConfigDirectory(); + if (!database_dir.empty()) { + // This duplicates the work which should have been done in + // EarlySetupForNSSInit. However, this function is idempotent so + // there's no harm done. + UseLocalCacheOfNSSDatabaseIfNFS(database_dir); + + // Initialize with a persistent database (likely, ~/.pki/nssdb). + // Use "sql:" which can be shared by multiple processes safely. + std::string nss_config_dir = + base::StringPrintf("sql:%s", database_dir.value().c_str()); +#if defined(OS_CHROMEOS) + status = NSS_Init(nss_config_dir.c_str()); +#else + status = NSS_InitReadWrite(nss_config_dir.c_str()); +#endif + if (status != SECSuccess) { + LOG(ERROR) << "Error initializing NSS with a persistent " + "database (" << nss_config_dir + << "): " << GetNSSErrorMessage(); + } + } + if (status != SECSuccess) { + VLOG(1) << "Initializing NSS without a persistent database."; + status = NSS_NoDB_Init(NULL); + if (status != SECSuccess) { + CrashOnNSSInitFailure(); + return; + } + } + + PK11_SetPasswordFunc(PKCS11PasswordFunc); + + // If we haven't initialized the password for the NSS databases, + // initialize an empty-string password so that we don't need to + // log in. + PK11SlotInfo* slot = PK11_GetInternalKeySlot(); + if (slot) { + // PK11_InitPin may write to the keyDB, but no other thread can use NSS + // yet, so we don't need to lock. + if (PK11_NeedUserInit(slot)) + PK11_InitPin(slot, NULL, NULL); + PK11_FreeSlot(slot); + } + + root_ = InitDefaultRootCerts(); +#endif // defined(USE_NSS_CERTS) + } + + // Disable MD5 certificate signatures. (They are disabled by default in + // NSS 3.14.) + NSS_SetAlgorithmPolicy(SEC_OID_MD5, 0, NSS_USE_ALG_IN_CERT_SIGNATURE); + NSS_SetAlgorithmPolicy(SEC_OID_PKCS1_MD5_WITH_RSA_ENCRYPTION, + 0, NSS_USE_ALG_IN_CERT_SIGNATURE); + } + + // 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 defined(OS_CHROMEOS) + STLDeleteValues(&chromeos_user_map_); +#endif + tpm_slot_.reset(); + if (root_) { + SECMOD_UnloadUserModule(root_); + SECMOD_DestroyModule(root_); + root_ = NULL; + } + if (chaps_module_) { + SECMOD_UnloadUserModule(chaps_module_); + SECMOD_DestroyModule(chaps_module_); + chaps_module_ = NULL; + } + + SECStatus status = NSS_Shutdown(); + if (status != SECSuccess) { + // We VLOG(1) because this failure is relatively harmless (leaking, but + // we're shutting down anyway). + VLOG(1) << "NSS_Shutdown failed; see http://crbug.com/4609"; + } + } + +#if defined(USE_NSS_CERTS) || defined(OS_IOS) + // Load nss's built-in root certs. + SECMODModule* InitDefaultRootCerts() { + SECMODModule* root = LoadModule("Root Certs", "libnssckbi.so", NULL); + if (root) + return root; + + // Aw, snap. Can't find/load root cert shared library. + // This will make it hard to talk to anybody via https. + // TODO(mattm): Re-add the NOTREACHED here when crbug.com/310972 is fixed. + return NULL; + } + + // 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()), NULL, PR_FALSE); + if (!module) { + LOG(ERROR) << "Error loading " << name << " module into NSS: " + << GetNSSErrorMessage(); + return NULL; + } + if (!module->loaded) { + LOG(ERROR) << "After loading " << name << ", loaded==false: " + << GetNSSErrorMessage(); + SECMOD_DestroyModule(module); + return NULL; + } + return module; + } +#endif + + 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()) { + scoped_ptr<base::Environment> env(base::Environment::Create()); + env->SetVar("NSS_DISABLE_HW_AES", "1"); + } + } + } + + // 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_; + bool initializing_tpm_token_; + typedef std::vector<base::Closure> TPMReadyCallbackList; + TPMReadyCallbackList tpm_ready_callback_list_; + SECMODModule* chaps_module_; + crypto::ScopedPK11Slot tpm_slot_; + SECMODModule* root_; +#if defined(OS_CHROMEOS) + typedef std::map<std::string, ChromeOSUserData*> ChromeOSUserMap; + ChromeOSUserMap chromeos_user_map_; + ScopedPK11Slot test_system_slot_; +#endif +#if defined(USE_NSS_CERTS) + // 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_CERTS) + + base::ThreadChecker thread_checker_; +}; + +// static +bool NSSInitSingleton::force_nodb_init_ = false; + +base::LazyInstance<NSSInitSingleton>::Leaky + g_nss_singleton = LAZY_INSTANCE_INITIALIZER; +} // namespace + +#if defined(USE_NSS_CERTS) +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()) + UseLocalCacheOfNSSDatabaseIfNFS(database_dir); +} +#endif + +void EnsureNSPRInit() { + g_nspr_singleton.Get(); +} + +void InitNSSSafely() { + // We might fork, but we haven't loaded any security modules. + DisableNSSForkCheck(); + // If we're sandboxed, we shouldn't be able to open user security modules, + // but it's more correct to tell NSS to not even try. + // Loading user security modules would have security implications. + ForceNSSNoDBInit(); + // Initialize NSS. + EnsureNSSInit(); +} + +void EnsureNSSInit() { + // Initializing SSL causes us to do blocking IO. + // Temporarily allow it until we fix + // http://code.google.com/p/chromium/issues/detail?id=59847 + base::ThreadRestrictions::ScopedAllowIO allow_io; + g_nss_singleton.Get(); +} + +void ForceNSSNoDBInit() { + NSSInitSingleton::ForceNoDBInit(); +} + +void DisableNSSForkCheck() { + scoped_ptr<base::Environment> env(base::Environment::Create()); + env->SetVar("NSS_STRICT_NOFORK", "DISABLED"); +} + +void LoadNSSLibraries() { + // Some NSS libraries are linked dynamically so load them here. +#if defined(USE_NSS_CERTS) + // Try to search for multiple directories to load the libraries. + std::vector<base::FilePath> paths; + + // Use relative path to Search PATH for the library files. + paths.push_back(base::FilePath()); + + // For Debian derivatives NSS libraries are located here. + paths.push_back(base::FilePath("/usr/lib/nss")); + + // Ubuntu 11.10 (Oneiric) and Debian Wheezy place the libraries here. +#if defined(ARCH_CPU_X86_64) + paths.push_back(base::FilePath("/usr/lib/x86_64-linux-gnu/nss")); +#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 // defined(ARCH_CPU_X86_64) + + // A list of library files to load. + std::vector<std::string> libs; + libs.push_back("libsoftokn3.so"); + libs.push_back("libfreebl3.so"); + + // For each combination of library file and path, check for existence and + // then load. + size_t loaded = 0; + for (size_t i = 0; i < libs.size(); ++i) { + for (size_t j = 0; j < paths.size(); ++j) { + base::FilePath path = paths[j].Append(libs[i]); + base::NativeLibrary lib = base::LoadNativeLibrary(path, NULL); + if (lib) { + ++loaded; + break; + } + } + } + + if (loaded == libs.size()) { + VLOG(3) << "NSS libraries loaded."; + } else { + LOG(ERROR) << "Failed to load NSS libraries."; + } +#endif // defined(USE_NSS_CERTS) +} + +bool CheckNSSVersion(const char* version) { + return !!NSS_VersionCheck(version); +} + +#if defined(USE_NSS_CERTS) +base::Lock* GetNSSWriteLock() { + return g_nss_singleton.Get().write_lock(); +} + +AutoNSSWriteLock::AutoNSSWriteLock() : lock_(GetNSSWriteLock()) { + // May be NULL if the lock is not needed in our version of NSS. + if (lock_) + lock_->Acquire(); +} + +AutoNSSWriteLock::~AutoNSSWriteLock() { + if (lock_) { + lock_->AssertAcquired(); + lock_->Release(); + } +} + +AutoSECMODListReadLock::AutoSECMODListReadLock() + : lock_(SECMOD_GetDefaultModuleListLock()) { + SECMOD_GetReadLock(lock_); + } + +AutoSECMODListReadLock::~AutoSECMODListReadLock() { + SECMOD_ReleaseReadLock(lock_); +} +#endif // defined(USE_NSS_CERTS) + +#if defined(OS_CHROMEOS) +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(); +} + +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); +} + +ScopedPK11Slot GetPublicSlotForChromeOSUser(const std::string& username_hash) { + return g_nss_singleton.Get().GetPublicSlotForChromeOSUser(username_hash); +} + +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) + +base::Time PRTimeToBaseTime(PRTime prtime) { + return base::Time::FromInternalValue( + prtime + base::Time::UnixEpoch().ToInternalValue()); +} + +PRTime BaseTimeToPRTime(base::Time time) { + return time.ToInternalValue() - base::Time::UnixEpoch().ToInternalValue(); +} + +#if !defined(OS_CHROMEOS) +PK11SlotInfo* GetPersistentNSSKeySlot() { + return g_nss_singleton.Get().GetPersistentNSSKeySlot(); +} +#endif + +} // namespace crypto |