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/. */
13 #include "mozilla/Base64.h"
14 #include "mozilla/Logging.h"
15 #include "MainThreadUtils.h"
18 // This is the implementation of LibSecret, an instantiation of OSKeyStore for
21 using namespace mozilla
;
23 LazyLogModule
gLibSecretLog("libsecret");
25 static PRLibrary
* libsecret
= nullptr;
27 typedef struct _SecretService SecretService
;
28 typedef struct _SecretCollection SecretCollection
;
31 SECRET_SCHEMA_NONE
= 0,
32 SECRET_SCHEMA_DONT_MATCH_NAME
= 1 << 1
36 SECRET_SCHEMA_ATTRIBUTE_STRING
= 0,
37 SECRET_SCHEMA_ATTRIBUTE_INTEGER
= 1,
38 SECRET_SCHEMA_ATTRIBUTE_BOOLEAN
= 2,
39 } SecretSchemaAttributeType
;
43 SecretSchemaAttributeType type
;
44 } SecretSchemaAttribute
;
48 SecretSchemaFlags flags
;
49 SecretSchemaAttribute attributes
[32];
63 SECRET_COLLECTION_NONE
= 0 << 0,
64 SECRET_COLLECTION_LOAD_ITEMS
= 1 << 1,
65 } SecretCollectionFlags
;
68 SECRET_SERVICE_NONE
= 0,
69 SECRET_SERVICE_OPEN_SESSION
= 1 << 1,
70 SECRET_SERVICE_LOAD_COLLECTIONS
= 1 << 2,
74 SECRET_ERROR_PROTOCOL
= 1,
75 SECRET_ERROR_IS_LOCKED
= 2,
76 SECRET_ERROR_NO_SUCH_OBJECT
= 3,
77 SECRET_ERROR_ALREADY_EXISTS
= 4,
80 #define SECRET_COLLECTION_DEFAULT "default"
82 typedef SecretCollection
* (*secret_collection_for_alias_sync_fn
)(
83 SecretService
*, const gchar
*, SecretCollectionFlags
, GCancellable
*,
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
*,
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
=
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
;
120 libsecret
= PR_LoadLibrary("libsecret-1.so.0");
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.
129 # define UNLOAD_LIBSECRET(x) \
133 # define UNLOAD_LIBSECRET(x) PR_UnloadLibrary(x)
136 #define FIND_FUNCTION_SYMBOL(function) \
137 function = (function##_fn)PR_FindFunctionSymbol(libsecret, #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
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
);
177 struct ScopedMaybeDelete
{
178 void operator()(T
* 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
>>
194 LibSecret::LibSecret() = default;
196 LibSecret::~LibSecret() {
197 MOZ_ASSERT(NS_IsMainThread());
198 if (!NS_IsMainThread()) {
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
);
216 static const SecretSchema kSchema
= {
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
234 ScopedGError
error(raw_error
);
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
244 error
.reset(raw_error
);
246 MOZ_LOG(gLibSecretLog
, LogLevel::Debug
,
247 ("Couldn't get a secret collection"));
248 return NS_ERROR_FAILURE
;
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
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
;
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
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
;
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
);
318 MOZ_LOG(gLibSecretLog
, LogLevel::Debug
, ("Error base64-encoding secret"));
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
);
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(
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
;
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;
364 ScopedPassword
s(secret_password_lookup_sync(
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
);
380 MOZ_LOG(gLibSecretLog
, LogLevel::Debug
, ("Error base64-decoding secret"));