chromium/chrome/browser/ash/kcer/kcer_factory_ash.cc

// Copyright 2024 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/ash/kcer/kcer_factory_ash.h"

#include "ash/constants/ash_switches.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "base/check_is_test.h"
#include "base/feature_list.h"
#include "base/task/bind_post_task.h"
#include "chrome/browser/ash/crosapi/chaps_service_ash.h"
#include "chrome/browser/ash/crosapi/crosapi_ash.h"
#include "chrome/browser/ash/crosapi/crosapi_manager.h"
#include "chrome/browser/ash/profiles/profile_helper.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/pref_names.h"
#include "chromeos/ash/components/browser_context_helper/browser_context_types.h"
#include "chromeos/ash/components/network/system_token_cert_db_storage.h"
#include "chromeos/ash/components/tpm/tpm_token_info_getter.h"
#include "chromeos/components/kcer/chaps/session_chaps_client.h"
#include "chromeos/components/kcer/extra_instances.h"
#include "chromeos/components/kcer/kcer.h"
#include "chromeos/components/kcer/kcer_token.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "content/public/browser/browser_thread.h"
#include "net/cert/nss_cert_database.h"

namespace kcer {
namespace {

PrefService* GetActiveUserPrefs() {
  if (!user_manager::UserManager::IsInitialized()) {
    return nullptr;
  }
  user_manager::UserManager* manager = user_manager::UserManager::Get();
  if (!manager) {
    return nullptr;
  }
  user_manager::User* user = manager->GetActiveUser();
  if (!user) {
    return nullptr;
  }
  return user->GetProfilePrefs();
}

const user_manager::User* GetUserByContext(content::BrowserContext* context) {
  if (!context) {
    return nullptr;
  }
  Profile* profile = Profile::FromBrowserContext(context);
  if (!profile) {
    return nullptr;
  }
  return ash::ProfileHelper::Get()->GetUserByProfile(profile);
}

// Returns the currently valid ChapsService. Might return a nullptr during early
// initialization and after shutdown.
crosapi::mojom::ChapsService* GetChapsService() {
  crosapi::mojom::ChapsService* chaps_service = nullptr;
  if (crosapi::CrosapiManager::IsInitialized() &&
      crosapi::CrosapiManager::Get() &&
      crosapi::CrosapiManager::Get()->crosapi_ash()) {
    chaps_service =
        crosapi::CrosapiManager::Get()->crosapi_ash()->chaps_service_ash();
  }
  if (!chaps_service) {
    LOG(ERROR) << "ChapsService mojo interface is not available";
  }
  return chaps_service;
}

KcerFactoryAsh::UniqueSlotId GetUniqueId(PK11SlotInfo* nss_slot) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
  return {PK11_GetModuleID(nss_slot), PK11_GetSlotID(nss_slot)};
}

base::WeakPtr<internal::KcerToken> PrepareOneTokenForNss(
    KcerFactoryAsh::KcerTokenMapNss* token_map,
    HighLevelChapsClient* chaps_client,
    crypto::ScopedPK11Slot nss_slot,
    Token token_type) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);

  if (!nss_slot) {
    return nullptr;
  }

  KcerFactoryAsh::UniqueSlotId id = GetUniqueId(nss_slot.get());
  if (id == GetUniqueId(PK11_GetInternalKeySlot())) {
    // NSSCertDatabase uses the internal slot as a dummy slot in some cases.
    // It's read-only and doesn't contain any certs. Kcer will map the internal
    // slot into a nullptr KcerToken. This can introduce behavioral changes,
    // such as "an empty list of certs is returned" -> "an error is returned",
    // but generally should work correctly.
    return nullptr;
  }

  auto iter = token_map->find(id);
  if (iter != token_map->end()) {
    return iter->second->GetWeakPtr();
  }

  std::unique_ptr<internal::KcerToken> new_token =
      internal::KcerToken::CreateForNss(token_type, chaps_client);
  new_token->InitializeForNss(std::move(nss_slot));
  base::WeakPtr<internal::KcerToken> result = new_token->GetWeakPtr();
  (*token_map)[id] = std::move(new_token);
  return result;
}

