blob: c21144c0544bcc4dac9e8d2de71137e50763a062 [file] [log] [blame]
// Copyright 2022 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/os_crypt/app_bound_encryption_win.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/metrics/histogram_base.h"
#include "base/metrics/histogram_samples.h"
#include "base/metrics/statistics_recorder.h"
#include "base/path_service.h"
#include "base/process/process_info.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/browser_features.h"
#include "chrome/elevation_service/elevator.h"
#include "chrome/install_static/buildflags.h"
#include "chrome/install_static/install_constants.h"
#include "chrome/install_static/install_details.h"
#include "chrome/install_static/install_util.h"
#include "chrome/install_static/test/scoped_install_details.h"
#include "chrome/installer/util/install_service_work_item.h"
#include "chrome/installer/util/util_constants.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "components/version_info/version_info_values.h"
#include "content/public/test/browser_test.h"
namespace {
void WaitForHistogram(const std::string& histogram_name) {
// Continue if histogram was already recorded.
if (base::StatisticsRecorder::FindHistogram(histogram_name))
return;
// Else, wait until the histogram is recorded.
base::RunLoop run_loop;
auto histogram_observer =
std::make_unique<base::StatisticsRecorder::ScopedHistogramSampleObserver>(
histogram_name,
base::BindLambdaForTesting(
[&](const char* histogram_name, uint64_t name_hash,
base::HistogramBase::Sample sample) { run_loop.Quit(); }));
run_loop.Run();
}
} // namespace
// This class allows system-level tests to be carried out that do not interfere
// with an existing system-level install.
class FakeInstallDetails : public install_static::PrimaryInstallDetails {
public:
// Copy template from first mode from install modes. Some of the values will
// then be overridden.
FakeInstallDetails() : constants_(install_static::kInstallModes[0]) {
// AppGuid determines registry locations, so use a test one.
#if BUILDFLAG(USE_GOOGLE_UPDATE_INTEGRATION)
constants_.app_guid = L"testguid";
#endif
// This is the CLSID of the test interface, used if
// kElevatorClsIdForTestingSwitch is supplied on the command line of the
// elevation service.
constants_.elevator_clsid = {elevation_service::kTestElevatorClsid};
// This is the IID of the non-channel specific IElevator Interface. See
// chrome/elevation_service/elevation_service_idl.idl.
constants_.elevator_iid = {
0xA949CB4E,
0xC4F9,
0x44C4,
{0xB2, 0x13, 0x6B, 0xF8, 0xAA, 0x9A, 0xC6,
0x9C}}; // IElevator IID and TypeLib
// {A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C}
// These are used to generate the name of the service, so keep them
// different from any real installs.
constants_.base_app_name = L"testapp";
constants_.base_app_id = L"testapp";
// This is needed for shell_integration::GetDefaultBrowser which runs on
// startup.
constants_.prog_id_prefix = L"TestHTM";
set_mode(&constants_);
set_system_level(true);
}
FakeInstallDetails(const FakeInstallDetails&) = delete;
FakeInstallDetails& operator=(const FakeInstallDetails&) = delete;
private:
install_static::InstallConstants constants_;
};
class AppBoundEncryptionWinTest : public InProcessBrowserTest {
public:
AppBoundEncryptionWinTest()
: scoped_install_details_(std::make_unique<FakeInstallDetails>()) {}
protected:
void SetUp() override {
if (base::GetCurrentProcessIntegrityLevel() != base::HIGH_INTEGRITY)
GTEST_SKIP() << "Elevation is required for this test.";
enable_metrics_feature_.InitAndEnableFeature(
features::kAppBoundEncryptionMetrics);
InstallService();
InProcessBrowserTest::SetUp();
}
void TearDown() override {
if (base::GetCurrentProcessIntegrityLevel() != base::HIGH_INTEGRITY)
return;
InProcessBrowserTest::TearDown();
UnInstallService();
}
base::HistogramTester histogram_tester_;
base::test::ScopedFeatureList enable_metrics_feature_;
private:
static bool InstallService() {
base::FilePath exe_dir;
base::PathService::Get(base::DIR_EXE, &exe_dir);
base::CommandLine service_cmd(
exe_dir.Append(installer::kElevationServiceExe));
service_cmd.AppendSwitch(
elevation_service::switches::kElevatorClsIdForTestingSwitch);
installer::InstallServiceWorkItem install_service_work_item(
install_static::GetElevationServiceName(),
install_static::GetElevationServiceDisplayName(), SERVICE_DEMAND_START,
service_cmd, base::CommandLine(base::CommandLine::NO_PROGRAM),
install_static::GetClientStateKeyPath(),
{install_static::GetElevatorClsid()},
{install_static::GetElevatorIid()});
install_service_work_item.set_best_effort(true);
install_service_work_item.set_rollback_enabled(false);
return install_service_work_item.Do();
}
static bool UnInstallService() {
return installer::InstallServiceWorkItem::DeleteService(
install_static::GetElevationServiceName(),
install_static::GetClientStateKeyPath(),
{install_static::GetElevatorClsid()},
{install_static::GetElevatorIid()});
}
install_static::ScopedInstallDetails scoped_install_details_;
};
// Test the basic interface to Encrypt and Decrypt data.
IN_PROC_BROWSER_TEST_F(AppBoundEncryptionWinTest, EncryptDecrypt) {
ASSERT_TRUE(install_static::IsSystemInstall());
const std::string plaintext("plaintext");
std::string ciphertext;
DWORD last_error;
HRESULT hr = os_crypt::EncryptAppBoundString(
ProtectionLevel::PATH_VALIDATION, plaintext, ciphertext, last_error);
ASSERT_HRESULT_SUCCEEDED(hr);
std::string returned_plaintext;
hr = os_crypt::DecryptAppBoundString(ciphertext, returned_plaintext,
last_error);
ASSERT_HRESULT_SUCCEEDED(hr);
EXPECT_EQ(plaintext, returned_plaintext);
}
// These tests verify that the metrics are recorded correctly. The first load of
// browser in the PRE_ test stores the "Test Key" with app-bound encryption and
// the second stage of the test verifies it can be retrieved successfully.
IN_PROC_BROWSER_TEST_F(AppBoundEncryptionWinTest, PRE_MetricsTest) {
ASSERT_TRUE(install_static::IsSystemInstall());
// These histograms are recorded on a background worker thread, so the test
// needs to wait until this task completes and the histograms are recorded.
WaitForHistogram("OSCrypt.AppBoundEncryption.Encrypt.ResultCode");
histogram_tester_.ExpectBucketCount(
"OSCrypt.AppBoundEncryption.Encrypt.ResultCode", S_OK, 1);
WaitForHistogram("OSCrypt.AppBoundEncryption.Encrypt.Time");
}
IN_PROC_BROWSER_TEST_F(AppBoundEncryptionWinTest, MetricsTest) {
ASSERT_TRUE(install_static::IsSystemInstall());
// These histograms are recorded on a background worker thread, so the test
// needs to wait until this task completes and the histograms are recorded.
WaitForHistogram("OSCrypt.AppBoundEncryption.Decrypt.ResultCode");
histogram_tester_.ExpectBucketCount(
"OSCrypt.AppBoundEncryption.Decrypt.ResultCode", S_OK, 1);
WaitForHistogram("OSCrypt.AppBoundEncryption.Decrypt.Time");
}
// Run this test manually to force uninstall the service.
IN_PROC_BROWSER_TEST_F(AppBoundEncryptionWinTest, MANUAL_Uninstall) {}
class AppBoundEncryptionWinTestNoService : public InProcessBrowserTest {
public:
AppBoundEncryptionWinTestNoService()
: scoped_install_details_(std::make_unique<FakeInstallDetails>()) {}
private:
install_static::ScopedInstallDetails scoped_install_details_;
};
IN_PROC_BROWSER_TEST_F(AppBoundEncryptionWinTestNoService, NoService) {
const std::string plaintext("plaintext");
std::string ciphertext;
DWORD last_error;
HRESULT hr = os_crypt::EncryptAppBoundString(
ProtectionLevel::PATH_VALIDATION, plaintext, ciphertext, last_error);
EXPECT_EQ(REGDB_E_CLASSNOTREG, hr);
EXPECT_EQ(DWORD{ERROR_GEN_FAILURE}, last_error);
}