// 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.
// These are unit tests for KcerRollbackHelper.
// KcerRollbackHelper is used for cleaning double written keys and certificates
// from software backed Chaps storage.
#include "chrome/browser/ash/kcer/nssdb_migration/kcer_rollback_helper.h"
#include <gmock/gmock.h>
#include <memory>
#include "ash/constants/ash_features.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/gmock_move_support.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/ash/login/users/fake_chrome_user_manager.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chromeos/components/kcer/chaps/mock_high_level_chaps_client.h"
#include "chromeos/constants/chromeos_features.h"
#include "chromeos/dbus/tpm_manager/tpm_manager_client.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
#include "components/sync_preferences/pref_service_mock_factory.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using base::Bucket;
using base::test::RunOnceCallback;
using base::test::RunOnceCallbackRepeatedly;
using testing::_;
namespace kcer::internal {
namespace {
const bool kDefaultFalse = false;
constexpr char kEmailId[] = "[email protected]";
constexpr uint32_t kSuccess = chromeos::PKCS11_CKR_OK;
class KcerRollbackHelperTest : public testing::Test {
public:
KcerRollbackHelperTest()
: task_environment_(base::test::TaskEnvironment::TimeSource::MOCK_TIME,
base::test::TaskEnvironment::MainThreadType::UI),
scoped_user_manager_(std::make_unique<ash::FakeChromeUserManager>()) {}
~KcerRollbackHelperTest() override = default;
void SetUp() override {
InitPrefStore();
InitTpmState();
InitDefaultExperimentState();
pref_registry_->RegisterBooleanPref(prefs::kNssChapsDualWrittenCertsExist,
kDefaultFalse);
rollback_helper_ = std::make_unique<internal::KcerRollbackHelper>(
&chaps_client_, pref_service_.get());
}
void PerformRollbackWithFastForward() {
rollback_helper_->PerformRollback();
task_environment_.FastForwardBy(base::Seconds(30));
task_environment_.RunUntilIdle();
}
bool IsRollbackRequired() {
return internal::KcerRollbackHelper::IsChapsRollbackRequired(
pref_service_.get());
}
protected:
content::BrowserTaskEnvironment task_environment_;
base::test::ScopedFeatureList feature_list_;
MockHighLevelChapsClient chaps_client_;
scoped_refptr<PrefRegistrySimple> pref_registry_;
std::unique_ptr<PrefService> pref_service_;
std::unique_ptr<internal::KcerRollbackHelper> rollback_helper_;
user_manager::ScopedUserManager scoped_user_manager_;
base::HistogramTester histogram_tester_;
void InitUser() {
ash::FakeChromeUserManager* fake_user_manager =
static_cast<ash::FakeChromeUserManager*>(
user_manager::UserManager::Get());
fake_user_manager->AddUser(AccountId::FromUserEmail(kEmailId));
}
private:
void InitPrefStore() {
scoped_refptr<TestingPrefStore> user_pref_store =
base::MakeRefCounted<TestingPrefStore>();
sync_preferences::PrefServiceMockFactory factory;
factory.set_user_prefs(user_pref_store);
pref_registry_ = base::MakeRefCounted<PrefRegistrySimple>();
pref_service_ = factory.Create(pref_registry_.get());
}
void InitTpmState() {
if (!ash::CryptohomePkcs11Client::Get()) {
ash::CryptohomePkcs11Client::Get()->InitializeFake();
}
chromeos::TpmManagerClient::Get()->InitializeFake();
}
void InitDefaultExperimentState() {
feature_list_.InitWithFeatures(
/*enabled_features=*/{ash::features::kEnableNssDbClientCertsRollback},
/*disabled_features=*/{
chromeos::features::kEnablePkcs12ToChapsDualWrite});
}
};
// No user, calling PerformRollback() has aborted because no user id.
TEST_F(KcerRollbackHelperTest, NoUserInitialisedRollbackFailed) {
PerformRollbackWithFastForward();
EXPECT_THAT(
histogram_tester_.GetAllSamples(kNssDbClientCertsRollback),
BucketsInclude(
Bucket(NssDbClientCertsRollbackEvent::kRollbackScheduled, 1),
Bucket(NssDbClientCertsRollbackEvent::kRollbackStarted, 1),
Bucket(NssDbClientCertsRollbackEvent::kFailedNoUserAccountId, 1),
Bucket(NssDbClientCertsRollbackEvent::kRollbackSuccessful, 0)));
}
// kNssChapsDualWrittenCertsExist is registered and false by default.
// Calling PerformRollback() has no errors.
TEST_F(KcerRollbackHelperTest, IsChapsRollbackRequiredFalse) {
InitUser();
EXPECT_FALSE(IsRollbackRequired());
std::vector<SessionChapsClient::ObjectHandle> object_list;
EXPECT_CALL(chaps_client_, FindObjects(_, _, _))
.WillOnce(RunOnceCallback<2>(object_list, kSuccess));
EXPECT_CALL(chaps_client_, DestroyObjectsWithRetries(_, object_list, _))
.WillOnce(RunOnceCallback<2>(kSuccess));
PerformRollbackWithFastForward();
EXPECT_FALSE(IsRollbackRequired());
EXPECT_THAT(
histogram_tester_.GetAllSamples(kNssDbClientCertsRollback),
BucketsInclude(
Bucket(NssDbClientCertsRollbackEvent::kRollbackScheduled, 1),
Bucket(NssDbClientCertsRollbackEvent::kRollbackStarted, 1),
Bucket(NssDbClientCertsRollbackEvent::kRollbackListSize0, 1),
Bucket(NssDbClientCertsRollbackEvent::kRollbackSuccessful, 1)));
}
// PerformRollback() is executed when rollback is required, rollback is no
// more required.
TEST_F(KcerRollbackHelperTest, DestroyDoubleWrittenObjectsInChapsTest) {
InitUser();
pref_service_->SetBoolean(prefs::kNssChapsDualWrittenCertsExist, true);
EXPECT_TRUE(IsRollbackRequired());
const std::vector<SessionChapsClient::ObjectHandle> kObjectHandles{
SessionChapsClient::ObjectHandle(20),
SessionChapsClient::ObjectHandle(30),
SessionChapsClient::ObjectHandle(40)};
EXPECT_CALL(chaps_client_, FindObjects(_, _, _))
.WillOnce(RunOnceCallback<2>(kObjectHandles, kSuccess));
EXPECT_CALL(chaps_client_, DestroyObjectsWithRetries(_, kObjectHandles, _))
.WillOnce(RunOnceCallback<2>(kSuccess));
PerformRollbackWithFastForward();
EXPECT_FALSE(IsRollbackRequired());
EXPECT_THAT(
histogram_tester_.GetAllSamples(kNssDbClientCertsRollback),
BucketsInclude(
Bucket(NssDbClientCertsRollbackEvent::kRollbackScheduled, 1),
Bucket(NssDbClientCertsRollbackEvent::kRollbackStarted, 1),
Bucket(NssDbClientCertsRollbackEvent::kRollbackListSize3, 1),
Bucket(NssDbClientCertsRollbackEvent::kRollbackSuccessful, 1)));
}
// PerformRollback() is executed when rollback is required with 5 ObjectHandle,
// rollback is no more required, kRollbackListSizeAbove3 recorded.
TEST_F(KcerRollbackHelperTest, Destroy5DoubleWrittenObjectsInChapsTest) {
InitUser();
pref_service_->SetBoolean(prefs::kNssChapsDualWrittenCertsExist, true);
EXPECT_TRUE(IsRollbackRequired());
const std::vector<SessionChapsClient::ObjectHandle> kObjectHandles{
SessionChapsClient::ObjectHandle(30),
SessionChapsClient::ObjectHandle(40),
SessionChapsClient::ObjectHandle(50),
SessionChapsClient::ObjectHandle(60),
SessionChapsClient::ObjectHandle(70)};
EXPECT_CALL(chaps_client_, FindObjects(_, _, _))
.WillOnce(RunOnceCallback<2>(kObjectHandles, kSuccess));
EXPECT_CALL(chaps_client_, DestroyObjectsWithRetries(_, kObjectHandles, _))
.WillOnce(RunOnceCallback<2>(kSuccess));
PerformRollbackWithFastForward();
EXPECT_FALSE(IsRollbackRequired());
EXPECT_THAT(
histogram_tester_.GetAllSamples(kNssDbClientCertsRollback),
BucketsInclude(
Bucket(NssDbClientCertsRollbackEvent::kRollbackScheduled, 1),
Bucket(NssDbClientCertsRollbackEvent::kRollbackStarted, 1),
Bucket(NssDbClientCertsRollbackEvent::kRollbackListSizeAbove3, 1),
Bucket(NssDbClientCertsRollbackEvent::kRollbackSuccessful, 1)));
}
// PerformRollback() is called when rollback is required, but waiting time was
// only 20 second, so execution has not happened. Then fast forward another 20
// sec and rollback has finished.
TEST_F(KcerRollbackHelperTest, RollbackTimeDidNotArrive) {
InitUser();
pref_service_->SetBoolean(prefs::kNssChapsDualWrittenCertsExist, true);
EXPECT_TRUE(IsRollbackRequired());
const std::vector<SessionChapsClient::ObjectHandle> kObjectHandles;
rollback_helper_->PerformRollback();
task_environment_.FastForwardBy(base::Seconds(20));
task_environment_.RunUntilIdle();
EXPECT_TRUE(IsRollbackRequired());
EXPECT_THAT(
histogram_tester_.GetAllSamples(kNssDbClientCertsRollback),
BucketsInclude(
Bucket(NssDbClientCertsRollbackEvent::kRollbackScheduled, 1),
Bucket(NssDbClientCertsRollbackEvent::kRollbackStarted, 0),
Bucket(NssDbClientCertsRollbackEvent::kRollbackListSize0, 0),
Bucket(NssDbClientCertsRollbackEvent::kRollbackSuccessful, 0)));
std::vector<SessionChapsClient::ObjectHandle> object_list;
EXPECT_CALL(chaps_client_, FindObjects(_, _, _))
.WillOnce(RunOnceCallback<2>(object_list, kSuccess));
EXPECT_CALL(chaps_client_, DestroyObjectsWithRetries(_, object_list, _))
.WillOnce(RunOnceCallback<2>(kSuccess));
task_environment_.FastForwardBy(base::Seconds(20));
task_environment_.RunUntilIdle();
EXPECT_FALSE(IsRollbackRequired());
EXPECT_THAT(
histogram_tester_.GetAllSamples(kNssDbClientCertsRollback),
BucketsInclude(
Bucket(NssDbClientCertsRollbackEvent::kRollbackScheduled, 1),
Bucket(NssDbClientCertsRollbackEvent::kRollbackStarted, 1),
Bucket(NssDbClientCertsRollbackEvent::kRollbackListSize0, 1),
Bucket(NssDbClientCertsRollbackEvent::kRollbackSuccessful, 1)));
}
// PerformRollback() is executed when rollback is
// required, found object list is empty, rollback is no more required.
TEST_F(KcerRollbackHelperTest, RollbackWithEmptyObjectsList) {
InitUser();
pref_service_->SetBoolean(prefs::kNssChapsDualWrittenCertsExist, true);
EXPECT_TRUE(IsRollbackRequired());
std::vector<SessionChapsClient::ObjectHandle> object_list;
EXPECT_CALL(chaps_client_, FindObjects(_, _, _))
.WillOnce(RunOnceCallback<2>(object_list, kSuccess));
EXPECT_CALL(chaps_client_, DestroyObjectsWithRetries(_, object_list, _))
.WillOnce(RunOnceCallback<2>(kSuccess));
PerformRollbackWithFastForward();
EXPECT_FALSE(IsRollbackRequired());
EXPECT_THAT(
histogram_tester_.GetAllSamples(kNssDbClientCertsRollback),
BucketsInclude(
Bucket(NssDbClientCertsRollbackEvent::kRollbackScheduled, 1),
Bucket(NssDbClientCertsRollbackEvent::kRollbackStarted, 1),
Bucket(NssDbClientCertsRollbackEvent::kRollbackListSize0, 1),
Bucket(NssDbClientCertsRollbackEvent::kRollbackSuccessful, 1)));
}
// IsChapsRollbackRequired() is called when rollback is
// required and experiment is active. True is returned.
TEST_F(KcerRollbackHelperTest, IsChapsRollbackRequiredTrueReturned) {
EXPECT_FALSE(IsRollbackRequired());
pref_service_->SetBoolean(prefs::kNssChapsDualWrittenCertsExist, true);
EXPECT_TRUE(IsRollbackRequired());
}
// IsChapsRollbackRequired() is called when rollback is
// required but experiment is not active. False is returned.
TEST_F(KcerRollbackHelperTest, ExperimentIsNotActiveFalseReturned) {
EXPECT_FALSE(IsRollbackRequired());
pref_service_->SetBoolean(prefs::kNssChapsDualWrittenCertsExist, true);
EXPECT_TRUE(IsRollbackRequired());
feature_list_.Reset();
feature_list_.InitWithFeatures(
/*enabled_features=*/{},
/*disabled_features=*/{chromeos::features::kEnablePkcs12ToChapsDualWrite,
ash::features::kEnableNssDbClientCertsRollback});
EXPECT_FALSE(IsRollbackRequired());
}
// IsChapsRollbackRequired() is called when rollback is
// required but import experiment is still active. False is returned.
TEST_F(KcerRollbackHelperTest, ImportExperimentIsActiveFalseReturned) {
EXPECT_FALSE(IsRollbackRequired());
pref_service_->SetBoolean(prefs::kNssChapsDualWrittenCertsExist, true);
EXPECT_TRUE(IsRollbackRequired());
feature_list_.Reset();
feature_list_.InitWithFeatures(
/*enabled_features=*/{chromeos::features::kEnablePkcs12ToChapsDualWrite,
ash::features::kEnableNssDbClientCertsRollback},
/*disabled_features=*/{});
EXPECT_FALSE(IsRollbackRequired());
}
// IsChapsRollbackRequired() is called when rollback is
// required. Import experiment is still active, rollback experiment is disabled.
// False is returned.
TEST_F(KcerRollbackHelperTest,
ImportExperimentIsActiveRollbackIsDisabledFalseReturned) {
EXPECT_FALSE(IsRollbackRequired());
pref_service_->SetBoolean(prefs::kNssChapsDualWrittenCertsExist, true);
EXPECT_TRUE(IsRollbackRequired());
feature_list_.Reset();
feature_list_.InitWithFeatures(
/*enabled_features=*/{chromeos::features::kEnablePkcs12ToChapsDualWrite},
/*disabled_features=*/{ash::features::kEnableNssDbClientCertsRollback});
EXPECT_FALSE(IsRollbackRequired());
}
} // namespace
} // namespace kcer::internal