// 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