Add SecretPortalKeyProvider
This change implements an OSCrypt async client using the Freedesktop Secret Service interface. Both gnome-keyring and KWallet expose this interface, so we may eventually remove both of those synchronous backends. Change-Id: I2462128bf4416e249044e73336961641929dde05 Bug: 40086962 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5792413 Reviewed-by: Will Harris <wfh@chromium.org> Reviewed-by: Ryo Hashimoto <hashimoto@chromium.org> Commit-Queue: Thomas Anderson <thomasanderson@chromium.org> Reviewed-by: Lei Zhang <thestig@chromium.org> Reviewed-by: Luc Nguyen <lucnguyen@google.com> Code-Coverage: findit-for-me@appspot.gserviceaccount.com <findit-for-me@appspot.gserviceaccount.com> Cr-Commit-Position: refs/heads/main@{#1354558}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
dbb4fd229e
commit
64808f96cd
chrome
components/os_crypt/async/browser
BUILD.gnDEPSsecret_portal_key_provider.ccsecret_portal_key_provider.hsecret_portal_key_provider_unittest.cctest_secret_portal.cctest_secret_portal.h
dbus
tools/metrics/histograms/metadata/others
@ -6901,7 +6901,10 @@ static_library("browser") {
|
||||
"dbus_memory_pressure_evaluator_linux.h",
|
||||
]
|
||||
|
||||
deps += [ "//chrome/common:chrome_features" ]
|
||||
deps += [
|
||||
"//chrome/common:chrome_features",
|
||||
"//components/os_crypt/async/browser:secret_portal_key_provider",
|
||||
]
|
||||
}
|
||||
|
||||
if (is_linux || is_chromeos_lacros) {
|
||||
|
@ -71,6 +71,15 @@ BASE_FEATURE(kClosedTabCache,
|
||||
"ClosedTabCache",
|
||||
base::FEATURE_DISABLED_BY_DEFAULT);
|
||||
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
// Enables usage of os_crypt_async::SecretPortalKeyProvider. Once
|
||||
// `kSecretPortalKeyProviderUseForEncryption` is enabled, this flag cannot be
|
||||
// disabled without losing data.
|
||||
BASE_FEATURE(kDbusSecretPortal,
|
||||
"DbusSecretPortal",
|
||||
base::FEATURE_DISABLED_BY_DEFAULT);
|
||||
#endif // BUILDFLAG(IS_LINUX)
|
||||
|
||||
// Destroy profiles when their last browser window is closed, instead of when
|
||||
// the browser exits.
|
||||
// On Lacros the feature is enabled only for secondary profiles, check the
|
||||
@ -338,6 +347,14 @@ BASE_FEATURE(kSandboxExternalProtocolBlockedWarning,
|
||||
"SandboxExternalProtocolBlockedWarning",
|
||||
base::FEATURE_ENABLED_BY_DEFAULT);
|
||||
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
// If true, encrypt new data with the key provided by SecretPortalKeyProvider.
|
||||
// Otherwise, it will only decrypt existing data.
|
||||
BASE_FEATURE(kSecretPortalKeyProviderUseForEncryption,
|
||||
"SecretPortalKeyProviderUseForEncryption",
|
||||
base::FEATURE_DISABLED_BY_DEFAULT);
|
||||
#endif // BUILDFLAG(IS_LINUX)
|
||||
|
||||
// This flag controls whether to trigger prerendering when the default search
|
||||
// engine suggests to prerender a search result.
|
||||
BASE_FEATURE(kSupportSearchSuggestionForPrerender2,
|
||||
|
@ -29,6 +29,10 @@ BASE_DECLARE_FEATURE(kCertificateTransparencyAskBeforeEnabling);
|
||||
BASE_DECLARE_FEATURE(kCertVerificationNetworkTime);
|
||||
BASE_DECLARE_FEATURE(kClosedTabCache);
|
||||
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
BASE_DECLARE_FEATURE(kDbusSecretPortal);
|
||||
#endif
|
||||
|
||||
BASE_DECLARE_FEATURE(kDestroyProfileOnBrowserClose);
|
||||
BASE_DECLARE_FEATURE(kDestroySystemProfiles);
|
||||
|
||||
@ -138,6 +142,11 @@ BASE_DECLARE_FEATURE(kRegisterOsUpdateHandlerWin);
|
||||
BASE_DECLARE_FEATURE(kRestartNetworkServiceUnsandboxedForFailedLaunch);
|
||||
BASE_DECLARE_FEATURE(kSandboxExternalProtocolBlocked);
|
||||
BASE_DECLARE_FEATURE(kSandboxExternalProtocolBlockedWarning);
|
||||
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
BASE_DECLARE_FEATURE(kSecretPortalKeyProviderUseForEncryption);
|
||||
#endif
|
||||
|
||||
BASE_DECLARE_FEATURE(kSupportSearchSuggestionForPrerender2);
|
||||
|
||||
#if !BUILDFLAG(IS_ANDROID)
|
||||
|
@ -256,6 +256,11 @@
|
||||
#include "components/enterprise/browser/controller/chrome_browser_cloud_management_controller.h"
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
#include "chrome/browser/browser_features.h"
|
||||
#include "components/os_crypt/async/browser/secret_portal_key_provider.h"
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(IS_WIN) || (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS))
|
||||
// How often to check if the persistent instance of Chrome needs to restart
|
||||
// to install an update.
|
||||
@ -1118,6 +1123,10 @@ void BrowserProcessImpl::RegisterPrefs(PrefRegistrySimple* registry) {
|
||||
registry->RegisterBooleanPref(metrics::prefs::kMetricsReportingEnabled,
|
||||
GoogleUpdateSettings::GetCollectStatsConsent());
|
||||
registry->RegisterBooleanPref(prefs::kDevToolsRemoteDebuggingAllowed, true);
|
||||
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
os_crypt_async::SecretPortalKeyProvider::RegisterLocalPrefs(registry);
|
||||
#endif
|
||||
}
|
||||
|
||||
GlobalFeatures* BrowserProcessImpl::GetFeatures() {
|
||||
@ -1395,6 +1404,17 @@ void BrowserProcessImpl::PreMainMessageLoopRun() {
|
||||
}
|
||||
#endif // BUILDFLAG(IS_WIN)
|
||||
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
if (base::FeatureList::IsEnabled(features::kDbusSecretPortal)) {
|
||||
providers.emplace_back(
|
||||
/*precedence=*/10u,
|
||||
std::make_unique<os_crypt_async::SecretPortalKeyProvider>(
|
||||
local_state(),
|
||||
base::FeatureList::IsEnabled(
|
||||
features::kSecretPortalKeyProviderUseForEncryption)));
|
||||
}
|
||||
#endif // BUILDFLAG(IS_LINUX)
|
||||
|
||||
os_crypt_async_ =
|
||||
std::make_unique<os_crypt_async::OSCryptAsync>(std::move(providers));
|
||||
|
||||
|
@ -35,6 +35,13 @@
|
||||
#include "chrome/install_static/test/scoped_install_details.h"
|
||||
#endif // BUILDFLAG(IS_WIN)
|
||||
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
#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"
|
||||
#endif // BUILDFLAG(IS_LINUX)
|
||||
|
||||
namespace {
|
||||
|
||||
enum TestConfiguration {
|
||||
@ -68,6 +75,10 @@ enum TestConfiguration {
|
||||
// have been data encrypted with this key before the policy was disabled.
|
||||
kOSCryptAsyncWithAppBoundProviderDisabledByPolicy,
|
||||
#endif // BUILDFLAG(IS_WIN)
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
// The Secret Portal key provider is being registered with Chrome.
|
||||
kOSCryptAsyncWithSecretPortalProvider,
|
||||
#endif // BUILDFLAG(IS_LINUX)
|
||||
};
|
||||
|
||||
enum MetricsExpectation {
|
||||
@ -77,6 +88,9 @@ enum MetricsExpectation {
|
||||
kAppBoundEncryptMetrics,
|
||||
kAppBoundDecryptMetrics,
|
||||
#endif // BUILDFLAG(IS_WIN)
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
kSecretPortalMetrics,
|
||||
#endif // BUILDFLAG(IS_LINUX)
|
||||
kNoMetrics,
|
||||
};
|
||||
|
||||
@ -122,6 +136,38 @@ class CookieEncryptionProviderBrowserTest
|
||||
std::make_unique<os_crypt::FakeInstallDetails>()) {}
|
||||
#endif // BUILDFLAG(IS_WIN)
|
||||
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
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)
|
||||
|
||||
void SetUp() override {
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
if ((IsElevationRequired(GetParam().before) ||
|
||||
@ -149,6 +195,9 @@ class CookieEncryptionProviderBrowserTest
|
||||
disabled_features.push_back(
|
||||
features::kRegisterAppBoundEncryptionProvider);
|
||||
#endif // BUILDFLAG(IS_WIN)
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
disabled_features.push_back(features::kDbusSecretPortal);
|
||||
#endif // BUILDFLAG(IS_LINUX)
|
||||
break;
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
case kOSCryptAsyncWithAppBoundProvider:
|
||||
@ -214,6 +263,15 @@ class CookieEncryptionProviderBrowserTest
|
||||
&policy_provider_);
|
||||
break;
|
||||
#endif // BUILDFLAG(IS_WIN)
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
case kOSCryptAsyncWithSecretPortalProvider:
|
||||
enabled_features.push_back(
|
||||
features::kUseOsCryptAsyncForCookieEncryption);
|
||||
enabled_features.push_back(features::kDbusSecretPortal);
|
||||
enabled_features.push_back(
|
||||
features::kSecretPortalKeyProviderUseForEncryption);
|
||||
break;
|
||||
#endif // BUILDFLAG(IS_LINUX)
|
||||
}
|
||||
scoped_feature_list_.InitWithFeatures(enabled_features, disabled_features);
|
||||
|
||||
@ -264,6 +322,15 @@ class CookieEncryptionProviderBrowserTest
|
||||
"OSCrypt.AppBoundProvider.Encrypt.ResultCode", 0);
|
||||
break;
|
||||
#endif // BUILDFLAG(IS_WIN)
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
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)
|
||||
case kNoMetrics:
|
||||
histogram_tester_.ExpectTotalCount(
|
||||
"OSCrypt.AppBoundProvider.Decrypt.ResultCode", 0);
|
||||
@ -285,6 +352,9 @@ class CookieEncryptionProviderBrowserTest
|
||||
std::optional<base::ScopedClosureRunner> maybe_uninstall_service_;
|
||||
testing::NiceMock<policy::MockConfigurationPolicyProvider> policy_provider_;
|
||||
#endif // BUILDFLAG(IS_WIN)
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
std::unique_ptr<os_crypt_async::TestSecretPortal> test_secret_portal_;
|
||||
#endif // BUILDFLAG(IS_LINUX)
|
||||
};
|
||||
|
||||
IN_PROC_BROWSER_TEST_P(CookieEncryptionProviderBrowserTest, PRE_CookieStorage) {
|
||||
@ -415,5 +485,12 @@ INSTANTIATE_TEST_SUITE_P(
|
||||
.metrics_expectation_before = kAppBoundEncryptMetrics,
|
||||
.metrics_expectation_after = kAppBoundDecryptMetrics},
|
||||
#endif // BUILDFLAG(IS_WIN)
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
{.name = "secret_portal",
|
||||
.before = kOSCryptAsyncWithSecretPortalProvider,
|
||||
.after = kOSCryptAsyncWithSecretPortalProvider,
|
||||
.metrics_expectation_before = kSecretPortalMetrics,
|
||||
.metrics_expectation_after = kSecretPortalMetrics},
|
||||
#endif // BUILDFLAG(IS_LINUX)
|
||||
}),
|
||||
[](const auto& info) { return info.param.name; });
|
||||
|
@ -11691,7 +11691,11 @@ if (!is_android && !is_chromeos_device) {
|
||||
if (ozone_platform_x11 && enable_remoting) {
|
||||
sources += [ "//ui/display/linux/test/virtual_display_util_linux_interactive_uitest.cc" ]
|
||||
}
|
||||
deps += [ "//ui/linux:linux_ui" ]
|
||||
deps += [
|
||||
"//components/os_crypt/async/browser:secret_portal_key_provider",
|
||||
"//components/os_crypt/async/browser:test_secret_portal",
|
||||
"//ui/linux:linux_ui",
|
||||
]
|
||||
}
|
||||
|
||||
if (is_linux || is_chromeos_lacros) {
|
||||
|
@ -56,6 +56,48 @@ if (is_win) {
|
||||
}
|
||||
}
|
||||
|
||||
if (is_linux) {
|
||||
source_set("secret_portal_key_provider") {
|
||||
sources = [
|
||||
"secret_portal_key_provider.cc",
|
||||
"secret_portal_key_provider.h",
|
||||
]
|
||||
|
||||
deps = [
|
||||
":key_provider_interface",
|
||||
"//base",
|
||||
"//components/dbus/properties",
|
||||
"//components/dbus/thread_linux",
|
||||
"//components/dbus/utils",
|
||||
"//components/os_crypt/async/common",
|
||||
"//components/os_crypt/sync",
|
||||
"//components/prefs",
|
||||
"//crypto",
|
||||
"//dbus",
|
||||
]
|
||||
}
|
||||
|
||||
source_set("test_secret_portal") {
|
||||
testonly = true
|
||||
|
||||
sources = [
|
||||
"test_secret_portal.cc",
|
||||
"test_secret_portal.h",
|
||||
]
|
||||
|
||||
deps = [
|
||||
":key_provider_interface",
|
||||
":secret_portal_key_provider",
|
||||
"//base",
|
||||
"//components/dbus/properties",
|
||||
"//components/dbus/utils",
|
||||
"//components/os_crypt/async/common",
|
||||
"//dbus",
|
||||
"//testing/gtest",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
source_set("unit_tests") {
|
||||
testonly = true
|
||||
sources = [ "os_crypt_async_unittest.cc" ]
|
||||
@ -79,4 +121,16 @@ source_set("unit_tests") {
|
||||
"//components/prefs:test_support",
|
||||
]
|
||||
}
|
||||
|
||||
if (is_linux) {
|
||||
sources += [ "secret_portal_key_provider_unittest.cc" ]
|
||||
deps += [
|
||||
":secret_portal_key_provider",
|
||||
"//components/dbus/properties",
|
||||
"//components/dbus/utils",
|
||||
"//components/prefs:test_support",
|
||||
"//dbus:test_support",
|
||||
"//testing/gmock",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,12 @@
|
||||
include_rules = [
|
||||
"+components/dbus",
|
||||
"+components/prefs",
|
||||
"+crypto",
|
||||
"+dbus",
|
||||
]
|
||||
|
||||
specific_include_rules = {
|
||||
".*_unittest\.cc": [
|
||||
"+components/os_crypt/sync",
|
||||
"+crypto",
|
||||
],
|
||||
"test_utils\.cc": [
|
||||
"+crypto",
|
||||
],
|
||||
}
|
||||
|
334
components/os_crypt/async/browser/secret_portal_key_provider.cc
Normal file
334
components/os_crypt/async/browser/secret_portal_key_provider.cc
Normal file
@ -0,0 +1,334 @@
|
||||
// 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 "components/os_crypt/async/browser/secret_portal_key_provider.h"
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <linux/limits.h>
|
||||
|
||||
#include <array>
|
||||
#include <utility>
|
||||
|
||||
#include "base/environment.h"
|
||||
#include "base/functional/bind.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/memory/scoped_refptr.h"
|
||||
#include "base/metrics/histogram_functions.h"
|
||||
#include "base/nix/xdg_util.h"
|
||||
#include "base/no_destructor.h"
|
||||
#include "base/numerics/safe_conversions.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "base/task/current_thread.h"
|
||||
#include "components/dbus/properties/types.h"
|
||||
#include "components/dbus/thread_linux/dbus_thread_linux.h"
|
||||
#include "components/dbus/utils/name_has_owner.h"
|
||||
#include "components/os_crypt/async/common/algorithm.mojom.h"
|
||||
#include "components/prefs/pref_registry_simple.h"
|
||||
#include "components/prefs/pref_service.h"
|
||||
#include "crypto/hkdf.h"
|
||||
#include "dbus/bus.h"
|
||||
#include "dbus/message.h"
|
||||
#include "dbus/object_proxy.h"
|
||||
|
||||
namespace os_crypt_async {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char kSaltForHkdf[] = "fdo_portal_secret_salt";
|
||||
constexpr char kInfoForHkdf[] = "HKDF-SHA-256 AES-256-GCM";
|
||||
|
||||
scoped_refptr<dbus::Bus> CreateBus() {
|
||||
dbus::Bus::Options options;
|
||||
options.bus_type = dbus::Bus::SESSION;
|
||||
options.connection_type = dbus::Bus::PRIVATE;
|
||||
options.dbus_task_runner = dbus_thread_linux::GetTaskRunner();
|
||||
return base::MakeRefCounted<dbus::Bus>(options);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
void SecretPortalKeyProvider::RegisterLocalPrefs(PrefRegistrySimple* registry) {
|
||||
registry->RegisterStringPref(kOsCryptTokenPrefName, "");
|
||||
registry->RegisterStringPref(kOsCryptPrevDesktopPrefName, "");
|
||||
registry->RegisterBooleanPref(kOsCryptPrevInitSuccessPrefName, false);
|
||||
}
|
||||
|
||||
SecretPortalKeyProvider::SecretPortalKeyProvider(PrefService* local_state,
|
||||
bool use_for_encryption)
|
||||
: SecretPortalKeyProvider(local_state, CreateBus(), use_for_encryption) {}
|
||||
|
||||
SecretPortalKeyProvider::~SecretPortalKeyProvider() {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
Finalize(InitStatus::kDestructedBeforeComplete);
|
||||
bus_->GetDBusTaskRunner()->PostTask(
|
||||
FROM_HERE, base::BindOnce(&dbus::Bus::ShutdownAndBlock, bus_));
|
||||
}
|
||||
|
||||
SecretPortalKeyProvider::SecretPortalKeyProvider(PrefService* local_state,
|
||||
scoped_refptr<dbus::Bus> bus,
|
||||
bool use_for_encryption)
|
||||
: local_state_(local_state),
|
||||
use_for_encryption_(use_for_encryption),
|
||||
bus_(bus) {}
|
||||
|
||||
void SecretPortalKeyProvider::GetKey(KeyCallback callback) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
CHECK(!key_callback_);
|
||||
CHECK(callback);
|
||||
key_callback_ = std::move(callback);
|
||||
|
||||
dbus_utils::NameHasOwner(
|
||||
bus_.get(), GetSecretServiceName(),
|
||||
base::BindOnce(&SecretPortalKeyProvider::OnNameHasOwnerResponse,
|
||||
weak_ptr_factory_.GetWeakPtr()));
|
||||
}
|
||||
|
||||
bool SecretPortalKeyProvider::UseForEncryption() {
|
||||
return use_for_encryption_;
|
||||
}
|
||||
|
||||
bool SecretPortalKeyProvider::IsCompatibleWithOsCryptSync() {
|
||||
return false;
|
||||
}
|
||||
|
||||
void SecretPortalKeyProvider::OnNameHasOwnerResponse(
|
||||
std::optional<bool> name_has_owner) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
if (!name_has_owner.value_or(false)) {
|
||||
return Finalize(InitStatus::kNoService);
|
||||
}
|
||||
|
||||
// Create a pipe to retrieve the secret.
|
||||
int fds[2];
|
||||
if (pipe2(fds, O_CLOEXEC) != 0) {
|
||||
LOG(ERROR) << "Failed to create pipe for secret retrieval.";
|
||||
return Finalize(InitStatus::kPipeFailed);
|
||||
}
|
||||
read_fd_ = base::ScopedFD(fds[0]);
|
||||
base::ScopedFD write_fd(fds[1]);
|
||||
|
||||
dbus::MethodCall method_call(kInterfaceSecret, kMethodRetrieveSecret);
|
||||
|
||||
response_path_ =
|
||||
std::make_unique<dbus::ObjectPath>(base::nix::XdgDesktopPortalRequestPath(
|
||||
bus_->GetConnectionName(), kHandleToken));
|
||||
|
||||
auto* response_proxy =
|
||||
bus_->GetObjectProxy(GetSecretServiceName(), *response_path_);
|
||||
response_proxy->ConnectToSignal(
|
||||
kInterfaceRequest, kSignalResponse,
|
||||
base::BindRepeating(&SecretPortalKeyProvider::OnResponseSignal,
|
||||
weak_ptr_factory_.GetWeakPtr()),
|
||||
base::BindOnce(&SecretPortalKeyProvider::OnSignalConnected,
|
||||
weak_ptr_factory_.GetWeakPtr()));
|
||||
|
||||
dbus::MessageWriter writer(&method_call);
|
||||
writer.AppendFileDescriptor(write_fd.get() /* the FD gets duplicated */);
|
||||
DbusDictionary options;
|
||||
if (local_state_->HasPrefPath(kOsCryptTokenPrefName)) {
|
||||
const std::string token = local_state_->GetString(kOsCryptTokenPrefName);
|
||||
if (!token.empty()) {
|
||||
options.Put("token", MakeDbusVariant(DbusString(token)));
|
||||
}
|
||||
}
|
||||
options.Put("handle_token", MakeDbusVariant(DbusString(kHandleToken)));
|
||||
options.Write(&writer);
|
||||
|
||||
auto* secret_proxy = bus_->GetObjectProxy(
|
||||
GetSecretServiceName(), dbus::ObjectPath(kObjectPathSecret));
|
||||
secret_proxy->CallMethod(
|
||||
&method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
|
||||
base::BindOnce(&SecretPortalKeyProvider::OnRetrieveSecretResponse,
|
||||
weak_ptr_factory_.GetWeakPtr()));
|
||||
}
|
||||
|
||||
void SecretPortalKeyProvider::OnRetrieveSecretResponse(
|
||||
dbus::Response* response) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
if (!response) {
|
||||
LOG(ERROR) << "Failed to retrieve secret: No response from portal.";
|
||||
return Finalize(InitStatus::kNoResponse);
|
||||
}
|
||||
|
||||
dbus::MessageReader reader(response);
|
||||
|
||||
// Read the object path of the response handle.
|
||||
dbus::ObjectPath response_path;
|
||||
if (!reader.PopObjectPath(&response_path)) {
|
||||
LOG(ERROR) << "Failed to retrieve secret: Invalid response format.";
|
||||
return Finalize(InitStatus::kInvalidResponseFormat);
|
||||
}
|
||||
CHECK(response_path_);
|
||||
const bool matches = response_path == *response_path_;
|
||||
response_path_.reset();
|
||||
if (!matches) {
|
||||
LOG(ERROR) << "Response path does not match.";
|
||||
return Finalize(InitStatus::kResponsePathMismatch);
|
||||
}
|
||||
|
||||
// Read the secret from the pipe. This must happen asynchronously because the
|
||||
// file may not become readable until the keyring is unlocked by typing a
|
||||
// password.
|
||||
read_watcher_ = base::FileDescriptorWatcher::WatchReadable(
|
||||
read_fd_.get(),
|
||||
base::BindRepeating(&SecretPortalKeyProvider::OnFdReadable,
|
||||
weak_ptr_factory_.GetWeakPtr()));
|
||||
}
|
||||
|
||||
void SecretPortalKeyProvider::OnSignalConnected(
|
||||
const std::string& interface_name,
|
||||
const std::string& signal_name,
|
||||
bool connected) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
if (!connected) {
|
||||
LOG(ERROR) << "Failed to connect to " << interface_name << "."
|
||||
<< signal_name;
|
||||
return Finalize(InitStatus::kSignalConnectFailed);
|
||||
}
|
||||
}
|
||||
|
||||
void SecretPortalKeyProvider::OnResponseSignal(dbus::Signal* signal) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
dbus::MessageReader reader(signal);
|
||||
uint32_t response;
|
||||
if (!reader.PopUint32(&response)) {
|
||||
LOG(ERROR) << "Failed to read response from signal.";
|
||||
return Finalize(InitStatus::kSignalReadFailed);
|
||||
}
|
||||
if (response != 0) {
|
||||
LOG(ERROR) << "Keyring unlock cancelled: " << response;
|
||||
return Finalize(InitStatus::kUserCancelledUnlock);
|
||||
}
|
||||
dbus::MessageReader dict_reader(nullptr);
|
||||
if (!reader.PopArray(&dict_reader)) {
|
||||
LOG(ERROR) << "Failed to read array.";
|
||||
return Finalize(InitStatus::kSignalParseFailed);
|
||||
}
|
||||
|
||||
bool got_token = false;
|
||||
while (dict_reader.HasMoreData()) {
|
||||
dbus::MessageReader dict_entry_reader(nullptr);
|
||||
if (!dict_reader.PopDictEntry(&dict_entry_reader)) {
|
||||
LOG(ERROR) << "Failed to read dict entry.";
|
||||
return Finalize(InitStatus::kSignalParseFailed);
|
||||
}
|
||||
std::string key;
|
||||
if (!dict_entry_reader.PopString(&key)) {
|
||||
LOG(ERROR) << "Failed to read key.";
|
||||
return Finalize(InitStatus::kSignalParseFailed);
|
||||
}
|
||||
if (key == "token") {
|
||||
std::string value;
|
||||
if (!dict_entry_reader.PopVariantOfString(&value)) {
|
||||
LOG(ERROR) << "Failed to read value.";
|
||||
return Finalize(InitStatus::kSignalParseFailed);
|
||||
}
|
||||
local_state_->SetString(kOsCryptTokenPrefName, value);
|
||||
got_token = true;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(https://crbug.com/40086962): Investigate if a token should be
|
||||
// required to continue.
|
||||
base::UmaHistogramBoolean(kUmaGotTokenBoolean, got_token);
|
||||
}
|
||||
|
||||
void SecretPortalKeyProvider::OnFdReadable() {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
std::array<uint8_t, PIPE_BUF> buffer;
|
||||
ssize_t bytes_read = read(read_fd_.get(), buffer.data(), buffer.size());
|
||||
if (bytes_read < 0) {
|
||||
LOG(ERROR) << "Failed to read secret from file descriptor.";
|
||||
return Finalize(InitStatus::kPipeReadFailed);
|
||||
}
|
||||
if (bytes_read > 0) {
|
||||
auto buffer_span =
|
||||
base::span(buffer).subspan(0u, base::checked_cast<size_t>(bytes_read));
|
||||
secret_.insert(secret_.end(), buffer_span.begin(), buffer_span.end());
|
||||
return;
|
||||
}
|
||||
|
||||
// EOF.
|
||||
read_watcher_.reset();
|
||||
read_fd_.reset();
|
||||
ReceivedSecret();
|
||||
}
|
||||
|
||||
void SecretPortalKeyProvider::ReceivedSecret() {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
if (secret_.empty()) {
|
||||
LOG(ERROR) << "Retrieved secret is empty.";
|
||||
return Finalize(InitStatus::kEmptySecret);
|
||||
}
|
||||
|
||||
auto hashed = crypto::HkdfSha256(
|
||||
base::span(secret_), base::as_byte_span(kSaltForHkdf),
|
||||
base::as_byte_span(kInfoForHkdf), Encryptor::Key::kAES256GCMKeySize);
|
||||
secret_.clear();
|
||||
|
||||
Encryptor::Key derived_key(hashed, mojom::Algorithm::kAES256GCM);
|
||||
Finalize(InitStatus::kSuccess, kKeyTag, std::move(derived_key));
|
||||
}
|
||||
|
||||
void SecretPortalKeyProvider::Finalize(InitStatus init_status) {
|
||||
CHECK_NE(init_status, InitStatus::kSuccess);
|
||||
Finalize(init_status, std::string(), std::nullopt);
|
||||
}
|
||||
|
||||
void SecretPortalKeyProvider::Finalize(InitStatus init_status,
|
||||
const std::string& tag,
|
||||
std::optional<Encryptor::Key> key) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
CHECK_EQ(init_status == InitStatus::kSuccess, key.has_value());
|
||||
if (!key_callback_) {
|
||||
// Already finalized.
|
||||
return;
|
||||
}
|
||||
|
||||
std::move(key_callback_).Run(tag, std::move(key));
|
||||
|
||||
response_path_.reset();
|
||||
read_watcher_.reset();
|
||||
read_fd_.reset();
|
||||
secret_.clear();
|
||||
|
||||
base::UmaHistogramEnumeration(kUmaInitStatusEnum, init_status);
|
||||
|
||||
std::string desktop;
|
||||
base::Environment::Create()->GetVar(base::nix::kXdgCurrentDesktopEnvVar,
|
||||
&desktop);
|
||||
|
||||
const bool success = init_status == InitStatus::kSuccess;
|
||||
|
||||
if (local_state_->HasPrefPath(kOsCryptPrevDesktopPrefName)) {
|
||||
const std::string prev_desktop =
|
||||
local_state_->GetString(kOsCryptPrevDesktopPrefName);
|
||||
if (desktop == prev_desktop) {
|
||||
bool prev_init_success =
|
||||
local_state_->GetBoolean(kOsCryptPrevInitSuccessPrefName);
|
||||
if (prev_init_success && !success) {
|
||||
base::UmaHistogramEnumeration(kUmaNewInitFailureEnum, init_status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
local_state_->SetString(kOsCryptPrevDesktopPrefName, desktop);
|
||||
local_state_->SetBoolean(kOsCryptPrevInitSuccessPrefName, success);
|
||||
}
|
||||
|
||||
// static
|
||||
std::string SecretPortalKeyProvider::GetSecretServiceName() {
|
||||
return GetSecretServiceNameForTest().value_or(kServiceSecret);
|
||||
}
|
||||
|
||||
// static
|
||||
std::optional<std::string>&
|
||||
SecretPortalKeyProvider::GetSecretServiceNameForTest() {
|
||||
static base::NoDestructor<std::optional<std::string>>
|
||||
secret_service_name_for_test;
|
||||
return *secret_service_name_for_test;
|
||||
}
|
||||
|
||||
} // namespace os_crypt_async
|
151
components/os_crypt/async/browser/secret_portal_key_provider.h
Normal file
151
components/os_crypt/async/browser/secret_portal_key_provider.h
Normal file
@ -0,0 +1,151 @@
|
||||
// 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.
|
||||
|
||||
#ifndef COMPONENTS_OS_CRYPT_ASYNC_BROWSER_SECRET_PORTAL_KEY_PROVIDER_H_
|
||||
#define COMPONENTS_OS_CRYPT_ASYNC_BROWSER_SECRET_PORTAL_KEY_PROVIDER_H_
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/files/file_descriptor_watcher_posix.h"
|
||||
#include "base/files/scoped_file.h"
|
||||
#include "base/gtest_prod_util.h"
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "base/memory/scoped_refptr.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "base/sequence_checker.h"
|
||||
#include "components/os_crypt/async/browser/key_provider.h"
|
||||
|
||||
class CookieEncryptionProviderBrowserTest;
|
||||
class PrefRegistrySimple;
|
||||
class PrefService;
|
||||
|
||||
namespace dbus {
|
||||
class Bus;
|
||||
class ObjectPath;
|
||||
class Response;
|
||||
class Signal;
|
||||
} // namespace dbus
|
||||
|
||||
namespace os_crypt_async {
|
||||
|
||||
class SecretPortalKeyProviderTest;
|
||||
|
||||
// The SecretPortalKeyProvider uses the org.freedesktop.portal.Secret interface
|
||||
// to retrieve an application-specific secret from the portal, which can then
|
||||
// be used to encrypt confidential data. This is the only interface exposed
|
||||
// from sandboxed environments like Flatpak.
|
||||
class SecretPortalKeyProvider : public KeyProvider {
|
||||
public:
|
||||
enum class InitStatus {
|
||||
// Values are logged in metrics, so should not be changed.
|
||||
kSuccess = 0,
|
||||
kNoService = 1,
|
||||
kPipeFailed = 2,
|
||||
kInvalidResponseFormat = 3,
|
||||
kResponsePathMismatch = 4,
|
||||
kPipeReadFailed = 5,
|
||||
kEmptySecret = 6,
|
||||
kNoResponse = 7,
|
||||
kSignalReadFailed = 8,
|
||||
kUserCancelledUnlock = 9,
|
||||
kDestructedBeforeComplete = 10,
|
||||
kSignalConnectFailed = 11,
|
||||
kSignalParseFailed = 12,
|
||||
kMaxValue = kSignalParseFailed,
|
||||
};
|
||||
|
||||
static void RegisterLocalPrefs(PrefRegistrySimple* registry);
|
||||
|
||||
SecretPortalKeyProvider(PrefService* local_state, bool use_for_encryption);
|
||||
|
||||
~SecretPortalKeyProvider() override;
|
||||
|
||||
private:
|
||||
friend class ::CookieEncryptionProviderBrowserTest;
|
||||
friend class SecretPortalKeyProviderTest;
|
||||
friend class TestSecretPortal;
|
||||
FRIEND_TEST_ALL_PREFIXES(SecretPortalKeyProviderTest, GetKey);
|
||||
|
||||
// DBus constants. Exposed in header for tests.
|
||||
static constexpr char kServiceSecret[] = "org.freedesktop.portal.Desktop";
|
||||
static constexpr char kObjectPathSecret[] = "/org/freedesktop/portal/desktop";
|
||||
static constexpr char kInterfaceSecret[] = "org.freedesktop.portal.Secret";
|
||||
static constexpr char kInterfaceRequest[] = "org.freedesktop.portal.Request";
|
||||
static constexpr char kMethodRetrieveSecret[] = "RetrieveSecret";
|
||||
static constexpr char kSignalResponse[] = "Response";
|
||||
|
||||
static constexpr char kOsCryptTokenPrefName[] = "os_crypt.portal.token";
|
||||
static constexpr char kOsCryptPrevDesktopPrefName[] =
|
||||
"os_crypt.portal.prev_desktop";
|
||||
static constexpr char kOsCryptPrevInitSuccessPrefName[] =
|
||||
"os_crypt.portal.prev_init_success";
|
||||
|
||||
static constexpr char kHandleToken[] = "cr_secret_portal_response_token";
|
||||
|
||||
static constexpr char kKeyTag[] = "v12";
|
||||
|
||||
static constexpr char kUmaInitStatusEnum[] =
|
||||
"OSCrypt.SecretPortalKeyProvider.InitStatus";
|
||||
static constexpr char kUmaNewInitFailureEnum[] =
|
||||
"OSCrypt.SecretPortalKeyProvider.NewInitFailure";
|
||||
static constexpr char kUmaGotTokenBoolean[] =
|
||||
"OSCrypt.SecretPortalKeyProvider.GotToken";
|
||||
|
||||
SecretPortalKeyProvider(PrefService* local_state,
|
||||
scoped_refptr<dbus::Bus> bus,
|
||||
bool use_for_encryption);
|
||||
|
||||
// KeyProvider:
|
||||
void GetKey(KeyCallback callback) override;
|
||||
bool UseForEncryption() override;
|
||||
bool IsCompatibleWithOsCryptSync() override;
|
||||
|
||||
void OnNameHasOwnerResponse(std::optional<bool> name_has_owner);
|
||||
|
||||
void OnRetrieveSecretResponse(dbus::Response* response);
|
||||
|
||||
void OnSignalConnected(const std::string& interface_name,
|
||||
const std::string& signal_name,
|
||||
bool connected);
|
||||
|
||||
void OnResponseSignal(dbus::Signal* signal);
|
||||
|
||||
void OnFdReadable();
|
||||
|
||||
void ReceivedSecret();
|
||||
|
||||
// Finalize with an empty tag and key for error cases.
|
||||
void Finalize(InitStatus init_status);
|
||||
|
||||
void Finalize(InitStatus init_status,
|
||||
const std::string& tag,
|
||||
std::optional<Encryptor::Key> key);
|
||||
|
||||
static std::string GetSecretServiceName();
|
||||
|
||||
static std::optional<std::string>& GetSecretServiceNameForTest();
|
||||
|
||||
const raw_ptr<PrefService> local_state_;
|
||||
|
||||
const bool use_for_encryption_;
|
||||
|
||||
scoped_refptr<dbus::Bus> bus_;
|
||||
|
||||
KeyCallback key_callback_;
|
||||
std::unique_ptr<dbus::ObjectPath> response_path_;
|
||||
base::ScopedFD read_fd_;
|
||||
std::unique_ptr<base::FileDescriptorWatcher::Controller> read_watcher_;
|
||||
std::vector<uint8_t> secret_;
|
||||
|
||||
SEQUENCE_CHECKER(sequence_checker_);
|
||||
|
||||
base::WeakPtrFactory<SecretPortalKeyProvider> weak_ptr_factory_{this};
|
||||
};
|
||||
|
||||
} // namespace os_crypt_async
|
||||
|
||||
#endif // COMPONENTS_OS_CRYPT_ASYNC_BROWSER_SECRET_PORTAL_KEY_PROVIDER_H_
|
@ -0,0 +1,191 @@
|
||||
// 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 "components/os_crypt/async/browser/secret_portal_key_provider.h"
|
||||
|
||||
#include "base/memory/scoped_refptr.h"
|
||||
#include "base/nix/xdg_util.h"
|
||||
#include "base/run_loop.h"
|
||||
#include "base/test/task_environment.h"
|
||||
#include "components/dbus/properties/types.h"
|
||||
#include "components/dbus/utils/name_has_owner.h"
|
||||
#include "components/prefs/testing_pref_service.h"
|
||||
#include "dbus/message.h"
|
||||
#include "dbus/mock_bus.h"
|
||||
#include "dbus/mock_object_proxy.h"
|
||||
#include "testing/gmock/include/gmock/gmock.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
using ::testing::_;
|
||||
using ::testing::Invoke;
|
||||
using ::testing::Return;
|
||||
|
||||
namespace os_crypt_async {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char kBusName[] = ":1.123";
|
||||
constexpr char kSecret[] = "secret_for_testing";
|
||||
constexpr char kPrefToken[] = "token_for_testing";
|
||||
|
||||
MATCHER_P2(MatchMethod, interface, member, "") {
|
||||
return arg->GetInterface() == interface && arg->GetMember() == member;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
class SecretPortalKeyProviderTest : public testing::Test {
|
||||
public:
|
||||
SecretPortalKeyProviderTest() = default;
|
||||
|
||||
void SetUp() override {
|
||||
SecretPortalKeyProvider::RegisterLocalPrefs(pref_service_.registry());
|
||||
|
||||
mock_bus_ = base::MakeRefCounted<dbus::MockBus>(dbus::Bus::Options());
|
||||
|
||||
mock_dbus_proxy_ = base::MakeRefCounted<dbus::MockObjectProxy>(
|
||||
mock_bus_.get(), DBUS_SERVICE_DBUS, dbus::ObjectPath(DBUS_PATH_DBUS));
|
||||
EXPECT_CALL(*mock_bus_, GetObjectProxy(DBUS_SERVICE_DBUS,
|
||||
dbus::ObjectPath(DBUS_PATH_DBUS)))
|
||||
.WillRepeatedly(Return(mock_dbus_proxy_.get()));
|
||||
|
||||
response_path_ = dbus::ObjectPath(base::nix::XdgDesktopPortalRequestPath(
|
||||
kBusName, SecretPortalKeyProvider::kHandleToken));
|
||||
mock_response_proxy_ = base::MakeRefCounted<dbus::MockObjectProxy>(
|
||||
mock_bus_.get(), SecretPortalKeyProvider::kServiceSecret,
|
||||
response_path_);
|
||||
EXPECT_CALL(
|
||||
*mock_bus_,
|
||||
GetObjectProxy(SecretPortalKeyProvider::kServiceSecret, response_path_))
|
||||
.WillRepeatedly(Return(mock_response_proxy_.get()));
|
||||
|
||||
mock_secret_proxy_ = base::MakeRefCounted<dbus::MockObjectProxy>(
|
||||
mock_bus_.get(), SecretPortalKeyProvider::kServiceSecret,
|
||||
dbus::ObjectPath(SecretPortalKeyProvider::kObjectPathSecret));
|
||||
EXPECT_CALL(*mock_bus_,
|
||||
GetObjectProxy(SecretPortalKeyProvider::kServiceSecret,
|
||||
dbus::ObjectPath(
|
||||
SecretPortalKeyProvider::kObjectPathSecret)))
|
||||
.WillRepeatedly(Return(mock_secret_proxy_.get()));
|
||||
|
||||
key_provider_ = base::WrapUnique(
|
||||
new SecretPortalKeyProvider(&pref_service_, mock_bus_, true));
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
EXPECT_CALL(*mock_bus_, GetDBusTaskRunner())
|
||||
.WillRepeatedly(
|
||||
Return(task_environment_.GetMainThreadTaskRunner().get()));
|
||||
|
||||
EXPECT_CALL(*mock_bus_, ShutdownAndBlock());
|
||||
|
||||
// Shutdown the bus to ensure clean-up
|
||||
key_provider_.reset();
|
||||
|
||||
base::RunLoop run_loop;
|
||||
run_loop.RunUntilIdle();
|
||||
}
|
||||
|
||||
protected:
|
||||
// An IO thread is required for FileDescriptorWatcher.
|
||||
base::test::TaskEnvironment task_environment_{
|
||||
base::test::TaskEnvironment::MainThreadType::IO};
|
||||
|
||||
scoped_refptr<dbus::MockBus> mock_bus_;
|
||||
scoped_refptr<dbus::MockObjectProxy> mock_dbus_proxy_;
|
||||
scoped_refptr<dbus::MockObjectProxy> mock_response_proxy_;
|
||||
scoped_refptr<dbus::MockObjectProxy> mock_secret_proxy_;
|
||||
dbus::ObjectPath response_path_;
|
||||
std::unique_ptr<SecretPortalKeyProvider> key_provider_;
|
||||
TestingPrefServiceSimple pref_service_;
|
||||
};
|
||||
|
||||
TEST_F(SecretPortalKeyProviderTest, GetKey) {
|
||||
EXPECT_CALL(
|
||||
*mock_dbus_proxy_,
|
||||
DoCallMethod(MatchMethod(DBUS_INTERFACE_DBUS, "NameHasOwner"), _, _))
|
||||
.WillOnce(Invoke([](dbus::MethodCall* method_call, int timeout_ms,
|
||||
dbus::ObjectProxy::ResponseCallback* callback) {
|
||||
dbus::MessageReader reader(method_call);
|
||||
std::string service_name;
|
||||
EXPECT_TRUE(reader.PopString(&service_name));
|
||||
EXPECT_EQ(service_name, SecretPortalKeyProvider::kServiceSecret);
|
||||
|
||||
auto response = dbus::Response::CreateEmpty();
|
||||
dbus::MessageWriter writer(response.get());
|
||||
writer.AppendBool(true);
|
||||
std::move(*callback).Run(response.get());
|
||||
}));
|
||||
|
||||
EXPECT_CALL(*mock_bus_, GetConnectionName()).WillOnce(Return(kBusName));
|
||||
|
||||
EXPECT_CALL(*mock_response_proxy_, DoConnectToSignal(_, _, _, _))
|
||||
.WillOnce(Invoke(
|
||||
[&](const std::string& interface_name, const std::string& signal_name,
|
||||
dbus::ObjectProxy::SignalCallback signal_callback,
|
||||
dbus::ObjectProxy::OnConnectedCallback* on_connected_callback) {
|
||||
EXPECT_EQ(interface_name,
|
||||
SecretPortalKeyProvider::kInterfaceRequest);
|
||||
EXPECT_EQ(signal_name, SecretPortalKeyProvider::kSignalResponse);
|
||||
|
||||
std::move(*on_connected_callback)
|
||||
.Run(interface_name, signal_name, true);
|
||||
|
||||
dbus::Signal signal(interface_name, signal_name);
|
||||
dbus::MessageWriter writer(&signal);
|
||||
constexpr uint32_t kResponseSuccess = 0;
|
||||
writer.AppendUint32(kResponseSuccess);
|
||||
DbusDictionary dict;
|
||||
dict.Put("token", MakeDbusVariant(DbusString(kPrefToken)));
|
||||
dict.Write(&writer);
|
||||
signal_callback.Run(&signal);
|
||||
}));
|
||||
|
||||
EXPECT_CALL(
|
||||
*mock_secret_proxy_,
|
||||
DoCallMethod(MatchMethod(SecretPortalKeyProvider::kInterfaceSecret,
|
||||
SecretPortalKeyProvider::kMethodRetrieveSecret),
|
||||
_, _))
|
||||
.WillOnce(Invoke([&](dbus::MethodCall* method_call, int timeout_ms,
|
||||
dbus::ObjectProxy::ResponseCallback* callback) {
|
||||
dbus::MessageReader reader(method_call);
|
||||
base::ScopedFD write_fd;
|
||||
EXPECT_TRUE(reader.PopFileDescriptor(&write_fd));
|
||||
EXPECT_EQ(write(write_fd.get(), kSecret, sizeof(kSecret)),
|
||||
static_cast<ssize_t>(sizeof(kSecret)));
|
||||
write_fd.reset();
|
||||
|
||||
auto response = dbus::Response::CreateEmpty();
|
||||
dbus::MessageWriter writer(response.get());
|
||||
writer.AppendObjectPath(dbus::ObjectPath(response_path_));
|
||||
std::move(*callback).Run(response.get());
|
||||
}));
|
||||
|
||||
bool callback_called = false;
|
||||
std::string key_tag;
|
||||
std::optional<Encryptor::Key> key;
|
||||
key_provider_->GetKey(base::BindOnce(
|
||||
[](bool& callback_called, std::string& key_tag,
|
||||
std::optional<Encryptor::Key>& key,
|
||||
const std::string& returned_key_tag,
|
||||
std::optional<Encryptor::Key> returned_key) {
|
||||
callback_called = true;
|
||||
key_tag = returned_key_tag;
|
||||
key = std::move(returned_key);
|
||||
},
|
||||
std::ref(callback_called), std::ref(key_tag), std::ref(key)));
|
||||
base::RunLoop run_loop;
|
||||
run_loop.RunUntilIdle();
|
||||
EXPECT_TRUE(callback_called);
|
||||
EXPECT_EQ(key_tag, SecretPortalKeyProvider::kKeyTag);
|
||||
EXPECT_TRUE(key.has_value());
|
||||
|
||||
EXPECT_TRUE(pref_service_.HasPrefPath(
|
||||
SecretPortalKeyProvider::kOsCryptTokenPrefName));
|
||||
EXPECT_EQ(
|
||||
pref_service_.GetString(SecretPortalKeyProvider::kOsCryptTokenPrefName),
|
||||
kPrefToken);
|
||||
}
|
||||
|
||||
} // namespace os_crypt_async
|
93
components/os_crypt/async/browser/test_secret_portal.cc
Normal file
93
components/os_crypt/async/browser/test_secret_portal.cc
Normal file
@ -0,0 +1,93 @@
|
||||
// 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 "components/os_crypt/async/browser/test_secret_portal.h"
|
||||
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/nix/xdg_util.h"
|
||||
#include "components/dbus/properties/types.h"
|
||||
#include "components/dbus/utils/name_has_owner.h"
|
||||
#include "dbus/message.h"
|
||||
#include "secret_portal_key_provider.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
namespace os_crypt_async {
|
||||
|
||||
TestSecretPortal::TestSecretPortal(bool pre_test) : pre_test_(pre_test) {
|
||||
dbus::Bus::Options bus_options;
|
||||
bus_options.bus_type = dbus::Bus::SESSION;
|
||||
bus_options.connection_type = dbus::Bus::PRIVATE;
|
||||
bus_ = base::MakeRefCounted<dbus::Bus>(bus_options);
|
||||
|
||||
exported_object_ = bus_->GetExportedObject(
|
||||
dbus::ObjectPath(SecretPortalKeyProvider::kObjectPathSecret));
|
||||
|
||||
EXPECT_TRUE(exported_object_->ExportMethodAndBlock(
|
||||
SecretPortalKeyProvider::kInterfaceSecret,
|
||||
SecretPortalKeyProvider::kMethodRetrieveSecret,
|
||||
base::BindRepeating(&TestSecretPortal::RetrieveSecret,
|
||||
weak_ptr_factory_.GetWeakPtr())));
|
||||
}
|
||||
|
||||
TestSecretPortal::~TestSecretPortal() {
|
||||
exported_object_ = nullptr;
|
||||
bus_->ShutdownAndBlock();
|
||||
}
|
||||
|
||||
std::string TestSecretPortal::BusName() const {
|
||||
return bus_->GetConnectionName();
|
||||
}
|
||||
|
||||
void TestSecretPortal::RetrieveSecret(
|
||||
dbus::MethodCall* method_call,
|
||||
dbus::ExportedObject::ResponseSender response_sender) {
|
||||
dbus::MessageReader reader(method_call);
|
||||
base::ScopedFD write_fd;
|
||||
EXPECT_TRUE(reader.PopFileDescriptor(&write_fd));
|
||||
|
||||
base::WriteFileDescriptor(write_fd.get(), "secret");
|
||||
write_fd.reset();
|
||||
|
||||
dbus::ObjectPath response_path;
|
||||
dbus::MessageReader dict_reader(nullptr);
|
||||
EXPECT_TRUE(reader.PopArray(&dict_reader));
|
||||
std::optional<std::string> token;
|
||||
while (dict_reader.HasMoreData()) {
|
||||
dbus::MessageReader dict_entry_reader(nullptr);
|
||||
EXPECT_TRUE(dict_reader.PopDictEntry(&dict_entry_reader));
|
||||
std::string key;
|
||||
std::string value;
|
||||
EXPECT_TRUE(dict_entry_reader.PopString(&key));
|
||||
if (key == "handle_token") {
|
||||
EXPECT_TRUE(dict_entry_reader.PopVariantOfString(&value));
|
||||
auto bus_name = method_call->GetSender();
|
||||
response_path = dbus::ObjectPath(
|
||||
base::nix::XdgDesktopPortalRequestPath(bus_name, value));
|
||||
}
|
||||
if (key == "token") {
|
||||
EXPECT_TRUE(dict_entry_reader.PopVariantOfString(&value));
|
||||
EXPECT_EQ(value, "the_token");
|
||||
token = value;
|
||||
}
|
||||
}
|
||||
auto* exported_response = bus_->GetExportedObject(response_path);
|
||||
|
||||
EXPECT_EQ(pre_test_, !token.has_value());
|
||||
|
||||
auto response = dbus::Response::FromMethodCall(method_call);
|
||||
dbus::MessageWriter writer(response.get());
|
||||
writer.AppendObjectPath(response_path);
|
||||
std::move(response_sender).Run(std::move(response));
|
||||
|
||||
dbus::Signal signal(SecretPortalKeyProvider::kInterfaceRequest,
|
||||
SecretPortalKeyProvider::kSignalResponse);
|
||||
dbus::MessageWriter signal_writer(&signal);
|
||||
signal_writer.AppendUint32(0);
|
||||
DbusDictionary dict;
|
||||
dict.Put("token", MakeDbusVariant(DbusString("the_token")));
|
||||
dict.Write(&signal_writer);
|
||||
exported_response->SendSignal(&signal);
|
||||
}
|
||||
|
||||
} // namespace os_crypt_async
|
46
components/os_crypt/async/browser/test_secret_portal.h
Normal file
46
components/os_crypt/async/browser/test_secret_portal.h
Normal file
@ -0,0 +1,46 @@
|
||||
// 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.
|
||||
|
||||
#ifndef COMPONENTS_OS_CRYPT_ASYNC_BROWSER_TEST_SECRET_PORTAL_H_
|
||||
#define COMPONENTS_OS_CRYPT_ASYNC_BROWSER_TEST_SECRET_PORTAL_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "base/memory/scoped_refptr.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "dbus/bus.h"
|
||||
#include "dbus/exported_object.h"
|
||||
#include "dbus/message.h"
|
||||
#include "dbus/object_path.h"
|
||||
|
||||
namespace os_crypt_async {
|
||||
|
||||
// This class implements the service side of the org.freedesktop.portal.Secret
|
||||
// interface for testing purposes.
|
||||
class TestSecretPortal {
|
||||
public:
|
||||
// `pre_test` indicates that this is like a browser first-run. If true, a new
|
||||
// token is given. If false, a handle to an existing token is expected.
|
||||
explicit TestSecretPortal(bool pre_test);
|
||||
|
||||
~TestSecretPortal();
|
||||
|
||||
std::string BusName() const;
|
||||
|
||||
private:
|
||||
scoped_refptr<dbus::Bus> bus_;
|
||||
raw_ptr<dbus::ExportedObject> exported_object_ = nullptr;
|
||||
|
||||
void RetrieveSecret(dbus::MethodCall* method_call,
|
||||
dbus::ExportedObject::ResponseSender response_sender);
|
||||
|
||||
const bool pre_test_;
|
||||
|
||||
base::WeakPtrFactory<TestSecretPortal> weak_ptr_factory_{this};
|
||||
};
|
||||
|
||||
} // namespace os_crypt_async
|
||||
|
||||
#endif // COMPONENTS_OS_CRYPT_ASYNC_BROWSER_TEST_SECRET_PORTAL_H_
|
@ -619,7 +619,7 @@ class CHROME_DBUS_EXPORT Bus : public base::RefCountedThreadSafe<Bus> {
|
||||
|
||||
// Return the unique name of the bus connection if it is connected to
|
||||
// D-BUS. Otherwise, return an empty string.
|
||||
std::string GetConnectionName();
|
||||
virtual std::string GetConnectionName();
|
||||
|
||||
// Returns true if the bus is connected to D-Bus.
|
||||
virtual bool IsConnected();
|
||||
|
@ -75,6 +75,7 @@ class MockBus : public Bus {
|
||||
MOCK_METHOD0(HasDBusThread, bool());
|
||||
MOCK_METHOD0(AssertOnOriginThread, void());
|
||||
MOCK_METHOD0(AssertOnDBusThread, void());
|
||||
MOCK_METHOD0(GetConnectionName, std::string());
|
||||
MOCK_METHOD0(IsConnected, bool());
|
||||
|
||||
protected:
|
||||
|
@ -124,6 +124,22 @@ chromium-metrics-reviews@google.com.
|
||||
<int value="3" label="Succeessfully got salient image url from local cache."/>
|
||||
</enum>
|
||||
|
||||
<enum name="SecretPortalKeyProviderInitStatus">
|
||||
<int value="0" label="Success"/>
|
||||
<int value="1" label="NoService"/>
|
||||
<int value="2" label="PipeFailed"/>
|
||||
<int value="3" label="InvalidResponseFormat"/>
|
||||
<int value="4" label="ResponsePathMismatch"/>
|
||||
<int value="5" label="PipeReadFailed"/>
|
||||
<int value="6" label="EmptySecret"/>
|
||||
<int value="7" label="NoResponse"/>
|
||||
<int value="8" label="SignalReadFailed"/>
|
||||
<int value="9" label="UserCancelledUnlock"/>
|
||||
<int value="10" label="DestructedBeforeComplete"/>
|
||||
<int value="11" label="SignalConnectFailed"/>
|
||||
<int value="12" label="SignalParseFailed"/>
|
||||
</enum>
|
||||
|
||||
<enum name="SilentPushEvent">
|
||||
<int value="0" label="New Silent Push request"/>
|
||||
<int value="1" label="Notification enforcement skipped"/>
|
||||
|
@ -6850,6 +6850,40 @@ chromium-metrics-reviews@google.com.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
<histogram name="OSCrypt.SecretPortalKeyProvider.GotToken" enum="Boolean"
|
||||
expires_after="2025-07-28">
|
||||
<owner>thomasanderson@chromium.org</owner>
|
||||
<owner>thestig@chromium.org</owner>
|
||||
<summary>
|
||||
True if a token was received from the service. The token is passed back to
|
||||
the service on subsequent usage to ensure the same secret will be returned.
|
||||
Emitted only on Linux when the desktop portal has emitted the request
|
||||
completed signal after user interaction is finished.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
<histogram name="OSCrypt.SecretPortalKeyProvider.InitStatus"
|
||||
enum="SecretPortalKeyProviderInitStatus" expires_after="2025-07-28">
|
||||
<owner>thomasanderson@chromium.org</owner>
|
||||
<owner>thestig@chromium.org</owner>
|
||||
<summary>
|
||||
The result of the org.freedesktop.portal.Secret OSCrypt Async Key Provider
|
||||
initialization. This is recorded once during browser startup if
|
||||
SecretPortalKeyProvider enabled, on Linux only.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
<histogram name="OSCrypt.SecretPortalKeyProvider.NewInitFailure"
|
||||
enum="SecretPortalKeyProviderInitStatus" expires_after="2025-07-28">
|
||||
<owner>thomasanderson@chromium.org</owner>
|
||||
<owner>thestig@chromium.org</owner>
|
||||
<summary>
|
||||
The result of the org.freedesktop.portal.Secret OSCrypt Async Key Provider
|
||||
initialization if the previous initialization succeeded and the current
|
||||
initialization failed, and the previous desktop matches the current desktop.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
<histogram name="OSCrypt.Win.Decrypt.Result" enum="BooleanSuccess"
|
||||
expires_after="2025-02-10">
|
||||
<owner>wfh@chromium.org</owner>
|
||||
|
Reference in New Issue
Block a user