Commit 557737f7 authored by mattm@google.com's avatar mattm@google.com

Initialize per-ChromeOS-user NSS slots and provide the functions to access them.

BUG=302124
R=mmenke@chromium.org, rsleevi@chromium.org, xiyuan@chromium.org

Review URL: https://codereview.chromium.org/53763003

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@239266 0039d316-1c4b-4281-b951-d872f2087c98
parent 637fc4cd
// Copyright (c) 2013 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.
#ifndef CHROME_BROWSER_NET_NSS_CONTEXT_H_
#define CHROME_BROWSER_NET_NSS_CONTEXT_H_
#include <string>
#include "base/callback.h"
#include "base/compiler_specific.h"
#include "crypto/scoped_nss_types.h"
namespace content {
class ResourceContext;
} // namespace content
// Returns a reference to the public slot for the user associated with
// |context|. Should be called only on the IO thread.
crypto::ScopedPK11Slot GetPublicNSSKeySlotForResourceContext(
content::ResourceContext* context);
// Returns a reference to the private slot for the user associated with
// |context|, if it is loaded. If it is not loaded and |callback| is non-null,
// the |callback| will be run once the slot is loaded.
// Should be called only on the IO thread.
crypto::ScopedPK11Slot GetPrivateNSSKeySlotForResourceContext(
content::ResourceContext* context,
const base::Callback<void(crypto::ScopedPK11Slot)>& callback)
WARN_UNUSED_RESULT;
#endif // CHROME_BROWSER_NET_NSS_CONTEXT_H_
// Copyright (c) 2013 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 "chrome/browser/net/nss_context.h"
#include "chrome/browser/profiles/profile_io_data.h"
#include "content/public/browser/browser_thread.h"
#include "crypto/nss_util_internal.h"
namespace {
std::string GetUsername(content::ResourceContext* context) {
return ProfileIOData::FromResourceContext(context)->username_hash();
}
} // namespace
crypto::ScopedPK11Slot GetPublicNSSKeySlotForResourceContext(
content::ResourceContext* context) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
return crypto::GetPublicSlotForChromeOSUser(GetUsername(context));
}
crypto::ScopedPK11Slot GetPrivateNSSKeySlotForResourceContext(
content::ResourceContext* context,
const base::Callback<void(crypto::ScopedPK11Slot)>& callback) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
return crypto::GetPrivateSlotForChromeOSUser(GetUsername(context), callback);
}
// Copyright (c) 2013 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 "chrome/browser/net/nss_context.h"
#include "content/public/browser/browser_thread.h"
#include "crypto/nss_util_internal.h"
crypto::ScopedPK11Slot GetPublicNSSKeySlotForResourceContext(
content::ResourceContext* context) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
return crypto::ScopedPK11Slot(crypto::GetPublicNSSKeySlot());
}
crypto::ScopedPK11Slot GetPrivateNSSKeySlotForResourceContext(
content::ResourceContext* context,
const base::Callback<void(crypto::ScopedPK11Slot)>& callback) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
return crypto::ScopedPK11Slot(crypto::GetPrivateNSSKeySlot());
}
......@@ -89,11 +89,17 @@
#if defined(OS_CHROMEOS)
#include "chrome/browser/chromeos/drive/drive_protocol_handler.h"
#include "chrome/browser/chromeos/login/user.h"
#include "chrome/browser/chromeos/login/user_manager.h"
#include "chrome/browser/chromeos/policy/policy_cert_service.h"
#include "chrome/browser/chromeos/policy/policy_cert_service_factory.h"
#include "chrome/browser/chromeos/policy/policy_cert_verifier.h"
#include "chrome/browser/chromeos/settings/cros_settings.h"
#include "chromeos/dbus/cryptohome_client.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/settings/cros_settings_names.h"
#include "crypto/nss_util.h"
#include "crypto/nss_util_internal.h"
#endif // defined(OS_CHROMEOS)
#if defined(USE_NSS)
......@@ -228,6 +234,112 @@ class DebugDevToolsInterceptor
};
#endif // defined(DEBUG_DEVTOOLS)
#if defined(OS_CHROMEOS)
// The following four functions are responsible for initializing NSS for each
// profile on ChromeOS, which has a separate NSS database and TPM slot
// per-profile.
//
// Initialization basically follows these steps:
// 1) Get some info from chromeos::UserManager about the User for this profile.
// 2) Tell nss_util to initialize the software slot for this profile.
// 3) Wait for the TPM module to be loaded by nss_util if it isn't already.
// 4) Ask CryptohomeClient which TPM slot id corresponds to this profile.
// 5) Tell nss_util to use that slot id on the TPM module.
//
// Some of these steps must happen on the UI thread, others must happen on the
// IO thread:
// UI thread IO Thread
//
// ProfileIOData::InitializeOnUIThread
// |
// chromeos::UserManager::GetUserByProfile
// \---------------------------------------v
// StartNSSInitOnIOThread
// |
// crypto::InitializeNSSForChromeOSUser
// |
// crypto::IsTPMTokenReady
// |
// StartTPMSlotInitializationOnIOThread
// v---------------------------------------/
// GetTPMInfoForUserOnUIThread
// |
// CryptohomeClient::Pkcs11GetTpmTokenInfoForUser
// |
// DidGetTPMInfoForUserOnUIThread
// \---------------------------------------v
// crypto::InitializeTPMForChromeOSUser
void DidGetTPMInfoForUserOnUIThread(const std::string& username_hash,
chromeos::DBusMethodCallStatus call_status,
const std::string& label,
const std::string& user_pin,
int slot_id) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (call_status == chromeos::DBUS_METHOD_CALL_FAILURE) {
NOTREACHED() << "dbus error getting TPM info for " << username_hash;
return;
}
DVLOG(1) << "Got TPM slot for " << username_hash << ": " << slot_id;
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
base::Bind(
&crypto::InitializeTPMForChromeOSUser, username_hash, slot_id));
}
void GetTPMInfoForUserOnUIThread(const std::string& username,
const std::string& username_hash) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
DVLOG(1) << "Getting TPM info from cryptohome for "
<< " " << username << " " << username_hash;
chromeos::DBusThreadManager::Get()
->GetCryptohomeClient()
->Pkcs11GetTpmTokenInfoForUser(
username,
base::Bind(&DidGetTPMInfoForUserOnUIThread, username_hash));
}
void StartTPMSlotInitializationOnIOThread(const std::string& username,
const std::string& username_hash) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
BrowserThread::PostTask(
BrowserThread::UI,
FROM_HERE,
base::Bind(&GetTPMInfoForUserOnUIThread, username, username_hash));
}
void StartNSSInitOnIOThread(const std::string& username,
const std::string& username_hash,
const base::FilePath& path,
bool is_primary_user) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
DVLOG(1) << "Starting NSS init for " << username
<< " hash:" << username_hash
<< " is_primary_user:" << is_primary_user;
if (!crypto::InitializeNSSForChromeOSUser(
username, username_hash, is_primary_user, path)) {
// If the user already exists in nss_util's map, it is already initialized
// or in the process of being initialized. In either case, there's no need
// to do anything.
return;
}
if (crypto::IsTPMTokenEnabledForNSS()) {
if (crypto::IsTPMTokenReady(base::Bind(
&StartTPMSlotInitializationOnIOThread, username, username_hash))) {
StartTPMSlotInitializationOnIOThread(username, username_hash);
} else {
DVLOG(1) << "Waiting for tpm ready ...";
}
} else {
crypto::InitializePrivateSoftwareSlotForChromeOSUser(username_hash);
}
}
#endif // defined(OS_CHROMEOS)
} // namespace
void ProfileIOData::InitializeOnUIThread(Profile* profile) {
......@@ -278,6 +390,25 @@ void ProfileIOData::InitializeOnUIThread(Profile* profile) {
params->managed_mode_url_filter =
managed_user_service->GetURLFilterForIOThread();
#endif
#if defined(OS_CHROMEOS)
chromeos::UserManager* user_manager = chromeos::UserManager::Get();
if (user_manager) {
chromeos::User* user = user_manager->GetUserByProfile(profile);
if (user) {
params->username_hash = user->username_hash();
bool is_primary_user = (user_manager->GetPrimaryUser() == user);
BrowserThread::PostTask(BrowserThread::IO,
FROM_HERE,
base::Bind(&StartNSSInitOnIOThread,
user->email(),
user->username_hash(),
profile->GetPath(),
is_primary_user));
}
}
if (params->username_hash.empty())
LOG(WARNING) << "no username_hash";
#endif
params->profile = profile;
profile_params_.reset(params.release());
......@@ -833,6 +964,7 @@ void ProfileIOData::Init(content::ProtocolHandlerMap* protocol_handlers) const {
main_request_context_->set_cert_verifier(
io_thread_globals->cert_verifier.get());
}
username_hash_ = profile_params_->username_hash;
#else
main_request_context_->set_cert_verifier(
io_thread_globals->cert_verifier.get());
......
......@@ -177,6 +177,12 @@ class ProfileIOData {
return transport_security_state_.get();
}
#if defined(OS_CHROMEOS)
std::string username_hash() const {
return username_hash_;
}
#endif
bool is_incognito() const {
return is_incognito_;
}
......@@ -270,6 +276,10 @@ class ProfileIOData {
scoped_refptr<const ManagedModeURLFilter> managed_mode_url_filter;
#endif
#if defined(OS_CHROMEOS)
std::string username_hash;
#endif
// The profile this struct was populated from. It's passed as a void* to
// ensure it's not accidently used on the IO thread. Before using it on the
// UI thread, call ProfileManager::IsValidProfile to ensure it's alive.
......@@ -494,6 +504,7 @@ class ProfileIOData {
http_server_properties_;
#if defined(OS_CHROMEOS)
mutable scoped_ptr<policy::PolicyCertVerifier> cert_verifier_;
mutable std::string username_hash_;
#endif
mutable scoped_ptr<net::TransportSecurityPersister>
......
......@@ -43,7 +43,7 @@ void CryptohomeWebUIHandler::OnPageLoaded(const base::ListValue* args) {
BrowserThread::PostTaskAndReplyWithResult(
BrowserThread::IO,
FROM_HERE,
base::Bind(&crypto::IsTPMTokenReady),
base::Bind(&crypto::IsTPMTokenReady, base::Closure()),
base::Bind(&CryptohomeWebUIHandler::DidGetNSSUtilInfoOnUIThread,
weak_ptr_factory_.GetWeakPtr()));
}
......
......@@ -46,10 +46,36 @@ class CertificateManagerBrowserTest : public options::OptionsUIBrowserTest {
policy::BrowserPolicyConnector::SetPolicyProviderForTesting(&provider_);
}
void SetUpOnIOThread() {
#if defined(OS_CHROMEOS)
test_nssdb_.reset(new crypto::ScopedTestNSSDB());
#endif
}
void TearDownOnIOThread() {
#if defined(OS_CHROMEOS)
test_nssdb_.reset();
#endif
}
virtual void SetUpOnMainThread() OVERRIDE {
content::BrowserThread::PostTask(
content::BrowserThread::IO,
FROM_HERE,
base::Bind(&CertificateManagerBrowserTest::SetUpOnIOThread, this));
content::RunAllPendingInMessageLoop(content::BrowserThread::IO);
content::RunAllPendingInMessageLoop();
}
virtual void CleanUpOnMainThread() OVERRIDE {
content::BrowserThread::PostTask(
content::BrowserThread::IO,
FROM_HERE,
base::Bind(&CertificateManagerBrowserTest::TearDownOnIOThread, this));
content::RunAllPendingInMessageLoop(content::BrowserThread::IO);
}
#if defined(OS_CHROMEOS)
void LoadONCPolicy(const std::string& filename) {
const std::string& user_policy_blob =
......@@ -86,7 +112,7 @@ class CertificateManagerBrowserTest : public options::OptionsUIBrowserTest {
policy::MockConfigurationPolicyProvider provider_;
#if defined(OS_CHROMEOS)
policy::DevicePolicyCrosTestHelper device_policy_test_helper_;
crypto::ScopedTestNSSDB test_nssdb_;
scoped_ptr<crypto::ScopedTestNSSDB> test_nssdb_;
#endif
};
......
......@@ -1225,6 +1225,9 @@
'browser/net/net_pref_observer.h',
'browser/net/network_stats.cc',
'browser/net/network_stats.h',
'browser/net/nss_context_chromeos.cc',
'browser/net/nss_context_linux.cc',
'browser/net/nss_context.h',
'browser/net/preconnect.cc',
'browser/net/preconnect.h',
'browser/net/predictor.cc',
......@@ -3115,6 +3118,9 @@
'sources!': [
'browser/certificate_manager_model.cc',
'browser/certificate_manager_model.h',
'browser/net/nss_context_chromeos.cc',
'browser/net/nss_context_linux.cc',
'browser/net/nss_context.h',
],
}],
['toolkit_uses_gtk == 1', {
......
......@@ -21,8 +21,10 @@
#include <sys/param.h>
#endif
#include <map>
#include <vector>
#include "base/callback.h"
#include "base/cpu.h"
#include "base/debug/alias.h"
#include "base/debug/stack_trace.h"
......@@ -35,6 +37,7 @@
#include "base/memory/scoped_ptr.h"
#include "base/metrics/histogram.h"
#include "base/native_library.h"
#include "base/stl_util.h"
#include "base/strings/stringprintf.h"
#include "base/threading/thread_checker.h"
#include "base/threading/thread_restrictions.h"
......@@ -89,6 +92,7 @@ base::FilePath GetDefaultConfigDirectory() {
LOG(ERROR) << "Failed to create " << dir.value() << " directory.";
dir.clear();
}
DVLOG(2) << "DefaultConfigDirectory: " << dir.value();
return dir;
}
......@@ -204,6 +208,58 @@ void CrashOnNSSInitFailure() {
LOG(FATAL) << "nss_error=" << nss_error << ", os_error=" << os_error;
}
#if defined(OS_CHROMEOS)
class ChromeOSUserData {
public:
ChromeOSUserData(ScopedPK11Slot public_slot, bool is_primary_user)
: public_slot_(public_slot.Pass()),
is_primary_user_(is_primary_user) {}
~ChromeOSUserData() {
if (public_slot_ && !is_primary_user_) {
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())));
}
}
private:
ScopedPK11Slot public_slot_;
ScopedPK11Slot private_slot_;
bool is_primary_user_;
typedef std::vector<base::Callback<void(ScopedPK11Slot)> >
SlotReadyCallbackList;
SlotReadyCallbackList tpm_ready_callback_list_;
};
#endif // defined(OS_CHROMEOS)
class NSSInitSingleton {
public:
#if defined(OS_CHROMEOS)
......@@ -225,6 +281,21 @@ class NSSInitSingleton {
}
}
PK11SlotInfo* OpenPersistentNSSDBForPath(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 NULL;
}
return OpenUserDB(nssdb_path, kNSSDatabaseName);
}
void EnableTPMTokenForNSS() {
DCHECK(thread_checker_.CalledOnValidThread());
......@@ -234,6 +305,11 @@ class NSSInitSingleton {
tpm_token_enabled_for_nss_ = true;
}
bool IsTPMTokenEnabledForNSS() {
DCHECK(thread_checker_.CalledOnValidThread());
return tpm_token_enabled_for_nss_;
}
bool InitializeTPMToken(int token_slot_id) {
DCHECK(thread_checker_.CalledOnValidThread());
......@@ -267,19 +343,42 @@ class NSSInitSingleton {
if (chaps_module_){
tpm_slot_ = GetTPMSlotForId(token_slot_id);
return tpm_slot_ != NULL;
if (!tpm_slot_)
return false;
TPMReadyCallbackList callback_list;
callback_list.swap(tpm_ready_callback_list_);
for (TPMReadyCallbackList::iterator i =
callback_list.begin();
i != callback_list.end();
++i) {
(*i).Run();
}
return true;
}
return false;
}
bool IsTPMTokenReady() {
// TODO(mattm): Change to DCHECK when callers have been fixed.
if (!thread_checker_.CalledOnValidThread()) {
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();
}
return tpm_slot_ != NULL;
if (tpm_slot_ != NULL)
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
......@@ -291,7 +390,7 @@ class NSSInitSingleton {
if (!chaps_module_)
return NULL;
VLOG(1) << "Poking chaps module.";
DVLOG(3) << "Poking chaps module.";
SECStatus rv = SECMOD_UpdateSlotList(chaps_module_);
if (rv != SECSuccess)
PLOG(ERROR) << "SECMOD_UpdateSlotList failed: " << PORT_GetError();
......@@ -301,11 +400,85 @@ class NSSInitSingleton {
LOG(ERROR) << "TPM slot " << slot_id << " not found.";
return slot;
}
bool InitializeNSSForChromeOSUser(
const std::string& email,
const std::string& username_hash,
bool is_primary_user,
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;
}
ScopedPK11Slot public_slot;
if (is_primary_user) {
DVLOG(2) << "Primary user, using GetPublicNSSKeySlot()";
public_slot.reset(GetPublicNSSKeySlot());
} else {
DVLOG(2) << "Opening NSS DB " << path.value();
public_slot.reset(OpenPersistentNSSDBForPath(path));
}
chromeos_user_map_[username_hash] =
new ChromeOSUserData(public_slot.Pass(), is_primary_user);
return true;
}
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());
chromeos_user_map_[username_hash]
->SetPrivateSlot(ScopedPK11Slot(GetTPMSlotForId(slot_id)));
}
void InitializePrivateSoftwareSlotForChromeOSUser(
const std::string& username_hash) {
DCHECK(thread_checker_.CalledOnValidThread());
LOG(WARNING) << "using software private slot for " << username_hash;
DCHECK(chromeos_user_map_.find(username_hash) != chromeos_user_map_.end());
chromeos_user_map_[username_hash]->SetPrivateSlot(
chromeos_user_map_[username_hash]->GetPublicSlot());
}
ScopedPK11Slot GetPublicSlotForChromeOSUser(
const std::string& username_hash) {
DCHECK(thread_checker_.CalledOnValidThread());
if (test_slot_) {
DVLOG(2) << "returning test_slot_ for " << username_hash;
return ScopedPK11Slot(PK11_ReferenceSlot(test_slot_));
}
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());
DCHECK(chromeos_user_map_.find(username_hash) != chromeos_user_map_.end());
if (test_slot_) {
DVLOG(2) << "returning test_slot_ for " << username_hash;
return ScopedPK11Slot(PK11_ReferenceSlot(test_slot_));
}
return chromeos_user_map_[username_hash]->GetPrivateSlot(callback);
}
#endif // defined(OS_CHROMEOS)
bool OpenTestNSSDB() {
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;
if (test_slot_)
return true;
......@@ -317,6 +490,10 @@ class NSSInitSingleton {