blob: 6288334542f425aceb6e4ab0fbb8f959b83fdf8c [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/os_crypt/libsecret_util_linux.h"
#include <dlfcn.h>
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
//
// LibsecretLoader
//
namespace {
// TODO(crbug.com/660005) A message that is attached to useless entries that we
// create, to explain its existence.
const char kExplanationMessage[] =
"Because of quirks in the gnome libsecret API, Chrome needs to store a "
"dummy entry to guarantee that this keyring was properly unlocked. More "
"details at http://crbug.com/660005.";
} // namespace
decltype(
&::secret_password_store_sync) LibsecretLoader::secret_password_store_sync =
nullptr;
decltype(
&::secret_service_search_sync) LibsecretLoader::secret_service_search_sync =
nullptr;
decltype(
&::secret_password_clear_sync) LibsecretLoader::secret_password_clear_sync =
nullptr;
decltype(&::secret_item_get_secret) LibsecretLoader::secret_item_get_secret =
nullptr;
decltype(&::secret_value_get_text) LibsecretLoader::secret_value_get_text =
nullptr;
decltype(
&::secret_item_get_attributes) LibsecretLoader::secret_item_get_attributes =
nullptr;
decltype(&::secret_item_load_secret_sync)
LibsecretLoader::secret_item_load_secret_sync = nullptr;
decltype(&::secret_value_unref) LibsecretLoader::secret_value_unref = nullptr;
bool LibsecretLoader::libsecret_loaded_ = false;
const LibsecretLoader::FunctionInfo LibsecretLoader::kFunctions[] = {
{"secret_item_get_secret",
reinterpret_cast<void**>(&secret_item_get_secret)},
{"secret_item_get_attributes",
reinterpret_cast<void**>(&secret_item_get_attributes)},
{"secret_item_load_secret_sync",
reinterpret_cast<void**>(&secret_item_load_secret_sync)},
{"secret_password_clear_sync",
reinterpret_cast<void**>(&secret_password_clear_sync)},
{"secret_password_store_sync",
reinterpret_cast<void**>(&secret_password_store_sync)},
{"secret_service_search_sync",
reinterpret_cast<void**>(&secret_service_search_sync)},
{"secret_value_get_text", reinterpret_cast<void**>(&secret_value_get_text)},
{"secret_value_unref", reinterpret_cast<void**>(&secret_value_unref)},
};
LibsecretLoader::SearchHelper::SearchHelper() = default;
LibsecretLoader::SearchHelper::~SearchHelper() {
if (error_)
g_error_free(error_);
if (results_)
g_list_free_full(results_, &g_object_unref);
}
void LibsecretLoader::SearchHelper::Search(const SecretSchema* schema,
GHashTable* attrs,
int flags) {
DCHECK(!results_);
results_ = LibsecretLoader::secret_service_search_sync(
nullptr, // default secret service
schema, attrs, static_cast<SecretSearchFlags>(flags),
nullptr, // no cancellable object
&error_);
}
// static
bool LibsecretLoader::EnsureLibsecretLoaded() {
return LoadLibsecret() && LibsecretIsAvailable();
}
// static
bool LibsecretLoader::LoadLibsecret() {
if (libsecret_loaded_)
return true;
static void* handle = dlopen("libsecret-1.so.0", RTLD_NOW | RTLD_GLOBAL);
if (!handle) {
// We wanted to use libsecret, but we couldn't load it. Warn, because
// either the user asked for this, or we autodetected it incorrectly. (Or
// the system has broken libraries, which is also good to warn about.)
// TODO(crbug.com/607435): Channel this message to the user-facing log
VLOG(1) << "Could not load libsecret-1.so.0: " << dlerror();
return false;
}
for (const auto& function : kFunctions) {
dlerror();
*function.pointer = dlsym(handle, function.name);
const char* error = dlerror();
if (error) {
VLOG(1) << "Unable to load symbol " << function.name << ": " << error;
dlclose(handle);
return false;
}
}
libsecret_loaded_ = true;
// We leak the library handle. That's OK: this function is called only once.
return true;
}
// static
bool LibsecretLoader::LibsecretIsAvailable() {
if (!libsecret_loaded_)
return false;
// A dummy query is made to check for availability, because libsecret doesn't
// have a dedicated availability function. For performance reasons, the query
// is meant to return an empty result.
LibsecretAttributesBuilder attrs;
attrs.Append("application", "chrome-string_to_get_empty_result");
const SecretSchema kDummySchema = {
"_chrome_dummy_schema",
SECRET_SCHEMA_DONT_MATCH_NAME,
{{"application", SECRET_SCHEMA_ATTRIBUTE_STRING},
{nullptr, SECRET_SCHEMA_ATTRIBUTE_STRING}}};
SearchHelper helper;
helper.Search(&kDummySchema, attrs.Get(),
SECRET_SEARCH_ALL | SECRET_SEARCH_UNLOCK);
return helper.success();
}
// TODO(crbug.com/660005) This is needed to properly unlock the default keyring.
// We don't need to ever read it.
void LibsecretLoader::EnsureKeyringUnlocked() {
const SecretSchema kDummySchema = {
"_chrome_dummy_schema_for_unlocking",
SECRET_SCHEMA_NONE,
{{"explanation", SECRET_SCHEMA_ATTRIBUTE_STRING},
{nullptr, SECRET_SCHEMA_ATTRIBUTE_STRING}}};
GError* error = nullptr;
bool success = LibsecretLoader::secret_password_store_sync(
&kDummySchema, nullptr /* default keyring */,
"Chrome Safe Storage Control" /* entry title */,
"The meaning of life" /* password */, nullptr, &error, "explanation",
kExplanationMessage,
nullptr /* null-terminated variable argument list */);
if (error) {
VLOG(1) << "Dummy store to unlock the default keyring failed: "
<< error->message;
g_error_free(error);
} else if (!success) {
VLOG(1) << "Dummy store to unlock the default keyring failed.";
}
}
//
// LibsecretAttributesBuilder
//
LibsecretAttributesBuilder::LibsecretAttributesBuilder() {
attrs_ = g_hash_table_new_full(g_str_hash, g_str_equal,
nullptr, // no deleter for keys
nullptr); // no deleter for values
}
LibsecretAttributesBuilder::~LibsecretAttributesBuilder() {
g_hash_table_destroy(attrs_);
}
void LibsecretAttributesBuilder::Append(const std::string& name,
const std::string& value) {
name_values_.push_back(name);
gpointer name_str =
static_cast<gpointer>(const_cast<char*>(name_values_.back().c_str()));
name_values_.push_back(value);
gpointer value_str =
static_cast<gpointer>(const_cast<char*>(name_values_.back().c_str()));
g_hash_table_insert(attrs_, name_str, value_str);
}
void LibsecretAttributesBuilder::Append(const std::string& name,
int64_t value) {
Append(name, base::Int64ToString(value));
}