diff --git a/Makefile.am b/Makefile.am index 24cd118..f34e7be 100644 --- a/Makefile.am +++ b/Makefile.am @@ -52,7 +52,10 @@ dist-hook: dist-check-valac distcleancheck_listfiles = \ find . -name '*.gc[dn][oa]' -prune -o -type f -print -TESTS_ENVIRONMENT = LD_LIBRARY_PATH=$(builddir)/.libs GI_TYPELIB_PATH=$(builddir) +TESTS_ENVIRONMENT = \ + LD_LIBRARY_PATH=$(builddir)/.libs \ + GI_TYPELIB_PATH=$(builddir) \ + abs_top_builddir=$(abs_top_builddir) TEST_EXTENSIONS = .py .js # Default executable tests diff --git a/libsecret/secret-backend.c b/libsecret/secret-backend.c index baa2e9a..30e3abb 100644 --- a/libsecret/secret-backend.c +++ b/libsecret/secret-backend.c @@ -149,17 +149,24 @@ backend_get_impl_type (void) GIOExtension *e; GIOExtensionPoint *ep; - envvar = g_getenv ("SECRET_BACKEND"); - if (envvar == NULL || *envvar == '\0') - extension_name = "service"; - else - extension_name = envvar; - g_type_ensure (secret_service_get_type ()); #ifdef WITH_GCRYPT g_type_ensure (secret_file_backend_get_type ()); #endif +#ifdef WITH_GCRYPT + if (g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS)) + extension_name = "file"; + else +#endif + { + envvar = g_getenv ("SECRET_BACKEND"); + if (envvar == NULL || *envvar == '\0') + extension_name = "service"; + else + extension_name = envvar; + } + ep = g_io_extension_point_lookup (SECRET_BACKEND_EXTENSION_POINT_NAME); e = g_io_extension_point_get_extension_by_name (ep, extension_name); if (e == NULL) { diff --git a/libsecret/secret-file-backend.c b/libsecret/secret-file-backend.c index 1152b9a..13c1aaf 100644 --- a/libsecret/secret-file-backend.c +++ b/libsecret/secret-file-backend.c @@ -21,6 +21,14 @@ #include "secret-private.h" #include "secret-retrievable.h" +#include "egg/egg-secure-memory.h" + +EGG_SECURE_DECLARE (secret_file_backend); + +#include +#include +#include + static void secret_file_backend_async_initable_iface (GAsyncInitableIface *iface); static void secret_file_backend_backend_iface (SecretBackendInterface *iface); @@ -138,6 +146,158 @@ on_collection_new_async (GObject *source_object, g_object_unref (task); } +typedef struct { + gint io_priority; + GFile *file; + GInputStream *stream; + gchar *buffer; +} InitClosure; + +static void +init_closure_free (gpointer data) +{ + InitClosure *init = data; + g_object_unref (init->file); + g_clear_object (&init->stream); + g_clear_pointer (&init->buffer, egg_secure_free); + g_slice_free (InitClosure, init); +} + +#define PASSWORD_SIZE 64 + +static void +on_read_all (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + GInputStream *stream = G_INPUT_STREAM (source_object); + GTask *task = G_TASK (user_data); + InitClosure *init = g_task_get_task_data (task); + gsize bytes_read; + SecretValue *password; + GError *error = NULL; + + if (!g_input_stream_read_all_finish (stream, result, &bytes_read, + &error)) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + if (bytes_read != PASSWORD_SIZE) { + g_task_return_new_error (task, + SECRET_ERROR, + SECRET_ERROR_PROTOCOL, + "invalid password returned from portal"); + g_object_unref (task); + return; + } + + password = secret_value_new (init->buffer, bytes_read, "text/plain"); + g_async_initable_new_async (SECRET_TYPE_FILE_COLLECTION, + init->io_priority, + g_task_get_cancellable (task), + on_collection_new_async, + task, + "file", init->file, + "password", password, + NULL); + secret_value_unref (password); +} + +static void +on_portal_retrieve_secret (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + GDBusConnection *connection = G_DBUS_CONNECTION (source_object); + GTask *task = G_TASK (user_data); + InitClosure *init = g_task_get_task_data (task); + GVariant *reply; + GError *error = NULL; + + reply = g_dbus_connection_call_with_unix_fd_list_finish (connection, + NULL, + result, + &error); + if (reply == NULL) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + init->buffer = egg_secure_alloc (PASSWORD_SIZE); + + g_input_stream_read_all_async (init->stream, + init->buffer, PASSWORD_SIZE, + G_PRIORITY_DEFAULT, + g_task_get_cancellable (task), + on_read_all, + task); +} + +static void +on_bus_get (GObject *source_object, + GAsyncResult *result, + gpointer user_data) +{ + GDBusConnection *connection; + GTask *task = G_TASK (user_data); + InitClosure *init = g_task_get_task_data (task); + GUnixFDList *fd_list; + gint fds[2]; + gint fd_index; + GVariantBuilder options; + GError *error = NULL; + + connection = g_bus_get_finish (result, &error); + if (connection == NULL) { + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + if (!g_unix_open_pipe (fds, FD_CLOEXEC, &error)) { + g_object_unref (connection); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + fd_list = g_unix_fd_list_new (); + fd_index = g_unix_fd_list_append (fd_list, fds[1], &error); + close (fds[1]); + if (fd_index < 0) { + close (fds[0]); + g_object_unref (fd_list); + g_object_unref (connection); + g_task_return_error (task, error); + g_object_unref (task); + return; + } + + close (fds[1]); + init->stream = g_unix_input_stream_new (fds[0], TRUE); + + g_variant_builder_init (&options, G_VARIANT_TYPE_VARDICT); + g_dbus_connection_call_with_unix_fd_list (connection, + "org.freedesktop.portal.Desktop", + "/org/freedesktop/portal/desktop", + "org.freedesktop.portal.Secret", + "RetrieveSecret", + g_variant_new ("(h@a{sv})", + fd_index, + g_variant_builder_end (&options)), + G_VARIANT_TYPE ("(o)"), + G_DBUS_CALL_FLAGS_NONE, + -1, + fd_list, + g_task_get_cancellable (task), + on_portal_retrieve_secret, + task); + g_object_unref (fd_list); +} + static void secret_file_backend_real_init_async (GAsyncInitable *initable, int io_priority, @@ -152,6 +312,7 @@ secret_file_backend_real_init_async (GAsyncInitable *initable, const gchar *envvar; GTask *task; GError *error = NULL; + InitClosure *init; gboolean ret; task = g_task_new (initable, cancellable, callback, user_data); @@ -190,23 +351,27 @@ secret_file_backend_real_init_async (GAsyncInitable *initable, } envvar = g_getenv ("SECRET_FILE_TEST_PASSWORD"); - if (envvar != NULL && *envvar != '\0') + if (envvar != NULL && *envvar != '\0') { password = secret_value_new (envvar, -1, "text/plain"); - else { + 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); + } else if (g_file_test ("/.flatpak-info", G_FILE_TEST_EXISTS)) { + init = g_slice_new0 (InitClosure); + init->io_priority = io_priority; + init->file = file; + g_task_set_task_data (task, init, init_closure_free); + g_bus_get (G_BUS_TYPE_SESSION, cancellable, on_bus_get, task); + } else { g_error ("master password is not retrievable"); - 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 diff --git a/meson.build b/meson.build index 4f2305c..a4d75bf 100644 --- a/meson.build +++ b/meson.build @@ -81,6 +81,10 @@ conf.set('_DEBUG', enable_debug) conf.set('HAVE_MLOCK', meson.get_compiler('c').has_function('mlock')) configure_file(output: 'config.h', configuration: conf) +# Test environment +test_env = environment() +test_env.set('abs_top_builddir', meson.source_root()) + # Subfolders subdir('po') subdir('egg') diff --git a/tool/Makefile.am b/tool/Makefile.am index 422e0d3..258a9b2 100644 --- a/tool/Makefile.am +++ b/tool/Makefile.am @@ -5,3 +5,7 @@ secret_tool_SOURCES = \ secret_tool_LDADD = \ libsecret-@SECRET_MAJOR@.la + +if WITH_GCRYPT +TESTS += tool/test-secret-tool.sh +endif diff --git a/tool/meson.build b/tool/meson.build index 686cf24..1bf9a84 100644 --- a/tool/meson.build +++ b/tool/meson.build @@ -9,3 +9,9 @@ secret_tool = executable('secret-tool', c_args: libsecret_cflags, install: true, ) + +if with_gcrypt and host_machine.system() != 'windows' + test('test-secret-tool.sh', + find_program('test-secret-tool.sh'), + env: test_env) +endif diff --git a/tool/test-secret-tool.sh b/tool/test-secret-tool.sh new file mode 100755 index 0000000..c7bccd7 --- /dev/null +++ b/tool/test-secret-tool.sh @@ -0,0 +1,102 @@ +#!/bin/sh + +set -e + +testdir=$PWD/test-secret-tool-$$ +test -d "$testdir" || mkdir "$testdir" + +cleanup () { + rm -rf "$testdir" +} +trap cleanup 0 + +cd "$testdir" + +SECRET_BACKEND=file +export SECRET_BACKEND + +SECRET_FILE_TEST_PATH=$testdir/keyring +export SECRET_FILE_TEST_PATH + +SECRET_FILE_TEST_PASSWORD=test +export SECRET_FILE_TEST_PASSWORD + +: ${DIFF=diff} + +echo 1..4 + +echo test1 | "$abs_top_builddir"/secret-tool store --label label1 foo bar +if test $? -eq 0; then + echo "ok 1 /secret-tool/store" +else + echo "not ok 1 /secret-tool/store" +fi + +echo test2 | "$abs_top_builddir"/secret-tool store --label label2 foo bar apple orange +if test $? -eq 0; then + echo "ok 1 /secret-tool/store" +else + echo "not ok 1 /secret-tool/store" +fi + +echo test1 > lookup.exp +"$abs_top_builddir"/secret-tool lookup foo bar > lookup.out +if ${DIFF} lookup.exp lookup.out > lookup.diff; then + echo "ok 2 /secret-tool/lookup" +else + echo "not ok 2 /secret-tool/lookup" + sed 's/^/# /' lookup.diff + exit 1 +fi + +cat > search.exp < search.out +if test $? -ne 0; then + echo "not ok 3 /secret-tool/search" + exit 1 +fi +if ${DIFF} search.exp search.out > search.diff; then + echo "ok 3 /secret-tool/search" +else + echo "not ok 3 /secret-tool/search" + sed 's/^/# /' search.diff + exit 1 +fi + +"$abs_top_builddir"/secret-tool clear apple orange +if test $? -eq 0; then + echo "ok 4 /secret-tool/clear" +else + echo "not ok 4 /secret-tool/clear" + exit 1 +fi + +cat > search-after-clear.exp < search-after-clear.out +if test $? -ne 0; then + echo "not ok 5 /secret-tool/search-after-clear" + exit 1 +fi +if ${DIFF} search-after-clear.exp search-after-clear.out > search-after-clear.diff; then + echo "ok 5 /secret-tool/search-after-clear" +else + echo "not ok 5 /secret-tool/search-after-clear" + sed 's/^/# /' search-after-clear.diff + exit 1 +fi