chromium/chrome/browser/ash/kcer/nssdb_migration/kcer_rollback_helper.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/nssdb_migration/kcer_rollback_helper.h"

#include <optional>

#include "ash/constants/ash_features.h"
#include "base/memory/weak_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/task/bind_post_task.h"
#include "chrome/common/pref_names.h"
#include "chromeos/ash/components/tpm/tpm_token_info_getter.h"
#include "chromeos/constants/chromeos_features.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "content/public/browser/browser_thread.h"
#include "crypto/chaps_support.h"

namespace kcer::internal {
namespace {
const char kDefaultErrorMessage[] = "NssDbClientCertsRollback aborted ";
const char kNssDbClientCertsRollbackMessage[] = "NssDbClientCertsRollback ";

extern "C" SECStatus PK11_ClearCertsCache(PK11SlotInfo* slot_info)
    __attribute__((weak));

NssDbClientCertsRollbackEvent GetListSizeEvent(
    const std::vector<SessionChapsClient::ObjectHandle>& handles_list) {
  switch (handles_list.size()) {
    case 0u:
      return NssDbClientCertsRollbackEvent::kRollbackListSize0;
    case 1u:
      return NssDbClientCertsRollbackEvent::kRollbackListSize1;
    case 2u:
      return NssDbClientCertsRollbackEvent::kRollbackListSize2;
    case 3u:
      return NssDbClientCertsRollbackEvent::kRollbackListSize3;
    default:
      return NssDbClientCertsRollbackEvent::kRollbackListSizeAbove3;
  }
}
}  // namespace

void RecordUmaEvent(NssDbClientCertsRollbackEvent event) {
  base::UmaHistogramEnumeration(kNssDbClientCertsRollback, event);
}

void ResetCertCacheData(SessionChapsClient::SlotId slot_id) {
  if (!PK11_ClearCertsCache) {
    return;
  }
  crypto::ScopedPK11SlotList slot_list(PK11_GetAllTokens(
      CKM_INVALID_MECHANISM, PR_FALSE, PR_FALSE, /**wincx*/ nullptr));
  if (!slot_list) {
    return;
  }
  for (PK11SlotListElement* elem = slot_list->head; elem; elem = elem->next) {
    crypto::ScopedPK11Slot slot_info =
        crypto::ScopedPK11Slot(PK11_ReferenceSlot(elem->slot));

    if (!crypto::IsSlotProvidedByChaps(slot_info.get())) {
      continue;
    }

    kcer::SessionChapsClient::SlotId cur_slot_id =
        kcer::SessionChapsClient::SlotId(PK11_GetSlotID(slot_info.get()));

    if (slot_id == cur_slot_id) {
      SECStatus result = PK11_ClearCertsCache(slot_info.get());
      if (result != SECSuccess) {
        RecordUmaEvent(NssDbClientCertsRollbackEvent::kCertCacheResetFailed);
        LOG(ERROR) << "Resetting slot certificates cache has failed with:"
                   << result;
      } else {
        RecordUmaEvent(
            NssDbClientCertsRollbackEvent::kCertCacheResetSuccessful);
      }
      break;
    }
  }
}

KcerRollbackHelper::KcerRollbackHelper(
    HighLevelChapsClient* high_level_chaps_client,
    PrefService* prefs_service)
    : high_level_chaps_client_(high_level_chaps_client),
      prefs_service_(prefs_service) {}

KcerRollbackHelper::~KcerRollbackHelper() = default;

// static
bool KcerRollbackHelper::IsChapsRollbackRequired(PrefService* pref_service) {
  if (!pref_service) {
    return false;
  }
  bool is_only_rollback_active =
      ash::features::IsNssDbClientCertsRollbackEnabled() &&
      !chromeos::features::IsPkcs12ToChapsDualWriteEnabled();

  if (is_only_rollback_active) {
    const PrefService::Preference* rollback_flag =
        pref_service->FindPreference(prefs::kNssChapsDualWrittenCertsExist);
    if (!rollback_flag) {
      RecordUmaEvent(NssDbClientCertsRollbackEvent::kRollbackFlagNotPresent);
      return false;
    }
    RecordUmaEvent(NssDbClientCertsRollbackEvent::kRollbackFlagPresent);
    return rollback_flag->GetValue()->GetBool();
  }
  return false;
}

std::optional<AccountId> GetAccountId() {
  if (!user_manager::UserManager::IsInitialized()) {
    LOG(ERROR) << kDefaultErrorMessage << "user manager is not initialised!";
    return std::nullopt;
  }

  const user_manager::User* active_user =
      user_manager::UserManager::Get()->GetActiveUser();
  if (!active_user) {
    LOG(ERROR) << kDefaultErrorMessage << "no active user!";
    return std::nullopt;
  }
  return std::make_optional(active_user->GetAccountId());
}

void KcerRollbackHelper::PerformRollback() const {
  RecordUmaEvent(NssDbClientCertsRollbackEvent::kRollbackScheduled);
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(&KcerRollbackHelper::FindUserToken,
                     weak_factory_.GetWeakPtr()),
      base::Seconds(30));
}

