// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "chrome/browser/ash/ownership/owner_key_loader.h"
#include <string>
#include <utility>
#include "base/check_is_test.h"
#include "base/feature_list.h"
#include "base/task/thread_pool.h"
#include "chrome/browser/ash/ownership/ownership_histograms.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/ash/settings/device_settings_service.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/net/nss_service.h"
#include "chrome/browser/net/nss_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/channel_info.h"
#include "components/ownership/owner_key_util.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "components/user_manager/user_manager.h"
#include "components/version_info/channel.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "net/cert/nss_cert_database.h"
namespace ash {
// Enable storing a newly created owner key in the private slot.
BASE_FEATURE(kStoreOwnerKeyInPrivateSlot,
"StoreOwnerKeyInPrivateSlot",
base::FEATURE_DISABLED_BY_DEFAULT);
// Enable migration of the owner key from the public to the private slot. This
// experiment represents the second stage of `kStoreOwnerKeyInPrivateSlot` and
// is only respected if kStoreOwnerKeyInPrivateSlot is enabled.
BASE_FEATURE(kMigrateOwnerKeyToPrivateSlot,
"MigrateOwnerKeyToPrivateSlot",
base::FEATURE_DISABLED_BY_DEFAULT);
bool IsStoreOwnerKeyInPrivateSlotEnabled() {
return base::FeatureList::IsEnabled(kStoreOwnerKeyInPrivateSlot);
}
bool ShouldMigrateOwnerKeyToPrivateSlot() {
return IsStoreOwnerKeyInPrivateSlotEnabled() &&
base::FeatureList::IsEnabled(kMigrateOwnerKeyToPrivateSlot);
}
namespace {
// Max number of attempts to generate a new owner key.
constexpr int kMaxGenerateAttempts = 5;
using WorkerTask =
base::OnceCallback<void(crypto::ScopedPK11Slot /*public_slot*/,
crypto::ScopedPK11Slot /*private_slot*/)>;
void LoadPublicKeyOnlyOnWorkerThread(
scoped_refptr<ownership::OwnerKeyUtil> owner_key_util,
base::OnceCallback<void(scoped_refptr<ownership::PublicKey>)>
ui_thread_callback) {
scoped_refptr<ownership::PublicKey> public_key =
owner_key_util->ImportPublicKey();
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(std::move(ui_thread_callback), public_key));
}
void LoadPrivateKeyOnWorkerThread(
scoped_refptr<ownership::OwnerKeyUtil> owner_key_util,
scoped_refptr<ownership::PublicKey> public_key,
base::OnceCallback<void(scoped_refptr<ownership::PrivateKey>,
bool found_in_public_slot)> ui_thread_callback,
crypto::ScopedPK11Slot public_slot,
crypto::ScopedPK11Slot private_slot) {
// TODO(davidben): FindPrivateKeyInSlot internally checks for a null slot if
// needbe. The null check should be in the caller rather than internally in
// the OwnerKeyUtil implementation. The tests currently get a null
// private_slot and expect the mock OwnerKeyUtil to still be called.
scoped_refptr<ownership::PrivateKey> private_key =
base::MakeRefCounted<ownership::PrivateKey>(
owner_key_util->FindPrivateKeyInSlot(public_key->data(),
private_slot.get()));
bool found_in_public_slot = false;
if (!private_key->key()) {
private_key = base::MakeRefCounted<ownership::PrivateKey>(
owner_key_util->FindPrivateKeyInSlot(public_key->data(),
public_slot.get()));
if (private_key->key()) {
// If the key is stored in the public slot, it might need to be migrated
// (depending on the experiment).
found_in_public_slot = true;
}
}
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(std::move(ui_thread_callback), private_key,
found_in_public_slot));
}
void GenerateNewOwnerKeyOnWorkerThread(
scoped_refptr<ownership::OwnerKeyUtil> owner_key_util,
base::OnceCallback<void(scoped_refptr<ownership::PublicKey>,
scoped_refptr<ownership::PrivateKey>)>
ui_thread_callback,
crypto::ScopedPK11Slot public_slot,
crypto::ScopedPK11Slot private_slot) {
crypto::ScopedSECKEYPrivateKey sec_priv_key;
if (private_slot && IsStoreOwnerKeyInPrivateSlotEnabled()) {
sec_priv_key = owner_key_util->GenerateKeyPair(private_slot.get());
RecordOwnerKeyEvent(OwnerKeyEvent::kPrivateSlotKeyGeneration,
bool(sec_priv_key));
} else if (public_slot) {
sec_priv_key = owner_key_util->GenerateKeyPair(public_slot.get());
RecordOwnerKeyEvent(OwnerKeyEvent::kPublicSlotKeyGeneration,
bool(sec_priv_key));
}
if (!sec_priv_key) {
LOG(ERROR) << "Failed to generate owner key";
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(std::move(ui_thread_callback), nullptr, nullptr));
return;
}
crypto::ScopedSECKEYPublicKey sec_pub_key(
SECKEY_ConvertToPublicKey(sec_priv_key.get()));
crypto::ScopedSECItem sec_pub_key_der(
SECKEY_EncodeDERSubjectPublicKeyInfo(sec_pub_key.get()));
if (!sec_pub_key_der.get()) {
LOG(ERROR) << "Failed to extract public key";
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(std::move(ui_thread_callback), nullptr, nullptr));
return;
}
scoped_refptr<ownership::PublicKey> public_key =
base::MakeRefCounted<ownership::PublicKey>(
/*is_persisted=*/false,
std::vector<uint8_t>(sec_pub_key_der->data,
sec_pub_key_der->data + sec_pub_key_der->len));
scoped_refptr<ownership::PrivateKey> private_key =
base::MakeRefCounted<ownership::PrivateKey>(std::move(sec_priv_key));
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(std::move(ui_thread_callback),
std::move(public_key), std::move(private_key)));
}
void PostOnWorkerThreadWithCertDb(WorkerTask worker_task,
net::NSSCertDatabase* nss_db) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
CHECK(nss_db);
// TODO(eseckler): It seems loading the key is important for the UsersPrivate
// extension API to work correctly during startup, which is why we cannot
// currently use the BEST_EFFORT TaskPriority here.
base::ThreadPool::PostTask(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(std::move(worker_task), nss_db->GetPublicSlot(),
nss_db->GetPrivateSlot()));
}
void GetCertDbAndPostOnWorkerThreadOnIO(NssCertDatabaseGetter nss_getter,
WorkerTask worker_task) {
DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
// Running |nss_getter| may either return a non-null pointer
// synchronously or invoke the given callback asynchronously with a non-null
// pointer. |callback_split| is used here to handle both cases.
auto callback_split = base::SplitOnceCallback(
base::BindOnce(&PostOnWorkerThreadWithCertDb, std::move(worker_task)));
net::NSSCertDatabase* database =
std::move(nss_getter).Run(std::move(callback_split.first));
if (database) {
std::move(callback_split.second).Run(database);
}
}
void GetCertDbAndPostOnWorkerThread(Profile* profile, WorkerTask worker_task) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
content::GetIOThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&GetCertDbAndPostOnWorkerThreadOnIO,
NssServiceFactory::GetForContext(profile)
->CreateNSSCertDatabaseGetterForIOThread(),
std::move(worker_task)));
}
inline bool IsKeyPresent(
const scoped_refptr<ownership::PublicKey>& public_key) {
return public_key && !public_key->is_empty();
}
inline bool IsKeyPresent(
const scoped_refptr<ownership::PrivateKey>& public_key) {
return public_key && public_key->key();
}
inline bool AreKeysPresent(
const scoped_refptr<ownership::PublicKey>& public_key,
const scoped_refptr<ownership::PrivateKey>& private_key) {
return IsKeyPresent(public_key) && IsKeyPresent(private_key);
}
bool UserCanBecomeOwner(const user_manager::User* user) {
if (!user) {
return false;
}
switch (user->GetType()) {
case user_manager::UserType::kRegular:
case user_manager::UserType::kChild:
return true;
case user_manager::UserType::kGuest:
case user_manager::UserType::kPublicAccount:
case user_manager::UserType::kKioskApp:
case user_manager::UserType::kWebKioskApp:
return false;
}
}
} // namespace
OwnerKeyLoader::OwnerKeyLoader(
Profile* profile,
DeviceSettingsService* device_settings_service,
scoped_refptr<ownership::OwnerKeyUtil> owner_key_util,
bool is_enterprise_managed,
KeypairCallback callback)
: profile_(profile),
device_settings_service_(device_settings_service),
owner_key_util_(std::move(owner_key_util)),
is_enterprise_managed_(is_enterprise_managed),
callback_(std::move(callback)) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(profile_);
DCHECK(owner_key_util_);
DCHECK(callback_);
if (!device_settings_service_) {
CHECK_IS_TEST();
}
}
OwnerKeyLoader::~OwnerKeyLoader() = default;
void OwnerKeyLoader::Run() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(callback_) << "Run() can only be called once.";
if (g_browser_process && g_browser_process->IsShuttingDown()) {
return std::move(callback_).Run(/*public_key=*/nullptr,
/*private_key=*/nullptr);
// `this` might be deleted here.
}
if (!device_settings_service_) {
CHECK_IS_TEST();
RecordOwnerKeyEvent(OwnerKeyEvent::kDeviceSettingsServiceIsNull,
/*success=*/false);
return std::move(callback_).Run(/*public_key=*/nullptr,
/*private_key=*/nullptr);
// `this` might be deleted here.
}
// Try loading the public key first. Most of the time it should already exist.
// Use TaskPriority::USER_VISIBLE priority because some user visible features
// need to know whether the current user is the owner or not (e.g.
// UsersPrivate API).
base::ThreadPool::PostTask(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::USER_VISIBLE,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&LoadPublicKeyOnlyOnWorkerThread, owner_key_util_,
base::BindOnce(&OwnerKeyLoader::OnPublicKeyLoaded,
weak_factory_.GetWeakPtr())));
}
void OwnerKeyLoader::OnPublicKeyLoaded(
scoped_refptr<ownership::PublicKey> public_key) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
public_key_ = std::move(public_key);
if (is_enterprise_managed_) {
RecordOwnerKeyEvent(OwnerKeyEvent::kManagedDevice,
/*success=*/IsKeyPresent(public_key_));
// Managed devices don't have private owner keys.
return std::move(callback_).Run(std::move(public_key_), nullptr);
// `this` might be deleted here.
}
if (IsKeyPresent(public_key_)) {
// Now check whether the current user has access to the private key
// associated with the public key.
return GetCertDbAndPostOnWorkerThread(
profile_,
base::BindOnce(&LoadPrivateKeyOnWorkerThread, owner_key_util_,
public_key_,
base::BindOnce(&OwnerKeyLoader::OnPrivateKeyLoaded,
weak_factory_.GetWeakPtr())));
}
// Public key was not found, if the current user is the owner, a new key
// should be generated.
MaybeGenerateNewKey();
}
void OwnerKeyLoader::OnPrivateKeyLoaded(
scoped_refptr<ownership::PrivateKey> private_key,
bool found_in_public_slot) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (IsKeyPresent(private_key)) {
RecordOwnerKeyEvent(OwnerKeyEvent::kOwnerHasKeys,
/*success=*/AreKeysPresent(public_key_, private_key));
RecordOwnerKeyEvent(OwnerKeyEvent::kOwnerKeyInPublicSlot,
/*success=*/found_in_public_slot);
if (ShouldMigrateOwnerKeyToPrivateSlot() && found_in_public_slot) {
// If the key was found in the public slot and the migration is enabled,
// then replace it by generating a new one in the private slot. The old
// key will be deleted by OwnerSettingsServiceAsh when the new key is
// saved as the owner key.
LOG(WARNING) << "Found owner key in public slot, migrating to "
"private slot.";
old_owner_key_ = private_key->ExtractKey();
GenerateNewKey();
RecordOwnerKeyEvent(OwnerKeyEvent::kMigrationToPrivateSlotStarted,
/*success=*/true);
return;
}
if (!ShouldMigrateOwnerKeyToPrivateSlot() &&
!IsStoreOwnerKeyInPrivateSlotEnabled() && !found_in_public_slot) {
// If all experiments are disabled but the key is in the private slot, it
// means they were reverted and it's probably better to migrate the owner
// key back to the public slot.
LOG(WARNING) << "Found owner key in private slot while the private slot "
"experiments are disabled, migrating to public slot.";
old_owner_key_ = private_key->ExtractKey();
GenerateNewKey();
RecordOwnerKeyEvent(OwnerKeyEvent::kMigrationToPublicSlotStarted,
/*success=*/true);
return;
}
// Success: both keys were loaded, the current user is the owner.
return std::move(callback_).Run(std::move(public_key_),
std::move(private_key));
// `this` might be deleted here.
}
// Private key failed to load. Maybe the current user is not the owner and
// doesn't have access to the key. Or the private key was lost. Make the
// decision and generate a new key if needed.
MaybeGenerateNewKey();
}
void OwnerKeyLoader::MaybeGenerateNewKey() {
const user_manager::User* user =
ProfileHelper::Get()->GetUserByProfile(profile_);
if (!UserCanBecomeOwner(user)) {
RecordOwnerKeyEvent(OwnerKeyEvent::kUserNotAnOwnerBasedOnUserType,
/*success=*/IsKeyPresent(public_key_));
return std::move(callback_).Run(public_key_, nullptr);
}
if (user->GetAccountId().GetUserEmail().empty()) {
RecordOwnerKeyEvent(OwnerKeyEvent::kUserNotAnOwnerBasedOnEmptyUsername,
/*success=*/IsKeyPresent(public_key_));
// This is not expected to happen, regular users should have a valid
// username.
return std::move(callback_).Run(public_key_, nullptr);
}
// Check device policies. If the owner key was never generated before, the
// policies will be empty. Also, in theory ChromeOS is allowed to lose the
// policies and recover, so be prepared for them to still be empty.
const enterprise_management::PolicyData* policy_data =
device_settings_service_->policy_data();
if (policy_data && policy_data->has_username()) {
// If the policy says that the current user is the owner, generate a new key
// pair for them.
if (policy_data->username() == user->GetAccountId().GetUserEmail()) {
// Expect public key to be present. It's not likely to lose private and
// public keys simultaneously, they are stored independently.
RecordOwnerKeyEvent(OwnerKeyEvent::kRegeneratingOwnerKeyBasedOnPolicy,
/*success=*/IsKeyPresent(public_key_));
LOG(WARNING) << "The owner key was lost. Generating a new one.";
return GenerateNewKey();
} else {
RecordOwnerKeyEvent(OwnerKeyEvent::kUserNotAnOwnerBasedOnPolicy,
/*success=*/IsKeyPresent(public_key_));
// The current user is not the owner, just return the public key.
return std::move(callback_).Run(std::move(public_key_), nullptr);
// `this` might be deleted here.
}
}
// If the policies are empty, check the local state PrefService.
std::optional<std::string> owner_email =
user_manager::UserManager::Get()->GetOwnerEmail();
if (owner_email.has_value() &&
owner_email.value() == user->GetAccountId().GetUserEmail()) {
// This brunch is more likely to be used before device policies are created
// for the first time, so expect the public key to not be present.
RecordOwnerKeyEvent(OwnerKeyEvent::kRegeneratingOwnerKeyBasedOnLocalState,
/*success=*/!IsKeyPresent(public_key_));
LOG(WARNING) << "Generating new owner key based on local state data.";
return GenerateNewKey();
} else if (owner_email.has_value() &&
owner_email.value() != user->GetAccountId().GetUserEmail()) {
RecordOwnerKeyEvent(OwnerKeyEvent::kUserNotAnOwnerBasedOnLocalState,
/*success=*/IsKeyPresent(public_key_));
return std::move(callback_).Run(std::move(public_key_), nullptr);
// `this` might be deleted here.
}
// If everything else is empty, check DeviceSettingsService. It remembers from
// the login screen whether this is the first user. It's checked after
// policies because it relies on local state (same as `GetOwnerEmail()`) and
// is less trustworthy.
if (device_settings_service_->GetWillEstablishConsumerOwnership()) {
// This should only happen on the first sign in when there's no previous
// public key.
RecordOwnerKeyEvent(OwnerKeyEvent::kEstablishingConsumerOwnership,
/*success=*/!IsKeyPresent(public_key_));
LOG(WARNING) << "Establishing consumer ownership.";
return GenerateNewKey();
}
// If there's no indication from `device_settings_service_` that this is the
// first user, but also no signs of other users ever taking ownership, take
// the ownership for the current user. This is not supposed to happen under
// normal circumstances, but that's what session_manager did before Chrome
// took over owner key generation. The main problem with this case is that if
// a device ends up with no owner and multiple users, the next user that signs
// in will become the owner (instead of the user that was created on the
// device first). But it's still better to have some owner on the device than
// none and it's hard to intentionally destroy all the signs of the existing
// owner to steal the ownership, so it's considered to be a reasonable
// fallback.
if (!IsKeyPresent(public_key_)) {
RecordOwnerKeyEvent(OwnerKeyEvent::kUnsureTakeOwnership,
/*success=*/true);
return GenerateNewKey();
}
RecordOwnerKeyEvent(OwnerKeyEvent::kUnsureUserNotAnOwner,
/*success=*/IsKeyPresent(public_key_));
// If the public key was successfully loaded, then some other user probably
// generated it and they are the owner and hold the private key for it. The
// current user doesn't seem to be the owner, just return the public key.
return std::move(callback_).Run(std::move(public_key_), nullptr);
// `this` might be deleted here.
}
void OwnerKeyLoader::GenerateNewKey() {
// Ensure owner account id is stored for the next time.
if (!user_manager::UserManager::Get()->GetOwnerEmail().has_value()) {
user_manager::User* user =
ash::ProfileHelper::Get()->GetUserByProfile(profile_);
if (user) {
user_manager::UserManager::Get()->RecordOwner(user->GetAccountId());
} else {
// Recording the owner is mainly useful for the next launch of Chrome,
// which doesn't happen in most tests. So just allow skipping it if the
// user was not set up.
CHECK_IS_TEST();
}
}
GetCertDbAndPostOnWorkerThread(
profile_,
base::BindOnce(&GenerateNewOwnerKeyOnWorkerThread, owner_key_util_,
base::BindOnce(&OwnerKeyLoader::OnNewKeyGenerated,
weak_factory_.GetWeakPtr())));
}
void OwnerKeyLoader::OnNewKeyGenerated(
scoped_refptr<ownership::PublicKey> public_key,
scoped_refptr<ownership::PrivateKey> private_key) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (AreKeysPresent(public_key, private_key)) {
RecordOwnerKeyEvent(OwnerKeyEvent::kOwnerKeyGenerated,
/*success=*/(generate_attempt_counter_ == 0));
LOG(WARNING) << "New owner key pair was generated.";
return std::move(callback_).Run(std::move(public_key),
std::move(private_key));
// `this` might be deleted here.
}
if (++generate_attempt_counter_ <= kMaxGenerateAttempts) {
// Key generation is not expected to fail, but it is too important to simply
// give up. Retry up to `kMaxGenerateAttempts` times if needed.
return GenerateNewKey();
}
// This case is not a success in general, but record whether the user will at
// least get the old public key.
RecordOwnerKeyEvent(OwnerKeyEvent::kFailedToGenerateOwnerKey,
/*success=*/IsKeyPresent(public_key_));
LOG(ERROR) << "Failed to generate new owner key.";
// Return at least the public key, if it was loaded. If Chrome is taking
// ownership for the first time, it should be null. If recovering from a lost
// private key, it should be not null.
return std::move(callback_).Run(std::move(public_key_), nullptr);
// `this` might be deleted here.
}
crypto::ScopedSECKEYPrivateKey OwnerKeyLoader::ExtractOldOwnerKey() {
return std::move(old_owner_key_);
}
} // namespace ash