// Finds KcerTokens in `token_map` for the slots from `nss_db` (or creates new
// ones) and returns weak pointers to them through the `callback`.
void PrepareTokensForNss(KcerFactoryAsh::KcerTokenMapNss* token_map,
                         HighLevelChapsClient* chaps_client,
                         KcerFactoryAsh::InitializeOnUIThreadCallback callback,
                         net::NSSCertDatabase* nss_db) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
  CHECK(token_map);
  if (!nss_db) {
    // Usually should not happen. Maybe possible on shutdown.
    return;
  }

  base::WeakPtr<internal::KcerToken> user_token_ptr = PrepareOneTokenForNss(
      token_map, chaps_client, nss_db->GetPrivateSlot(), Token::kUser);
  base::WeakPtr<internal::KcerToken> device_token_ptr = PrepareOneTokenForNss(
      token_map, chaps_client, nss_db->GetSystemSlot(), Token::kDevice);

  return std::move(callback).Run(std::move(user_token_ptr),
                                 std::move(device_token_ptr));
}

void GetNssDbOnIOThread(
    NssCertDatabaseGetter nss_db_getter,
    base::OnceCallback<void(net::NSSCertDatabase* nss_db)> callback) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::IO);

  auto split_callback = base::SplitOnceCallback(std::move(callback));

  net::NSSCertDatabase* cert_db =
      std::move(nss_db_getter).Run(std::move(split_callback.first));
  // If the NSS database was already available, |cert_db| is non-null and
  // |did_get_cert_db_callback| has not been called. Call it explicitly.
  if (cert_db) {
    return std::move(split_callback.second).Run(cert_db);
  }
}

}  // namespace

BASE_FEATURE(kKcerWithoutNss,
             "kKcerWithoutNss",
             base::FeatureState::FEATURE_DISABLED_BY_DEFAULT);

//====================== KcerService ===========================================

KcerFactoryAsh::KcerService::KcerService(
    std::unique_ptr<internal::KcerImpl> kcer_instance)
    : kcer(std::move(kcer_instance)) {}

KcerFactoryAsh::KcerService::~KcerService() = default;

//====================== KcerFactoryAsh ========================================

// static
base::WeakPtr<Kcer> KcerFactoryAsh::GetKcer(Profile* profile) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  return GetInstance()->GetKcerImpl(profile);
}

// static
KcerFactoryAsh* KcerFactoryAsh::GetInstance() {
  static base::NoDestructor<KcerFactoryAsh> instance;
  return instance.get();
}

// static
bool KcerFactoryAsh::IsHighLevelChapsClientInitialized() {
  // Also check `session_chaps_client_` because it's mandatory dependency.
  return (GetInstance()->session_chaps_client_ &&
          GetInstance()->high_level_chaps_client_);
}

// static
void KcerFactoryAsh::RecordPkcs12CertDualWritten() {
  GetInstance()->RecordPkcs12CertDualWrittenImpl();
}

KcerFactoryAsh::KcerFactoryAsh()
    : ProfileKeyedServiceFactory(
          "KcerFactoryAsh",
          // See chrome/browser/profiles/profile_keyed_service_factory.md for
          // descriptions of different profile types.
          ProfileSelections::Builder()
              // Chrome allows using keys and client certificates inside
              // incognito and they are shared with the original profile.
              // Redirect off-the-record profiles to their originals, so a
              // single shared instance of Kcer is created for them.
              .WithRegular(ProfileSelection::kRedirectedToOriginal)
              // Guest profiles should behave the same as regular profiles, i.e.
              // normal guest profiles get their own instance of Kcer,
              // off-the-record guest profiles are redirected to their original
              // guest profiles.
              .WithGuest(ProfileSelection::kRedirectedToOriginal)
              // System profiles don't exist in Ash.
              .WithSystem(ProfileSelection::kNone)
              // Ash internal profiles are used by ash for the sign-in and lock
              // screens. All code is expected to use DeviceKcer on these
              // screens (will also be automatically returned from GetKcer()).
              .WithAshInternals(ProfileSelection::kNone)
              .Build()) {
  DependsOn(NssServiceFactory::GetInstance());

  // Unretained is safe, `this` is never destroyed.
  content::GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(&KcerFactoryAsh::Initialize, base::Unretained(this)));
}

KcerFactoryAsh::~KcerFactoryAsh() = default;

