Backed out changeset f53842753805 (bug 1804872) for causing reftest failures on 15535...
[gecko.git] / security / manager / ssl / LibSecret.cpp
blobff5270878a0f064159820933e81f403eb04d9cbc
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "LibSecret.h"
9 #include <gio/gio.h>
10 #include <gmodule.h>
11 #include <memory>
13 #include "mozilla/Base64.h"
14 #include "mozilla/Logging.h"
15 #include "MainThreadUtils.h"
16 #include "prlink.h"
18 // This is the implementation of LibSecret, an instantiation of OSKeyStore for
19 // Linux.
21 using namespace mozilla;
23 LazyLogModule gLibSecretLog("libsecret");
25 static PRLibrary* libsecret = nullptr;
27 typedef struct _SecretService SecretService;
28 typedef struct _SecretCollection SecretCollection;
30 typedef enum {
31 SECRET_SCHEMA_NONE = 0,
32 SECRET_SCHEMA_DONT_MATCH_NAME = 1 << 1
33 } SecretSchemaFlags;
35 typedef enum {
36 SECRET_SCHEMA_ATTRIBUTE_STRING = 0,
37 SECRET_SCHEMA_ATTRIBUTE_INTEGER = 1,
38 SECRET_SCHEMA_ATTRIBUTE_BOOLEAN = 2,
39 } SecretSchemaAttributeType;
41 typedef struct {
42 const gchar* name;
43 SecretSchemaAttributeType type;
44 } SecretSchemaAttribute;
46 typedef struct {
47 const gchar* name;
48 SecretSchemaFlags flags;
49 SecretSchemaAttribute attributes[32];
51 /* <private> */
52 gint reserved;
53 gpointer reserved1;
54 gpointer reserved2;
55 gpointer reserved3;
56 gpointer reserved4;
57 gpointer reserved5;
58 gpointer reserved6;
59 gpointer reserved7;
60 } SecretSchema;
62 typedef enum {
63 SECRET_COLLECTION_NONE = 0 << 0,
64 SECRET_COLLECTION_LOAD_ITEMS = 1 << 1,
65 } SecretCollectionFlags;
67 typedef enum {
68 SECRET_SERVICE_NONE = 0,
69 SECRET_SERVICE_OPEN_SESSION = 1 << 1,
70 SECRET_SERVICE_LOAD_COLLECTIONS = 1 << 2,
71 } SecretServiceFlags;
73 typedef enum {
74 SECRET_ERROR_PROTOCOL = 1,
75 SECRET_ERROR_IS_LOCKED = 2,
76 SECRET_ERROR_NO_SUCH_OBJECT = 3,
77 SECRET_ERROR_ALREADY_EXISTS = 4,
78 } SecretError;
80 #define SECRET_COLLECTION_DEFAULT "default"
82 typedef SecretCollection* (*secret_collection_for_alias_sync_fn)(
83 SecretService*, const gchar*, SecretCollectionFlags, GCancellable*,
84 GError**);
85 typedef SecretService* (*secret_service_get_sync_fn)(SecretServiceFlags,
86 GCancellable*, GError**);
87 typedef gint (*secret_service_lock_sync_fn)(SecretService*, GList*,
88 GCancellable*, GList**, GError**);
89 typedef gint (*secret_service_unlock_sync_fn)(SecretService*, GList*,
90 GCancellable*, GList**, GError**);
91 typedef gboolean (*secret_password_clear_sync_fn)(const SecretSchema*,
92 GCancellable*, GError**, ...);
93 typedef gchar* (*secret_password_lookup_sync_fn)(const SecretSchema*,
94 GCancellable*, GError**, ...);
95 typedef gboolean (*secret_password_store_sync_fn)(const SecretSchema*,
96 const gchar*, const gchar*,
97 const gchar*, GCancellable*,
98 GError**, ...);
99 typedef void (*secret_password_free_fn)(const gchar*);
100 typedef GQuark (*secret_error_get_quark_fn)();
102 static secret_collection_for_alias_sync_fn secret_collection_for_alias_sync =
103 nullptr;
104 static secret_service_get_sync_fn secret_service_get_sync = nullptr;
105 static secret_service_lock_sync_fn secret_service_lock_sync = nullptr;
106 static secret_service_unlock_sync_fn secret_service_unlock_sync = nullptr;
107 static secret_password_clear_sync_fn secret_password_clear_sync = nullptr;
108 static secret_password_lookup_sync_fn secret_password_lookup_sync = nullptr;
109 static secret_password_store_sync_fn secret_password_store_sync = nullptr;
110 static secret_password_free_fn secret_password_free = nullptr;
111 static secret_error_get_quark_fn secret_error_get_quark = nullptr;
113 nsresult MaybeLoadLibSecret() {
114 MOZ_ASSERT(NS_IsMainThread());
115 if (!NS_IsMainThread()) {
116 return NS_ERROR_NOT_SAME_THREAD;
119 if (!libsecret) {
120 libsecret = PR_LoadLibrary("libsecret-1.so.0");
121 if (!libsecret) {
122 return NS_ERROR_NOT_AVAILABLE;
125 // With TSan, we cannot unload libsecret once we have loaded it because
126 // TSan does not support unloading libraries that are matched from its
127 // suppression list. Hence we just keep the library loaded in TSan builds.
128 #ifdef MOZ_TSAN
129 # define UNLOAD_LIBSECRET(x) \
130 do { \
131 } while (0)
132 #else
133 # define UNLOAD_LIBSECRET(x) PR_UnloadLibrary(x)
134 #endif
136 #define FIND_FUNCTION_SYMBOL(function) \
137 function = (function##_fn)PR_FindFunctionSymbol(libsecret, #function); \
138 if (!(function)) { \
139 UNLOAD_LIBSECRET(libsecret); \
140 libsecret = nullptr; \
141 return NS_ERROR_NOT_AVAILABLE; \
143 FIND_FUNCTION_SYMBOL(secret_collection_for_alias_sync);
144 FIND_FUNCTION_SYMBOL(secret_service_get_sync);
145 FIND_FUNCTION_SYMBOL(secret_service_lock_sync);
146 FIND_FUNCTION_SYMBOL(secret_service_unlock_sync);
147 FIND_FUNCTION_SYMBOL(secret_password_clear_sync);
148 FIND_FUNCTION_SYMBOL(secret_password_lookup_sync);
149 FIND_FUNCTION_SYMBOL(secret_password_store_sync);
150 FIND_FUNCTION_SYMBOL(secret_password_free);
151 FIND_FUNCTION_SYMBOL(secret_error_get_quark);
152 #undef FIND_FUNCTION_SYMBOL
155 return NS_OK;
158 struct ScopedDelete {
159 void operator()(SecretService* ss) {
160 if (ss) g_object_unref(ss);
162 void operator()(SecretCollection* sc) {
163 if (sc) g_object_unref(sc);
165 void operator()(GError* error) {
166 if (error) g_error_free(error);
168 void operator()(GList* list) {
169 if (list) g_list_free(list);
171 void operator()(char* val) {
172 if (val) secret_password_free(val);
176 template <class T>
177 struct ScopedMaybeDelete {
178 void operator()(T* ptr) {
179 if (ptr) {
180 ScopedDelete del;
181 del(ptr);
186 typedef std::unique_ptr<GError, ScopedMaybeDelete<GError>> ScopedGError;
187 typedef std::unique_ptr<GList, ScopedMaybeDelete<GList>> ScopedGList;
188 typedef std::unique_ptr<char, ScopedMaybeDelete<char>> ScopedPassword;
189 typedef std::unique_ptr<SecretCollection, ScopedMaybeDelete<SecretCollection>>
190 ScopedSecretCollection;
191 typedef std::unique_ptr<SecretService, ScopedMaybeDelete<SecretService>>
192 ScopedSecretService;
194 LibSecret::LibSecret() = default;
196 LibSecret::~LibSecret() {
197 MOZ_ASSERT(NS_IsMainThread());
198 if (!NS_IsMainThread()) {
199 return;
201 if (libsecret) {
202 secret_collection_for_alias_sync = nullptr;
203 secret_service_get_sync = nullptr;
204 secret_service_lock_sync = nullptr;
205 secret_service_unlock_sync = nullptr;
206 secret_password_clear_sync = nullptr;
207 secret_password_lookup_sync = nullptr;
208 secret_password_store_sync = nullptr;
209 secret_password_free = nullptr;
210 secret_error_get_quark = nullptr;
211 UNLOAD_LIBSECRET(libsecret);
212 libsecret = nullptr;
216 static const SecretSchema kSchema = {
217 "mozilla.firefox",
218 SECRET_SCHEMA_NONE,
219 {{"string", SECRET_SCHEMA_ATTRIBUTE_STRING}, /* the label */
220 {"NULL", SECRET_SCHEMA_ATTRIBUTE_STRING}}};
222 nsresult GetScopedServices(ScopedSecretService& aSs,
223 ScopedSecretCollection& aSc) {
224 MOZ_ASSERT(secret_service_get_sync && secret_collection_for_alias_sync);
225 if (!secret_service_get_sync || !secret_collection_for_alias_sync) {
226 return NS_ERROR_FAILURE;
228 GError* raw_error = nullptr;
229 aSs = ScopedSecretService(secret_service_get_sync(
230 static_cast<SecretServiceFlags>(
231 SECRET_SERVICE_OPEN_SESSION), // SecretServiceFlags
232 nullptr, // GCancellable
233 &raw_error));
234 ScopedGError error(raw_error);
235 if (error || !aSs) {
236 MOZ_LOG(gLibSecretLog, LogLevel::Debug, ("Couldn't get a secret service"));
237 return NS_ERROR_FAILURE;
240 aSc = ScopedSecretCollection(secret_collection_for_alias_sync(
241 aSs.get(), "default", static_cast<SecretCollectionFlags>(0),
242 nullptr, // GCancellable
243 &raw_error));
244 error.reset(raw_error);
245 if (!aSc) {
246 MOZ_LOG(gLibSecretLog, LogLevel::Debug,
247 ("Couldn't get a secret collection"));
248 return NS_ERROR_FAILURE;
250 return NS_OK;
253 nsresult LibSecret::Lock() {
254 MOZ_ASSERT(secret_service_lock_sync);
255 if (!secret_service_lock_sync) {
256 return NS_ERROR_FAILURE;
258 ScopedSecretService ss;
259 ScopedSecretCollection sc;
260 if (NS_FAILED(GetScopedServices(ss, sc))) {
261 return NS_ERROR_FAILURE;
264 GError* raw_error = nullptr;
265 GList* collections = nullptr;
266 ScopedGList collectionList(g_list_append(collections, sc.get()));
267 int numLocked = secret_service_lock_sync(ss.get(), collectionList.get(),
268 nullptr, // GCancellable
269 nullptr, // list of locked items
270 &raw_error);
271 ScopedGError error(raw_error);
272 if (numLocked != 1) {
273 MOZ_LOG(gLibSecretLog, LogLevel::Debug,
274 ("Couldn't lock secret collection"));
275 return NS_ERROR_FAILURE;
277 return NS_OK;
280 nsresult LibSecret::Unlock() {
281 MOZ_ASSERT(secret_service_unlock_sync);
282 if (!secret_service_unlock_sync) {
283 return NS_ERROR_FAILURE;
285 // Accessing the secret service might unlock it.
286 ScopedSecretService ss;
287 ScopedSecretCollection sc;
288 if (NS_FAILED(GetScopedServices(ss, sc))) {
289 return NS_ERROR_FAILURE;
291 GError* raw_error = nullptr;
292 GList* collections = nullptr;
293 ScopedGList collectionList(g_list_append(collections, sc.get()));
294 int numLocked = secret_service_unlock_sync(ss.get(), collectionList.get(),
295 nullptr, // GCancellable
296 nullptr, // list of unlocked items
297 &raw_error);
298 ScopedGError error(raw_error);
299 if (numLocked != 1) {
300 MOZ_LOG(gLibSecretLog, LogLevel::Debug,
301 ("Couldn't unlock secret collection"));
302 return NS_ERROR_FAILURE;
304 return NS_OK;
307 nsresult LibSecret::StoreSecret(const nsACString& aSecret,
308 const nsACString& aLabel) {
309 MOZ_ASSERT(secret_password_store_sync);
310 if (!secret_password_store_sync) {
311 return NS_ERROR_FAILURE;
313 // libsecret expects a null-terminated string, so to be safe we store the
314 // secret (which could be arbitrary bytes) base64-encoded.
315 nsAutoCString base64;
316 nsresult rv = Base64Encode(aSecret, base64);
317 if (NS_FAILED(rv)) {
318 MOZ_LOG(gLibSecretLog, LogLevel::Debug, ("Error base64-encoding secret"));
319 return rv;
321 GError* raw_error = nullptr;
322 bool stored = secret_password_store_sync(
323 &kSchema, SECRET_COLLECTION_DEFAULT, PromiseFlatCString(aLabel).get(),
324 PromiseFlatCString(base64).get(),
325 nullptr, // GCancellable
326 &raw_error, "string", PromiseFlatCString(aLabel).get(), nullptr);
327 ScopedGError error(raw_error);
328 if (raw_error) {
329 MOZ_LOG(gLibSecretLog, LogLevel::Debug, ("Error storing secret"));
330 return NS_ERROR_FAILURE;
333 return stored ? NS_OK : NS_ERROR_FAILURE;
336 nsresult LibSecret::DeleteSecret(const nsACString& aLabel) {
337 MOZ_ASSERT(secret_password_clear_sync && secret_error_get_quark);
338 if (!secret_password_clear_sync || !secret_error_get_quark) {
339 return NS_ERROR_FAILURE;
341 GError* raw_error = nullptr;
342 Unused << secret_password_clear_sync(
343 &kSchema,
344 nullptr, // GCancellable
345 &raw_error, "string", PromiseFlatCString(aLabel).get(), nullptr);
346 ScopedGError error(raw_error);
347 if (raw_error && !(raw_error->domain == secret_error_get_quark() &&
348 raw_error->code == SECRET_ERROR_NO_SUCH_OBJECT)) {
349 MOZ_LOG(gLibSecretLog, LogLevel::Debug, ("Error deleting secret"));
350 return NS_ERROR_FAILURE;
353 return NS_OK;
356 nsresult LibSecret::RetrieveSecret(const nsACString& aLabel,
357 /* out */ nsACString& aSecret) {
358 MOZ_ASSERT(secret_password_lookup_sync && secret_password_free);
359 if (!secret_password_lookup_sync || !secret_password_free) {
360 return NS_ERROR_FAILURE;
362 GError* raw_error = nullptr;
363 aSecret.Truncate();
364 ScopedPassword s(secret_password_lookup_sync(
365 &kSchema,
366 nullptr, // GCancellable
367 &raw_error, "string", PromiseFlatCString(aLabel).get(), nullptr));
368 ScopedGError error(raw_error);
369 if (raw_error || !s) {
370 MOZ_LOG(gLibSecretLog, LogLevel::Debug,
371 ("Error retrieving secret or didn't find it"));
372 return NS_ERROR_FAILURE;
374 // libsecret expects a null-terminated string, so to be safe we store the
375 // secret (which could be arbitrary bytes) base64-encoded, which means we have
376 // to base64-decode it here.
377 nsAutoCString base64Encoded(s.get());
378 nsresult rv = Base64Decode(base64Encoded, aSecret);
379 if (NS_FAILED(rv)) {
380 MOZ_LOG(gLibSecretLog, LogLevel::Debug, ("Error base64-decoding secret"));
381 return rv;
384 return NS_OK;