blob: d46f17ec03951ef0fa840a350e87025671a1d6f5 [file] [log] [blame]
// Copyright 2021 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_service_chromeos.h"
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/callback.h"
#include "base/callback_list.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/single_thread_task_runner.h"
#include "base/supports_user_data.h"
#include "chrome/browser/ash/login/startup_utils.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/net/nss_context.h"
#include "chrome/browser/profiles/profile.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/userdataauth/cryptohome_pkcs11_client.h"
#include "chromeos/settings/cros_settings_names.h"
#include "chromeos/tpm/tpm_token_info_getter.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "crypto/nss_util.h"
#include "crypto/nss_util_internal.h"
#include "net/cert/nss_cert_database_chromeos.h"
using content::BrowserThread;
namespace {
// The following four functions are responsible for initializing NSS for each
// profile on ChromeOS Ash, which has a separate NSS database and TPM slot
// per-profile.
//
// Initialization basically follows these steps:
// 1) Get some info from user_manager::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 CryptohomePkcs11Client 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
//
// NssServiceChromeOS::NssServiceChromeOS
// |
// ProfileHelper::Get()->GetUserByProfile()
// \---------------------------------------v
// StartNSSInitOnIOThread
// |
// crypto::InitializeNSSForChromeOSUser
// |
// crypto::IsTPMTokenReady
// |
// StartTPMSlotInitializationOnIOThread
// v---------------------------------------/
// GetTPMInfoForUserOnUIThread
// |
// chromeos::TPMTokenInfoGetter::Start
// |
// DidGetTPMInfoForUserOnUIThread
// \---------------------------------------v
// crypto::InitializeTPMForChromeOSUser
void DidGetTPMInfoForUserOnUIThread(
std::unique_ptr<chromeos::TPMTokenInfoGetter> getter,
const std::string& username_hash,
base::Optional<user_data_auth::TpmTokenInfo> token_info) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (token_info.has_value() && token_info->slot() != -1) {
DVLOG(1) << "Got TPM slot for " << username_hash << ": "
<< token_info->slot();
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&crypto::InitializeTPMForChromeOSUser,
username_hash, token_info->slot()));
} else {
NOTREACHED() << "TPMTokenInfoGetter reported invalid token.";
}
}
void GetTPMInfoForUserOnUIThread(const AccountId& account_id,
const std::string& username_hash) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DVLOG(1) << "Getting TPM info from cryptohome for "
<< " " << account_id.Serialize() << " " << username_hash;
std::unique_ptr<chromeos::TPMTokenInfoGetter> scoped_token_info_getter =
chromeos::TPMTokenInfoGetter::CreateForUserToken(
account_id, chromeos::CryptohomePkcs11Client::Get(),
base::ThreadTaskRunnerHandle::Get());
chromeos::TPMTokenInfoGetter* token_info_getter =
scoped_token_info_getter.get();
// Bind |token_info_getter| to the callback to ensure it does not go away
// before TPM token info is fetched.
token_info_getter->Start(base::BindOnce(&DidGetTPMInfoForUserOnUIThread,
std::move(scoped_token_info_getter),
username_hash));
}
void StartTPMSlotInitializationOnIOThread(const AccountId& account_id,
const std::string& username_hash) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&GetTPMInfoForUserOnUIThread, account_id, username_hash));
}
void StartNSSInitOnIOThread(const AccountId& account_id,
const std::string& username_hash,
const base::FilePath& path) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DVLOG(1) << "Starting NSS init for " << account_id.Serialize()
<< " hash:" << username_hash;
// Make sure NSS is initialized for the user.
crypto::InitializeNSSForChromeOSUser(username_hash, path);
// Check if it's OK to initialize TPM for the user before continuing. This
// may not be the case if the TPM slot initialization was previously
// requested for the same user.
if (!crypto::ShouldInitializeTPMForChromeOSUser(username_hash))
return;
crypto::WillInitializeTPMForChromeOSUser(username_hash);
if (crypto::IsTPMTokenEnabledForNSS()) {
if (crypto::IsTPMTokenReady(
base::BindOnce(&StartTPMSlotInitializationOnIOThread, account_id,
username_hash))) {
StartTPMSlotInitializationOnIOThread(account_id, username_hash);
} else {
DVLOG(1) << "Waiting for tpm ready ...";
}
} else {
crypto::InitializePrivateSoftwareSlotForChromeOSUser(username_hash);
}
}
// Used to convert a callback that takes a net::NSSCertDatabase to one that
// takes a net::NSSCertDatabaseChromeOS.
void CallWithNSSCertDatabase(
base::OnceCallback<void(net::NSSCertDatabase*)> callback,
net::NSSCertDatabaseChromeOS* db) {
std::move(callback).Run(db);
}
} // namespace
// Creates and manages a NSSCertDatabaseChromeOS. Created on the UI thread, but
// all other calls are made on the IO thread.
class NssServiceChromeOS::NSSCertDatabaseChromeOSManager {
public:
typedef base::OnceCallback<void(net::NSSCertDatabaseChromeOS*)>
GetNSSCertDatabaseCallback;
NSSCertDatabaseChromeOSManager(std::string username_hash,
bool user_is_affiliated)
: username_hash_(std::move(username_hash)) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (user_is_affiliated) {
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(
&NSSCertDatabaseChromeOSManager::EnableNSSSystemKeySlot,
weak_ptr_factory_.GetWeakPtr()));
}
}
NSSCertDatabaseChromeOSManager(const NSSCertDatabaseChromeOSManager&) =
delete;
NSSCertDatabaseChromeOSManager& operator=(
const NSSCertDatabaseChromeOSManager&) = delete;
~NSSCertDatabaseChromeOSManager() { DCHECK_CURRENTLY_ON(BrowserThread::IO); }
net::NSSCertDatabaseChromeOS* GetNSSCertDatabaseChromeOS(
NSSCertDatabaseChromeOSManager::GetNSSCertDatabaseCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (nss_cert_database_)
return nss_cert_database_.get();
ready_callback_list_.AddUnsafe(std::move(callback));
// If database creation has already started, nothing else to do.
if (database_creation_started_)
return nullptr;
// Otherwise, start creating the database.
database_creation_started_ = true;
crypto::ScopedPK11Slot private_slot(crypto::GetPrivateSlotForChromeOSUser(
username_hash_,
base::BindOnce(&NSSCertDatabaseChromeOSManager::DidGetPrivateSlot,
weak_ptr_factory_.GetWeakPtr())));
if (private_slot)
DidGetPrivateSlot(std::move(private_slot));
return nullptr;
}
// Just like GetNSSCertDatabaseChromeOS(), but uses net::NSSCertDatabase
// instead of net::NSSCertDatabaseChromeOS.
net::NSSCertDatabase* GetNSSCertDatabase(
base::OnceCallback<void(net::NSSCertDatabase*)> callback) {
return GetNSSCertDatabaseChromeOS(
base::BindOnce(&CallWithNSSCertDatabase, std::move(callback)));
}
private:
using ReadyCallbackList =
base::OnceCallbackList<GetNSSCertDatabaseCallback::RunType>;
void EnableNSSSystemKeySlot() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
// This should only be called once.
DCHECK(!pending_system_slot_);
crypto::ScopedPK11Slot system_slot = crypto::GetSystemNSSKeySlot(
base::BindOnce(&NSSCertDatabaseChromeOSManager::SetSystemSlotOfDB,
weak_ptr_factory_.GetWeakPtr()));
if (system_slot)
SetSystemSlotOfDB(std::move(system_slot));
}
void SetSystemSlotOfDB(crypto::ScopedPK11Slot system_slot) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
DCHECK(system_slot);
pending_system_slot_ = std::move(system_slot);
base::RepeatingCallback<void(net::NSSCertDatabaseChromeOS*)> callback =
base::BindRepeating(&NSSCertDatabaseChromeOSManager::SetSystemSlot,
weak_ptr_factory_.GetWeakPtr());
net::NSSCertDatabaseChromeOS* db = GetNSSCertDatabaseChromeOS(callback);
if (db)
SetSystemSlot(db);
}
void SetSystemSlot(net::NSSCertDatabaseChromeOS* db) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
db->SetSystemSlot(std::move(pending_system_slot_));
}
void DidGetPrivateSlot(crypto::ScopedPK11Slot private_slot) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
nss_cert_database_ = std::make_unique<net::NSSCertDatabaseChromeOS>(
crypto::GetPublicSlotForChromeOSUser(username_hash_),
std::move(private_slot));
ready_callback_list_.Notify(nss_cert_database_.get());
}
const std::string username_hash_;
bool database_creation_started_ = false;
crypto::ScopedPK11Slot pending_system_slot_;
std::unique_ptr<net::NSSCertDatabaseChromeOS> nss_cert_database_;
ReadyCallbackList ready_callback_list_;
base::WeakPtrFactory<NSSCertDatabaseChromeOSManager> weak_ptr_factory_{this};
};
NssServiceChromeOS::NssServiceChromeOS(Profile* profile) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
const user_manager::User* user =
chromeos::ProfileHelper::Get()->GetUserByProfile(profile);
// No need to initialize NSS for users with empty username hash:
// Getters for a user's NSS slots always return a null slot if the user's
// username hash is empty, even when the NSS is not initialized for the
// user.
std::string username_hash;
bool user_is_affiliated = false;
if (user && !user->username_hash().empty()) {
username_hash = user->username_hash();
DCHECK(!username_hash.empty());
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&StartNSSInitOnIOThread, user->GetAccountId(),
username_hash, profile->GetPath()));
user_is_affiliated = user->IsAffiliated();
}
DCHECK(!(username_hash.empty() && user_is_affiliated));
nss_cert_database_manager_ = std::make_unique<NSSCertDatabaseChromeOSManager>(
std::move(username_hash), user_is_affiliated);
}
NssServiceChromeOS::~NssServiceChromeOS() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
content::GetIOThreadTaskRunner({})->DeleteSoon(
FROM_HERE, std::move(nss_cert_database_manager_));
}
NssCertDatabaseGetter
NssServiceChromeOS::CreateNSSCertDatabaseGetterForIOThread() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
return base::BindOnce(&NSSCertDatabaseChromeOSManager::GetNSSCertDatabase,
base::Unretained(nss_cert_database_manager_.get()));
}