void KcerFactoryAsh::Initialize() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (UseKcerWithoutNss()) {
    StartInitializingDeviceKcerWithoutNss();
  } else {
    StartInitializingDeviceKcerForNss();
  }

  // Check whether prefs for the active user are already available. If yes,
  // continue with the potential rollback, otherwise observe session_controller
  // and wait for the user. If Chrome is restarted with the correct user
  // instead of adding a new one on user login, then
  // OnActiveUserPrefServiceChanged() might not be called.
  PrefService* pref_service = GetActiveUserPrefs();
  if (pref_service) {
    return MaybeScheduleRollbackForCertDoubleWrite(pref_service);
  }
  if (ash::Shell::HasInstance() && ash::Shell::Get()->session_controller()) {
    ash::Shell::Get()->session_controller()->AddObserver(this);
  } else {
    CHECK_IS_TEST();
  }
}

void KcerFactoryAsh::RegisterProfilePrefs(
    user_prefs::PrefRegistrySyncable* registry) {
  registry->RegisterBooleanPref(prefs::kNssChapsDualWrittenCertsExist,
                                /*default_value=*/false);
}

base::WeakPtr<Kcer> KcerFactoryAsh::GetKcerImpl(Profile* profile) {
  if (ash::IsSigninBrowserContext(profile) ||
      ash::IsLockScreenBrowserContext(profile)) {
    if (ash::switches::IsSigninFrameClientCertsEnabled()) {
      // Sign-in and lock screen profiles should only have access to the device
      // token.
      return ExtraInstances::GetDeviceKcer();
    } else {
      return ExtraInstances::GetEmptyKcer();
    }
  }

  if (ash::IsLockScreenAppBrowserContext(profile)) {
    // Returning an empty Kcer here is not a strict requirement, but seem to be
    // the status quo for now.
    return ExtraInstances::GetEmptyKcer();
  }

  if (!ash::IsUserBrowserContext(profile)) {
    return ExtraInstances::GetEmptyKcer();
  }

  content::BrowserContext* context = GetBrowserContextToUse(profile);
  if (!context) {
    return ExtraInstances::GetEmptyKcer();
  }
  if (profile->IsSystemProfile()) {
    // System profiles are not expected to exist in Ash, but add a fallback for
    // them just in case.
    return ExtraInstances::GetEmptyKcer();
  }
  if (!ash::IsUserBrowserContext(profile)) {
    // This should be returned for Ash Internal profiles, primarily for the
    // SignIn profile.
    return ExtraInstances::GetDeviceKcer();
  }

  KcerService* service = static_cast<KcerService*>(
      GetServiceForBrowserContext(context, /*create=*/true));
  if (!service) {
    return ExtraInstances::GetEmptyKcer();
  }

  return service->kcer->GetWeakPtr();
}

void KcerFactoryAsh::RecordPkcs12CertDualWrittenImpl() {
  user_manager::UserManager* manager = user_manager::UserManager::Get();
  if (!manager) {
    return;
  }
  user_manager::User* user = manager->GetActiveUser();
  if (!user) {
    return;
  }
  PrefService* prefs = user->GetProfilePrefs();
  if (!prefs) {
    return;
  }
  prefs->SetBoolean(prefs::kNssChapsDualWrittenCertsExist, true);
}

bool KcerFactoryAsh::ServiceIsCreatedWithBrowserContext() const {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  // This should be true because Kcer for the primary context needs to be
  // created as soon as possible. It is used by the components through
  // kcer::ExtraInstance::GetDefaultKcer() and on consumer devices to determine
  // whether the current user is the owner.
  return true;
}

std::unique_ptr<KeyedService>
KcerFactoryAsh::BuildServiceInstanceForBrowserContext(
    content::BrowserContext* context) const {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  auto new_kcer = std::make_unique<internal::KcerImpl>();

  // This code assumes that by the time BuildServiceInstanceForBrowserContext is
  // called, the context is initialized enough for IsPrimaryContext() to work
  // correctly.
  if (ash::ProfileHelper::IsPrimaryProfile(
          Profile::FromBrowserContext(context))) {
    ExtraInstances::Get()->SetDefaultKcer(new_kcer->GetWeakPtr());
  }

  // Run StartInitializingKcerInstance asynchronously, so the service is fully
  // created and registered before continuing.
  content::GetUIThreadTaskRunner({})->PostTask(
      FROM_HERE,
      base::BindOnce(&KcerFactoryAsh::StartInitializingKcerInstance,
                     base::Unretained(const_cast<KcerFactoryAsh*>(this)),
                     new_kcer->GetWeakPtr(),
                     // TODO(crbug.com/40061562): Remove
                     // `UnsafeDanglingUntriaged`
                     base::UnsafeDanglingUntriaged(context)));

  return std::make_unique<KcerService>(std::move(new_kcer));
}

