| // 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)); |
| } |