1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "components/os_crypt/libsecret_util_linux.h"
6
7 #include <dlfcn.h>
8
9 #include "base/logging.h"
10 #include "base/strings/string_number_conversions.h"
11
12 //
13 // LibsecretLoader
14 //
15
16 namespace {
17
18 // TODO(crbug.com/660005) A message that is attached to useless entries that we
19 // create, to explain its existence.
20 const char kExplanationMessage[] =
21 "Because of quirks in the gnome libsecret API, Chrome needs to store a "
22 "dummy entry to guarantee that this keyring was properly unlocked. More "
23 "details at http://crbug.com/660005.";
24
25 } // namespace
26
27 decltype(
28 &::secret_password_store_sync) LibsecretLoader::secret_password_store_sync =
29 nullptr;
30 decltype(
31 &::secret_service_search_sync) LibsecretLoader::secret_service_search_sync =
32 nullptr;
33 decltype(
34 &::secret_password_clear_sync) LibsecretLoader::secret_password_clear_sync =
35 nullptr;
36 decltype(&::secret_item_get_secret) LibsecretLoader::secret_item_get_secret =
37 nullptr;
38 decltype(&::secret_item_get_created) LibsecretLoader::secret_item_get_created =
39 nullptr;
40 decltype(
41 &::secret_item_get_modified) LibsecretLoader::secret_item_get_modified =
42 nullptr;
43 decltype(&::secret_value_get_text) LibsecretLoader::secret_value_get_text =
44 nullptr;
45 decltype(
46 &::secret_item_get_attributes) LibsecretLoader::secret_item_get_attributes =
47 nullptr;
48 decltype(&::secret_item_load_secret_sync)
49 LibsecretLoader::secret_item_load_secret_sync = nullptr;
50 decltype(&::secret_value_unref) LibsecretLoader::secret_value_unref = nullptr;
51
52 bool LibsecretLoader::libsecret_loaded_ = false;
53
54 const LibsecretLoader::FunctionInfo LibsecretLoader::kFunctions[] = {
55 {"secret_item_get_secret",
56 reinterpret_cast<void**>(&secret_item_get_secret)},
57 {"secret_item_get_attributes",
58 reinterpret_cast<void**>(&secret_item_get_attributes)},
59 {"secret_item_get_created",
60 reinterpret_cast<void**>(&secret_item_get_created)},
61 {"secret_item_get_modified",
62 reinterpret_cast<void**>(&secret_item_get_modified)},
63 {"secret_item_load_secret_sync",
64 reinterpret_cast<void**>(&secret_item_load_secret_sync)},
65 {"secret_password_clear_sync",
66 reinterpret_cast<void**>(&secret_password_clear_sync)},
67 {"secret_password_store_sync",
68 reinterpret_cast<void**>(&secret_password_store_sync)},
69 {"secret_service_search_sync",
70 reinterpret_cast<void**>(&secret_service_search_sync)},
71 {"secret_value_get_text", reinterpret_cast<void**>(&secret_value_get_text)},
72 {"secret_value_unref", reinterpret_cast<void**>(&secret_value_unref)},
73 };
74
75 LibsecretLoader::SearchHelper::SearchHelper() = default;
~SearchHelper()76 LibsecretLoader::SearchHelper::~SearchHelper() {
77 if (error_)
78 g_error_free(error_);
79 if (results_)
80 g_list_free_full(results_, &g_object_unref);
81 }
82
Search(const SecretSchema * schema,GHashTable * attrs,int flags)83 void LibsecretLoader::SearchHelper::Search(const SecretSchema* schema,
84 GHashTable* attrs,
85 int flags) {
86 DCHECK(!results_);
87 results_ = LibsecretLoader::secret_service_search_sync(
88 nullptr, // default secret service
89 schema, attrs, static_cast<SecretSearchFlags>(flags),
90 nullptr, // no cancellable object
91 &error_);
92 }
93
94 // static
EnsureLibsecretLoaded()95 bool LibsecretLoader::EnsureLibsecretLoaded() {
96 return LoadLibsecret() && LibsecretIsAvailable();
97 }
98
99 // static
LoadLibsecret()100 bool LibsecretLoader::LoadLibsecret() {
101 if (libsecret_loaded_)
102 return true;
103
104 static void* handle = dlopen("libsecret-1.so.0", RTLD_NOW | RTLD_GLOBAL);
105 if (!handle) {
106 // We wanted to use libsecret, but we couldn't load it. Warn, because
107 // either the user asked for this, or we autodetected it incorrectly. (Or
108 // the system has broken libraries, which is also good to warn about.)
109 // TODO(crbug.com/607435): Channel this message to the user-facing log
110 VLOG(1) << "Could not load libsecret-1.so.0: " << dlerror();
111 return false;
112 }
113
114 for (const auto& function : kFunctions) {
115 dlerror();
116 *function.pointer = dlsym(handle, function.name);
117 const char* error = dlerror();
118 if (error) {
119 VLOG(1) << "Unable to load symbol " << function.name << ": " << error;
120 dlclose(handle);
121 return false;
122 }
123 }
124
125 libsecret_loaded_ = true;
126 // We leak the library handle. That's OK: this function is called only once.
127 return true;
128 }
129
130 // static
LibsecretIsAvailable()131 bool LibsecretLoader::LibsecretIsAvailable() {
132 if (!libsecret_loaded_)
133 return false;
134 // A dummy query is made to check for availability, because libsecret doesn't
135 // have a dedicated availability function. For performance reasons, the query
136 // is meant to return an empty result.
137 LibsecretAttributesBuilder attrs;
138 attrs.Append("application", "chrome-string_to_get_empty_result");
139 const SecretSchema kDummySchema = {
140 "_chrome_dummy_schema",
141 SECRET_SCHEMA_DONT_MATCH_NAME,
142 {{"application", SECRET_SCHEMA_ATTRIBUTE_STRING},
143 {nullptr, SECRET_SCHEMA_ATTRIBUTE_STRING}}};
144
145 SearchHelper helper;
146 helper.Search(&kDummySchema, attrs.Get(),
147 SECRET_SEARCH_ALL | SECRET_SEARCH_UNLOCK);
148 return helper.success();
149 }
150
151 // TODO(crbug.com/660005) This is needed to properly unlock the default keyring.
152 // We don't need to ever read it.
EnsureKeyringUnlocked()153 void LibsecretLoader::EnsureKeyringUnlocked() {
154 const SecretSchema kDummySchema = {
155 "_chrome_dummy_schema_for_unlocking",
156 SECRET_SCHEMA_NONE,
157 {{"explanation", SECRET_SCHEMA_ATTRIBUTE_STRING},
158 {nullptr, SECRET_SCHEMA_ATTRIBUTE_STRING}}};
159
160 GError* error = nullptr;
161 bool success = LibsecretLoader::secret_password_store_sync(
162 &kDummySchema, nullptr /* default keyring */,
163 "Chrome Safe Storage Control" /* entry title */,
164 "The meaning of life" /* password */, nullptr, &error, "explanation",
165 kExplanationMessage,
166 nullptr /* null-terminated variable argument list */);
167 if (error) {
168 VLOG(1) << "Dummy store to unlock the default keyring failed: "
169 << error->message;
170 g_error_free(error);
171 } else if (!success) {
172 VLOG(1) << "Dummy store to unlock the default keyring failed.";
173 }
174 }
175
176 //
177 // LibsecretAttributesBuilder
178 //
179
LibsecretAttributesBuilder()180 LibsecretAttributesBuilder::LibsecretAttributesBuilder() {
181 attrs_ = g_hash_table_new_full(g_str_hash, g_str_equal,
182 nullptr, // no deleter for keys
183 nullptr); // no deleter for values
184 }
185
~LibsecretAttributesBuilder()186 LibsecretAttributesBuilder::~LibsecretAttributesBuilder() {
187 g_hash_table_destroy(attrs_);
188 }
189
Append(const std::string & name,const std::string & value)190 void LibsecretAttributesBuilder::Append(const std::string& name,
191 const std::string& value) {
192 name_values_.push_back(name);
193 gpointer name_str =
194 static_cast<gpointer>(const_cast<char*>(name_values_.back().c_str()));
195 name_values_.push_back(value);
196 gpointer value_str =
197 static_cast<gpointer>(const_cast<char*>(name_values_.back().c_str()));
198 g_hash_table_insert(attrs_, name_str, value_str);
199 }
200
Append(const std::string & name,int64_t value)201 void LibsecretAttributesBuilder::Append(const std::string& name,
202 int64_t value) {
203 Append(name, base::NumberToString(value));
204 }
205