bool KcerFactoryAsh::UseKcerWithoutNss() const {
  return base::FeatureList::IsEnabled(kKcerWithoutNss);
}

void KcerFactoryAsh::StartInitializingKcerInstance(
    base::WeakPtr<internal::KcerImpl> kcer_service,
    content::BrowserContext* context) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (!kcer_service) {
    return;
  }

  if (UseKcerWithoutNss()) {
    return StartInitializingKcerWithoutNss(std::move(kcer_service), context);
  } else {
    return StartInitializingKcerForNss(std::move(kcer_service), context);
  }
}

void KcerFactoryAsh::StartInitializingKcerWithoutNss(
    base::WeakPtr<internal::KcerImpl> kcer_service,
    content::BrowserContext* context) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (!kcer_service) {
    return;
  }

  const user_manager::User* user = GetUserByContext(context);
  if (!user) {
    return KcerFactoryAsh::InitializeKcerInstanceWithoutNss(
        kcer_service, /*user_token_id=*/std::nullopt,
        /*device_token_id=*/std::nullopt);
  }

  if (user->IsAffiliated()) {
    return GetDeviceTokenInfo(std::move(kcer_service), user->GetAccountId());
  }

  return GetUserTokenInfo(std::move(kcer_service), user->GetAccountId(),
                          /*scoped_device_token_info_getter=*/nullptr,
                          /*device_token_info=*/std::nullopt);
}

void KcerFactoryAsh::GetDeviceTokenInfo(
    base::WeakPtr<internal::KcerImpl> kcer_service,
    AccountId account_id) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (!kcer_service) {
    return;
  }

  std::unique_ptr<ash::TPMTokenInfoGetter> scoped_device_token_info_getter =
      ash::TPMTokenInfoGetter::CreateForSystemToken(
          ash::CryptohomePkcs11Client::Get(),
          base::SingleThreadTaskRunner::GetCurrentDefault());
  ash::TPMTokenInfoGetter* device_token_info_getter =
      scoped_device_token_info_getter.get();

  // Bind `scoped_device_token_info_getter` to the callback to ensure it does
  // not go away before TPM token info is fetched. `Unretained` is safe, the
  // factory is never destroyed.
  device_token_info_getter->Start(
      base::BindOnce(&KcerFactoryAsh::GetUserTokenInfo, base::Unretained(this),
                     std::move(kcer_service), std::move(account_id),
                     std::move(scoped_device_token_info_getter)));
}

void KcerFactoryAsh::GetUserTokenInfo(
    base::WeakPtr<internal::KcerImpl> kcer_service,
    AccountId account_id,
    std::unique_ptr<ash::TPMTokenInfoGetter> scoped_device_token_info_getter,
    std::optional<user_data_auth::TpmTokenInfo> device_token_info) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (!kcer_service) {
    return;
  }

  std::unique_ptr<ash::TPMTokenInfoGetter> scoped_user_token_info_getter =
      ash::TPMTokenInfoGetter::CreateForUserToken(
          account_id, ash::CryptohomePkcs11Client::Get(),
          base::SingleThreadTaskRunner::GetCurrentDefault());
  ash::TPMTokenInfoGetter* user_token_info_getter =
      scoped_user_token_info_getter.get();

  // Bind `scoped_user_token_info_getter` to the callback to ensure it does not
  // go away before TPM token info is fetched. `Unretained` is safe, the factory
  // is never destroyed.
  user_token_info_getter->Start(
      base::BindOnce(&KcerFactoryAsh::GotAllTokenInfos, base::Unretained(this),
                     std::move(kcer_service), std::move(device_token_info),
                     std::move(scoped_user_token_info_getter)));
}

void KcerFactoryAsh::GotAllTokenInfos(
    base::WeakPtr<internal::KcerImpl> kcer_service,
    std::optional<user_data_auth::TpmTokenInfo> device_token_info,
    std::unique_ptr<ash::TPMTokenInfoGetter> scoped_user_token_info_getter,
    std::optional<user_data_auth::TpmTokenInfo> user_token_info) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (!kcer_service) {
    return;
  }

  std::optional<SessionChapsClient::SlotId> user_token_id;
  if (user_token_info) {
    user_token_id = SessionChapsClient::SlotId(
        static_cast<uint64_t>(user_token_info->slot()));
  }
  std::optional<SessionChapsClient::SlotId> device_token_id;
  if (device_token_info) {
    device_token_id = SessionChapsClient::SlotId(
        static_cast<uint64_t>(device_token_info->slot()));
  }

  InitializeKcerInstanceWithoutNss(kcer_service, user_token_id,
                                   device_token_id);
}