void KcerRollbackHelper::FindUserToken() const {
  RecordUmaEvent(NssDbClientCertsRollbackEvent::kRollbackStarted);
  std::optional<AccountId> account_id = GetAccountId();
  if (!account_id.has_value()) {
    LOG(ERROR) << kDefaultErrorMessage << "no account_id";
    RecordUmaEvent(NssDbClientCertsRollbackEvent::kFailedNoUserAccountId);
    return;
  }

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

  ash::TPMTokenInfoGetter* user_token_info_getter =
      scoped_user_token_info_getter.get();
  // Pass scoped_user_token_info_getter to keep it alive.
  user_token_info_getter->Start(base::BindOnce(
      &KcerRollbackHelper::FindUserSlotId, weak_factory_.GetWeakPtr(),
      std::move(scoped_user_token_info_getter)));
}

void KcerRollbackHelper::FindUserSlotId(
    std::unique_ptr<ash::TPMTokenInfoGetter> scoped_user_token_info_getter,
    std::optional<user_data_auth::TpmTokenInfo> user_token_info) const {
  SessionChapsClient::SlotId user_slot_id;
  if (user_token_info) {
    user_slot_id = SessionChapsClient::SlotId(
        static_cast<uint64_t>(user_token_info->slot()));
  } else {
    LOG(ERROR) << kDefaultErrorMessage << "no slot info was found";
    RecordUmaEvent(NssDbClientCertsRollbackEvent::kFailedNoSlotInfoFound);
    return;
  }
  SelectAndDeleteDoubleWrittenObjects(user_slot_id);
}

void KcerRollbackHelper::SelectAndDeleteDoubleWrittenObjects(
    SessionChapsClient::SlotId slot_id) const {
  constexpr chromeos::PKCS11_CK_BBOOL kTrue = chromeos::PKCS11_CK_TRUE;

  chaps::AttributeList attributes;
  kcer::AddAttribute(attributes,
                     pkcs11_custom_attributes::kCkaChromeOsMigratedFromNss,
                     kcer::MakeSpan(&kTrue));

  SessionChapsClient::FindObjectsCallback find_callback =
      base::BindOnce(&KcerRollbackHelper::DestroyObjectsInSlot,
                     weak_factory_.GetWeakPtr(), slot_id);
  return high_level_chaps_client_->FindObjects(slot_id, std::move(attributes),
                                               std::move(find_callback));
}

void KcerRollbackHelper::DestroyObjectsInSlot(
    SessionChapsClient::SlotId slot_id,
    std::vector<SessionChapsClient::ObjectHandle> handles_list,
    uint32_t result_code) const {
  RecordUmaEvent(GetListSizeEvent(handles_list));
  SessionChapsClient::DestroyObjectCallback destroy_objects_callback =
      base::BindOnce(&KcerRollbackHelper::ResetCacheAndRollbackFlag,
                     weak_factory_.GetWeakPtr(), slot_id);

  return high_level_chaps_client_->DestroyObjectsWithRetries(
      slot_id, handles_list, std::move(destroy_objects_callback));
}

void KcerRollbackHelper::ResetCacheAndRollbackFlag(
    SessionChapsClient::SlotId slot_id,
    uint32_t result_code) const {
  ResetCertCacheData(slot_id);
  if (result_code != chromeos::PKCS11_CKR_OK) {
    LOG(ERROR) << "Not all objects were deleted due to" << result_code;
    RecordUmaEvent(NssDbClientCertsRollbackEvent::kFailedNotAllObjectsDeleted);
    return;
  }

  const PrefService::Preference* rollback_flag =
      prefs_service_->FindPreference(prefs::kNssChapsDualWrittenCertsExist);
  if (!rollback_flag) {
    LOG(ERROR) << "Resetting " << kNssDbClientCertsRollbackMessage
               << "flag while it was not set";
    RecordUmaEvent(
        NssDbClientCertsRollbackEvent::kFailedFlagResetNotSuccessful);
    return;
  }
  prefs_service_->ClearPref(prefs::kNssChapsDualWrittenCertsExist);
  RecordUmaEvent(NssDbClientCertsRollbackEvent::kRollbackSuccessful);
}

}  // namespace kcer::internal