bug 1498909 - dynamically load libsecret at runtime if available r=franziskus,jcj

Enough linux-based systems don't have libsecret that we can't make it a
requirement on linux. For those that do, however, we can dynamically load the
library at runtime. For those that don't, we can fall back to NSS.

Differential Revision: https://phabricator.services.mozilla.com/D9969

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Dana Keeler 2019-01-03 00:39:45 +00:00
Родитель 012ea52a90
Коммит 74e6b5cabe
7 изменённых файлов: 278 добавлений и 90 удалений

Просмотреть файл

@ -1350,20 +1350,3 @@ if CONFIG['MOZ_WAYLAND']:
'wayland-egl.h',
'wayland-util.h',
]
if CONFIG['MOZ_LIB_SECRET']:
system_headers += [
'libsecret/secret.h',
'libsecret/secret-attributes.h',
'libsecret/secret-collection.h',
'libsecret/secret-enum-types.h',
'libsecret/secret-item.h',
'libsecret/secret-password.h',
'libsecret/secret-paths.h',
'libsecret/secret-prompt.h',
'libsecret/secret-schema.h',
'libsecret/secret-schemas.h',
'libsecret/secret-types.h',
'libsecret/secret-value.h',
'libsecret/secret-service.h',
]

Просмотреть файл