base::WeakPtr<internal::KcerToken> KcerFactoryAsh::GetTokenWithoutNss(
    std::optional<SessionChapsClient::SlotId> token_id,
    Token token_type) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  if (!token_id) {
    return nullptr;
  }

  auto iter = chaps_tokens_ui_.find(token_id.value());
  if (iter != chaps_tokens_ui_.end()) {
    return iter->second->GetWeakPtr();
  }

  if (!EnsureHighLevelChapsClientInitialized()) {
    return nullptr;
  }

  std::unique_ptr<internal::KcerToken> new_token =
      internal::KcerToken::CreateWithoutNss(token_type,
                                            high_level_chaps_client_.get());
  new_token->InitializeWithoutNss(token_id.value());
  base::WeakPtr<internal::KcerToken> result = new_token->GetWeakPtr();
  chaps_tokens_ui_[token_id.value()] = std::move(new_token);
  return result;
}

bool KcerFactoryAsh::EnsureHighLevelChapsClientInitialized() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (IsHighLevelChapsClientInitialized()) {
    return true;
  }

  session_chaps_client_ = std::make_unique<SessionChapsClientImpl>(
      base::BindRepeating(&GetChapsService));
  high_level_chaps_client_ =
      std::make_unique<HighLevelChapsClientImpl>(session_chaps_client_.get());

  return IsHighLevelChapsClientInitialized();
}

void KcerFactoryAsh::InitializeKcerInstanceWithoutNss(
    base::WeakPtr<internal::KcerImpl> kcer_service,
    std::optional<SessionChapsClient::SlotId> user_token_id,
    std::optional<SessionChapsClient::SlotId> device_token_id) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (!kcer_service) {
    return;
  }

  kcer_service->Initialize(content::GetUIThreadTaskRunner({}),
                           GetTokenWithoutNss(user_token_id, Token::kUser),
                           GetTokenWithoutNss(device_token_id, Token::kDevice));
}

void KcerFactoryAsh::StartInitializingKcerForNss(
    base::WeakPtr<internal::KcerImpl> kcer_service,
    content::BrowserContext* context) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (!kcer_service) {
    return;
  }

  NssService* nss_service = NssServiceFactory::GetForContext(context);
  if (!nss_service) {
    return InitializeKcerInstanceForNss(std::move(kcer_service), nullptr,
                                        nullptr);
  }

  EnsureHighLevelChapsClientInitialized();

  auto nss_db_getter = nss_service->CreateNSSCertDatabaseGetterForIOThread();

  // Unretained is safe, the factory is never destroyed. But the service can be
  // destroyed. Keep a weak pointer to it, so the initialization methods can
  // check whether they need to continue.
  auto initialize_callback_ui = base::BindPostTask(
      content::GetUIThreadTaskRunner({}),
      base::BindOnce(&KcerFactoryAsh::InitializeKcerInstanceForNss,
                     base::Unretained(this), std::move(kcer_service)));

  auto prepare_tokens_on_io =
      base::BindOnce(GetPrepareTokensForNssOnIOThreadFunctor(),
                     std::move(initialize_callback_ui));

  content::GetIOThreadTaskRunner({})->PostTask(
      FROM_HERE, base::BindOnce(&GetNssDbOnIOThread, std::move(nss_db_getter),
                                std::move(prepare_tokens_on_io)));
}

base::OnceCallback<void(KcerFactoryAsh::InitializeOnUIThreadCallback,
                        net::NSSCertDatabase*)>
KcerFactoryAsh::GetPrepareTokensForNssOnIOThreadFunctor() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  EnsureHighLevelChapsClientInitialized();
  // `high_level_chaps_client_` is never destroyed (as a part of the factory),
  // so it's ok to pass it by a raw pointer. PrepareTokensForNss() will run on
  // the IO thread, but it only needs `high_level_chaps_client_` to pass it
  // further, where it will only be used on the UI thread.
  return base::BindOnce(&PrepareTokensForNss, &nss_tokens_io_,
                        high_level_chaps_client_.get());
}

