Commit 2d642b5b authored by Daiki Ueno's avatar Daiki Ueno Committed by Daiki Ueno

secret-file-backend: New backend for storing secrets in file

This adds a new backend based on locally stored file.
parent 9cfad7c6
......@@ -60,6 +60,17 @@ libsecret_PRIVATE = \
libsecret/secret-util.c \
$(NULL)
if WITH_GCRYPT
libsecret_PRIVATE += \
libsecret/secret-file-backend.h \
libsecret/secret-file-backend.c \
libsecret/secret-file-collection.h \
libsecret/secret-file-collection.c \
libsecret/secret-file-item.h \
libsecret/secret-file-item.c \
$(NULL)
endif
libsecret_@SECRET_MAJOR@_la_SOURCES = \
$(libsecret_PUBLIC) \
$(libsecret_PRIVATE) \
......@@ -247,6 +258,15 @@ test_session_LDADD = $(libsecret_LIBS)
test_value_SOURCES = libsecret/test-value.c
test_value_LDADD = $(libsecret_LIBS)
if WITH_GCRYPT
C_TESTS += \
test-file-collection \
$(NULL)
test_file_collection_SOURCES = libsecret/test-file-collection.c
test_file_collection_LDADD = $(libsecret_LIBS)
endif
JS_TESTS = \
libsecret/test-js-lookup.js \
libsecret/test-js-clear.js \
......@@ -377,4 +397,5 @@ EXTRA_DIST += \
libsecret/mock-service-prompt.py \
$(JS_TESTS) \
$(PY_TESTS) \
libsecret/fixtures \
$(NULL)
......@@ -35,6 +35,14 @@ libsecret_headers = [
'secret-value.h',
]
if with_gcrypt
libsecret_sources += [
'secret-file-backend.c',
'secret-file-collection.c',
'secret-file-item.c',
]
endif
version_numbers = meson.project_version().split('.')
version_major = version_numbers[0].to_int()
version_minor = version_numbers[1].to_int()
......@@ -168,7 +176,7 @@ pkg.generate(description: 'GObject bindings for Secret Service API (Unstable)',
requires: libsecret)
# Tests
mock_cflags = [
test_cflags = [
libsecret_cflags,
'-DSRCDIR="@0@"'.format(meson.source_root()),
]
......@@ -176,7 +184,7 @@ mock_cflags = [
mock_service_lib = static_library('mock-service',
'mock-service.c',
dependencies: glib_deps,
c_args: mock_cflags,
c_args: test_cflags,
include_directories: config_h_dir,
)
......@@ -193,6 +201,12 @@ test_names = [
'test-collection',
]
if with_gcrypt
test_names += [
'test-file-collection',
]
endif
foreach _test : test_names
test_bin = executable(_test,
......@@ -200,7 +214,7 @@ foreach _test : test_names
dependencies: libsecret_dep,
link_with: mock_service_lib,
include_directories: config_h_dir,
c_args: libsecret_cflags,
c_args: test_cflags,
)
test(_test, test_bin)
......
......@@ -15,6 +15,11 @@
#include "config.h"
#include "secret-backend.h"
#ifdef WITH_GCRYPT
#include "secret-file-backend.h"
#endif
#include "secret-private.h"
#include "libsecret/secret-enum-types.h"
......@@ -151,6 +156,9 @@ backend_get_impl_type (void)
extension_name = envvar;
g_type_ensure (secret_service_get_type ());
#ifdef WITH_GCRYPT
g_type_ensure (secret_file_backend_get_type ());
#endif
ep = g_io_extension_point_lookup (SECRET_BACKEND_EXTENSION_POINT_NAME);
e = g_io_extension_point_get_extension_by_name (ep, extension_name);
......
/* libsecret - GLib wrapper for Secret Service
*
* Copyright 2019 Red Hat, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the licence or (at
* your option) any later version.
*
* See the included COPYING file for more information.
*
* Author: Daiki Ueno
*/
#include "config.h"
#include "secret-backend.h"
#include "secret-file-backend.h"
#include "secret-file-collection.h"
#include "secret-file-item.h"
#include "secret-private.h"
#include "secret-retrievable.h"
static void secret_file_backend_async_initable_iface (GAsyncInitableIface *iface);
static void secret_file_backend_backend_iface (SecretBackendInterface *iface);
struct _SecretFileBackend {
GObject parent;
SecretFileCollection *collection;
SecretServiceFlags init_flags;
};
G_DEFINE_TYPE_WITH_CODE (SecretFileBackend, secret_file_backend, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, secret_file_backend_async_initable_iface);
G_IMPLEMENT_INTERFACE (SECRET_TYPE_BACKEND, secret_file_backend_backend_iface);
_secret_backend_ensure_extension_point ();
g_io_extension_point_implement (SECRET_BACKEND_EXTENSION_POINT_NAME,
g_define_type_id,
"file",
0)
);
enum {
PROP_0,
PROP_FLAGS
};
static void
secret_file_backend_init (SecretFileBackend *self)
{
}
static void
secret_file_backend_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
SecretFileBackend *self = SECRET_FILE_BACKEND (object);
switch (prop_id) {
case PROP_FLAGS:
self->init_flags = g_value_get_flags (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
secret_file_backend_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
SecretFileBackend *self = SECRET_FILE_BACKEND (object);
switch (prop_id) {
case PROP_FLAGS:
g_value_set_flags (value, self->init_flags);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
secret_file_backend_finalize (GObject *object)
{
SecretFileBackend *self = SECRET_FILE_BACKEND (object);
g_clear_object (&self->collection);
G_OBJECT_CLASS (secret_file_backend_parent_class)->finalize (object);
}
static void
secret_file_backend_class_init (SecretFileBackendClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->set_property = secret_file_backend_set_property;
object_class->get_property = secret_file_backend_get_property;
object_class->finalize = secret_file_backend_finalize;
/**
* SecretFileBackend:flags:
*
* A set of flags describing which parts of the secret file have
* been initialized.
*/
g_object_class_override_property (object_class, PROP_FLAGS, "flags");
}
static void
on_collection_new_async (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
GTask *task = G_TASK (user_data);
SecretFileBackend *self = g_task_get_source_object (task);
GObject *object;
GError *error = NULL;
object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object),
result,
&error);
if (object == NULL) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
self->collection = SECRET_FILE_COLLECTION (object);
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
secret_file_backend_real_init_async (GAsyncInitable *initable,
int io_priority,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
gchar *path;
GFile *file;
GFile *dir;
SecretValue *password;
const gchar *envvar;
GTask *task;
GError *error = NULL;
gboolean ret;
task = g_task_new (initable, cancellable, callback, user_data);
envvar = g_getenv ("SECRET_FILE_TEST_PATH");
if (envvar != NULL && *envvar != '\0')
path = g_strdup (envvar);
else {
path = g_build_filename (g_get_user_data_dir (),
"keyrings",
SECRET_COLLECTION_DEFAULT ".keyring",
NULL);
}
file = g_file_new_for_path (path);
g_free (path);
dir = g_file_get_parent (file);
if (dir == NULL) {
g_task_return_new_error (task,
G_IO_ERROR,
G_IO_ERROR_INVALID_ARGUMENT,
"not a valid path");
g_object_unref (file);
g_object_unref (task);
return;
}
ret = g_file_make_directory_with_parents (dir, cancellable, &error);
g_object_unref (dir);
if (!ret) {
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS))
g_clear_error (&error);
else {
g_task_return_error (task, error);
g_object_unref (file);
g_object_unref (task);
return;
}
}
envvar = g_getenv ("SECRET_FILE_TEST_PASSWORD");
if (envvar != NULL && *envvar != '\0')
password = secret_value_new (envvar, -1, "text/plain");
else {
g_task_return_new_error (task,
G_IO_ERROR,
G_IO_ERROR_INVALID_ARGUMENT,
"master password is not retrievable");
g_object_unref (task);
return;
}
g_async_initable_new_async (SECRET_TYPE_FILE_COLLECTION,
io_priority,
cancellable,
on_collection_new_async,
task,
"file", file,
"password", password,
NULL);
g_object_unref (file);
secret_value_unref (password);
}
static gboolean
secret_file_backend_real_init_finish (GAsyncInitable *initable,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (g_task_is_valid (result, initable), FALSE);
return g_task_propagate_boolean (G_TASK (result), error);
}
static void
secret_file_backend_async_initable_iface (GAsyncInitableIface *iface)
{
iface->init_async = secret_file_backend_real_init_async;
iface->init_finish = secret_file_backend_real_init_finish;
}
static void
on_collection_write (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
SecretFileCollection *collection =
SECRET_FILE_COLLECTION (source_object);
GTask *task = G_TASK (user_data);
GError *error = NULL;
if (!secret_file_collection_write_finish (collection, result, &error)) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
g_task_return_boolean (task, TRUE);
g_object_unref (task);
}
static void
secret_file_backend_real_store (SecretBackend *backend,
const SecretSchema *schema,
GHashTable *attributes,
const gchar *collection,
const gchar *label,
SecretValue *value,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
SecretFileBackend *self = SECRET_FILE_BACKEND (backend);
GTask *task;
GError *error = NULL;
/* Warnings raised already */
if (schema != NULL && !_secret_attributes_validate (schema, attributes, G_STRFUNC, FALSE))
return;
task = g_task_new (self, cancellable, callback, user_data);
if (!secret_file_collection_replace (self->collection,
attributes,
label,
value,
&error)) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
secret_file_collection_write (self->collection,
cancellable,
on_collection_write,
task);
}
static gboolean
secret_file_backend_real_store_finish (SecretBackend *backend,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (g_task_is_valid (result, backend), FALSE);
return g_task_propagate_boolean (G_TASK (result), error);
}
static void
on_retrieve_secret (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
SecretRetrievable *retrievable = SECRET_RETRIEVABLE (source_object);
GTask *task = G_TASK (user_data);
SecretValue *value;
GError *error;
value = secret_retrievable_retrieve_secret_finish (retrievable,
result,
&error);
g_object_unref (retrievable);
if (value == NULL) {
g_task_return_error (task, error);
g_object_unref (task);
}
g_task_return_pointer (task, value, secret_value_unref);
g_object_unref (task);
}
static void
secret_file_backend_real_lookup (SecretBackend *backend,
const SecretSchema *schema,
GHashTable *attributes,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
SecretFileBackend *self = SECRET_FILE_BACKEND (backend);
GTask *task;
GList *matches;
GVariant *variant;
SecretFileItem *item;
GError *error = NULL;
/* Warnings raised already */
if (schema != NULL && !_secret_attributes_validate (schema, attributes, G_STRFUNC, TRUE))
return;
task = g_task_new (self, cancellable, callback, user_data);
matches = secret_file_collection_search (self->collection, attributes);
if (matches == NULL) {
g_task_return_pointer (task, NULL, NULL);
g_object_unref (task);
return;
}
variant = g_variant_ref (matches->data);
g_list_free_full (matches, (GDestroyNotify)g_variant_unref);
item = _secret_file_item_decrypt (variant, self->collection, &error);
g_variant_unref (variant);
if (item == NULL) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
secret_retrievable_retrieve_secret (SECRET_RETRIEVABLE (item),
cancellable,
on_retrieve_secret,
task);
}
static SecretValue *
secret_file_backend_real_lookup_finish (SecretBackend *backend,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (g_task_is_valid (result, backend), NULL);
return g_task_propagate_pointer (G_TASK (result), error);
}
static void
secret_file_backend_real_clear (SecretBackend *backend,
const SecretSchema *schema,
GHashTable *attributes,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
SecretFileBackend *self = SECRET_FILE_BACKEND (backend);
GTask *task;
GError *error = NULL;
gboolean ret;
/* Warnings raised already */
if (schema != NULL && !_secret_attributes_validate (schema, attributes, G_STRFUNC, TRUE))
return;
task = g_task_new (self, cancellable, callback, user_data);
ret = secret_file_collection_clear (self->collection, attributes, &error);
if (error != NULL) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
/* No need to write as nothing has been removed. */
if (!ret) {
g_task_return_boolean (task, FALSE);
g_object_unref (task);
return;
}
secret_file_collection_write (self->collection,
cancellable,
on_collection_write,
task);
}
static gboolean
secret_file_backend_real_clear_finish (SecretBackend *backend,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (g_task_is_valid (result, backend), FALSE);
return g_task_propagate_boolean (G_TASK (result), error);
}
static void
unref_objects (gpointer data)
{
GList *list = data;
g_list_free_full (list, g_object_unref);
}
static void
secret_file_backend_real_search (SecretBackend *backend,
const SecretSchema *schema,
GHashTable *attributes,
SecretSearchFlags flags,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data)
{
SecretFileBackend *self = SECRET_FILE_BACKEND (backend);
GTask *task;
GList *matches;
GList *results = NULL;
GList *l;
GError *error = NULL;
/* Warnings raised already */
if (schema != NULL && !_secret_attributes_validate (schema, attributes, G_STRFUNC, FALSE))
return;
task = g_task_new (self, cancellable, callback, user_data);
matches = secret_file_collection_search (self->collection, attributes);
for (l = matches; l; l = g_list_next (l)) {
SecretFileItem *item = _secret_file_item_decrypt (l->data, self->collection, &error);
if (item == NULL) {
g_task_return_error (task, error);
g_object_unref (task);
return;
}
results = g_list_append (results, item);
}
g_list_free_full (matches, (GDestroyNotify)g_variant_unref);
g_task_return_pointer (task, results, unref_objects);
g_object_unref (task);
}
static GList *
secret_file_backend_real_search_finish (SecretBackend *backend,
GAsyncResult *result,
GError **error)
{
g_return_val_if_fail (g_task_is_valid (result, backend), NULL);
return g_task_propagate_pointer (G_TASK (result), error);
}
static void
secret_file_backend_backend_iface (SecretBackendInterface *iface)
{
iface->store = secret_file_backend_real_store;
iface->store_finish = secret_file_backend_real_store_finish;
iface->lookup = secret_file_backend_real_lookup;
iface->lookup_finish = secret_file_backend_real_lookup_finish;
iface->clear = secret_file_backend_real_clear;
iface->clear_finish = secret_file_backend_real_clear_finish;
iface->search = secret_file_backend_real_search;
iface->search_finish = secret_file_backend_real_search_finish;
}
/* libsecret - GLib wrapper for Secret Service
*
* Copyright 2019 Red Hat, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the licence or (at
* your option) any later version.
*
* See the included COPYING file for more information.
*
* Author: Daiki Ueno
*/
#if !defined (__SECRET_INSIDE_HEADER__) && !defined (SECRET_COMPILATION)
#error "Only <libsecret/secret.h> can be included directly."
#endif
#ifndef __SECRET_FILE_BACKEND_H__
#define __SECRET_FILE_BACKEND_H__
#include <glib-object.h>
G_BEGIN_DECLS
#define SECRET_TYPE_FILE_BACKEND (secret_file_backend_get_type ())
G_DECLARE_FINAL_TYPE (SecretFileBackend, secret_file_backend, SECRET, FILE_BACKEND, GObject)
G_END_DECLS
#endif /* __SECRET_FILE_BACKEND_H__ */
This diff is collapsed.
/* libsecret - GLib wrapper for Secret Service
*
* Copyright 2019 Red Hat, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the licence or (at
* your option) any later version.
*
* See the included COPYING file for more information.
*
* Author: Daiki Ueno
*/
#if !defined (__SECRET_INSIDE_HEADER__) && !defined (SECRET_COMPILATION)
#error "Only <libsecret/secret.h> can be included directly."
#endif
#ifndef __SECRET_FILE_COLLECTION_H__
#define __SECRET_FILE_COLLECTION_H__
#include "secret-file-item.h"
#include "secret-value.h"
G_BEGIN_DECLS
#define SECRET_TYPE_FILE_COLLECTION (secret_file_collection_get_type ())
G_DECLARE_FINAL_TYPE (SecretFileCollection, secret_file_collection, SECRET, FILE_COLLECTION, GObject)
gboolean secret_file_collection_replace (SecretFileCollection *self,
GHashTable *attributes,
const gchar *label,
SecretValue *value,
GError **error);
GList *secret_file_collection_search (SecretFileCollection *self,
GHashTable *attributes);
gboolean secret_file_collection_clear (SecretFileCollection *self,
GHashTable *attributes,
GError **error);
void secret_file_collection_write (SecretFileCollection *self,
GCancellable *cancellable,
GAsyncReadyCallback callback,
gpointer user_data);
gboolean secret_file_collection_write_finish
(SecretFileCollection *self,
GAsyncResult *result,
GError **error);
SecretFileItem *_secret_file_item_decrypt
(GVariant *encrypted,
SecretFileCollection *collection,
GError **error);
G_END_DECLS
#endif /* __SECRET_FILE_COLLECTION_H__ */
/* libsecret - GLib wrapper for Secret Service
*
* Copyright 2019 Red Hat, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2.1 of the licence or (at
* your option) any later version.
*
* See the included COPYING file for more information.
*
* Author: Daiki Ueno
*/
#include "config.h"
#include "secret-file-item.h"
#include "secret-retrievable.h"
#include "secret-value.h"
struct _SecretFileItem
{
GObject parent;
GHashTable *attributes;
gchar *label;
guint64 created;
guint64 modified;
SecretValue *value;
GVariant *encrypted;
};
static void secret_file_item_retrievable_iface (SecretRetrievableInterface *iface);
G_DEFINE_TYPE_WITH_CODE (SecretFileItem, secret_file_item, G_TYPE_OBJECT,
G_IMPLEMENT_INTERFACE (SECRET_TYPE_RETRIEVABLE, secret_file_item_retrievable_iface);
);
enum {
PROP_0,
PROP_ATTRIBUTES,