@ -6,9 +6,12 @@
#include "LibSecret.h"
#include <gio/gio.h>
#include <gmodule.h>
#include <memory>
#include "mozilla/Base64.h"
#include "prlink.h"
// This is the implementation of LibSecret, an instantiation of OSKeyStore for
// Linux.
@ -17,9 +20,180 @@ 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 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_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;
}
#define FIND_FUNCTION_SYMBOL(function) \
function = (function##_fn)PR_FindFunctionSymbol(libsecret, #function); \
if (!(function)) { \
PR_UnloadLibrary(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_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() {}
LibSecret::~LibSecret() {}
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_password_clear_sync = nullptr;
secret_password_lookup_sync = nullptr;
secret_password_store_sync = nullptr;
secret_password_free = nullptr;
secret_error_get_quark = nullptr;
PR_UnloadLibrary(libsecret);
libsecret = nullptr;
}
}
static const SecretSchema kSchema = {
"mozilla.firefox",
@ -29,6 +203,10 @@ static const SecretSchema kSchema = {
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>(
@ -55,6 +233,10 @@ nsresult GetScopedServices(ScopedSecretService& aSs,
}
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))) {
@ -90,10 +272,22 @@ nsresult LibSecret::Unlock() {
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(aSecret).get(),
PromiseFlatCString(base64).get(),
nullptr, // GCancellable
&raw_error, "string", PromiseFlatCString(aLabel).get(), nullptr);
ScopedGError error(raw_error);
@ -106,22 +300,31 @@ nsresult LibSecret::StoreSecret(const nsACString& aSecret,
}
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;
bool r = secret_password_clear_sync(
Unused << secret_password_clear_sync(
&kSchema,
nullptr, // GCancellable
&raw_error, "string", PromiseFlatCString(aLabel).get(), nullptr);
ScopedGError error(raw_error);
if (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 r ? NS_OK : 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(
@ -134,7 +337,15 @@ nsresult LibSecret::RetrieveSecret(const nsACString& aLabel,
("Error retrieving secret or didn't find it"));
return NS_ERROR_FAILURE;
}
aSecret.Assign(s.get(), strlen(s.get()));
// 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;
}

Просмотреть файл

@ -4,66 +4,14 @@
* 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/. */
#ifndef MOZ_LIB_SECRET
#error LibSecret OSKeyStore included when MOZ_LIB_SECRET is not defined!
#endif
#ifndef LibSecret_h
#define LibSecret_h
#include "OSKeyStore.h"
#include <libsecret/secret.h>
#include <memory>
#include <vector>
#include "nsString.h"
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()(SecretValue* val) {
if (val) secret_value_unref(val);
}
void operator()(SecretItem* val) {
if (val) g_object_unref(val);
}
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);
}
}
};
#define SCOPED(x) typedef std::unique_ptr<x, ScopedMaybeDelete<x>> Scoped##x
SCOPED(SecretService);
SCOPED(SecretCollection);
SCOPED(GError);
SCOPED(GList);
SCOPED(SecretValue);
SCOPED(SecretItem);
typedef std::unique_ptr<char, ScopedMaybeDelete<char>> ScopedPassword;
#undef SCOPED
nsresult MaybeLoadLibSecret();
class LibSecret final : public AbstractOSKeyStore {
public:

Просмотреть файл

@ -12,12 +12,13 @@
#include "nsXPCOM.h"
#include "pk11pub.h"
#ifdef MOZ_LIB_SECRET
#include "LibSecret.h"
#elif defined(XP_MACOSX)
#if defined(XP_MACOSX)
#include "KeychainSecret.h"
#elif defined(XP_WIN)
#include "CredentialManagerSecret.h"
#elif defined(MOZ_WIDGET_GTK)
#include "LibSecret.h"
#include "NSSKeyStore.h"
#else
#include "NSSKeyStore.h"
#endif
@ -27,8 +28,6 @@ NS_IMPL_ISUPPORTS(OSKeyStore, nsIOSKeyStore, nsIObserver)
using namespace mozilla;
using dom::Promise;
mozilla::LazyLogModule gOSKeyStoreLog("oskeystore");
OSKeyStore::OSKeyStore()
: mKs(nullptr), mKsThread(nullptr), mKsIsNSSKeyStore(false) {
MOZ_ASSERT(NS_IsMainThread());
@ -36,12 +35,17 @@ OSKeyStore::OSKeyStore()
return;
}
#ifdef MOZ_LIB_SECRET
mKs.reset(new LibSecret());
#elif defined(XP_MACOSX)
#if defined(XP_MACOSX)
mKs.reset(new KeychainSecret());
#elif defined(XP_WIN)
mKs.reset(new CredentialManagerSecret());
#elif defined(MOZ_WIDGET_GTK)
if (NS_SUCCEEDED(MaybeLoadLibSecret())) {
mKs.reset(new LibSecret());
} else {
mKs.reset(new NSSKeyStore());
mKsIsNSSKeyStore = true;
}
#else
mKs.reset(new NSSKeyStore());
mKsIsNSSKeyStore = true;

Просмотреть файл

@ -141,12 +141,12 @@ UNIFIED_SOURCES += [
'TransportSecurityInfo.cpp',
]
if CONFIG['MOZ_LIB_SECRET']:
if 'gtk' in CONFIG['MOZ_WIDGET_TOOLKIT']:
UNIFIED_SOURCES += [
'LibSecret.cpp',
]
CFLAGS += CONFIG['MOZ_LIB_SECRET_CFLAGS']
CXXFLAGS += CONFIG['MOZ_LIB_SECRET_CFLAGS']
CFLAGS += CONFIG['GLIB_CFLAGS']
CXXFLAGS += CONFIG['GLIB_CFLAGS']
if CONFIG['OS_ARCH'] == 'Darwin':
UNIFIED_SOURCES += [

Просмотреть файл

@ -161,3 +161,48 @@ add_task(async function() {
await delete_all_secrets();
});
// Test that using a recovery phrase works.
add_task(async function() {
await delete_all_secrets();
let keystore = Cc["@mozilla.org/security/oskeystore;1"]
.getService(Ci.nsIOSKeyStore);
let recoveryPhrase = await keystore.asyncGenerateSecret(LABELS[0]);
ok(recoveryPhrase, "A recovery phrase should've been created.");
let text = new Uint8Array([0x01, 0x00, 0x01]);
let ciphertext = await keystore.asyncEncryptBytes(LABELS[0], text.length, text);
ok(ciphertext, "We should have a ciphertext now.");
await keystore.asyncDeleteSecret(LABELS[0]);
// Decrypting should fail after deleting the secret.
await keystore.asyncDecryptBytes(LABELS[0], ciphertext)
.then(() => ok(false, "decrypting didn't throw as expected after deleting the secret"))
.catch(() => ok(true, "decrypting threw as expected after deleting the secret"));
await keystore.asyncRecoverSecret(LABELS[0], recoveryPhrase);
let plaintext = await keystore.asyncDecryptBytes(LABELS[0], ciphertext);
ok(plaintext.toString() == text.toString(), "Decrypted plaintext should be the same as text.");
await delete_all_secrets();
});
// Test that trying to use a non-base64 recovery phrase fails.
add_task(async function() {
await delete_all_secrets();
let keystore = Cc["@mozilla.org/security/oskeystore;1"]
.getService(Ci.nsIOSKeyStore);
await keystore.asyncRecoverSecret(LABELS[0], "@##$^&*()#$^&*(@#%&*_")
.then(() => ok(false, "base64-decoding non-base64 should have failed but didn't"))
.catch(() => ok(true, "base64-decoding non-base64 failed as expected"));
ok(!await keystore.asyncSecretAvailable(LABELS[0]),
"we didn't recover a secret, so the secret shouldn't be available");
let recoveryPhrase = await keystore.asyncGenerateSecret(LABELS[0]);
ok(recoveryPhrase && recoveryPhrase.length > 0,
"we should be able to re-use that label to generate a new secret");
await delete_all_secrets();
});

Просмотреть файл

@ -215,9 +215,6 @@ if CONFIG['MOZ_ANDROID_GOOGLE_VR']:
OS_LIBS += CONFIG['MOZ_CAIRO_OSLIBS']
OS_LIBS += CONFIG['MOZ_WEBRTC_X11_LIBS']
if CONFIG['MOZ_LIB_SECRET']:
OS_LIBS += CONFIG['MOZ_LIB_SECRET_LIBS']
if CONFIG['MOZ_SYSTEM_JPEG']:
OS_LIBS += CONFIG['MOZ_JPEG_LIBS']