blob: 41f605944267d6a20733f7a9bc869c536be31e97 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// 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.h"
#include <memory>
#include <utility>
#include "base/callback_list.h"
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/memory/weak_ptr.h"
#include "base/supports_user_data.h"
#include "base/task/single_thread_task_runner.h"
#include "chrome/browser/ash/login/startup_utils.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chromeos/ash/components/dbus/dbus_thread_manager.h"
#include "chromeos/ash/components/dbus/userdataauth/cryptohome_pkcs11_client.h"
#include "chromeos/ash/components/settings/cros_settings_names.h"
#include "chromeos/ash/components/tpm/tpm_token_info_getter.h"
#include "chromeos/components/kiosk/kiosk_utils.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, 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
//
// NssService::NssService
// |
// ProfileHelper::Get()->GetUserByProfile()
// \---------------------------------------v
// StartNSSInitOnIOThread
// |
// crypto::InitializeNSSForChromeOSUser
// |
// crypto::IsTPMTokenEnabled
// |
// StartTPMSlotInitializationOnIOThread
// v---------------------------------------/
// GetTPMInfoForUserOnUIThread
// |
// ash::TPMTokenInfoGetter::Start
// |
// DidGetTPMInfoForUserOnUIThread
// \---------------------------------------v
// crypto::InitializeTPMForChromeOSUser
void DidGetTPMInfoForUserOnUIThread(
std::unique_ptr<ash::TPMTokenInfoGetter> getter,
const std::string& username_hash,
std::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<ash::TPMTokenInfoGetter> scoped_token_info_getter =
ash::TPMTokenInfoGetter::CreateForUserToken(
account_id, ash::CryptohomePkcs11Client::Get(),
base::SingleThreadTaskRunner::GetCurrentDefault());
ash::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,
bool is_tpm_token_enabled) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!is_tpm_token_enabled) {
crypto::InitializePrivateSoftwareSlotForChromeOSUser(username_hash);
return;
}
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,
bool is_kiosk) {
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.
if (is_kiosk) {
// Kiosk sessions don't have the UI that could result in interactions with
// the public slot. Kiosk users are also not owner users and can't have
// the owner key in the public slot. So the public slot is not used in
// Kiosk sessions and can be replaced by the internal slot. This is done
// mainly because Chrome sometimes fails to load the public slot and has
// to crash because of that.
crypto::InitializeNSSForChromeOSUserWithSlot(
username_hash, crypto::ScopedPK11Slot(PK11_GetInternalKeySlot()));
} else {
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);
crypto::IsTPMTokenEnabled(base::BindOnce(
&StartTPMSlotInitializationOnIOThread, account_id, username_hash));
}
} // namespace
// Creates and manages a NSSCertDatabaseChromeOS. Created on the UI thread, but
// all other calls are made on the IO thread.
class NssService::NSSCertDatabaseChromeOSManager {
public:
using GetNSSCertDatabaseCallback =
base::OnceCallback<void(net::NSSCertDatabase*)>;
NSSCertDatabaseChromeOSManager(std::string username_hash,
bool enable_system_slot)
: username_hash_(std::move(username_hash)),
enable_system_slot_(enable_system_slot) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
}
NSSCertDatabaseChromeOSManager(const NSSCertDatabaseChromeOSManager&) =
delete;
NSSCertDatabaseChromeOSManager& operator=(
const NSSCertDatabaseChromeOSManager&) = delete;
~NSSCertDatabaseChromeOSManager() { DCHECK_CURRENTLY_ON(BrowserThread::IO); }
net::NSSCertDatabase* GetNSSCertDatabase(
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_started_) {
database_creation_started_ = true;
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&NSSCertDatabaseChromeOSManager::StartDatabaseCreation,
weak_ptr_factory_.GetWeakPtr()));
}
return nullptr;
}
private:
void StartDatabaseCreation() {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
crypto::ScopedPK11Slot private_slot(crypto::GetPrivateSlotForChromeOSUser(
username_hash_,
base::BindOnce(&NSSCertDatabaseChromeOSManager::DidGetPrivateSlot,
weak_ptr_factory_.GetWeakPtr())));
if (private_slot)
DidGetPrivateSlot(std::move(private_slot));
}
void DidGetPrivateSlot(crypto::ScopedPK11Slot private_slot) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
if (!enable_system_slot_) {
CreateDatabase(std::move(private_slot),
/*system_slot=*/crypto::ScopedPK11Slot());
return;
}
crypto::GetSystemNSSKeySlot(base::BindOnce(
&NSSCertDatabaseChromeOSManager::CreateDatabase,
weak_ptr_factory_.GetWeakPtr(), std::move(private_slot)));
}
void CreateDatabase(crypto::ScopedPK11Slot private_slot,
crypto::ScopedPK11Slot system_slot) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
auto public_slot = crypto::GetPublicSlotForChromeOSUser(username_hash_);
if (!public_slot) {
// The public slot can be null in tests. Consider using FakeNssService if
// a specific NSS behavior is required in tests.
// Sometimes the slot also fails to load. This will prevent importing new
// client certificates into it and the owner key won't be found, but
// overall this is not a critical error and should resolve itself after a
// reboot. The existing certificates should keep working. Other slots are
// unaffected and certificates for the public slot are currently
// dual-written into the private slot as well.
// Initialize the slot with the read-only internal NSS slot to prevent
// ChromeOS from crashing on the CHECK in the NSSCertDatabase constructor.
public_slot = crypto::ScopedPK11Slot(PK11_GetInternalKeySlot());
}
nss_cert_database_ = std::make_unique<net::NSSCertDatabaseChromeOS>(
std::move(public_slot), std::move(private_slot));
if (system_slot)
nss_cert_database_->SetSystemSlot(std::move(system_slot));
ready_callback_list_.Notify(nss_cert_database_.get());
}
const std::string username_hash_;
bool enable_system_slot_ = false;
bool database_creation_started_ = false;
std::unique_ptr<net::NSSCertDatabaseChromeOS> nss_cert_database_;
base::OnceCallbackList<GetNSSCertDatabaseCallback::RunType>
ready_callback_list_;
base::WeakPtrFactory<NSSCertDatabaseChromeOSManager> weak_ptr_factory_{this};
};
NssService::NssService(content::BrowserContext* context) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(context);
Profile* profile = Profile::FromBrowserContext(context);
const user_manager::User* user =
ash::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 enable_system_slot = 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(),
chromeos::IsKioskSession()));
enable_system_slot = user->IsAffiliated();
}
DCHECK(!(username_hash.empty() && enable_system_slot));
nss_cert_database_manager_ = std::make_unique<NSSCertDatabaseChromeOSManager>(
std::move(username_hash), enable_system_slot);
}
NssService::~NssService() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
content::GetIOThreadTaskRunner({})->DeleteSoon(
FROM_HERE, std::move(nss_cert_database_manager_));
}
NssCertDatabaseGetter NssService::CreateNSSCertDatabaseGetterForIOThread() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
return base::BindOnce(&NSSCertDatabaseChromeOSManager::GetNSSCertDatabase,
base::Unretained(nss_cert_database_manager_.get()));
}