blob: 927f1abd0e0b46bc9b5c89883f8e77e4fb3a9f16 [file] [log] [blame]
// Copyright (c) 2008-2010 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 "base/nss_util.h"
#include "base/nss_util_internal.h"
#include <nss.h>
#include <plarena.h>
#include <prerror.h>
#include <prinit.h>
#include <prtime.h>
#include <pk11pub.h>
#include <secmod.h>
#include "base/file_util.h"
#include "base/logging.h"
#include "base/singleton.h"
#include "base/string_util.h"
#if defined(USE_NSS)
#include "base/lock.h"
#include "base/scoped_ptr.h"
#endif // defined(USE_NSS)
// On some platforms, we use NSS for SSL only -- we don't use NSS for crypto
// or certificate verification, and we don't use the NSS certificate and key
// databases.
#if defined(OS_MACOSX) || defined(OS_WIN)
#define USE_NSS_FOR_SSL_ONLY 1
#endif
namespace {
#if !defined(USE_NSS_FOR_SSL_ONLY)
std::string GetDefaultConfigDirectory() {
const char* home = getenv("HOME");
if (home == NULL) {
LOG(ERROR) << "$HOME is not set.";
return "";
}
FilePath dir(home);
dir = dir.AppendASCII(".pki").AppendASCII("nssdb");
if (!file_util::CreateDirectory(dir)) {
LOG(ERROR) << "Failed to create ~/.pki/nssdb directory.";
return "";
}
return dir.value();
}
// On non-chromeos platforms, return the default config directory.
// On chromeos, return a read-only directory with fake root CA certs for testing
// (which will not exist on non-testing images). These root CA certs are used
// by the local Google Accounts server mock we use when testing our login code.
// If this directory is not present, NSS_Init() will fail. It is up to the
// caller to failover to NSS_NoDB_Init() at that point.
std::string GetInitialConfigDirectory() {
#if defined(OS_CHROMEOS)
static const char kReadOnlyCertDB[] = "/etc/fake_root_ca/nssdb";
return std::string(kReadOnlyCertDB);
#else
return GetDefaultConfigDirectory();
#endif // defined(OS_CHROMEOS)
}
// Load nss's built-in root certs.
SECMODModule *InitDefaultRootCerts() {
const char* kModulePath = "libnssckbi.so";
char modparams[1024];
snprintf(modparams, sizeof(modparams),
"name=\"Root Certs\" library=\"%s\"", kModulePath);
SECMODModule *root = SECMOD_LoadUserModule(modparams, NULL, PR_FALSE);
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.
NOTREACHED();
return NULL;
}
#endif // !defined(USE_NSS_FOR_SSL_ONLY)
// A singleton to initialize/deinitialize NSPR.
// Separate from the NSS singleton because we initialize NSPR on the UI thread.
class NSPRInitSingleton {
public:
NSPRInitSingleton() {
PR_Init(PR_USER_THREAD, PR_PRIORITY_NORMAL, 0);
}
~NSPRInitSingleton() {
PL_ArenaFinish();
PRStatus prstatus = PR_Cleanup();
if (prstatus != PR_SUCCESS) {
LOG(ERROR) << "PR_Cleanup failed; was NSPR initialized on wrong thread?";
}
}
};
class NSSInitSingleton {
public:
NSSInitSingleton()
: real_db_slot_(NULL),
root_(NULL),
chromeos_user_logged_in_(false) {
base::EnsureNSPRInit();
// We *must* have NSS >= 3.12.3. See bug 26448.
COMPILE_ASSERT(
(NSS_VMAJOR == 3 && NSS_VMINOR == 12 && NSS_VPATCH >= 3) ||
(NSS_VMAJOR == 3 && NSS_VMINOR > 12) ||
(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.12.3")) {
// It turns out many people have misconfigured NSS setups, where
// their run-time NSPR doesn't match the one their NSS was compiled
// against. So rather than aborting, complain loudly.
LOG(ERROR) << "NSS_VersionCheck(\"3.12.3\") failed. "
"We depend on NSS >= 3.12.3, and this error is not fatal "
"only because many people have busted NSS setups (for "
"example, using the wrong version of NSPR). "
"Please upgrade to the latest NSS and NSPR, and if you "
"still get this error, contact your distribution "
"maintainer.";
}
SECStatus status = SECFailure;
#if defined(USE_NSS_FOR_SSL_ONLY)
// Use the system certificate store, so initialize NSS without database.
status = NSS_NoDB_Init(NULL);
if (status != SECSuccess) {
LOG(ERROR) << "Error initializing NSS without a persistent "
"database: NSS error code " << PR_GetError();
}
#else
std::string database_dir = GetInitialConfigDirectory();
if (!database_dir.empty()) {
// Initialize with a persistent database (likely, ~/.pki/nssdb).
// Use "sql:" which can be shared by multiple processes safely.
std::string nss_config_dir =
StringPrintf("sql:%s", database_dir.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
<< "): NSS error code " << PR_GetError();
}
}
if (status != SECSuccess) {
LOG(WARNING) << "Initialize NSS without a persistent database "
"(~/.pki/nssdb).";
status = NSS_NoDB_Init(NULL);
if (status != SECSuccess) {
LOG(ERROR) << "Error initializing NSS without a persistent "
"database: NSS error code " << PR_GetError();
return;
}
}
// 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);
}
// TODO(davidben): When https://bugzilla.mozilla.org/show_bug.cgi?id=564011
// is fixed, we will no longer need the lock. We should detect this and not
// initialize a Lock here.
write_lock_.reset(new Lock());
root_ = InitDefaultRootCerts();
#endif // defined(USE_NSS_FOR_SSL_ONLY)
}
~NSSInitSingleton() {
if (real_db_slot_) {
SECMOD_CloseUserDB(real_db_slot_);
PK11_FreeSlot(real_db_slot_);
real_db_slot_ = NULL;
}
if (root_) {
SECMOD_UnloadUserModule(root_);
SECMOD_DestroyModule(root_);
root_ = NULL;
}
SECStatus status = NSS_Shutdown();
if (status != SECSuccess) {
// We LOG(INFO) because this failure is relatively harmless
// (leaking, but we're shutting down anyway).
LOG(INFO) << "NSS_Shutdown failed; see "
"http://code.google.com/p/chromium/issues/detail?id=4609";
}
}
#if defined(OS_CHROMEOS)
void OpenPersistentNSSDB() {
if (!chromeos_user_logged_in_) {
chromeos_user_logged_in_ = true;
const std::string modspec =
StringPrintf("configDir='%s' tokenDescription='Real NSS database'",
GetDefaultConfigDirectory().c_str());
real_db_slot_ = SECMOD_OpenUserDB(modspec.c_str());
if (real_db_slot_ == NULL) {
LOG(ERROR) << "Error opening persistent database (" << modspec
<< "): NSS error code " << PR_GetError();
} else {
if (PK11_NeedUserInit(real_db_slot_))
PK11_InitPin(real_db_slot_, NULL, NULL);
}
}
}
#endif // defined(OS_CHROMEOS)
PK11SlotInfo* GetDefaultKeySlot() {
if (real_db_slot_)
return PK11_ReferenceSlot(real_db_slot_);
return PK11_GetInternalKeySlot();
}
#if defined(USE_NSS)
Lock* write_lock() {
return write_lock_.get();
}
#endif // defined(USE_NSS)
private:
PK11SlotInfo* real_db_slot_; // Overrides internal key slot if non-NULL.
SECMODModule *root_;
bool chromeos_user_logged_in_;
#if defined(USE_NSS)
scoped_ptr<Lock> write_lock_;
#endif // defined(USE_NSS)
};
} // namespace
namespace base {
void EnsureNSPRInit() {
Singleton<NSPRInitSingleton>::get();
}
void EnsureNSSInit() {
Singleton<NSSInitSingleton>::get();
}
#if defined(USE_NSS)
Lock* GetNSSWriteLock() {
return Singleton<NSSInitSingleton>::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();
}
}
#endif // defined(USE_NSS)
#if defined(OS_CHROMEOS)
void OpenPersistentNSSDB() {
Singleton<NSSInitSingleton>::get()->OpenPersistentNSSDB();
}
#endif
// TODO(port): Implement this more simply. We can convert by subtracting an
// offset (the difference between NSPR's and base::Time's epochs).
Time PRTimeToBaseTime(PRTime prtime) {
PRExplodedTime prxtime;
PR_ExplodeTime(prtime, PR_GMTParameters, &prxtime);
base::Time::Exploded exploded;
exploded.year = prxtime.tm_year;
exploded.month = prxtime.tm_month + 1;
exploded.day_of_week = prxtime.tm_wday;
exploded.day_of_month = prxtime.tm_mday;
exploded.hour = prxtime.tm_hour;
exploded.minute = prxtime.tm_min;
exploded.second = prxtime.tm_sec;
exploded.millisecond = prxtime.tm_usec / 1000;
return Time::FromUTCExploded(exploded);
}
PK11SlotInfo* GetDefaultNSSKeySlot() {
return Singleton<NSSInitSingleton>::get()->GetDefaultKeySlot();
}
} // namespace base