void KcerFactoryAsh::InitializeKcerInstanceForNss(
    base::WeakPtr<internal::KcerImpl> kcer_service,
    base::WeakPtr<internal::KcerToken> user_token,
    base::WeakPtr<internal::KcerToken> device_token) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  if (!kcer_service) {
    return;
  }
  kcer_service->Initialize(content::GetIOThreadTaskRunner({}), user_token,
                           device_token);
}

void KcerFactoryAsh::StartInitializingDeviceKcerWithoutNss() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  std::unique_ptr<ash::TPMTokenInfoGetter> scoped_device_token_info_getter =
      ash::TPMTokenInfoGetter::CreateForSystemToken(
          ash::CryptohomePkcs11Client::Get(),
          base::SingleThreadTaskRunner::GetCurrentDefault());
  ash::TPMTokenInfoGetter* device_token_info_getter =
      scoped_device_token_info_getter.get();

  // Bind |device_token_info_getter| to the callback to ensure it does not go
  // away before TPM token info is fetched. `Unretained` is safe, the factory is
  // never destroyed.
  device_token_info_getter->Start(base::BindOnce(
      &KcerFactoryAsh::InitializeDeviceKcerWithoutNss, base::Unretained(this),
      std::move(scoped_device_token_info_getter)));
}

void KcerFactoryAsh::InitializeDeviceKcerWithoutNss(
    std::unique_ptr<ash::TPMTokenInfoGetter> scoped_device_token_info_getter,
    std::optional<user_data_auth::TpmTokenInfo> device_token_info) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  std::optional<SessionChapsClient::SlotId> device_token_id;
  if (device_token_info) {
    device_token_id = SessionChapsClient::SlotId(
        static_cast<uint64_t>(device_token_info->slot()));
  }

  base::WeakPtr<internal::KcerToken> device_token;
  if (device_token_id) {
    device_token = GetTokenWithoutNss(device_token_id, Token::kDevice);
  }

  ExtraInstances::Get()->InitializeDeviceKcer(
      content::GetIOThreadTaskRunner({}), std::move(device_token));
}

void KcerFactoryAsh::StartInitializingDeviceKcerForNss() {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

  if (!ash::SystemTokenCertDbStorage::Get()) {
    CHECK_IS_TEST();
    return;
  }

  auto initialize_callback_ui = base::BindPostTask(
      content::GetUIThreadTaskRunner({}),
      base::BindOnce(&KcerFactoryAsh::InitializeDeviceKcerForNss,
                     base::Unretained(this)));
  auto prepare_tokens_on_io = base::BindPostTask(
      content::GetIOThreadTaskRunner({}),
      base::BindOnce(GetPrepareTokensForNssOnIOThreadFunctor(),
                     std::move(initialize_callback_ui)));

  // SystemTokenCertDbStorage looks suspicious because it returns the database
  // to the UI thread and not to the IO thread like NssService. For now just
  // forward the database immediately to the IO thread (which is done implicitly
  // by binding `prepare_tokens_on_io` to the IO thread). The "done" callback
  // will return the pointer to the device token to the UI thread.
  ash::SystemTokenCertDbStorage::Get()->GetDatabase(
      std::move(prepare_tokens_on_io));
}

void KcerFactoryAsh::InitializeDeviceKcerForNss(
    base::WeakPtr<internal::KcerToken> /*user_token*/,
    base::WeakPtr<internal::KcerToken> device_token) {
  DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
  ExtraInstances::Get()->InitializeDeviceKcer(
      content::GetIOThreadTaskRunner({}), std::move(device_token));
}

void KcerFactoryAsh::OnActiveUserPrefServiceChanged(PrefService* pref_service) {
  MaybeScheduleRollbackForCertDoubleWrite(pref_service);
}

void KcerFactoryAsh::MaybeScheduleRollbackForCertDoubleWrite(
    PrefService* pref_service) {
  if (rollback_helper_) {
    rollback_helper_.reset();
  }
  if (!pref_service) {
    return;
  }
  EnsureHighLevelChapsClientInitialized();
  if (internal::KcerRollbackHelper::IsChapsRollbackRequired(pref_service)) {
    rollback_helper_ = std::make_unique<internal::KcerRollbackHelper>(
        high_level_chaps_client_.get(), pref_service);

    return rollback_helper_->PerformRollback();
  }
}

}  // namespace kcer