| // 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 <optional> |
| #include <string> |
| #include <vector> |
| |
| #include "base/functional/callback.h" |
| #include "base/process/process_info.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/test_future.h" |
| #include "build/config/linux/dbus/buildflags.h" |
| #include "chrome/browser/browser_features.h" |
| #include "chrome/browser/net/cookie_encryption_provider_impl.h" |
| #include "chrome/browser/policy/chrome_browser_policy_connector.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "components/policy/core/common/mock_configuration_policy_provider.h" |
| #include "components/policy/core/common/policy_map.h" |
| #include "components/policy/policy_constants.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/test_launcher.h" |
| #include "net/cookies/canonical_cookie.h" |
| #include "services/network/public/mojom/cookie_manager.mojom.h" |
| #include "services/network/public/mojom/network_context.mojom.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| #if BUILDFLAG(IS_WIN) |
| #include "chrome/browser/os_crypt/test_support.h" |
| #include "chrome/common/chrome_paths_internal.h" |
| #include "chrome/install_static/test/scoped_install_details.h" |
| #include "chrome/windows_services/service_program/test_support/scoped_log_grabber.h" |
| #endif // BUILDFLAG(IS_WIN) |
| |
| #if BUILDFLAG(IS_LINUX) && BUILDFLAG(USE_DBUS) |
| #include "base/test/scoped_command_line.h" |
| #include "chrome/browser/chrome_browser_main.h" |
| #include "chrome/browser/chrome_browser_main_extra_parts.h" |
| #include "components/os_crypt/async/browser/secret_portal_key_provider.h" |
| #include "components/os_crypt/async/browser/test_secret_portal.h" |
| #include "components/password_manager/core/browser/password_manager_switches.h" |
| #endif // BUILDFLAG(IS_LINUX) && BUILDFLAG(USE_DBUS) |
| |
| namespace { |
| |
| enum TestConfiguration { |
| // Network Service is using Async API, i.e. cookie_encryption_provider is |
| // being supplied to the profile network context params. A default set of key |
| // providers are used in this configuration. |
| kOSCryptAsync, |
| #if BUILDFLAG(IS_WIN) |
| // This is the same as `kOSCryptAsync` but without the service being correctly |
| // installed/running. This allows testing of failure conditions. |
| kOSCryptAsyncNoService, |
| // This is the same as `kOSCryptAsync` but with App-Bound |
| // encryption disabled by policy. If run on a fresh profile it should not |
| // generate or store a key. However, if run on a profile where policy was |
| // previously enabled, it should successfully decrypt the key, as there might |
| // have been data encrypted with this key before the policy was disabled. |
| kOSCryptAsyncDisabledByPolicy, |
| #endif // BUILDFLAG(IS_WIN) |
| #if BUILDFLAG(IS_LINUX) && BUILDFLAG(USE_DBUS) |
| // The Secret Portal key provider is being registered with Chrome. |
| kOSCryptAsyncWithSecretPortalProvider, |
| #endif // BUILDFLAG(IS_LINUX) && BUILDFLAG(USE_DBUS) |
| }; |
| |
| enum MetricsExpectation { |
| kNotChecked, |
| kOSCryptAsyncMetrics, |
| #if BUILDFLAG(IS_WIN) |
| kAppBoundEncryptMetrics, |
| kAppBoundDecryptMetrics, |
| #endif // BUILDFLAG(IS_WIN) |
| #if BUILDFLAG(IS_LINUX) && BUILDFLAG(USE_DBUS) |
| kSecretPortalMetrics, |
| #endif // BUILDFLAG(IS_LINUX) && BUILDFLAG(USE_DBUS) |
| kNoMetrics, |
| }; |
| |
| struct TestCase { |
| std::string name; |
| bool expect_pass = true; |
| TestConfiguration before; |
| TestConfiguration after; |
| MetricsExpectation metrics_expectation_before = kNotChecked; |
| MetricsExpectation metrics_expectation_after = kNotChecked; |
| }; |
| |
| #if BUILDFLAG(IS_WIN) |
| bool IsElevationRequired(TestConfiguration configuration) { |
| switch (configuration) { |
| case kOSCryptAsyncNoService: |
| return false; |
| case kOSCryptAsync: |
| case kOSCryptAsyncDisabledByPolicy: |
| return true; |
| } |
| } |
| #endif // BUILDFLAG(IS_WIN) |
| |
| } // namespace |
| |
| class CookieEncryptionProviderBrowserTest |
| : public InProcessBrowserTest, |
| public testing::WithParamInterface<TestCase> { |
| public: |
| #if BUILDFLAG(IS_WIN) |
| CookieEncryptionProviderBrowserTest() |
| : scoped_install_details_( |
| std::make_unique<os_crypt::FakeInstallDetails>()) {} |
| #endif // BUILDFLAG(IS_WIN) |
| |
| #if BUILDFLAG(IS_LINUX) && BUILDFLAG(USE_DBUS) |
| void CreatedBrowserMainParts(content::BrowserMainParts* parts) override { |
| InProcessBrowserTest::CreatedBrowserMainParts(parts); |
| |
| base::OnceClosure initialize_server = base::BindOnce( |
| [](CookieEncryptionProviderBrowserTest* self) { |
| self->test_secret_portal_ = |
| std::make_unique<os_crypt_async::TestSecretPortal>( |
| content::IsPreTest()); |
| os_crypt_async::SecretPortalKeyProvider:: |
| GetSecretServiceNameForTest() = |
| self->test_secret_portal_->BusName(); |
| }, |
| base::Unretained(this)); |
| class PostCreateThreadsObserver : public ChromeBrowserMainExtraParts { |
| public: |
| explicit PostCreateThreadsObserver(base::OnceClosure post_create_threads) |
| : post_create_threads_(std::move(post_create_threads)) {} |
| |
| void PostCreateThreads() override { |
| std::move(post_create_threads_).Run(); |
| } |
| |
| private: |
| base::OnceClosure post_create_threads_; |
| }; |
| static_cast<ChromeBrowserMainParts*>(parts)->AddParts( |
| std::make_unique<PostCreateThreadsObserver>( |
| std::move(initialize_server))); |
| } |
| #endif // BUILDFLAG(IS_LINUX) && BUILDFLAG(USE_DBUS) |
| |
| void SetUp() override { |
| #if BUILDFLAG(IS_WIN) |
| if ((IsElevationRequired(GetParam().before) || |
| IsElevationRequired(GetParam().after)) && |
| base::GetCurrentProcessIntegrityLevel() != base::HIGH_INTEGRITY) { |
| GTEST_SKIP() << "Elevation is required for this test."; |
| } |
| |
| // Browser tests use a custom user data dir, which would normally result in |
| // App-Bound encryption being disabled with |
| // `SupportLevel::kNotUsingDefaultUserDataDir`, so this call forces the |
| // non-standard testing data dir to be considered a default one. |
| chrome::SetUsingDefaultUserDataDirectoryForTesting(true); |
| #endif // BUILDFLAG(IS_WIN) |
| |
| auto configuration = |
| content::IsPreTest() ? GetParam().before : GetParam().after; |
| |
| std::vector<base::test::FeatureRef> enabled_features; |
| std::vector<base::test::FeatureRef> disabled_features; |
| |
| switch (configuration) { |
| case kOSCryptAsync: |
| #if BUILDFLAG(IS_WIN) |
| maybe_uninstall_service_ = os_crypt::InstallService(log_grabber_); |
| EXPECT_TRUE(maybe_uninstall_service_.has_value()); |
| #endif // BUILDFLAG(IS_WIN) |
| #if BUILDFLAG(IS_LINUX) && BUILDFLAG(USE_DBUS) |
| disabled_features.push_back(features::kDbusSecretPortal); |
| #endif // BUILDFLAG(IS_LINUX) && BUILDFLAG(USE_DBUS) |
| break; |
| #if BUILDFLAG(IS_WIN) |
| case kOSCryptAsyncNoService: |
| break; |
| case kOSCryptAsyncDisabledByPolicy: |
| maybe_uninstall_service_ = os_crypt::InstallService(log_grabber_); |
| EXPECT_TRUE(maybe_uninstall_service_.has_value()); |
| policy_provider_.SetDefaultReturns( |
| /*is_initialization_complete_return=*/true, |
| /*is_first_policy_load_complete_return=*/true); |
| policy::PolicyMap values; |
| // Disable App-Bound Encryption by policy. |
| values.Set(policy::key::kApplicationBoundEncryptionEnabled, |
| policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_MACHINE, |
| policy::POLICY_SOURCE_CLOUD, base::Value(false), nullptr); |
| policy_provider_.UpdateChromePolicy(values); |
| policy::BrowserPolicyConnector::SetPolicyProviderForTesting( |
| &policy_provider_); |
| break; |
| #endif // BUILDFLAG(IS_WIN) |
| #if BUILDFLAG(IS_LINUX) && BUILDFLAG(USE_DBUS) |
| case kOSCryptAsyncWithSecretPortalProvider: |
| enabled_features.push_back(features::kDbusSecretPortal); |
| enabled_features.push_back( |
| features::kSecretPortalKeyProviderUseForEncryption); |
| break; |
| #endif // BUILDFLAG(IS_LINUX) && BUILDFLAG(USE_DBUS) |
| } |
| scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features); |
| |
| #if BUILDFLAG(IS_LINUX) && BUILDFLAG(USE_DBUS) |
| command_line_ = std::make_unique<base::test::ScopedCommandLine>(); |
| command_line_->GetProcessCommandLine()->AppendSwitchASCII( |
| password_manager::kPasswordStore, ""); |
| #endif |
| |
| InProcessBrowserTest::SetUp(); |
| } |
| |
| void TearDown() override { |
| if (IsSkipped()) { |
| return; |
| } |
| |
| #if BUILDFLAG(IS_WIN) |
| maybe_uninstall_service_.reset(); |
| #endif // BUILDFLAG(IS_WIN) |
| |
| auto metrics_expectation = content::IsPreTest() |
| ? GetParam().metrics_expectation_before |
| : GetParam().metrics_expectation_after; |
| switch (metrics_expectation) { |
| case kNotChecked: |
| break; |
| case kOSCryptAsyncMetrics: |
| histogram_tester_.ExpectTotalCount("OSCrypt.AsyncInitialization.Time", |
| 1); |
| #if BUILDFLAG(IS_WIN) |
| histogram_tester_.ExpectBucketCount("OSCrypt.DPAPIProvider.Status", |
| /*success*/ 0, 1); |
| #endif // BUILDFLAG(IS_WIN) |
| break; |
| #if BUILDFLAG(IS_WIN) |
| case kAppBoundEncryptMetrics: |
| // In the pre-test the generation of a new key happens, followed by an |
| // Encrypt. |
| histogram_tester_.ExpectBucketCount( |
| "OSCrypt.AppBoundProvider.KeyRetrieval.Status", /*kKeyNotFound*/ 1, |
| 1); |
| histogram_tester_.ExpectBucketCount( |
| "OSCrypt.AppBoundProvider.Encrypt.ResultCode", S_OK, 1); |
| histogram_tester_.ExpectTotalCount( |
| "OSCrypt.AppBoundProvider.Encrypt.ResultLastError", 0); |
| histogram_tester_.ExpectTotalCount( |
| "OSCrypt.AppBoundProvider.Decrypt.ResultCode", 0); |
| break; |
| case kAppBoundDecryptMetrics: |
| histogram_tester_.ExpectBucketCount( |
| "OSCrypt.AppBoundProvider.KeyRetrieval.Status", /*kSuccess*/ 0, 1); |
| histogram_tester_.ExpectBucketCount( |
| "OSCrypt.AppBoundProvider.Decrypt.ResultCode", S_OK, 1); |
| histogram_tester_.ExpectTotalCount( |
| "OSCrypt.AppBoundProvider.Decrypt.ResultLastError", 0); |
| histogram_tester_.ExpectTotalCount( |
| "OSCrypt.AppBoundProvider.Encrypt.ResultCode", 0); |
| break; |
| #endif // BUILDFLAG(IS_WIN) |
| #if BUILDFLAG(IS_LINUX) && BUILDFLAG(USE_DBUS) |
| case kSecretPortalMetrics: |
| histogram_tester_.ExpectBucketCount( |
| os_crypt_async::SecretPortalKeyProvider::kUmaInitStatusEnum, |
| os_crypt_async::SecretPortalKeyProvider::InitStatus::kSuccess, 1); |
| histogram_tester_.ExpectTotalCount( |
| os_crypt_async::SecretPortalKeyProvider::kUmaNewInitFailureEnum, 0); |
| break; |
| #endif // BUILDFLAG(IS_LINUX) && BUILDFLAG(USE_DBUS) |
| case kNoMetrics: |
| histogram_tester_.ExpectTotalCount( |
| "OSCrypt.AppBoundProvider.Decrypt.ResultCode", 0); |
| histogram_tester_.ExpectTotalCount( |
| "OSCrypt.AppBoundProvider.Encrypt.ResultCode", 0); |
| break; |
| } |
| |
| #if BUILDFLAG(IS_LINUX) && BUILDFLAG(USE_DBUS) |
| command_line_.reset(); |
| #endif |
| |
| InProcessBrowserTest::TearDown(); |
| } |
| |
| private: |
| #if BUILDFLAG(IS_WIN) |
| install_static::ScopedInstallDetails scoped_install_details_; |
| #endif // BUILDFLAG(IS_WIN) |
| base::test::ScopedFeatureList scoped_feature_list_; |
| base::HistogramTester histogram_tester_; |
| #if BUILDFLAG(IS_WIN) |
| ScopedLogGrabber log_grabber_; |
| std::optional<base::ScopedClosureRunner> maybe_uninstall_service_; |
| testing::NiceMock<policy::MockConfigurationPolicyProvider> policy_provider_; |
| #endif // BUILDFLAG(IS_WIN) |
| #if BUILDFLAG(IS_LINUX) && BUILDFLAG(USE_DBUS) |
| std::unique_ptr<os_crypt_async::TestSecretPortal> test_secret_portal_; |
| std::unique_ptr<base::test::ScopedCommandLine> command_line_; |
| #endif // BUILDFLAG(IS_LINUX) && BUILDFLAG(USE_DBUS) |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(CookieEncryptionProviderBrowserTest, PRE_CookieStorage) { |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| EXPECT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), embedded_test_server()->GetURL("/set_cookie_header.html"))); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(CookieEncryptionProviderBrowserTest, CookieStorage) { |
| mojo::Remote<network::mojom::CookieManager> cookie_manager; |
| browser() |
| ->profile() |
| ->GetDefaultStoragePartition() |
| ->GetNetworkContext() |
| ->GetCookieManager(cookie_manager.BindNewPipeAndPassReceiver()); |
| base::test::TestFuture<const net::CookieList&> future; |
| cookie_manager->GetAllCookies(future.GetCallback()); |
| auto cookies = future.Take(); |
| if (GetParam().expect_pass) { |
| ASSERT_EQ(cookies.size(), 1u); |
| EXPECT_EQ(cookies[0].Name(), "name"); |
| EXPECT_EQ(cookies[0].Value(), "Good"); |
| } else { |
| ASSERT_TRUE(cookies.empty()); |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| /* no prefix */, |
| CookieEncryptionProviderBrowserTest, |
| testing::ValuesIn<TestCase>({ |
| {.name = "async", |
| .before = kOSCryptAsync, |
| .after = kOSCryptAsync, |
| .metrics_expectation_before = kOSCryptAsyncMetrics, |
| .metrics_expectation_after = kOSCryptAsyncMetrics}, |
| #if BUILDFLAG(IS_WIN) |
| // This test will result in App-Bound not being able to provide a key, |
| // so it will not be registered, and the cookies will instead be |
| // encrypted with the second provider which is DPAPI, and then these can |
| // successfully be decrypted. |
| {.name = "app_bound_encryption_no_service_on_encrypt", |
| .before = kOSCryptAsyncNoService, |
| .after = kOSCryptAsync}, |
| // This test will result in App-Bound being able to provide a key and |
| // it's used for encryption, but in the second part of the test, since |
| // the service does not exist it will not be able to decrypt it. |
| {.name = "app_bound_encryption_no_service_on_decrypt", |
| .expect_pass = false, |
| .before = kOSCryptAsync, |
| .after = kOSCryptAsyncNoService}, |
| // This test verifies that if App-Bound encryption is disabled by |
| // policy, then the provider does not generate a key. This means any |
| // data encrypted in the first stage of the test should decrypt using |
| // just the DPAPI provider. |
| {.name = "app_bound_encryption_disabled_by_policy", |
| .before = kOSCryptAsyncDisabledByPolicy, |
| .after = kOSCryptAsync, |
| .metrics_expectation_before = kNoMetrics}, |
| // This test verifies that if App-Bound encryption is first enabled by |
| // policy (the default), then subsequently disabled by policy, then the |
| // key is still successfully registered as there might be data that was |
| // previously encrypted using the key. |
| {.name = "app_bound_encryption_disabled_by_policy_later", |
| .before = kOSCryptAsync, |
| .after = kOSCryptAsyncDisabledByPolicy, |
| .metrics_expectation_before = kAppBoundEncryptMetrics, |
| .metrics_expectation_after = kAppBoundDecryptMetrics}, |
| #endif // BUILDFLAG(IS_WIN) |
| #if BUILDFLAG(IS_LINUX) && BUILDFLAG(USE_DBUS) |
| {.name = "secret_portal", |
| .before = kOSCryptAsyncWithSecretPortalProvider, |
| .after = kOSCryptAsyncWithSecretPortalProvider, |
| .metrics_expectation_before = kSecretPortalMetrics, |
| .metrics_expectation_after = kSecretPortalMetrics}, |
| #endif // BUILDFLAG(IS_LINUX) && BUILDFLAG(USE_DBUS) |
| }), |
| [](const auto& info) { return info.param.name; }); |