mirror of
https://gitlab.gnome.org/GNOME/libsecret.git
synced 2024-12-22 04:38:55 +00:00
secret-file-backend: New backend for storing secrets in file
This adds a new backend based on locally stored file.
This commit is contained in:
parent
9cfad7c621
commit
2d642b5b7d
@ -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)
|
||||
|
BIN
libsecret/fixtures/default.keyring
Normal file
BIN
libsecret/fixtures/default.keyring
Normal file
Binary file not shown.
@ -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);
|
||||
|
498
libsecret/secret-file-backend.c
Normal file
498
libsecret/secret-file-backend.c
Normal file
@ -0,0 +1,498 @@
|
||||
/* 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;
|
||||
}
|
31
libsecret/secret-file-backend.h
Normal file
31
libsecret/secret-file-backend.h
Normal file
@ -0,0 +1,31 @@
|
||||
/* 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__ */
|
842
libsecret/secret-file-collection.c
Normal file
842
libsecret/secret-file-collection.c
Normal file
@ -0,0 +1,842 @@
|
||||
/* 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-collection.h"
|
||||
|
||||
#include "egg/egg-secure-memory.h"
|
||||
|
||||
EGG_SECURE_DECLARE (secret_file_collection);
|
||||
|
||||
#ifdef WITH_GCRYPT
|
||||
#include <gcrypt.h>
|
||||
#endif
|
||||
|
||||
#define PBKDF2_HASH_ALGO GCRY_MD_SHA256
|
||||
#define SALT_SIZE 32
|
||||
#define ITERATION_COUNT 100000
|
||||
|
||||
#define MAC_ALGO GCRY_MAC_HMAC_SHA256
|
||||
#define MAC_SIZE 32
|
||||
|
||||
#define CIPHER_ALGO GCRY_CIPHER_AES256
|
||||
#define CIPHER_BLOCK_SIZE 16
|
||||
#define IV_SIZE CIPHER_BLOCK_SIZE
|
||||
|
||||
#define KEYRING_FILE_HEADER "GnomeKeyring\n\r\0\n"
|
||||
#define KEYRING_FILE_HEADER_LEN 16
|
||||
|
||||
#define MAJOR_VERSION 1
|
||||
#define MINOR_VERSION 0
|
||||
|
||||
struct _SecretFileCollection
|
||||
{
|
||||
GObject parent;
|
||||
GFile *file;
|
||||
gchar *etag;
|
||||
SecretValue *password;
|
||||
GBytes *salt;
|
||||
guint32 iteration_count;
|
||||
GDateTime *modified;
|
||||
guint64 usage_count;
|
||||
GBytes *key;
|
||||
GVariant *items;
|
||||
};
|
||||
|
||||
static void secret_file_collection_async_initable_iface (GAsyncInitableIface *iface);
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (SecretFileCollection, secret_file_collection, G_TYPE_OBJECT,
|
||||
G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, secret_file_collection_async_initable_iface);
|
||||
);
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_FILE,
|
||||
PROP_PASSWORD
|
||||
};
|
||||
|
||||
static gboolean
|
||||
derive (SecretFileCollection *self)
|
||||
{
|
||||
const gchar *password;
|
||||
gsize n_password;
|
||||
gchar *key;
|
||||
gsize n_salt;
|
||||
gcry_error_t gcry;
|
||||
|
||||
password = secret_value_get (self->password, &n_password);
|
||||
|
||||
key = egg_secure_alloc (CIPHER_BLOCK_SIZE);
|
||||
self->key = g_bytes_new_with_free_func (key,
|
||||
CIPHER_BLOCK_SIZE,
|
||||
egg_secure_free,
|
||||
key);
|
||||
|
||||
n_salt = g_bytes_get_size (self->salt);
|
||||
gcry = gcry_kdf_derive (password, n_password,
|
||||
GCRY_KDF_PBKDF2, PBKDF2_HASH_ALGO,
|
||||
g_bytes_get_data (self->salt, NULL), n_salt,
|
||||
self->iteration_count, CIPHER_BLOCK_SIZE, key);
|
||||
return (gcry != 0) ? FALSE : TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
calculate_mac (SecretFileCollection *self,
|
||||
const guint8 *value, gsize n_value,
|
||||
guint8 *buffer)
|
||||
{
|
||||
gcry_mac_hd_t hd;
|
||||
gcry_error_t gcry;
|
||||
gconstpointer secret;
|
||||
gsize n_secret;
|
||||
gboolean ret = FALSE;
|
||||
|
||||
gcry = gcry_mac_open (&hd, MAC_ALGO, 0, NULL);
|
||||
g_return_val_if_fail (gcry == 0, FALSE);
|
||||
|
||||
secret = g_bytes_get_data (self->key, &n_secret);
|
||||
gcry = gcry_mac_setkey (hd, secret, n_secret);
|
||||
if (gcry != 0)
|
||||
goto out;
|
||||
|
||||
gcry = gcry_mac_write (hd, value, n_value);
|
||||
if (gcry != 0)
|
||||
goto out;
|
||||
|
||||
n_value = MAC_SIZE;
|
||||
gcry = gcry_mac_read (hd, buffer, &n_value);
|
||||
if (gcry != 0)
|
||||
goto out;
|
||||
|
||||
if (n_value != MAC_SIZE)
|
||||
goto out;
|
||||
|
||||
ret = TRUE;
|
||||
out:
|
||||
gcry_mac_close (hd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
decrypt (SecretFileCollection *self,
|
||||
guint8 *data,
|
||||
gsize n_data)
|
||||
{
|
||||
gcry_cipher_hd_t hd;
|
||||
gcry_error_t gcry;
|
||||
gconstpointer secret;
|
||||
gsize n_secret;
|
||||
gboolean ret = FALSE;
|
||||
|
||||
gcry = gcry_cipher_open (&hd, CIPHER_ALGO, GCRY_CIPHER_MODE_CBC, 0);
|
||||
if (gcry != 0)
|
||||
goto out;
|
||||
|
||||
secret = g_bytes_get_data (self->key, &n_secret);
|
||||
gcry = gcry_cipher_setkey (hd, secret, n_secret);
|
||||
if (gcry != 0)
|
||||
goto out;
|
||||
|
||||
gcry = gcry_cipher_setiv (hd, data + n_data, IV_SIZE);
|
||||
if (gcry != 0)
|
||||
goto out;
|
||||
|
||||
gcry = gcry_cipher_decrypt (hd, data, n_data, NULL, 0);
|
||||
if (gcry != 0)
|
||||
goto out;
|
||||
|
||||
ret = TRUE;
|
||||
out:
|
||||
(void) gcry_cipher_close (hd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
encrypt (SecretFileCollection *self,
|
||||
guint8 *data,
|
||||
gsize n_data)
|
||||
{
|
||||
gcry_cipher_hd_t hd;
|
||||
gcry_error_t gcry;
|
||||
gconstpointer secret;
|
||||
gsize n_secret;
|
||||
gboolean ret = FALSE;
|
||||
|
||||
gcry = gcry_cipher_open (&hd, CIPHER_ALGO, GCRY_CIPHER_MODE_CBC, 0);
|
||||
if (gcry != 0)
|
||||
goto out;
|
||||
|
||||
secret = g_bytes_get_data (self->key, &n_secret);
|
||||
gcry = gcry_cipher_setkey (hd, secret, n_secret);
|
||||
if (gcry != 0)
|
||||
goto out;
|
||||
|
||||
gcry_create_nonce (data + n_data, IV_SIZE);
|
||||
|
||||
gcry = gcry_cipher_setiv (hd, data + n_data, IV_SIZE);
|
||||
if (gcry != 0)
|
||||
goto out;
|
||||
|
||||
gcry = gcry_cipher_encrypt (hd, data, n_data, NULL, 0);
|
||||
if (gcry != 0)
|
||||
goto out;
|
||||
|
||||
ret = TRUE;
|
||||
out:
|
||||
(void) gcry_cipher_close (hd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void
|
||||
secret_file_collection_init (SecretFileCollection *self)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
secret_file_collection_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SecretFileCollection *self = SECRET_FILE_COLLECTION (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_FILE:
|
||||
self->file = g_value_dup_object (value);
|
||||
break;
|
||||
case PROP_PASSWORD:
|
||||
self->password = g_value_dup_boxed (value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
secret_file_collection_get_property (GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
switch (prop_id) {
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
secret_file_collection_finalize (GObject *object)
|
||||
{
|
||||
SecretFileCollection *self = SECRET_FILE_COLLECTION (object);
|
||||
|
||||
g_object_unref (self->file);
|
||||
g_free (self->etag);
|
||||
|
||||
secret_value_unref (self->password);
|
||||
|
||||
g_clear_pointer (&self->salt, g_bytes_unref);
|
||||
g_clear_pointer (&self->key, g_bytes_unref);
|
||||
g_clear_pointer (&self->items, g_variant_unref);
|
||||
g_clear_pointer (&self->modified, g_date_time_unref);
|
||||
|
||||
G_OBJECT_CLASS (secret_file_collection_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
secret_file_collection_class_init (SecretFileCollectionClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
object_class->set_property = secret_file_collection_set_property;
|
||||
object_class->get_property = secret_file_collection_get_property;
|
||||
object_class->finalize = secret_file_collection_finalize;
|
||||
|
||||
g_object_class_install_property (object_class, PROP_FILE,
|
||||
g_param_spec_object ("file", "File", "File",
|
||||
G_TYPE_FILE, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
|
||||
g_object_class_install_property (object_class, PROP_PASSWORD,
|
||||
g_param_spec_boxed ("password", "password", "Password",
|
||||
SECRET_TYPE_VALUE,
|
||||
G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
|
||||
}
|
||||
|
||||
static void
|
||||
on_load_contents (GObject *source_object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
GFile *file = G_FILE (source_object);
|
||||
GTask *task = G_TASK (user_data);
|
||||
SecretFileCollection *self = g_task_get_source_object (task);
|
||||
gchar *contents;
|
||||
gchar *p;
|
||||
gsize length;
|
||||
GVariant *variant;
|
||||
GVariant *salt_array;
|
||||
guint32 salt_size;
|
||||
guint64 modified_time;
|
||||
gconstpointer data;
|
||||
gsize n_data;
|
||||
GError *error = NULL;
|
||||
gboolean ret;
|
||||
|
||||
ret = g_file_load_contents_finish (file, result,
|
||||
&contents, &length,
|
||||
&self->etag,
|
||||
&error);
|
||||
|
||||
if (!ret) {
|
||||
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) {
|
||||
GVariantBuilder builder;
|
||||
guint8 salt[SALT_SIZE];
|
||||
|
||||
g_clear_error (&error);
|
||||
|
||||
gcry_create_nonce (salt, sizeof(salt));
|
||||
self->salt = g_bytes_new (salt, sizeof(salt));
|
||||
self->iteration_count = ITERATION_COUNT;
|
||||
self->modified = g_date_time_new_now_utc ();
|
||||
self->usage_count = 0;
|
||||
|
||||
if (!derive (self)) {
|
||||
g_task_return_new_error (task,
|
||||
SECRET_ERROR,
|
||||
SECRET_ERROR_PROTOCOL,
|
||||
"couldn't derive key");
|
||||
g_object_unref (task);
|
||||
return;
|
||||
}
|
||||
|
||||
g_variant_builder_init (&builder,
|
||||
G_VARIANT_TYPE ("a(a{say}ay)"));
|
||||
self->items = g_variant_builder_end (&builder);
|
||||
g_variant_ref_sink (self->items);
|
||||
g_task_return_boolean (task, TRUE);
|
||||
g_object_unref (task);
|
||||
return;
|
||||
}
|
||||
|
||||
g_task_return_error (task, error);
|
||||
g_object_unref (task);
|
||||
return;
|
||||
}
|
||||
|
||||
p = contents;
|
||||
if (length < KEYRING_FILE_HEADER_LEN ||
|
||||
memcmp (p, KEYRING_FILE_HEADER, KEYRING_FILE_HEADER_LEN) != 0) {
|
||||
g_task_return_new_error (task,
|
||||
SECRET_ERROR,
|
||||
SECRET_ERROR_INVALID_FILE_FORMAT,
|
||||
"file header mismatch");
|
||||
g_object_unref (task);
|
||||
return;
|
||||
}
|
||||
p += KEYRING_FILE_HEADER_LEN;
|
||||
length -= KEYRING_FILE_HEADER_LEN;
|
||||
|
||||
if (length < 2 || *p != MAJOR_VERSION || *(p + 1) != MINOR_VERSION) {
|
||||
g_task_return_new_error (task,
|
||||
SECRET_ERROR,
|
||||
SECRET_ERROR_INVALID_FILE_FORMAT,
|
||||
"version mismatch");
|
||||
g_object_unref (task);
|
||||
return;
|
||||
}
|
||||
p += 2;
|
||||
length -= 2;
|
||||
|
||||
variant = g_variant_new_from_data (G_VARIANT_TYPE ("(uayutua(a{say}ay))"),
|
||||
p,
|
||||
length,
|
||||
TRUE,
|
||||
g_free,
|
||||
contents);
|
||||
g_variant_get (variant, "(u@ayutu@a(a{say}ay))",
|
||||
&salt_size, &salt_array, &self->iteration_count,
|
||||
&modified_time, &self->usage_count,
|
||||
&self->items);
|
||||
|
||||
self->modified = g_date_time_new_from_unix_utc (modified_time);
|
||||
|
||||
data = g_variant_get_fixed_array (salt_array, &n_data, sizeof(guint8));
|
||||
g_assert (n_data == salt_size);
|
||||
|
||||
self->salt = g_bytes_new (data, n_data);
|
||||
if (!derive (self)) {
|
||||
g_task_return_new_error (task,
|
||||
SECRET_ERROR,
|
||||
SECRET_ERROR_PROTOCOL,
|
||||
"couldn't derive key");
|
||||
goto out;
|
||||
}
|
||||
|
||||
g_task_return_boolean (task, TRUE);
|
||||
|
||||
out:
|
||||
g_variant_unref (salt_array);
|
||||
g_variant_unref (variant);
|
||||
g_object_unref (task);
|
||||
}
|
||||
|
||||
static void
|
||||
secret_file_collection_real_init_async (GAsyncInitable *initable,
|
||||
int io_priority,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
SecretFileCollection *self = SECRET_FILE_COLLECTION (initable);
|
||||
GTask *task;
|
||||
|
||||
task = g_task_new (initable, cancellable, callback, user_data);
|
||||
|
||||
g_file_load_contents_async (self->file, cancellable, on_load_contents, task);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
secret_file_collection_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_collection_async_initable_iface (GAsyncInitableIface *iface)
|
||||
{
|
||||
iface->init_async = secret_file_collection_real_init_async;
|
||||
iface->init_finish = secret_file_collection_real_init_finish;
|
||||
}
|
||||
|
||||
static GVariant *
|
||||
hash_attributes (SecretFileCollection *self,
|
||||
GHashTable *attributes)
|
||||
{
|
||||
GVariantBuilder builder;
|
||||
guint8 buffer[MAC_SIZE];
|
||||
GList *keys;
|
||||
GList *l;
|
||||
|
||||
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{say}"));
|
||||
|
||||
keys = g_hash_table_get_keys (attributes);
|
||||
keys = g_list_sort (keys, (GCompareFunc) g_strcmp0);
|
||||
|
||||
for (l = keys; l; l = g_list_next (l)) {
|
||||
const gchar *value;
|
||||
GVariant *variant;
|
||||
|
||||
value = g_hash_table_lookup (attributes, l->data);
|
||||
if (!calculate_mac (self, (guint8 *)value, strlen (value), buffer)) {
|
||||
g_list_free (keys);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
variant = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
|
||||
buffer,
|
||||
MAC_SIZE,
|
||||
sizeof(guint8));
|
||||
g_variant_builder_add (&builder, "{s@ay}", l->data, variant);
|
||||
}
|
||||
g_list_free (keys);
|
||||
|
||||
return g_variant_builder_end (&builder);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
hashed_attributes_match (SecretFileCollection *self,
|
||||
GVariant *hashed_attributes,
|
||||
GHashTable *attributes)
|
||||
{
|
||||
GHashTableIter iter;
|
||||
GVariant *hashed_attribute = NULL;
|
||||
gpointer key;
|
||||
gpointer value;
|
||||
guint8 buffer[MAC_SIZE];
|
||||
|
||||
g_hash_table_iter_init (&iter, attributes);
|
||||
while (g_hash_table_iter_next (&iter, &key, &value)) {
|
||||
const guint8 *data;
|
||||
gsize n_data;
|
||||
|
||||
if (!g_variant_lookup (hashed_attributes, key,
|
||||
"@ay", &hashed_attribute))
|
||||
return FALSE;
|
||||
|
||||
data = g_variant_get_fixed_array (hashed_attribute,
|
||||
&n_data, sizeof(guint8));
|
||||
if (n_data != MAC_SIZE) {
|
||||
g_variant_unref (hashed_attribute);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!calculate_mac (self, value, strlen ((char *)value), buffer)) {
|
||||
g_variant_unref (hashed_attribute);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (memcmp (data, buffer, MAC_SIZE) != 0) {
|
||||
g_variant_unref (hashed_attribute);
|
||||
return FALSE;
|
||||
}
|
||||
g_variant_unref (hashed_attribute);
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
secret_file_collection_replace (SecretFileCollection *self,
|
||||
GHashTable *attributes,
|
||||
const gchar *label,
|
||||
SecretValue *value,
|
||||
GError **error)
|
||||
{
|
||||
GVariantBuilder builder;
|
||||
GVariant *hashed_attributes;
|
||||
GVariantIter iter;
|
||||
GVariant *child;
|
||||
SecretFileItem *item;
|
||||
GVariant *serialized_item;
|
||||
guint8 *data = NULL;
|
||||
gsize n_data;
|
||||
gsize n_padded;
|
||||
GVariant *variant;
|
||||
GDateTime *created = NULL;
|
||||
GDateTime *modified;
|
||||
|
||||
hashed_attributes = hash_attributes (self, attributes);
|
||||
if (!hashed_attributes) {
|
||||
g_set_error (error,
|
||||
SECRET_ERROR,
|
||||
SECRET_ERROR_PROTOCOL,
|
||||
"couldn't calculate mac");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* Filter out the existing item */
|
||||
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(a{say}ay)"));
|
||||
g_variant_iter_init (&iter, self->items);
|
||||
while ((child = g_variant_iter_next_value (&iter)) != NULL) {
|
||||
GVariant *_hashed_attributes;
|
||||
g_variant_get (child, "(@a{say}ay)", &_hashed_attributes, NULL);
|
||||
if (g_variant_equal (hashed_attributes, _hashed_attributes)) {
|
||||
SecretFileItem *existing =
|
||||
_secret_file_item_decrypt (child, self, error);
|
||||
guint64 created_time;
|
||||
|
||||
if (existing == NULL) {
|
||||
g_variant_builder_clear (&builder);
|
||||
g_variant_unref (child);
|
||||
g_variant_unref (_hashed_attributes);
|
||||
return FALSE;
|
||||
}
|
||||
g_object_get (existing, "created", &created_time, NULL);
|
||||
g_object_unref (existing);
|
||||
|
||||
created = g_date_time_new_from_unix_utc (created_time);
|
||||
} else {
|
||||
g_variant_builder_add_value (&builder, child);
|
||||
}
|
||||
g_variant_unref (child);
|
||||
g_variant_unref (_hashed_attributes);
|
||||
}
|
||||
|
||||
modified = g_date_time_new_now_utc ();
|
||||
if (created == NULL)
|
||||
created = g_date_time_ref (modified);
|
||||
|
||||
/* Create a new item and append it */
|
||||
item = g_object_new (SECRET_TYPE_FILE_ITEM,
|
||||
"attributes", attributes,
|
||||
"label", label,
|
||||
"value", value,
|
||||
"created", g_date_time_to_unix (created),
|
||||
"modified", g_date_time_to_unix (modified),
|
||||
NULL);
|
||||
|
||||
g_date_time_unref (created);
|
||||
g_date_time_unref (modified);
|
||||
|
||||
serialized_item = secret_file_item_serialize (item);
|
||||
g_object_unref (item);
|
||||
|
||||
/* Encrypt the item with PKCS #7 padding */
|
||||
n_data = g_variant_get_size (serialized_item);
|
||||
n_padded = ((n_data + CIPHER_BLOCK_SIZE) / CIPHER_BLOCK_SIZE) *
|
||||
CIPHER_BLOCK_SIZE;
|
||||
data = egg_secure_alloc (n_padded + IV_SIZE + MAC_SIZE);
|
||||
g_variant_store (serialized_item, data);
|
||||
g_variant_unref (serialized_item);
|
||||
memset (data + n_data, n_padded - n_data, n_padded - n_data);
|
||||
if (!encrypt (self, data, n_padded)) {
|
||||
egg_secure_free (data);
|
||||
g_set_error (error,
|
||||
SECRET_ERROR,
|
||||
SECRET_ERROR_PROTOCOL,
|
||||
"couldn't encrypt item");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!calculate_mac (self, data, n_padded + IV_SIZE,
|
||||
data + n_padded + IV_SIZE)) {
|
||||
egg_secure_free (data);
|
||||
g_set_error (error,
|
||||
SECRET_ERROR,
|
||||
SECRET_ERROR_PROTOCOL,
|
||||
"couldn't calculate mac");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
self->usage_count++;
|
||||
g_date_time_unref (self->modified);
|
||||
self->modified = g_date_time_new_now_utc ();
|
||||
|
||||
variant = g_variant_new_from_data (G_VARIANT_TYPE ("ay"),
|
||||
data,
|
||||
n_padded + IV_SIZE + MAC_SIZE,
|
||||
TRUE,
|
||||
egg_secure_free,
|
||||
data);
|
||||
variant = g_variant_new ("(@a{say}@ay)", hashed_attributes, variant);
|
||||
g_variant_builder_add_value (&builder, variant);
|
||||
|
||||
g_variant_unref (self->items);
|
||||
self->items = g_variant_builder_end (&builder);
|
||||
g_variant_ref_sink (self->items);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
GList *
|
||||
secret_file_collection_search (SecretFileCollection *self,
|
||||
GHashTable *attributes)
|
||||
{
|
||||
GVariantIter iter;
|
||||
GVariant *child;
|
||||
GList *result = NULL;
|
||||
|
||||
g_variant_iter_init (&iter, self->items);
|
||||
while ((child = g_variant_iter_next_value (&iter)) != NULL) {
|
||||
GVariant *hashed_attributes;
|
||||
gboolean matched;
|
||||
|
||||
g_variant_get (child, "(@a{say}ay)", &hashed_attributes, NULL);
|
||||
matched = hashed_attributes_match (self,
|
||||
hashed_attributes,
|
||||
attributes);
|
||||
g_variant_unref (hashed_attributes);
|
||||
if (matched)
|
||||
result = g_list_append (result, g_variant_ref (child));
|
||||
g_variant_unref (child);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
SecretFileItem *
|
||||
_secret_file_item_decrypt (GVariant *encrypted,
|
||||
SecretFileCollection *collection,
|
||||
GError **error)
|
||||
{
|
||||
GVariant *blob;
|
||||
gconstpointer padded;
|
||||
gsize n_data;
|
||||
gsize n_padded;
|
||||
guint8 *data;
|
||||
SecretFileItem *item;
|
||||
GVariant *serialized_item;
|
||||
guint8 mac[MAC_SIZE];
|
||||
|
||||
g_variant_get (encrypted, "(a{say}@ay)", NULL, &blob);
|
||||
|
||||
/* Decrypt the item */
|
||||
padded = g_variant_get_fixed_array (blob, &n_padded, sizeof(guint8));
|
||||
data = egg_secure_alloc (n_padded);
|
||||
memcpy (data, padded, n_padded);
|
||||
g_variant_unref (blob);
|
||||
|
||||
if (n_padded < IV_SIZE + MAC_SIZE) {
|
||||
egg_secure_free (data);
|
||||
g_set_error (error,
|
||||
SECRET_ERROR,
|
||||
SECRET_ERROR_PROTOCOL,
|
||||
"couldn't calculate mac");
|
||||
return FALSE;
|
||||
}
|
||||
n_padded -= IV_SIZE + MAC_SIZE;
|
||||
|
||||
if (!calculate_mac (collection, data, n_padded + IV_SIZE, mac)) {
|
||||
egg_secure_free (data);
|
||||
g_set_error (error,
|
||||
SECRET_ERROR,
|
||||
SECRET_ERROR_PROTOCOL,
|
||||
"couldn't calculate mac");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (memcmp (data + n_padded + IV_SIZE, mac, MAC_SIZE) != 0) {
|
||||
egg_secure_free (data);
|
||||
g_set_error (error,
|
||||
SECRET_ERROR,
|
||||
SECRET_ERROR_PROTOCOL,
|
||||
"mac doesn't match");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!decrypt (collection, data, n_padded)) {
|
||||
egg_secure_free (data);
|
||||
g_set_error (error,
|
||||
SECRET_ERROR,
|
||||
SECRET_ERROR_PROTOCOL,
|
||||
"couldn't decrypt item");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* Remove PKCS #7 padding */
|
||||
n_data = n_padded - data[n_padded - 1];
|
||||
|
||||
serialized_item =
|
||||
g_variant_new_from_data (G_VARIANT_TYPE ("(a{ss}sttay)"),
|
||||
data,
|
||||
n_data,
|
||||
TRUE,
|
||||
egg_secure_free,
|
||||
data);
|
||||
item = secret_file_item_deserialize (serialized_item);
|
||||
g_variant_unref (serialized_item);
|
||||
return item;
|
||||
}
|
||||
|
||||
gboolean
|
||||
secret_file_collection_clear (SecretFileCollection *self,
|
||||
GHashTable *attributes,
|
||||
GError **error)
|
||||
{
|
||||
GVariantBuilder builder;
|
||||
GVariantIter items;
|
||||
GVariant *child;
|
||||
gboolean removed = FALSE;
|
||||
|
||||
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a(a{say}ay)"));
|
||||
g_variant_iter_init (&items, self->items);
|
||||
while ((child = g_variant_iter_next_value (&items)) != NULL) {
|
||||
GVariant *hashed_attributes;
|
||||
gboolean matched;
|
||||
|
||||
g_variant_get (child, "(@a{say}ay)", &hashed_attributes, NULL);
|
||||
matched = hashed_attributes_match (self,
|
||||
hashed_attributes,
|
||||
attributes);
|
||||
g_variant_unref (hashed_attributes);
|
||||
if (matched)
|
||||
removed = TRUE;
|
||||
else
|
||||
g_variant_builder_add_value (&builder, child);
|
||||
g_variant_unref (child);
|
||||
}
|
||||
|
||||
g_variant_unref (self->items);
|
||||
self->items = g_variant_builder_end (&builder);
|
||||
g_variant_ref_sink (self->items);
|
||||
|
||||
return removed;
|
||||
}
|
||||
|
||||
static void
|
||||
on_replace_contents (GObject *source_object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
GFile *file = G_FILE (source_object);
|
||||
GTask *task = G_TASK (user_data);
|
||||
SecretFileCollection *self = g_task_get_source_object (task);
|
||||
GError *error = NULL;
|
||||
|
||||
if (!g_file_replace_contents_finish (file, result, &self->etag, &error)) {
|
||||
g_task_return_error (task, error);
|
||||
g_object_unref (task);
|
||||
return;
|
||||
}
|
||||
|
||||
g_task_return_boolean (task, TRUE);
|
||||
g_object_unref (task);
|
||||
}
|
||||
|
||||
void
|
||||
secret_file_collection_write (SecretFileCollection *self,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
GTask *task;
|
||||
guint8 *contents;
|
||||
gsize n_contents;
|
||||
guint8 *p;
|
||||
GVariant *salt_array;
|
||||
GVariant *variant;
|
||||
|
||||
salt_array = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
|
||||
g_bytes_get_data (self->salt, NULL),
|
||||
g_bytes_get_size (self->salt),
|
||||
sizeof(guint8));
|
||||
variant = g_variant_new ("(u@ayutu@a(a{say}ay))",
|
||||
g_bytes_get_size (self->salt),
|
||||
salt_array,
|
||||
self->iteration_count,
|
||||
g_date_time_to_unix (self->modified),
|
||||
self->usage_count,
|
||||
self->items);
|
||||
|
||||
g_variant_get_data (variant); /* force serialize */
|
||||
n_contents = KEYRING_FILE_HEADER_LEN + 2 + g_variant_get_size (variant);
|
||||
contents = g_new (guint8, n_contents);
|
||||
|
||||
p = contents;
|
||||
memcpy (p, KEYRING_FILE_HEADER, KEYRING_FILE_HEADER_LEN);
|
||||
p += KEYRING_FILE_HEADER_LEN;
|
||||
|
||||
*p++ = MAJOR_VERSION;
|
||||
*p++ = MINOR_VERSION;
|
||||
|
||||
g_variant_store (variant, p);
|
||||
g_variant_unref (variant);
|
||||
|
||||
task = g_task_new (self, cancellable, callback, user_data);
|
||||
g_task_set_task_data (task, contents, g_free);
|
||||
g_file_replace_contents_async (self->file,
|
||||
(gchar *) contents,
|
||||
n_contents,
|
||||
self->etag,
|
||||
TRUE,
|
||||
G_FILE_CREATE_PRIVATE |
|
||||
G_FILE_CREATE_REPLACE_DESTINATION,
|
||||
cancellable,
|
||||
on_replace_contents,
|
||||
task);
|
||||
}
|
||||
|
||||
gboolean
|
||||
secret_file_collection_write_finish (SecretFileCollection *self,
|
||||
GAsyncResult *result,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail (g_task_is_valid (result, self), FALSE);
|
||||
|
||||
return g_task_propagate_boolean (G_TASK (result), error);
|
||||
}
|
56
libsecret/secret-file-collection.h
Normal file
56
libsecret/secret-file-collection.h
Normal file
@ -0,0 +1,56 @@
|
||||
/* 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__ */
|
252
libsecret/secret-file-item.c
Normal file
252
libsecret/secret-file-item.c
Normal file
@ -0,0 +1,252 @@
|
||||
/* 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,
|
||||
PROP_LABEL,
|
||||
PROP_CREATED,
|
||||
PROP_MODIFIED,
|
||||
PROP_VALUE
|
||||
};
|
||||
|
||||
static void
|
||||
secret_file_item_init (SecretFileItem *self)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
secret_file_item_set_property (GObject *object,
|
||||
guint prop_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SecretFileItem *self = SECRET_FILE_ITEM (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_ATTRIBUTES:
|
||||
self->attributes = g_value_dup_boxed (value);
|
||||
break;
|
||||
case PROP_LABEL:
|
||||
self->label = g_value_dup_string (value);
|
||||
break;
|
||||
case PROP_CREATED:
|
||||
self->created = g_value_get_uint64 (value);
|
||||
break;
|
||||
case PROP_MODIFIED:
|
||||
self->modified = g_value_get_uint64 (value);
|
||||
break;
|
||||
case PROP_VALUE:
|
||||
self->value = g_value_dup_boxed (value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
secret_file_item_get_property (GObject *object,
|
||||
guint prop_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
SecretFileItem *self = SECRET_FILE_ITEM (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_ATTRIBUTES:
|
||||
g_value_set_boxed (value, self->attributes);
|
||||
break;
|
||||
case PROP_LABEL:
|
||||
g_value_set_string (value, self->label);
|
||||
break;
|
||||
case PROP_CREATED:
|
||||
g_value_set_uint64 (value, self->created);
|
||||
break;
|
||||
case PROP_MODIFIED:
|
||||
g_value_set_uint64 (value, self->modified);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
secret_file_item_finalize (GObject *object)
|
||||
{
|
||||
SecretFileItem *self = SECRET_FILE_ITEM (object);
|
||||
|
||||
g_hash_table_unref (self->attributes);
|
||||
g_free (self->label);
|
||||
secret_value_unref (self->value);
|
||||
G_OBJECT_CLASS (secret_file_item_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
secret_file_item_class_init (SecretFileItemClass *klass)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
gobject_class->set_property = secret_file_item_set_property;
|
||||
gobject_class->get_property = secret_file_item_get_property;
|
||||
gobject_class->finalize = secret_file_item_finalize;
|
||||
|
||||
g_object_class_override_property (gobject_class, PROP_ATTRIBUTES, "attributes");
|
||||
g_object_class_override_property (gobject_class, PROP_LABEL, "label");
|
||||
g_object_class_override_property (gobject_class, PROP_CREATED, "created");
|
||||
g_object_class_override_property (gobject_class, PROP_MODIFIED, "modified");
|
||||
g_object_class_install_property (gobject_class, PROP_VALUE,
|
||||
g_param_spec_boxed ("value", "Value", "Value",
|
||||
SECRET_TYPE_VALUE, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
|
||||
}
|
||||
|
||||
static void
|
||||
secret_file_item_retrieve_secret (SecretRetrievable *retrievable,
|
||||
GCancellable *cancellable,
|
||||
GAsyncReadyCallback callback,
|
||||
gpointer user_data)
|
||||
{
|
||||
SecretFileItem *self = SECRET_FILE_ITEM (retrievable);
|
||||
GTask *task = g_task_new (retrievable, cancellable, callback, user_data);
|
||||
|
||||
g_task_return_pointer (task,
|
||||
secret_value_ref (self->value),
|
||||
secret_value_unref);
|
||||
g_object_unref (task);
|
||||
}
|
||||
|
||||
static SecretValue *
|
||||
secret_file_item_retrieve_secret_finish (SecretRetrievable *retrievable,
|
||||
GAsyncResult *result,
|
||||
GError **error)
|
||||
{
|
||||
g_return_val_if_fail (g_task_is_valid (result, retrievable), NULL);
|
||||
|
||||
return g_task_propagate_pointer (G_TASK (result), error);
|
||||
}
|
||||
|
||||
static void
|
||||
secret_file_item_retrievable_iface (SecretRetrievableInterface *iface)
|
||||
{
|
||||
iface->retrieve_secret = secret_file_item_retrieve_secret;
|
||||
iface->retrieve_secret_finish = secret_file_item_retrieve_secret_finish;
|
||||
}
|
||||
|
||||
static GHashTable *
|
||||
variant_to_attributes (GVariant *variant)
|
||||
{
|
||||
GVariantIter iter;
|
||||
gchar *key;
|
||||
gchar *value;
|
||||
GHashTable *attributes;
|
||||
|
||||
attributes = g_hash_table_new_full (g_str_hash, g_str_equal,
|
||||
g_free, g_free);
|
||||
|
||||
g_variant_iter_init (&iter, variant);
|
||||
while (g_variant_iter_next (&iter, "{ss}", &key, &value))
|
||||
g_hash_table_insert (attributes, key, value);
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
SecretFileItem *
|
||||
secret_file_item_deserialize (GVariant *serialized)
|
||||
{
|
||||
GVariant *attributes_variant;
|
||||
GHashTable *attributes;
|
||||
const gchar *label;
|
||||
guint64 created;
|
||||
guint64 modified;
|
||||
GVariant *array;
|
||||
const gchar *secret;
|
||||
gsize n_secret;
|
||||
SecretValue *value;
|
||||
SecretFileItem *result;
|
||||
|
||||
g_variant_get (serialized, "(@a{ss}&stt@ay)",
|
||||
&attributes_variant, &label, &created, &modified, &array);
|
||||
|
||||
secret = g_variant_get_fixed_array (array, &n_secret, sizeof(gchar));
|
||||
value = secret_value_new (secret, n_secret, "text/plain");
|
||||
|
||||
attributes = variant_to_attributes (attributes_variant);
|
||||
g_variant_unref (attributes_variant);
|
||||
|
||||
result = g_object_new (SECRET_TYPE_FILE_ITEM,
|
||||
"attributes", attributes,
|
||||
"label", label,
|
||||
"created", created,
|
||||
"modified", modified,
|
||||
"value", value,
|
||||
NULL);
|
||||
g_hash_table_unref (attributes);
|
||||
g_variant_unref (array);
|
||||
secret_value_unref (value);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
GVariant *
|
||||
secret_file_item_serialize (SecretFileItem *self)
|
||||
{
|
||||
GVariantBuilder builder;
|
||||
GHashTableIter iter;
|
||||
gpointer key;
|
||||
gpointer value;
|
||||
GVariant *variant;
|
||||
const gchar *secret;
|
||||
gsize n_secret;
|
||||
|
||||
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{ss}"));
|
||||
g_hash_table_iter_init (&iter, self->attributes);
|
||||
while (g_hash_table_iter_next (&iter, &key, &value))
|
||||
g_variant_builder_add (&builder, "{ss}", key, value);
|
||||
|
||||
secret = secret_value_get (self->value, &n_secret);
|
||||
variant = g_variant_new_fixed_array (G_VARIANT_TYPE_BYTE,
|
||||
secret, n_secret, sizeof(guint8));
|
||||
|
||||
variant = g_variant_new ("(@a{ss}stt@ay)",
|
||||
g_variant_builder_end (&builder),
|
||||
self->label,
|
||||
self->created,
|
||||
self->modified,
|
||||
variant);
|
||||
g_variant_get_data (variant); /* force serialize */
|
||||
return g_variant_ref_sink (variant);
|
||||
}
|
34
libsecret/secret-file-item.h
Normal file
34
libsecret/secret-file-item.h
Normal file
@ -0,0 +1,34 @@
|
||||
/* 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_ITEM_H__
|
||||
#define __SECRET_FILE_ITEM_H__
|
||||
|
||||
#include <glib-object.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define SECRET_TYPE_FILE_ITEM (secret_file_item_get_type ())
|
||||
G_DECLARE_FINAL_TYPE (SecretFileItem, secret_file_item, SECRET, FILE_ITEM, GObject)
|
||||
|
||||
SecretFileItem *secret_file_item_deserialize (GVariant *serialized);
|
||||
GVariant *secret_file_item_serialize (SecretFileItem *self);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __SECRET_FILE_ITEM_H__ */
|
@ -32,6 +32,7 @@ typedef enum {
|
||||
SECRET_ERROR_IS_LOCKED = 2,
|
||||
SECRET_ERROR_NO_SUCH_OBJECT = 3,
|
||||
SECRET_ERROR_ALREADY_EXISTS = 4,
|
||||
SECRET_ERROR_INVALID_FILE_FORMAT = 5,
|
||||
} SecretError;
|
||||
|
||||
#define SECRET_COLLECTION_DEFAULT "default"
|
||||
|
364
libsecret/test-file-collection.c
Normal file
364
libsecret/test-file-collection.c
Normal file
@ -0,0 +1,364 @@
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include "egg/egg-testing.h"
|
||||
#include "secret-file-collection.h"
|
||||
#include "secret-retrievable.h"
|
||||
#include "secret-schema.h"
|
||||
|
||||
typedef struct {
|
||||
gchar *directory;
|
||||
GMainLoop *loop;
|
||||
SecretFileCollection *collection;
|
||||
} Test;
|
||||
|
||||
static void
|
||||
on_new_async (GObject *source_object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
Test *test = user_data;
|
||||
GObject *object;
|
||||
GError *error = NULL;
|
||||
|
||||
object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object),
|
||||
result,
|
||||
&error);
|
||||
test->collection = SECRET_FILE_COLLECTION (object);
|
||||
g_main_loop_quit (test->loop);
|
||||
g_assert_no_error (error);
|
||||
}
|
||||
|
||||
static void
|
||||
setup (Test *test,
|
||||
gconstpointer data)
|
||||
{
|
||||
GFile *file;
|
||||
gchar *path;
|
||||
SecretValue *password;
|
||||
gchar *fixture = NULL;
|
||||
|
||||
if (data != NULL)
|
||||
fixture = g_build_filename (SRCDIR, "libsecret", "fixtures", data, NULL);
|
||||
test->directory = egg_tests_create_scratch_directory (fixture, NULL);
|
||||
g_free (fixture);
|
||||
|
||||
test->loop = g_main_loop_new (NULL, TRUE);
|
||||
|
||||
path = g_build_filename (test->directory, "default.keyring", NULL);
|
||||
file = g_file_new_for_path (path);
|
||||
g_free (path);
|
||||
|
||||
password = secret_value_new ("password", -1, "text/plain");
|
||||
|
||||
g_async_initable_new_async (SECRET_TYPE_FILE_COLLECTION,
|
||||
G_PRIORITY_DEFAULT,
|
||||
NULL,
|
||||
on_new_async,
|
||||
test,
|
||||
"file", file,
|
||||
"password", password,
|
||||
NULL);
|
||||
|
||||
g_object_unref (file);
|
||||
secret_value_unref (password);
|
||||
|
||||
g_main_loop_run (test->loop);
|
||||
}
|
||||
|
||||
static void
|
||||
teardown (Test *test,
|
||||
gconstpointer unused)
|
||||
{
|
||||
egg_tests_remove_scratch_directory (test->directory);
|
||||
g_free (test->directory);
|
||||
|
||||
g_clear_object (&test->collection);
|
||||
g_main_loop_unref (test->loop);
|
||||
}
|
||||
|
||||
static void
|
||||
test_init (Test *test,
|
||||
gconstpointer unused)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
test_replace (Test *test,
|
||||
gconstpointer unused)
|
||||
{
|
||||
GHashTable *attributes;
|
||||
SecretValue *value;
|
||||
GError *error = NULL;
|
||||
gboolean ret;
|
||||
|
||||
attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
|
||||
g_hash_table_insert (attributes, g_strdup ("foo"), g_strdup ("a"));
|
||||
g_hash_table_insert (attributes, g_strdup ("bar"), g_strdup ("b"));
|
||||
g_hash_table_insert (attributes, g_strdup ("baz"), g_strdup ("c"));
|
||||
|
||||
value = secret_value_new ("test1", -1, "text/plain");
|
||||
ret = secret_file_collection_replace (test->collection,
|
||||
attributes, "label", value,
|
||||
&error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (ret);
|
||||
secret_value_unref (value);
|
||||
|
||||
value = secret_value_new ("test2", -1, "text/plain");
|
||||
ret = secret_file_collection_replace (test->collection,
|
||||
attributes, "label", value,
|
||||
&error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (ret);
|
||||
secret_value_unref (value);
|
||||
g_hash_table_unref (attributes);
|
||||
}
|
||||
|
||||
static void
|
||||
test_clear (Test *test,
|
||||
gconstpointer unused)
|
||||
{
|
||||
GHashTable *attributes;
|
||||
SecretValue *value;
|
||||
GError *error = NULL;
|
||||
gboolean ret;
|
||||
|
||||
attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
|
||||
g_hash_table_insert (attributes, g_strdup ("foo"), g_strdup ("a"));
|
||||
g_hash_table_insert (attributes, g_strdup ("bar"), g_strdup ("b"));
|
||||
g_hash_table_insert (attributes, g_strdup ("baz"), g_strdup ("c"));
|
||||
|
||||
value = secret_value_new ("test1", -1, "text/plain");
|
||||
ret = secret_file_collection_replace (test->collection,
|
||||
attributes, "label", value,
|
||||
&error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (ret);
|
||||
secret_value_unref (value);
|
||||
|
||||
ret = secret_file_collection_clear (test->collection,
|
||||
attributes,
|
||||
&error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (ret);
|
||||
g_hash_table_unref (attributes);
|
||||
}
|
||||
|
||||
static void
|
||||
test_search (Test *test,
|
||||
gconstpointer unused)
|
||||
{
|
||||
GHashTable *attributes;
|
||||
SecretValue *value;
|
||||
GError *error = NULL;
|
||||
GList *matches;
|
||||
gboolean ret;
|
||||
|
||||
attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
|
||||
g_hash_table_insert (attributes, g_strdup ("foo"), g_strdup ("a"));
|
||||
g_hash_table_insert (attributes, g_strdup ("bar"), g_strdup ("b"));
|
||||
g_hash_table_insert (attributes, g_strdup ("baz"), g_strdup ("c"));
|
||||
|
||||
value = secret_value_new ("test1", -1, "text/plain");
|
||||
ret = secret_file_collection_replace (test->collection,
|
||||
attributes, "label", value,
|
||||
&error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (ret);
|
||||
secret_value_unref (value);
|
||||
|
||||
g_hash_table_remove (attributes, "foo");
|
||||
|
||||
value = secret_value_new ("test2", -1, "text/plain");
|
||||
ret = secret_file_collection_replace (test->collection,
|
||||
attributes, "label", value,
|
||||
&error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (ret);
|
||||
secret_value_unref (value);
|
||||
|
||||
matches = secret_file_collection_search (test->collection, attributes);
|
||||
g_assert_cmpint (g_list_length (matches), ==, 2);
|
||||
g_list_free_full (matches, (GDestroyNotify)g_variant_unref);
|
||||
|
||||
g_hash_table_unref (attributes);
|
||||
}
|
||||
|
||||
static void
|
||||
test_decrypt (Test *test,
|
||||
gconstpointer unused)
|
||||
{
|
||||
GHashTable *attributes;
|
||||
SecretValue *value;
|
||||
GError *error = NULL;
|
||||
GList *matches;
|
||||
SecretFileItem *item;
|
||||
const gchar *secret;
|
||||
gsize n_secret;
|
||||
gchar *label;
|
||||
gboolean ret;
|
||||
|
||||
attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
|
||||
g_hash_table_insert (attributes, g_strdup ("foo"), g_strdup ("a"));
|
||||
g_hash_table_insert (attributes, g_strdup ("bar"), g_strdup ("b"));
|
||||
g_hash_table_insert (attributes, g_strdup ("baz"), g_strdup ("c"));
|
||||
|
||||
value = secret_value_new ("test1", -1, "text/plain");
|
||||
ret = secret_file_collection_replace (test->collection,
|
||||
attributes, "label", value,
|
||||
&error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (ret);
|
||||
secret_value_unref (value);
|
||||
|
||||
matches = secret_file_collection_search (test->collection, attributes);
|
||||
g_assert_cmpint (g_list_length (matches), ==, 1);
|
||||
|
||||
item = _secret_file_item_decrypt ((GVariant *)matches->data,
|
||||
test->collection,
|
||||
&error);
|
||||
g_list_free_full (matches, (GDestroyNotify)g_variant_unref);
|
||||
g_assert_no_error (error);
|
||||
g_assert_nonnull (item);
|
||||
|
||||
g_object_get (item, "label", &label, NULL);
|
||||
g_assert_cmpstr (label, ==, "label");
|
||||
g_free (label);
|
||||
|
||||
value = secret_retrievable_retrieve_secret_sync (SECRET_RETRIEVABLE (item),
|
||||
NULL,
|
||||
&error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
secret = secret_value_get (value, &n_secret);
|
||||
g_assert_cmpstr (secret, ==, "test1");
|
||||
|
||||
secret_value_unref (value);
|
||||
g_object_unref (item);
|
||||
g_hash_table_unref (attributes);
|
||||
}
|
||||
|
||||
static void
|
||||
on_write (GObject *source_object,
|
||||
GAsyncResult *result,
|
||||
gpointer user_data)
|
||||
{
|
||||
SecretFileCollection *collection =
|
||||
SECRET_FILE_COLLECTION (source_object);
|
||||
Test *test = user_data;
|
||||
GError *error = NULL;
|
||||
gboolean ret;
|
||||
|
||||
ret = secret_file_collection_write_finish (collection,
|
||||
result,
|
||||
&error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (ret);
|
||||
|
||||
g_main_loop_quit (test->loop);
|
||||
}
|
||||
|
||||
static void
|
||||
test_write (Test *test,
|
||||
gconstpointer unused)
|
||||
{
|
||||
GHashTable *attributes;
|
||||
SecretValue *value;
|
||||
GError *error = NULL;
|
||||
gboolean ret;
|
||||
|
||||
attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
|
||||
g_hash_table_insert (attributes, g_strdup ("foo"), g_strdup ("a"));
|
||||
g_hash_table_insert (attributes, g_strdup ("bar"), g_strdup ("b"));
|
||||
g_hash_table_insert (attributes, g_strdup ("baz"), g_strdup ("c"));
|
||||
|
||||
value = secret_value_new ("test1", -1, "text/plain");
|
||||
ret = secret_file_collection_replace (test->collection,
|
||||
attributes, "label1", value,
|
||||
&error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (ret);
|
||||
secret_value_unref (value);
|
||||
g_hash_table_unref (attributes);
|
||||
|
||||
attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
|
||||
g_hash_table_insert (attributes, g_strdup ("apple"), g_strdup ("a"));
|
||||
g_hash_table_insert (attributes, g_strdup ("orange"), g_strdup ("b"));
|
||||
g_hash_table_insert (attributes, g_strdup ("banana"), g_strdup ("c"));
|
||||
|
||||
value = secret_value_new ("test1", -1, "text/plain");
|
||||
ret = secret_file_collection_replace (test->collection,
|
||||
attributes, "label2", value,
|
||||
&error);
|
||||
g_assert_no_error (error);
|
||||
g_assert_true (ret);
|
||||
secret_value_unref (value);
|
||||
g_hash_table_unref (attributes);
|
||||
|
||||
secret_file_collection_write (test->collection,
|
||||
NULL,
|
||||
on_write,
|
||||
test);
|
||||
|
||||
g_main_loop_run (test->loop);
|
||||
}
|
||||
|
||||
static void
|
||||
test_read (Test *test,
|
||||
gconstpointer unused)
|
||||
{
|
||||
GHashTable *attributes;
|
||||
SecretValue *value;
|
||||
GError *error = NULL;
|
||||
GList *matches;
|
||||
SecretFileItem *item;
|
||||
const gchar *secret;
|
||||
gsize n_secret;
|
||||
gchar *label;
|
||||
|
||||
attributes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
|
||||
g_hash_table_insert (attributes, g_strdup ("foo"), g_strdup ("a"));
|
||||
|
||||
matches = secret_file_collection_search (test->collection, attributes);
|
||||
g_assert_cmpint (g_list_length (matches), ==, 1);
|
||||
|
||||
item = _secret_file_item_decrypt ((GVariant *)matches->data,
|
||||
test->collection,
|
||||
&error);
|
||||
g_list_free_full (matches, (GDestroyNotify)g_variant_unref);
|
||||
g_assert_no_error (error);
|
||||
g_assert_nonnull (item);
|
||||
|
||||
g_object_get (item, "label", &label, NULL);
|
||||
g_assert_cmpstr (label, ==, "label1");
|
||||
g_free (label);
|
||||
|
||||
value = secret_retrievable_retrieve_secret_sync (SECRET_RETRIEVABLE (item),
|
||||
NULL,
|
||||
&error);
|
||||
g_assert_no_error (error);
|
||||
|
||||
secret = secret_value_get (value, &n_secret);
|
||||
g_assert_cmpstr (secret, ==, "test1");
|
||||
|
||||
secret_value_unref (value);
|
||||
g_object_unref (item);
|
||||
g_hash_table_unref (attributes);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char **argv)
|
||||
{
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
g_set_prgname ("test-file-collection");
|
||||
g_test_add ("/file-collection/init", Test, NULL, setup, test_init, teardown);
|
||||
g_test_add ("/file-collection/replace", Test, NULL, setup, test_replace, teardown);
|
||||
g_test_add ("/file-collection/clear", Test, NULL, setup, test_clear, teardown);
|
||||
g_test_add ("/file-collection/search", Test, NULL, setup, test_search, teardown);
|
||||
g_test_add ("/file-collection/decrypt", Test, NULL, setup, test_decrypt, teardown);
|
||||
g_test_add ("/file-collection/write", Test, NULL, setup, test_write, teardown);
|
||||
g_test_add ("/file-collection/read", Test, "default.keyring", setup, test_read, teardown);
|
||||
|
||||
return egg_tests_run_with_loop ();
|
||||
}
|
Loading…
Reference in New Issue
Block a user