summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/LibSecret.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
commit0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch)
treea31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /security/manager/ssl/LibSecret.cpp
parentInitial commit. (diff)
downloadfirefox-esr-upstream/115.8.0esr.tar.xz
firefox-esr-upstream/115.8.0esr.zip
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'security/manager/ssl/LibSecret.cpp')
-rw-r--r--security/manager/ssl/LibSecret.cpp385
1 files changed, 385 insertions, 0 deletions
diff --git a/security/manager/ssl/LibSecret.cpp b/security/manager/ssl/LibSecret.cpp
new file mode 100644
index 0000000000..ff5270878a
--- /dev/null
+++ b/security/manager/ssl/LibSecret.cpp
@@ -0,0 +1,385 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "LibSecret.h"
+
+#include <gio/gio.h>
+#include <gmodule.h>
+#include <memory>
+
+#include "mozilla/Base64.h"
+#include "mozilla/Logging.h"
+#include "MainThreadUtils.h"
+#include "prlink.h"
+
+// This is the implementation of LibSecret, an instantiation of OSKeyStore for
+// Linux.
+
+using namespace mozilla;
+
+LazyLogModule gLibSecretLog("libsecret");
+
+static PRLibrary* libsecret = nullptr;
+
+typedef struct _SecretService SecretService;
+typedef struct _SecretCollection SecretCollection;
+
+typedef enum {
+ SECRET_SCHEMA_NONE = 0,
+ SECRET_SCHEMA_DONT_MATCH_NAME = 1 << 1
+} SecretSchemaFlags;
+
+typedef enum {
+ SECRET_SCHEMA_ATTRIBUTE_STRING = 0,
+ SECRET_SCHEMA_ATTRIBUTE_INTEGER = 1,
+ SECRET_SCHEMA_ATTRIBUTE_BOOLEAN = 2,
+} SecretSchemaAttributeType;
+
+typedef struct {
+ const gchar* name;
+ SecretSchemaAttributeType type;
+} SecretSchemaAttribute;
+
+typedef struct {
+ const gchar* name;
+ SecretSchemaFlags flags;
+ SecretSchemaAttribute attributes[32];
+
+ /* <private> */
+ gint reserved;
+ gpointer reserved1;
+ gpointer reserved2;
+ gpointer reserved3;
+ gpointer reserved4;
+ gpointer reserved5;
+ gpointer reserved6;
+ gpointer reserved7;
+} SecretSchema;
+
+typedef enum {
+ SECRET_COLLECTION_NONE = 0 << 0,
+ SECRET_COLLECTION_LOAD_ITEMS = 1 << 1,
+} SecretCollectionFlags;
+
+typedef enum {
+ SECRET_SERVICE_NONE = 0,
+ SECRET_SERVICE_OPEN_SESSION = 1 << 1,
+ SECRET_SERVICE_LOAD_COLLECTIONS = 1 << 2,
+} SecretServiceFlags;
+
+typedef enum {
+ SECRET_ERROR_PROTOCOL = 1,
+ SECRET_ERROR_IS_LOCKED = 2,
+ SECRET_ERROR_NO_SUCH_OBJECT = 3,
+ SECRET_ERROR_ALREADY_EXISTS = 4,
+} SecretError;
+
+#define SECRET_COLLECTION_DEFAULT "default"
+
+typedef SecretCollection* (*secret_collection_for_alias_sync_fn)(
+ SecretService*, const gchar*, SecretCollectionFlags, GCancellable*,
+ GError**);
+typedef SecretService* (*secret_service_get_sync_fn)(SecretServiceFlags,
+ GCancellable*, GError**);
+typedef gint (*secret_service_lock_sync_fn)(SecretService*, GList*,
+ GCancellable*, GList**, GError**);
+typedef gint (*secret_service_unlock_sync_fn)(SecretService*, GList*,
+ GCancellable*, GList**, GError**);
+typedef gboolean (*secret_password_clear_sync_fn)(const SecretSchema*,
+ GCancellable*, GError**, ...);
+typedef gchar* (*secret_password_lookup_sync_fn)(const SecretSchema*,
+ GCancellable*, GError**, ...);
+typedef gboolean (*secret_password_store_sync_fn)(const SecretSchema*,
+ const gchar*, const gchar*,
+ const gchar*, GCancellable*,
+ GError**, ...);
+typedef void (*secret_password_free_fn)(const gchar*);
+typedef GQuark (*secret_error_get_quark_fn)();
+
+static secret_collection_for_alias_sync_fn secret_collection_for_alias_sync =
+ nullptr;
+static secret_service_get_sync_fn secret_service_get_sync = nullptr;
+static secret_service_lock_sync_fn secret_service_lock_sync = nullptr;
+static secret_service_unlock_sync_fn secret_service_unlock_sync = nullptr;
+static secret_password_clear_sync_fn secret_password_clear_sync = nullptr;
+static secret_password_lookup_sync_fn secret_password_lookup_sync = nullptr;
+static secret_password_store_sync_fn secret_password_store_sync = nullptr;
+static secret_password_free_fn secret_password_free = nullptr;
+static secret_error_get_quark_fn secret_error_get_quark = nullptr;
+
+nsresult MaybeLoadLibSecret() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!NS_IsMainThread()) {
+ return NS_ERROR_NOT_SAME_THREAD;
+ }
+
+ if (!libsecret) {
+ libsecret = PR_LoadLibrary("libsecret-1.so.0");
+ if (!libsecret) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+// With TSan, we cannot unload libsecret once we have loaded it because
+// TSan does not support unloading libraries that are matched from its
+// suppression list. Hence we just keep the library loaded in TSan builds.
+#ifdef MOZ_TSAN
+# define UNLOAD_LIBSECRET(x) \
+ do { \
+ } while (0)
+#else
+# define UNLOAD_LIBSECRET(x) PR_UnloadLibrary(x)
+#endif
+
+#define FIND_FUNCTION_SYMBOL(function) \
+ function = (function##_fn)PR_FindFunctionSymbol(libsecret, #function); \
+ if (!(function)) { \
+ UNLOAD_LIBSECRET(libsecret); \
+ libsecret = nullptr; \
+ return NS_ERROR_NOT_AVAILABLE; \
+ }
+ FIND_FUNCTION_SYMBOL(secret_collection_for_alias_sync);
+ FIND_FUNCTION_SYMBOL(secret_service_get_sync);
+ FIND_FUNCTION_SYMBOL(secret_service_lock_sync);
+ FIND_FUNCTION_SYMBOL(secret_service_unlock_sync);
+ FIND_FUNCTION_SYMBOL(secret_password_clear_sync);
+ FIND_FUNCTION_SYMBOL(secret_password_lookup_sync);
+ FIND_FUNCTION_SYMBOL(secret_password_store_sync);
+ FIND_FUNCTION_SYMBOL(secret_password_free);
+ FIND_FUNCTION_SYMBOL(secret_error_get_quark);
+#undef FIND_FUNCTION_SYMBOL
+ }
+
+ return NS_OK;
+}
+
+struct ScopedDelete {
+ void operator()(SecretService* ss) {
+ if (ss) g_object_unref(ss);
+ }
+ void operator()(SecretCollection* sc) {
+ if (sc) g_object_unref(sc);
+ }
+ void operator()(GError* error) {
+ if (error) g_error_free(error);
+ }
+ void operator()(GList* list) {
+ if (list) g_list_free(list);
+ }
+ void operator()(char* val) {
+ if (val) secret_password_free(val);
+ }
+};
+
+template <class T>
+struct ScopedMaybeDelete {
+ void operator()(T* ptr) {
+ if (ptr) {
+ ScopedDelete del;
+ del(ptr);
+ }
+ }
+};
+
+typedef std::unique_ptr<GError, ScopedMaybeDelete<GError>> ScopedGError;
+typedef std::unique_ptr<GList, ScopedMaybeDelete<GList>> ScopedGList;
+typedef std::unique_ptr<char, ScopedMaybeDelete<char>> ScopedPassword;
+typedef std::unique_ptr<SecretCollection, ScopedMaybeDelete<SecretCollection>>
+ ScopedSecretCollection;
+typedef std::unique_ptr<SecretService, ScopedMaybeDelete<SecretService>>
+ ScopedSecretService;
+
+LibSecret::LibSecret() = default;
+
+LibSecret::~LibSecret() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!NS_IsMainThread()) {
+ return;
+ }
+ if (libsecret) {
+ secret_collection_for_alias_sync = nullptr;
+ secret_service_get_sync = nullptr;
+ secret_service_lock_sync = nullptr;
+ secret_service_unlock_sync = nullptr;
+ secret_password_clear_sync = nullptr;
+ secret_password_lookup_sync = nullptr;
+ secret_password_store_sync = nullptr;
+ secret_password_free = nullptr;
+ secret_error_get_quark = nullptr;
+ UNLOAD_LIBSECRET(libsecret);
+ libsecret = nullptr;
+ }
+}
+
+static const SecretSchema kSchema = {
+ "mozilla.firefox",
+ SECRET_SCHEMA_NONE,
+ {{"string", SECRET_SCHEMA_ATTRIBUTE_STRING}, /* the label */
+ {"NULL", SECRET_SCHEMA_ATTRIBUTE_STRING}}};
+
+nsresult GetScopedServices(ScopedSecretService& aSs,
+ ScopedSecretCollection& aSc) {
+ MOZ_ASSERT(secret_service_get_sync && secret_collection_for_alias_sync);
+ if (!secret_service_get_sync || !secret_collection_for_alias_sync) {
+ return NS_ERROR_FAILURE;
+ }
+ GError* raw_error = nullptr;
+ aSs = ScopedSecretService(secret_service_get_sync(
+ static_cast<SecretServiceFlags>(
+ SECRET_SERVICE_OPEN_SESSION), // SecretServiceFlags
+ nullptr, // GCancellable
+ &raw_error));
+ ScopedGError error(raw_error);
+ if (error || !aSs) {
+ MOZ_LOG(gLibSecretLog, LogLevel::Debug, ("Couldn't get a secret service"));
+ return NS_ERROR_FAILURE;
+ }
+
+ aSc = ScopedSecretCollection(secret_collection_for_alias_sync(
+ aSs.get(), "default", static_cast<SecretCollectionFlags>(0),
+ nullptr, // GCancellable
+ &raw_error));
+ error.reset(raw_error);
+ if (!aSc) {
+ MOZ_LOG(gLibSecretLog, LogLevel::Debug,
+ ("Couldn't get a secret collection"));
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+nsresult LibSecret::Lock() {
+ MOZ_ASSERT(secret_service_lock_sync);
+ if (!secret_service_lock_sync) {
+ return NS_ERROR_FAILURE;
+ }
+ ScopedSecretService ss;
+ ScopedSecretCollection sc;
+ if (NS_FAILED(GetScopedServices(ss, sc))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ GError* raw_error = nullptr;
+ GList* collections = nullptr;
+ ScopedGList collectionList(g_list_append(collections, sc.get()));
+ int numLocked = secret_service_lock_sync(ss.get(), collectionList.get(),
+ nullptr, // GCancellable
+ nullptr, // list of locked items
+ &raw_error);
+ ScopedGError error(raw_error);
+ if (numLocked != 1) {
+ MOZ_LOG(gLibSecretLog, LogLevel::Debug,
+ ("Couldn't lock secret collection"));
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+nsresult LibSecret::Unlock() {
+ MOZ_ASSERT(secret_service_unlock_sync);
+ if (!secret_service_unlock_sync) {
+ return NS_ERROR_FAILURE;
+ }
+ // Accessing the secret service might unlock it.
+ ScopedSecretService ss;
+ ScopedSecretCollection sc;
+ if (NS_FAILED(GetScopedServices(ss, sc))) {
+ return NS_ERROR_FAILURE;
+ }
+ GError* raw_error = nullptr;
+ GList* collections = nullptr;
+ ScopedGList collectionList(g_list_append(collections, sc.get()));
+ int numLocked = secret_service_unlock_sync(ss.get(), collectionList.get(),
+ nullptr, // GCancellable
+ nullptr, // list of unlocked items
+ &raw_error);
+ ScopedGError error(raw_error);
+ if (numLocked != 1) {
+ MOZ_LOG(gLibSecretLog, LogLevel::Debug,
+ ("Couldn't unlock secret collection"));
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+nsresult LibSecret::StoreSecret(const nsACString& aSecret,
+ const nsACString& aLabel) {
+ MOZ_ASSERT(secret_password_store_sync);
+ if (!secret_password_store_sync) {
+ return NS_ERROR_FAILURE;
+ }
+ // libsecret expects a null-terminated string, so to be safe we store the
+ // secret (which could be arbitrary bytes) base64-encoded.
+ nsAutoCString base64;
+ nsresult rv = Base64Encode(aSecret, base64);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(gLibSecretLog, LogLevel::Debug, ("Error base64-encoding secret"));
+ return rv;
+ }
+ GError* raw_error = nullptr;
+ bool stored = secret_password_store_sync(
+ &kSchema, SECRET_COLLECTION_DEFAULT, PromiseFlatCString(aLabel).get(),
+ PromiseFlatCString(base64).get(),
+ nullptr, // GCancellable
+ &raw_error, "string", PromiseFlatCString(aLabel).get(), nullptr);
+ ScopedGError error(raw_error);
+ if (raw_error) {
+ MOZ_LOG(gLibSecretLog, LogLevel::Debug, ("Error storing secret"));
+ return NS_ERROR_FAILURE;
+ }
+
+ return stored ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult LibSecret::DeleteSecret(const nsACString& aLabel) {
+ MOZ_ASSERT(secret_password_clear_sync && secret_error_get_quark);
+ if (!secret_password_clear_sync || !secret_error_get_quark) {
+ return NS_ERROR_FAILURE;
+ }
+ GError* raw_error = nullptr;
+ Unused << secret_password_clear_sync(
+ &kSchema,
+ nullptr, // GCancellable
+ &raw_error, "string", PromiseFlatCString(aLabel).get(), nullptr);
+ ScopedGError error(raw_error);
+ if (raw_error && !(raw_error->domain == secret_error_get_quark() &&
+ raw_error->code == SECRET_ERROR_NO_SUCH_OBJECT)) {
+ MOZ_LOG(gLibSecretLog, LogLevel::Debug, ("Error deleting secret"));
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult LibSecret::RetrieveSecret(const nsACString& aLabel,
+ /* out */ nsACString& aSecret) {
+ MOZ_ASSERT(secret_password_lookup_sync && secret_password_free);
+ if (!secret_password_lookup_sync || !secret_password_free) {
+ return NS_ERROR_FAILURE;
+ }
+ GError* raw_error = nullptr;
+ aSecret.Truncate();
+ ScopedPassword s(secret_password_lookup_sync(
+ &kSchema,
+ nullptr, // GCancellable
+ &raw_error, "string", PromiseFlatCString(aLabel).get(), nullptr));
+ ScopedGError error(raw_error);
+ if (raw_error || !s) {
+ MOZ_LOG(gLibSecretLog, LogLevel::Debug,
+ ("Error retrieving secret or didn't find it"));
+ return NS_ERROR_FAILURE;
+ }
+ // libsecret expects a null-terminated string, so to be safe we store the
+ // secret (which could be arbitrary bytes) base64-encoded, which means we have
+ // to base64-decode it here.
+ nsAutoCString base64Encoded(s.get());
+ nsresult rv = Base64Decode(base64Encoded, aSecret);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(gLibSecretLog, LogLevel::Debug, ("Error base64-decoding secret"));
+ return rv;
+ }
+
+ return NS_OK;
+}