blob: d822e2514c38768d0ceb97ae83ee030cdee80e41 [file] [log] [blame]
// 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/freedesktop_secret_key_provider.h"
#include <algorithm>
#include <tuple>
#include <utility>
#include "base/base64.h"
#include "base/check.h"
#include "base/containers/span.h"
#include "base/environment.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/ref_counted_memory.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/notreached.h"
#include "base/rand_util.h"
#include "base/strings/string_util.h"
#include "components/dbus/thread_linux/dbus_thread_linux.h"
#include "components/dbus/utils/call_method.h"
#include "components/dbus/utils/connect_to_signal.h"
#include "components/dbus/utils/variant.h"
#include "components/os_crypt/async/browser/posix_key_provider.h"
#include "components/os_crypt/async/common/algorithm.mojom.h"
#include "crypto/kdf.h"
#include "dbus/message.h"
#include "dbus/object_path.h"
namespace os_crypt_async {
namespace {
constexpr char kUmaInitStatus[] =
"OSCrypt.FreedesktopSecretKeyProvider.InitStatus";
constexpr char kUmaErrorDetail[] =
"OSCrypt.FreedesktopSecretKeyProvider.$1.ErrorDetail";
// These constants are duplicated from the sync backend.
constexpr char kEncryptionTag[] = "v11";
constexpr char kSalt[] = "saltysalt";
constexpr size_t kDerivedKeySizeInBits = 128;
constexpr size_t kEncryptionIterations = 1;
constexpr size_t kSecretLengthBytes = 16;
FreedesktopSecretKeyProvider::ErrorDetail DbusErrorToErrorDetail(
const dbus_utils::CallMethodError& error) {
switch (error.status) {
case dbus_utils::CallMethodErrorStatus::kNoResponse:
return FreedesktopSecretKeyProvider::ErrorDetail::kNoResponse;
case dbus_utils::CallMethodErrorStatus::kInvalidResponseFormat:
return FreedesktopSecretKeyProvider::ErrorDetail::kInvalidReplyFormat;
case dbus_utils::CallMethodErrorStatus::kExtraDataInResponse:
return FreedesktopSecretKeyProvider::ErrorDetail::kExtraDataInResponse;
case dbus_utils::CallMethodErrorStatus::kErrorResponse:
return FreedesktopSecretKeyProvider::ErrorDetail::kErrorResponse;
}
NOTREACHED();
}
const char* InitStatusToString(
FreedesktopSecretKeyProvider::InitStatus status) {
switch (status) {
case FreedesktopSecretKeyProvider::InitStatus::kSuccess:
return "Success";
case FreedesktopSecretKeyProvider::InitStatus::kCreateCollectionFailed:
return "CreateCollectionFailed";
case FreedesktopSecretKeyProvider::InitStatus::kCreateItemFailed:
return "CreateItemFailed";
case FreedesktopSecretKeyProvider::InitStatus::kEmptySecret:
return "EmptySecret";
case FreedesktopSecretKeyProvider::InitStatus::kGetSecretFailed:
return "GetSecretFailed";
case FreedesktopSecretKeyProvider::InitStatus::kGnomeKeyringDeadlock:
return "GnomeKeyringDeadlock";
case FreedesktopSecretKeyProvider::InitStatus::kNoService:
return "NoService";
case FreedesktopSecretKeyProvider::InitStatus::kReadAliasFailed:
return "ReadAliasFailed";
case FreedesktopSecretKeyProvider::InitStatus::kSearchItemsFailed:
return "SearchItemsFailed";
case FreedesktopSecretKeyProvider::InitStatus::kSessionFailure:
return "SessionFailure";
case FreedesktopSecretKeyProvider::InitStatus::kUnlockFailed:
return "UnlockFailed";
case FreedesktopSecretKeyProvider::InitStatus::kDisabled:
return "Disabled";
case FreedesktopSecretKeyProvider::InitStatus::kKWalletNoService:
return "KWalletNoService";
case FreedesktopSecretKeyProvider::InitStatus::kKWalletDisabled:
return "KWalletDisabled";
case FreedesktopSecretKeyProvider::InitStatus::kKWalletNoNetworkWallet:
return "KWalletNoNetworkWallet";
case FreedesktopSecretKeyProvider::InitStatus::kKWalletOpenFailed:
return "KWalletOpenFailed";
case FreedesktopSecretKeyProvider::InitStatus::kKWalletNoSecret:
return "KWalletNoSecret";
case FreedesktopSecretKeyProvider::InitStatus::kKWalletFolderCheckFailed:
return "KWalletFolderCheckFailed";
case FreedesktopSecretKeyProvider::InitStatus::kKWalletFolderCreationFailed:
return "KWalletFolderCreationFailed";
case FreedesktopSecretKeyProvider::InitStatus::kKWalletEntryCheckFailed:
return "KWalletEntryCheckFailed";
case FreedesktopSecretKeyProvider::InitStatus::kKWalletReadFailed:
return "KWalletReadFailed";
case FreedesktopSecretKeyProvider::InitStatus::kKWalletWriteFailed:
return "KWalletWriteFailed";
}
NOTREACHED();
}
} // namespace
// A helper class to handle a Secret Service prompt. It is templated on the
// return type expected from the prompt.
template <typename T>
class FreedesktopSecretKeyProvider::Prompter
: public base::RefCountedThreadSafe<Prompter<T>> {
public:
using PromptCallback = base::OnceCallback<void(
base::expected<T, FreedesktopSecretKeyProvider::ErrorDetail>)>;
template <dbus_utils::SignatureLiteral ArgsSig,
dbus_utils::SignatureLiteral RetsSig,
typename... Args>
static void Prompt(scoped_refptr<dbus::Bus> bus,
dbus::ObjectProxy* object_proxy,
const std::string& interface_name,
const std::string& method_name,
PromptCallback callback,
const Args&... args) {
auto handler =
base::MakeRefCounted<Prompter<T>>(std::move(bus), std::move(callback));
dbus_utils::CallMethod<ArgsSig, RetsSig>(
object_proxy, interface_name, method_name,
base::BindOnce(&Prompter::OnReply, handler), args...);
}
Prompter(scoped_refptr<dbus::Bus> bus, PromptCallback callback)
: bus_(std::move(bus)), callback_(std::move(callback)) {}
Prompter(const Prompter&) = delete;
Prompter& operator=(const Prompter&) = delete;
private:
friend class base::RefCountedThreadSafe<Prompter<T>>;
using ErrorDetail = FreedesktopSecretKeyProvider::ErrorDetail;
~Prompter() {
Finish(base::unexpected(ErrorDetail::kDestructedBeforeComplete));
}
void OnReply(base::expected<std::tuple<T, dbus::ObjectPath>,
dbus_utils::CallMethodError> reply) {
if (!reply.has_value()) {
Finish(base::unexpected(DbusErrorToErrorDetail(reply.error())));
return;
}
auto& [value, prompt] = reply.value();
if (prompt.value() == "/") {
Finish(std::move(value));
} else {
prompt_path_ = prompt;
StartPrompt();
}
}
void StartPrompt() {
auto* prompt_proxy = bus_->GetObjectProxy(
FreedesktopSecretKeyProvider::kSecretServiceName, prompt_path_);
dbus_utils::ConnectToSignal(
prompt_proxy, FreedesktopSecretKeyProvider::kSecretPromptInterface,
"Completed",
base::BindRepeating(&Prompter::OnPromptCompletedSignal, this),
base::BindOnce(&Prompter::OnSignalConnected, this));
dbus_utils::CallMethod<"s", "">(
prompt_proxy, FreedesktopSecretKeyProvider::kSecretPromptInterface,
FreedesktopSecretKeyProvider::kMethodPrompt,
base::BindOnce(&Prompter::OnPromptResponse, this), "");
}
void OnPromptResponse(dbus_utils::CallMethodResult<> response) {
if (!response.has_value()) {
LOG(ERROR) << "Prompt call returned no response.";
Finish(base::unexpected(DbusErrorToErrorDetail(response.error())));
}
}
void OnSignalConnected(const std::string& interface_name,
const std::string& signal_name,
bool connected) {
if (!connected) {
LOG(ERROR) << "Failed to connect to Prompt.Completed signal.";
Finish(base::unexpected(ErrorDetail::kPromptFailedSignalConnection));
}
}
void OnPromptCompletedSignal(
dbus_utils::ConnectToSignalResult<bool, dbus_utils::Variant> result) {
if (!result.has_value()) {
LOG(ERROR) << "Failed to read Prompt.Completed signal args.";
Finish(base::unexpected(ErrorDetail::kInvalidSignalFormat));
return;
}
auto& [dismissed, variant] = result.value();
if (dismissed) {
Finish(base::unexpected(ErrorDetail::kPromptDismissed));
return;
}
auto value = std::move(variant).Take<T>();
if (!value) {
LOG(ERROR) << "Failed to parse prompt result.";
Finish(base::unexpected(ErrorDetail::kInvalidVariantFormat));
return;
}
Finish(base::ok(std::move(*value)));
}
void Finish(base::expected<T, ErrorDetail> result) {
if (!prompt_path_.value().empty()) {
bus_->RemoveObjectProxy(FreedesktopSecretKeyProvider::kSecretServiceName,
prompt_path_, base::DoNothing());
prompt_path_ = dbus::ObjectPath();
}
if (callback_) {
std::move(callback_).Run(std::move(result));
}
bus_.reset();
}
scoped_refptr<dbus::Bus> bus_;
PromptCallback callback_;
dbus::ObjectPath prompt_path_;
};
FreedesktopSecretKeyProvider::FreedesktopSecretKeyProvider(
const std::string& password_store,
const std::string& product_name,
scoped_refptr<dbus::Bus> bus)
: password_store_(password_store),
product_name_(product_name),
bus_(std::move(bus)) {
if (!bus_) {
bus_ = dbus_thread_linux::GetSharedSessionBus();
}
}
FreedesktopSecretKeyProvider::~FreedesktopSecretKeyProvider() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void FreedesktopSecretKeyProvider::GetKey(KeyCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(callback);
key_callback_ = std::move(callback);
if (!secret_for_testing_.empty()) {
DeriveKeyFromSecret(base::as_byte_span(secret_for_testing_));
return;
}
// Reset state in case GetKey is called multiple times.
kwallet_proxy_ = nullptr;
kwallet_handle_ = kKWalletInvalidHandle;
kwallet_transaction_id_ = kKWalletInvalidTransactionId;
session_opened_ = false;
session_proxy_ = nullptr;
default_collection_proxy_ = nullptr;
if (password_store_ == "basic") {
// Use PosixKeyProvider.
FinalizeFailure(InitStatus::kDisabled, ErrorDetail::kNone);
} else if (password_store_ == "gnome-libsecret") {
InitializeFreedesktopSecretService();
} else if (password_store_ == "kwallet") {
InitializeKWallet(kKWalletDService, kKWalletDPath);
} else if (password_store_ == "kwallet5") {
InitializeKWallet(kKWalletD5Service, kKWalletD5Path);
} else if (password_store_ == "kwallet6") {
InitializeKWallet(kKWalletD6Service, kKWalletD6Path);
} else {
if (!password_store_.empty()) {
LOG(ERROR) << "Unknown password store: " << password_store_;
}
auto env = base::Environment::Create();
switch (base::nix::GetDesktopEnvironment(env.get())) {
case base::nix::DESKTOP_ENVIRONMENT_KDE3:
case base::nix::DESKTOP_ENVIRONMENT_KDE4:
InitializeKWallet(kKWalletDService, kKWalletDPath);
break;
case base::nix::DESKTOP_ENVIRONMENT_KDE5:
InitializeKWallet(kKWalletD5Service, kKWalletD5Path);
break;
case base::nix::DESKTOP_ENVIRONMENT_KDE6:
InitializeKWallet(kKWalletD6Service, kKWalletD6Path);
break;
case base::nix::DESKTOP_ENVIRONMENT_OTHER:
case base::nix::DESKTOP_ENVIRONMENT_CINNAMON:
case base::nix::DESKTOP_ENVIRONMENT_DEEPIN:
case base::nix::DESKTOP_ENVIRONMENT_GNOME:
case base::nix::DESKTOP_ENVIRONMENT_PANTHEON:
case base::nix::DESKTOP_ENVIRONMENT_UKUI:
case base::nix::DESKTOP_ENVIRONMENT_UNITY:
case base::nix::DESKTOP_ENVIRONMENT_XFCE:
case base::nix::DESKTOP_ENVIRONMENT_LXQT:
case base::nix::DESKTOP_ENVIRONMENT_COSMIC:
InitializeFreedesktopSecretService();
break;
}
}
}
bool FreedesktopSecretKeyProvider::UseForEncryption() {
return true;
}
bool FreedesktopSecretKeyProvider::IsCompatibleWithOsCryptSync() {
return true;
}
void FreedesktopSecretKeyProvider::InitializeFreedesktopSecretService() {
dbus_utils::CheckForServiceAndStart(
bus_, kSecretServiceName,
base::BindOnce(&FreedesktopSecretKeyProvider::OnServiceStarted,
weak_ptr_factory_.GetWeakPtr()));
}
void FreedesktopSecretKeyProvider::OnServiceStarted(
std::optional<bool> service_started) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!service_started.value_or(false)) {
FinalizeFailure(InitStatus::kNoService, ErrorDetail::kNone);
return;
}
auto* service_proxy = bus_->GetObjectProxy(
kSecretServiceName, dbus::ObjectPath(kSecretServicePath));
dbus_utils::CallMethod<"s", "o">(
service_proxy, kSecretServiceInterface, kMethodReadAlias,
base::BindOnce(&FreedesktopSecretKeyProvider::OnReadAliasDefault,
weak_ptr_factory_.GetWeakPtr()),
kDefaultAlias);
}
void FreedesktopSecretKeyProvider::OnReadAliasDefault(
dbus_utils::CallMethodResultSig<"o"> collection_path_result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!collection_path_result.has_value()) {
FinalizeFailure(InitStatus::kReadAliasFailed,
DbusErrorToErrorDetail(collection_path_result.error()));
return;
}
const auto& collection_path = std::get<0>(collection_path_result.value());
if (collection_path.value() != "/") {
default_collection_proxy_ =
bus_->GetObjectProxy(kSecretServiceName, collection_path);
dbus_utils::CallMethod<"ss", "v">(
default_collection_proxy_, kPropertiesInterface, kMethodGet,
base::BindOnce(
&FreedesktopSecretKeyProvider::OnGetCollectionLabelResponse,
weak_ptr_factory_.GetWeakPtr()),
kSecretCollectionInterface, kLabelProperty);
} else {
// No default collection, create it
auto* service_proxy = bus_->GetObjectProxy(
kSecretServiceName, dbus::ObjectPath(kSecretServicePath));
std::map<std::string, dbus_utils::Variant> props;
props.emplace(kSecretCollectionLabelProperty,
dbus_utils::Variant::Wrap<"s">(kDefaultCollectionLabel));
Prompter<dbus::ObjectPath>::Prompt<"a{sv}s", "oo">(
bus_, service_proxy, kSecretServiceInterface, kMethodCreateCollection,
base::BindOnce(&FreedesktopSecretKeyProvider::OnCreateCollection,
weak_ptr_factory_.GetWeakPtr()),
props, kDefaultAlias);
}
}
void FreedesktopSecretKeyProvider::OnGetCollectionLabelResponse(
dbus_utils::CallMethodResultSig<"v"> variant_result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!variant_result.has_value()) {
LOG(ERROR) << "Get(Label) failed.";
FinalizeFailure(InitStatus::kGnomeKeyringDeadlock,
DbusErrorToErrorDetail(variant_result.error()));
return;
}
auto label_variant =
std::move(std::get<0>(variant_result.value())).Take<std::string>();
if (!label_variant) {
LOG(ERROR) << "Label property missing or invalid.";
FinalizeFailure(InitStatus::kGnomeKeyringDeadlock,
ErrorDetail::kInvalidVariantFormat);
return;
}
// Label property read successfully
UnlockDefaultCollection();
}
void FreedesktopSecretKeyProvider::OnCreateCollection(
base::expected<dbus::ObjectPath, ErrorDetail> create_collection_reply) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!create_collection_reply.has_value()) {
FinalizeFailure(InitStatus::kCreateCollectionFailed,
create_collection_reply.error());
return;
}
if (create_collection_reply->value() == "/") {
FinalizeFailure(InitStatus::kCreateCollectionFailed,
ErrorDetail::kEmptyObjectPaths);
return;
}
default_collection_proxy_ =
bus_->GetObjectProxy(kSecretServiceName, create_collection_reply.value());
UnlockDefaultCollection();
}
void FreedesktopSecretKeyProvider::UnlockDefaultCollection() {
auto* service_proxy = bus_->GetObjectProxy(
kSecretServiceName, dbus::ObjectPath(kSecretServicePath));
std::vector<dbus::ObjectPath> objects = {
default_collection_proxy_->object_path()};
Prompter<std::vector<dbus::ObjectPath>>::Prompt<"ao", "aoo">(
bus_, service_proxy, kSecretServiceInterface, kMethodUnlock,
base::BindOnce(&FreedesktopSecretKeyProvider::OnUnlock,
weak_ptr_factory_.GetWeakPtr()),
objects);
}
void FreedesktopSecretKeyProvider::OnUnlock(
base::expected<std::vector<dbus::ObjectPath>, ErrorDetail>
unlocked_collection) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!unlocked_collection.has_value()) {
FinalizeFailure(InitStatus::kUnlockFailed, unlocked_collection.error());
return;
}
if (unlocked_collection->empty()) {
FinalizeFailure(InitStatus::kUnlockFailed, ErrorDetail::kEmptyObjectPaths);
return;
}
// Unlocked now
OpenSession();
}
void FreedesktopSecretKeyProvider::OpenSession() {
auto* service_proxy = bus_->GetObjectProxy(
kSecretServiceName, dbus::ObjectPath(kSecretServicePath));
dbus_utils::CallMethod<"sv", "vo">(
service_proxy, kSecretServiceInterface, kMethodOpenSession,
base::BindOnce(&FreedesktopSecretKeyProvider::OnOpenSession,
weak_ptr_factory_.GetWeakPtr()),
kAlgorithmPlain, dbus_utils::Variant::Wrap<"s">(kInputPlain));
}
void FreedesktopSecretKeyProvider::OnOpenSession(
dbus_utils::CallMethodResultSig<"vo"> session_reply) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!session_reply.has_value()) {
FinalizeFailure(InitStatus::kSessionFailure,
DbusErrorToErrorDetail(session_reply.error()));
return;
}
const auto& [_, result] = session_reply.value();
session_proxy_ = bus_->GetObjectProxy(kSecretServiceName, result);
session_opened_ = true;
std::map<std::string, std::string> search_attrs{
{kApplicationAttributeKey, kAppName}};
dbus_utils::CallMethod<"a{ss}", "ao">(
default_collection_proxy_, kSecretCollectionInterface, kMethodSearchItems,
base::BindOnce(&FreedesktopSecretKeyProvider::OnSearchItems,
weak_ptr_factory_.GetWeakPtr()),
search_attrs);
}
void FreedesktopSecretKeyProvider::OnSearchItems(
dbus_utils::CallMethodResultSig<"ao"> results) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!results.has_value()) {
FinalizeFailure(InitStatus::kSearchItemsFailed,
DbusErrorToErrorDetail(results.error()));
return;
}
const auto& result_paths = std::get<0>(results.value());
if (result_paths.empty()) {
// No items found, create a new secret.
CreateItem(base::MakeRefCounted<base::RefCountedString>(
base::Base64Encode(base::RandBytesAsVector(kSecretLengthBytes))));
return;
}
auto* item_proxy =
bus_->GetObjectProxy(kSecretServiceName, result_paths.front());
dbus_utils::CallMethod<"o", "(oayays)">(
item_proxy, kSecretItemInterface, kMethodGetSecret,
base::BindOnce(&FreedesktopSecretKeyProvider::OnGetSecret,
weak_ptr_factory_.GetWeakPtr()),
session_proxy_->object_path());
}
void FreedesktopSecretKeyProvider::OnGetSecret(
dbus_utils::CallMethodResultSig<"(oayays)"> secret_reply) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!secret_reply.has_value()) {
FinalizeFailure(InitStatus::kGetSecretFailed,
DbusErrorToErrorDetail(secret_reply.error()));
return;
}
const auto& [session_path, parameters, value, content_type] =
std::get<0>(secret_reply.value());
if (value.empty()) {
LOG(ERROR) << "GetSecret returned an empty secret.";
FinalizeFailure(InitStatus::kEmptySecret, ErrorDetail::kNone);
return;
}
DeriveKeyFromSecret(base::span(value));
}
void FreedesktopSecretKeyProvider::InitializeKWallet(
const char* kwallet_service,
const char* kwallet_path) {
kwallet_proxy_ =
bus_->GetObjectProxy(kwallet_service, dbus::ObjectPath(kwallet_path));
dbus_utils::CheckForServiceAndStart(
bus_.get(), kwallet_service,
base::BindOnce(&FreedesktopSecretKeyProvider::OnKWalletServiceStarted,
weak_ptr_factory_.GetWeakPtr()));
}
void FreedesktopSecretKeyProvider::OnKWalletServiceStarted(
std::optional<bool> service_started) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!service_started.value_or(false)) {
FinalizeFailure(InitStatus::kKWalletNoService, ErrorDetail::kNone);
return;
}
dbus_utils::CallMethod<"", "b">(
kwallet_proxy_, kKWalletInterface, kKWalletMethodIsEnabled,
base::BindOnce(&FreedesktopSecretKeyProvider::OnKWalletIsEnabled,
weak_ptr_factory_.GetWeakPtr()));
}
void FreedesktopSecretKeyProvider::OnKWalletIsEnabled(
dbus_utils::CallMethodResultSig<"b"> is_enabled) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!is_enabled.has_value() || !std::get<0>(is_enabled.value())) {
FinalizeFailure(InitStatus::kKWalletDisabled,
is_enabled.has_value()
? ErrorDetail::kNone
: DbusErrorToErrorDetail(is_enabled.error()));
return;
}
dbus_utils::CallMethod<"", "s">(
kwallet_proxy_, kKWalletInterface, kKWalletMethodNetworkWallet,
base::BindOnce(&FreedesktopSecretKeyProvider::OnKWalletNetworkWallet,
weak_ptr_factory_.GetWeakPtr()));
}
void FreedesktopSecretKeyProvider::OnKWalletNetworkWallet(
dbus_utils::CallMethodResultSig<"s"> wallet_name) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!wallet_name.has_value()) {
FinalizeFailure(InitStatus::kKWalletNoNetworkWallet,
DbusErrorToErrorDetail(wallet_name.error()));
return;
}
dbus_utils::ConnectToSignal(
kwallet_proxy_, kKWalletInterface, kKWalletSignalWalletAsyncOpened,
base::BindRepeating(
&FreedesktopSecretKeyProvider::OnKWalletWalletAsyncOpened,
weak_ptr_factory_.GetWeakPtr()),
base::BindOnce(&FreedesktopSecretKeyProvider::OnSignalConnected,
weak_ptr_factory_.GetWeakPtr()));
// handleSession = true means KWallet will take care of keeping the wallet
// open as long as the application is running.
constexpr bool kHandleSession = true;
dbus_utils::CallMethod<"sxsb", "i">(
kwallet_proxy_, kKWalletInterface, kKWalletMethodOpenAsync,
base::BindOnce(&FreedesktopSecretKeyProvider::OnKWalletOpenAsync,
weak_ptr_factory_.GetWeakPtr()),
std::get<0>(wallet_name.value()), 0, product_name_, kHandleSession);
}
void FreedesktopSecretKeyProvider::OnKWalletOpenAsync(
dbus_utils::CallMethodResultSig<"i"> t_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!t_id.has_value()) {
FinalizeFailure(InitStatus::kKWalletOpenFailed,
DbusErrorToErrorDetail(t_id.error()));
return;
}
kwallet_transaction_id_ = std::get<0>(t_id.value());
}
void FreedesktopSecretKeyProvider::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 " << signal_name << " signal.";
FinalizeFailure(InitStatus::kKWalletOpenFailed,
ErrorDetail::kPromptFailedSignalConnection);
}
}
void FreedesktopSecretKeyProvider::OnKWalletWalletAsyncOpened(
dbus_utils::ConnectToSignalResult<int32_t, int32_t> result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (kwallet_transaction_id_ == kKWalletInvalidTransactionId) {
return;
}
if (!result.has_value()) {
LOG(ERROR) << "Failed to read walletAsyncOpened signal args.";
FinalizeFailure(InitStatus::kKWalletOpenFailed,
ErrorDetail::kInvalidSignalFormat);
return;
}
auto& [t_id, handle] = result.value();
if (t_id != kwallet_transaction_id_) {
return;
}
// Reset transaction ID to avoid processing the signal again.
kwallet_transaction_id_ = kKWalletInvalidTransactionId;
OnKWalletOpen(handle);
}
void FreedesktopSecretKeyProvider::OnKWalletOpen(int32_t handle) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
kwallet_handle_ = handle;
if (kwallet_handle_ == kKWalletInvalidHandle) {
FinalizeFailure(InitStatus::kKWalletOpenFailed,
ErrorDetail::kKWalletApiReturnedError);
return;
}
dbus_utils::CallMethod<"iss", "b">(
kwallet_proxy_, kKWalletInterface, kKWalletMethodHasFolder,
base::BindOnce(&FreedesktopSecretKeyProvider::OnKWalletHasFolder,
weak_ptr_factory_.GetWeakPtr()),
kwallet_handle_, kKWalletFolder, product_name_);
}
void FreedesktopSecretKeyProvider::OnKWalletHasFolder(
dbus_utils::CallMethodResultSig<"b"> has_folder) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!has_folder.has_value()) {
FinalizeFailure(InitStatus::kKWalletFolderCheckFailed,
DbusErrorToErrorDetail(has_folder.error()));
return;
}
if (std::get<0>(has_folder.value())) {
dbus_utils::CallMethod<"isss", "b">(
kwallet_proxy_, kKWalletInterface, kKWalletMethodHasEntry,
base::BindOnce(&FreedesktopSecretKeyProvider::OnKWalletHasEntry,
weak_ptr_factory_.GetWeakPtr()),
kwallet_handle_, kKWalletFolder, kKeyName, product_name_);
} else {
dbus_utils::CallMethod<"iss", "b">(
kwallet_proxy_, kKWalletInterface, kKWalletMethodCreateFolder,
base::BindOnce(&FreedesktopSecretKeyProvider::OnKWalletCreateFolder,
weak_ptr_factory_.GetWeakPtr()),
kwallet_handle_, kKWalletFolder, product_name_);
}
}
void FreedesktopSecretKeyProvider::OnKWalletCreateFolder(
dbus_utils::CallMethodResultSig<"b"> success) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!success.has_value()) {
FinalizeFailure(InitStatus::kKWalletFolderCreationFailed,
DbusErrorToErrorDetail(success.error()));
return;
}
if (!std::get<0>(success.value())) {
FinalizeFailure(InitStatus::kKWalletFolderCreationFailed,
ErrorDetail::kKWalletApiReturnedFalse);
return;
}
GenerateAndWriteKWalletPassword();
}
void FreedesktopSecretKeyProvider::OnKWalletHasEntry(
dbus_utils::CallMethodResultSig<"b"> has_entry) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!has_entry.has_value()) {
FinalizeFailure(InitStatus::kKWalletEntryCheckFailed,
DbusErrorToErrorDetail(has_entry.error()));
return;
}
if (std::get<0>(has_entry.value())) {
dbus_utils::CallMethod<"isss", "s">(
kwallet_proxy_, kKWalletInterface, kKWalletMethodReadPassword,
base::BindOnce(&FreedesktopSecretKeyProvider::OnKWalletReadPassword,
weak_ptr_factory_.GetWeakPtr()),
kwallet_handle_, kKWalletFolder, kKeyName, product_name_);
} else {
GenerateAndWriteKWalletPassword();
}
}
void FreedesktopSecretKeyProvider::OnKWalletReadPassword(
dbus_utils::CallMethodResultSig<"s"> secret_reply) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!secret_reply.has_value()) {
FinalizeFailure(InitStatus::kKWalletReadFailed,
DbusErrorToErrorDetail(secret_reply.error()));
return;
}
std::string secret = std::move(std::get<0>(secret_reply.value()));
if (secret.empty()) {
// The synchronous KWallet backend generates a new password if the
// existing one is empty, so that logic is duplicated here.
LOG(ERROR) << "Existing KWallet password is empty. Generating a new one.";
GenerateAndWriteKWalletPassword();
return;
}
DeriveKeyFromSecret(base::as_byte_span(secret));
}
void FreedesktopSecretKeyProvider::GenerateAndWriteKWalletPassword() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto secret = base::MakeRefCounted<base::RefCountedString>(
base::Base64Encode(base::RandBytesAsVector(kSecretLengthBytes)));
dbus_utils::CallMethod<"issss", "i">(
kwallet_proxy_, kKWalletInterface, kKWalletMethodWritePassword,
base::BindOnce(&FreedesktopSecretKeyProvider::OnKWalletWritePassword,
weak_ptr_factory_.GetWeakPtr(), secret),
kwallet_handle_, kKWalletFolder, kKeyName, secret->as_string(),
product_name_);
}
void FreedesktopSecretKeyProvider::OnKWalletWritePassword(
scoped_refptr<base::RefCountedMemory> generated_secret,
dbus_utils::CallMethodResultSig<"i"> return_code) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!return_code.has_value()) {
FinalizeFailure(InitStatus::kKWalletWriteFailed,
DbusErrorToErrorDetail(return_code.error()));
return;
}
int32_t kwallet_code = std::get<0>(return_code.value());
if (kwallet_code != 0) {
LOG(ERROR) << "KWallet writePassword failed with code: " << kwallet_code;
FinalizeFailure(InitStatus::kKWalletWriteFailed,
ErrorDetail::kKWalletApiReturnedError);
return;
}
DeriveKeyFromSecret(*generated_secret);
}
void FreedesktopSecretKeyProvider::CreateItem(
scoped_refptr<base::RefCountedMemory> secret) {
std::map<std::string, std::string> attributes{
{kApplicationAttributeKey, kAppName},
{kSchemaAttributeKey, kSchemaAttributeValue}};
std::map<std::string, dbus_utils::Variant> props;
props.emplace(kSecretItemAttributesProperty,
dbus_utils::Variant::Wrap<"a{ss}">(std::move(attributes)));
props.emplace(kSecretItemLabelProperty,
dbus_utils::Variant::Wrap<"s">(kKeyName));
std::vector<uint8_t> secret_bytes(secret->begin(), secret->end());
auto secret_struct =
std::make_tuple(session_proxy_->object_path(), std::vector<uint8_t>(),
std::move(secret_bytes), kMimePlain);
auto* collection_proxy = bus_->GetObjectProxy(
kSecretServiceName, default_collection_proxy_->object_path());
Prompter<dbus::ObjectPath>::Prompt<"a{sv}(oayays)b", "oo">(
bus_, collection_proxy, kSecretCollectionInterface, kMethodCreateItem,
base::BindOnce(&FreedesktopSecretKeyProvider::OnCreateItem,
weak_ptr_factory_.GetWeakPtr(), secret),
props, secret_struct, false);
}
void FreedesktopSecretKeyProvider::OnCreateItem(
scoped_refptr<base::RefCountedMemory> secret,
base::expected<dbus::ObjectPath, ErrorDetail> created_item) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!created_item.has_value()) {
FinalizeFailure(InitStatus::kCreateItemFailed, created_item.error());
return;
}
if (created_item->value().empty()) {
FinalizeFailure(InitStatus::kCreateItemFailed,
ErrorDetail::kEmptyObjectPaths);
return;
}
DeriveKeyFromSecret(*secret);
}
void FreedesktopSecretKeyProvider::DeriveKeyFromSecret(
base::span<const uint8_t> secret) {
static_assert(kDerivedKeySizeInBits % 8 == 0);
std::array<uint8_t, kDerivedKeySizeInBits / 8> key_bytes;
crypto::kdf::DeriveKeyPbkdf2HmacSha1(
{kEncryptionIterations}, secret,
base::as_byte_span(base::span_from_cstring(kSalt)), key_bytes,
crypto::SubtlePassKey{});
Encryptor::Key key(key_bytes, mojom::Algorithm::kAES128CBC);
FinalizeSuccess(std::move(key));
}
void FreedesktopSecretKeyProvider::FinalizeSuccess(Encryptor::Key key) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
RecordInitStatus(InitStatus::kSuccess, ErrorDetail::kNone);
std::move(key_callback_).Run(kEncryptionTag, std::move(key));
CloseSession();
}
void FreedesktopSecretKeyProvider::FinalizeFailure(InitStatus status,
ErrorDetail detail) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!key_callback_) {
return;
}
RecordInitStatus(status, detail);
// Fallback to PosixKeyProvider.
PosixKeyProvider fallback_key_provider;
// PosixKeyProvider::GetKey runs synchronously, so `fallback_key_provider`
// doesn't need to outlive the callback.
fallback_key_provider.GetKey(std::move(key_callback_));
CloseSession();
}
void FreedesktopSecretKeyProvider::RecordInitStatus(InitStatus status,
ErrorDetail detail) {
// Log the high-level InitStatus.
base::UmaHistogramEnumeration(kUmaInitStatus, status);
// If there was an error, also log the error detail.
if (status != InitStatus::kSuccess) {
auto histogram_name = base::ReplaceStringPlaceholders(
kUmaErrorDetail, std::vector<std::string>{InitStatusToString(status)},
nullptr);
base::UmaHistogramEnumeration(histogram_name, detail);
}
}
void FreedesktopSecretKeyProvider::CloseSession() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (session_opened_) {
dbus_utils::CallMethod<"", "">(session_proxy_, kSecretSessionInterface,
kMethodClose, base::DoNothing());
}
if (kwallet_handle_ != kKWalletInvalidHandle) {
dbus_utils::CallMethod<"ibs", "i">(kwallet_proxy_, kKWalletInterface,
kKWalletMethodClose, base::DoNothing(),
kwallet_handle_, false, product_name_);
kwallet_handle_ = kKWalletInvalidHandle;
}
}
} // namespace os_crypt_async