diff options
author | Daiki Ueno <dueno@src.gnome.org> | 2019-07-27 05:36:41 +0200 |
---|---|---|
committer | Daiki Ueno <ueno@gnu.org> | 2019-09-30 14:33:15 +0000 |
commit | 4010e268e6b88668e4ff017538ec42096057591c (patch) | |
tree | e42677f62e0de7436e98323e7106f81cfc48ba35 /daemon | |
parent | d463f42495484437de8cec69bea9b6814ee32479 (diff) | |
download | gnome-keyring-4010e268e6b88668e4ff017538ec42096057591c.tar.gz |
dbus: Implement secret portal backend
Diffstat (limited to 'daemon')
-rw-r--r-- | daemon/.gitignore | 1 | ||||
-rw-r--r-- | daemon/Makefile.am | 6 | ||||
-rw-r--r-- | daemon/dbus/Makefile.am | 35 | ||||
-rw-r--r-- | daemon/dbus/gkd-secret-portal.c | 438 | ||||
-rw-r--r-- | daemon/dbus/gkd-secret-portal.h | 33 | ||||
-rw-r--r-- | daemon/dbus/gkd-secret-service.c | 7 | ||||
-rw-r--r-- | daemon/dbus/gkd-secret-types.h | 3 | ||||
-rw-r--r-- | daemon/dbus/org.freedesktop.impl.portal.Request.xml | 47 | ||||
-rw-r--r-- | daemon/dbus/org.freedesktop.impl.portal.Secret.xml | 65 | ||||
-rw-r--r-- | daemon/dbus/test-dbus-portal.c | 215 | ||||
-rw-r--r-- | daemon/dbus/test-service.c | 1 | ||||
-rw-r--r-- | daemon/gnome-keyring.portal | 4 | ||||
-rw-r--r-- | daemon/org.freedesktop.impl.portal.Secret.service.in | 3 |
13 files changed, 855 insertions, 3 deletions
diff --git a/daemon/.gitignore b/daemon/.gitignore index a6d75155..4db92394 100644 --- a/daemon/.gitignore +++ b/daemon/.gitignore @@ -1,6 +1,7 @@ /gnome-keyring-daemon /org.gnome.keyring.service /org.freedesktop.secrets.service +/org.freedesktop.impl.portal.Secret.service /gnome-keyring-pkcs11.desktop /gnome-keyring-pkcs11.desktop.in /gnome-keyring-secrets.desktop diff --git a/daemon/Makefile.am b/daemon/Makefile.am index 48c1652a..60b2f794 100644 --- a/daemon/Makefile.am +++ b/daemon/Makefile.am @@ -32,7 +32,8 @@ gnome_keyring_daemon_CFLAGS = \ service_in_files = \ daemon/org.gnome.keyring.service.in \ - daemon/org.freedesktop.secrets.service.in + daemon/org.freedesktop.secrets.service.in \ + daemon/org.freedesktop.impl.portal.Secret.service.in servicedir = $(DBUS_SERVICES_DIR) service_DATA = $(service_in_files:.service.in=.service) @@ -47,6 +48,9 @@ desktop_DATA = $(desktop_in_files:.desktop.in=.desktop) .desktop.in.in.desktop.in: $(AM_V_GEN) $(MSGFMT) --desktop --template $< -d $(top_srcdir)/po -o $@ +portaldir = $(datadir)/xdg-desktop-portal/portals +dist_portal_DATA = daemon/gnome-keyring.portal + EXTRA_DIST += \ $(service_in_files) \ $(desktop_in_files) \ diff --git a/daemon/dbus/Makefile.am b/daemon/dbus/Makefile.am index 9d0b2e1a..8e03be2c 100644 --- a/daemon/dbus/Makefile.am +++ b/daemon/dbus/Makefile.am @@ -32,10 +32,30 @@ daemon/dbus/gkd-internal-generated.h: daemon/dbus/org.gnome.keyring.InternalUnsu daemon/dbus/gkd-internal-generated.c: daemon/dbus/gkd-internal-generated.h @: # generated as a side-effect +daemon/dbus/gkd-portal-generated.h: daemon/dbus/org.freedesktop.impl.portal.Secret.xml + $(AM_V_GEN) gdbus-codegen --interface-prefix org.freedesktop.impl.portal.Secret. \ + --generate-c-code $(srcdir)/daemon/dbus/gkd-portal-generated \ + --c-namespace Gkd \ + --annotate "org.freedesktop.impl.portal.Secret" "org.gtk.GDBus.C.Name" ExportedPortal \ + $(srcdir)/daemon/dbus/org.freedesktop.impl.portal.Secret.xml +daemon/dbus/gkd-portal-generated.c: daemon/dbus/gkd-portal-generated.h + @: # generated as a side-effect + +daemon/dbus/gkd-portal-request-generated.h: daemon/dbus/org.freedesktop.impl.portal.Request.xml + $(AM_V_GEN) gdbus-codegen --interface-prefix org.freedesktop.impl.portal.Request. \ + --generate-c-code $(srcdir)/daemon/dbus/gkd-portal-request-generated \ + --c-namespace Gkd \ + --annotate "org.freedesktop.impl.portal.Request" "org.gtk.GDBus.C.Name" ExportedPortalRequest \ + $(srcdir)/daemon/dbus/org.freedesktop.impl.portal.Request.xml +daemon/dbus/gkd-portal-request-generated.c: daemon/dbus/gkd-portal-request-generated.h + @: # generated as a side-effect + EXTRA_DIST += \ daemon/dbus/org.freedesktop.Secrets.xml \ daemon/dbus/org.gnome.keyring.Daemon.xml \ daemon/dbus/org.gnome.keyring.InternalUnsupportedGuiltRiddenInterface.xml \ + daemon/dbus/org.freedesktop.impl.portal.Secret.xml \ + daemon/dbus/org.freedesktop.impl.portal.Request.xml \ $(NULL) BUILT_SOURCES += \ @@ -44,7 +64,11 @@ BUILT_SOURCES += \ daemon/dbus/gkd-internal-generated.c \ daemon/dbus/gkd-internal-generated.h \ daemon/dbus/gkd-secrets-generated.c \ - daemon/dbus/gkd-secrets-generated.h + daemon/dbus/gkd-secrets-generated.h \ + daemon/dbus/gkd-portal-generated.c \ + daemon/dbus/gkd-portal-generated.h \ + daemon/dbus/gkd-portal-request-generated.c \ + daemon/dbus/gkd-portal-request-generated.h libgkd_dbus_la_SOURCES = \ $(BUILT_SOURCES) \ @@ -68,6 +92,8 @@ libgkd_dbus_la_SOURCES = \ daemon/dbus/gkd-secret-lock.h \ daemon/dbus/gkd-secret-objects.c \ daemon/dbus/gkd-secret-objects.h \ + daemon/dbus/gkd-secret-portal.c \ + daemon/dbus/gkd-secret-portal.h \ daemon/dbus/gkd-secret-property.c \ daemon/dbus/gkd-secret-property.h \ daemon/dbus/gkd-secret-prompt.c \ @@ -125,7 +151,8 @@ daemon_dbus_TESTS = \ test-dbus-search \ test-dbus-items \ test-dbus-signals \ - test-dbus-lock + test-dbus-lock \ + test-dbus-portal test_dbus_util_SOURCES = daemon/dbus/test-dbus-util.c test_dbus_util_LDADD = $(daemon_dbus_LIBS) @@ -144,5 +171,9 @@ test_dbus_signals_CFLAGS = $(daemon_dbus_CFLAGS) test_dbus_lock_SOURCES = daemon/dbus/test-dbus-lock.c test_dbus_lock_LDADD = $(daemon_dbus_LIBS) +test_dbus_portal_SOURCES = daemon/dbus/test-dbus-portal.c +test_dbus_portal_LDADD = $(daemon_dbus_LIBS) +test_dbus_portal_CFLAGS = $(daemon_dbus_CFLAGS) + check_PROGRAMS += $(daemon_dbus_TESTS) TESTS += $(daemon_dbus_TESTS) diff --git a/daemon/dbus/gkd-secret-portal.c b/daemon/dbus/gkd-secret-portal.c new file mode 100644 index 00000000..4663b7b9 --- /dev/null +++ b/daemon/dbus/gkd-secret-portal.c @@ -0,0 +1,438 @@ +/* + * gnome-keyring + * + * Copyright (C) 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 License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see + * <http://www.gnu.org/licenses/>. + */ + +#include "config.h" + +#include "gkd-secret-portal.h" + +#include <gck/gck.h> +#include "pkcs11/pkcs11i.h" +#include <gcrypt.h> + +#include "gkd-portal-generated.h" +#include "gkd-portal-request-generated.h" +#include "gkd-secret-property.h" +#include "gkd-secret-service.h" +#include <gio/gunixfdlist.h> +#include <gio/gunixoutputstream.h> + +#define PORTAL_DEFAULT_KEY_SIZE 64 + +static gboolean +portal_method_retrieve_secret (GkdExportedPortal *skeleton, + GDBusMethodInvocation *invocation, + GUnixFDList *fd_list, + const gchar *arg_handle, + const gchar *arg_app_id, + GVariant *arg_fd, + GVariant *arg_options, + GkdSecretPortal *self); + +struct _GkdSecretPortal { + GObject parent; + GkdSecretService *service; + GkdExportedPortal *skeleton; + GkdExportedPortalRequest *request_skeleton; + gchar *collection; + GCancellable *cancellable; +}; + +G_DEFINE_TYPE (GkdSecretPortal, gkd_secret_portal, G_TYPE_OBJECT); + +enum { + PROP_0, + PROP_SERVICE +}; + +static void +gkd_secret_portal_init (GkdSecretPortal *self) +{ +#if WITH_DEBUG + const gchar *collection = g_getenv ("GNOME_KEYRING_TEST_LOGIN"); + if (collection && collection[0]) + self->collection = g_strdup (collection); + else +#endif + self->collection = g_strdup ("login"); + self->cancellable = g_cancellable_new (); +} + +static void +gkd_secret_portal_constructed (GObject *object) +{ + GkdSecretPortal *self = GKD_SECRET_PORTAL (object); + GDBusConnection *connection = gkd_secret_service_get_connection (self->service); + GError *error = NULL; + + self->skeleton = gkd_exported_portal_skeleton_new (); + g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self->skeleton), + connection, + PORTAL_SERVICE_PATH, + &error); + + if (error != NULL) { + g_warning ("could not register portal interface service on session bus: %s", error->message); + g_clear_error (&error); + } + + g_signal_connect (self->skeleton, "handle-retrieve-secret", + G_CALLBACK (portal_method_retrieve_secret), self); + + G_OBJECT_CLASS (gkd_secret_portal_parent_class)->constructed (object); +} + +static void +gkd_secret_portal_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GkdSecretPortal *self = GKD_SECRET_PORTAL (object); + + switch (prop_id) { + case PROP_SERVICE: + self->service = g_value_dup_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +gkd_secret_portal_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); +} + +static void +gkd_secret_portal_finalize (GObject *object) +{ + GkdSecretPortal *self = GKD_SECRET_PORTAL (object); + + g_clear_object (&self->skeleton); + g_clear_object (&self->request_skeleton); + g_free (self->collection); + g_object_unref (self->cancellable); + + G_OBJECT_CLASS (gkd_secret_portal_parent_class)->finalize (object); +} + +static void +gkd_secret_portal_class_init (GkdSecretPortalClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + gobject_class->constructed = gkd_secret_portal_constructed; + gobject_class->set_property = gkd_secret_portal_set_property; + gobject_class->get_property = gkd_secret_portal_get_property; + gobject_class->finalize = gkd_secret_portal_finalize; + + g_object_class_install_property (gobject_class, PROP_SERVICE, + g_param_spec_object ("service", "Service", "Secret Service", + GKD_SECRET_TYPE_SERVICE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); +} + +static gboolean +request_method_close (GkdExportedPortalRequest *skeleton, + GDBusMethodInvocation *invocation, + GkdSecretPortal *self) +{ + g_cancellable_cancel (self->cancellable); + g_dbus_interface_skeleton_unexport (G_DBUS_INTERFACE_SKELETON (skeleton)); + return TRUE; +} + +static gboolean +create_application_attributes (const char *app_id, + GckBuilder *builder) +{ + GVariantBuilder attributes; + + g_variant_builder_init (&attributes, G_VARIANT_TYPE ("a{ss}")); + g_variant_builder_add (&attributes, "{ss}", "app_id", app_id); + + return gkd_secret_property_parse_fields (g_variant_builder_end (&attributes), + builder); +} + +static gboolean +unlock_collection (GkdSecretPortal *self, + GckObject *collection, + GError **error) +{ + GckBuilder builder = GCK_BUILDER_INIT; + GckSession *session; + GckObject *object; + + session = gkd_secret_service_internal_pkcs11_session (self->service); + gck_builder_add_ulong (&builder, CKA_CLASS, CKO_G_CREDENTIAL); + gck_builder_add_ulong (&builder, CKA_G_OBJECT, + gck_object_get_handle (collection)); + gck_builder_add_boolean (&builder, CKA_GNOME_TRANSIENT, TRUE); + gck_builder_add_data (&builder, CKA_VALUE, NULL, 0); + + object = gck_session_create_object (session, gck_builder_end (&builder), + self->cancellable, error); + if (object == NULL) + return FALSE; + g_object_unref (object); + + return TRUE; +} + +static gboolean +ensure_collection (GkdSecretPortal *self, + GError **error) +{ + GckBuilder builder = GCK_BUILDER_INIT; + GckSession *session; + GList *objects; + gpointer data; + gsize n_data; + gboolean retval = TRUE; + + /* Find login collection */ + session = gkd_secret_service_internal_pkcs11_session (self->service); + gck_builder_add_ulong (&builder, CKA_CLASS, CKO_G_COLLECTION); + gck_builder_add_string (&builder, CKA_ID, self->collection); + objects = gck_session_find_objects (session, gck_builder_end (&builder), + NULL, error); + if (*error != NULL) + return FALSE; + if (objects == NULL) { + g_set_error (error, + G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, + "Collection %s doesn't exist", + self->collection); + return FALSE; + } + + /* Check if it is locked */ + data = gck_object_get_data (objects->data, CKA_G_LOCKED, + self->cancellable, &n_data, error); + if (data == NULL) + return FALSE; + if (n_data != 1) { + g_set_error (error, + G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, + "couldn't check if %s is locked", + self->collection); + return FALSE; + } + + /* Unlock the collection if it is locked */ + if (*((CK_BBOOL*)data) == CK_TRUE) + retval = unlock_collection (self, objects->data, error); + gck_list_unref_free (objects); + + return retval; +} + +static guint8 * +lookup_secret_value (GkdSecretPortal *self, + const char *app_id, + gsize *n_value, + GError **error) +{ + GckBuilder builder = GCK_BUILDER_INIT; + GckObject *search; + GckSession *session; + gpointer data; + gsize n_data; + + if (!create_application_attributes (app_id, &builder)) { + gck_builder_clear (&builder); + g_set_error (error, + G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, + "Invalid data in attributes argument"); + return NULL; + } + + /* Find items matching the collection and fields */ + gck_builder_add_ulong (&builder, CKA_CLASS, CKO_G_SEARCH); + gck_builder_add_boolean (&builder, CKA_TOKEN, FALSE); + gck_builder_add_string (&builder, CKA_G_COLLECTION, self->collection); + + /* Create the search object */ + session = gkd_secret_service_internal_pkcs11_session (self->service); + search = gck_session_create_object (session, + gck_builder_end (&builder), + NULL, error); + if (search == NULL) + return NULL; + + /* Get the matched item handles, and delete the search object */ + data = gck_object_get_data (search, CKA_G_MATCHED, NULL, &n_data, error); + gck_object_destroy (search, NULL, NULL); + g_object_unref (search); + + if (data == NULL) + return NULL; + + if (n_data > 0) { + /* Return the first matching item if any */ + GList *items; + guint8 *value; + + /* Build a list of object handles */ + items = gck_objects_from_handle_array (session, + data, + n_data / sizeof (CK_OBJECT_HANDLE)); + g_free (data); + + value = gck_object_get_data (GCK_OBJECT (items->data), + CKA_VALUE, + NULL, + n_value, + error); + gck_list_unref_free (items); + return value; + } + + return NULL; +} + +static guint8 * +create_secret_value (GkdSecretPortal *self, + const char *app_id, + gsize *n_value, + GError **error) +{ + GckBuilder builder = GCK_BUILDER_INIT; + GckObject *item; + GckSession *session; + guint8 *value; + + value = g_new0 (guint8, PORTAL_DEFAULT_KEY_SIZE); + *n_value = PORTAL_DEFAULT_KEY_SIZE; + + gcry_randomize (value, *n_value, GCRY_STRONG_RANDOM); + + /* Create a new item */ + if (!create_application_attributes (app_id, &builder)) { + gck_builder_clear (&builder); + g_free (value); + g_set_error (error, + G_DBUS_ERROR, + G_DBUS_ERROR_FAILED, + "Invalid data in attributes argument"); + return NULL; + } + + gck_builder_add_string (&builder, CKA_G_COLLECTION, self->collection); + gck_builder_add_ulong (&builder, CKA_CLASS, CKO_SECRET_KEY); + gck_builder_add_boolean (&builder, CKA_TOKEN, TRUE); + gck_builder_add_data (&builder, CKA_VALUE, value, *n_value); + + session = gkd_secret_service_internal_pkcs11_session (self->service); + item = gck_session_create_object (session, + gck_builder_end (&builder), + self->cancellable, + error); + if (item == NULL) { + g_free (value); + return NULL; + } + g_object_unref (item); + + return value; +} + +static gboolean +portal_method_retrieve_secret (GkdExportedPortal *skeleton, + GDBusMethodInvocation *invocation, + GUnixFDList *fd_list, + const gchar *arg_handle, + const gchar *arg_app_id, + GVariant *arg_fd, + GVariant *arg_options, + GkdSecretPortal *self) +{ + int idx, fd; + GError *error = NULL; + guint8 *value = NULL; + gsize n_value = 0; + GOutputStream *stream; + GVariantBuilder builder; + + g_variant_get (arg_fd, "h", &idx); + fd = g_unix_fd_list_get (fd_list, idx, NULL); + + g_clear_object (&self->request_skeleton); + self->request_skeleton = gkd_exported_portal_request_skeleton_new (); + if (!g_dbus_interface_skeleton_export (G_DBUS_INTERFACE_SKELETON (self->request_skeleton), + g_dbus_method_invocation_get_connection (invocation), + arg_handle, &error)) { + g_warning ("error exporting request: %s\n", error->message); + g_clear_error (&error); + } else { + g_signal_connect (self->request_skeleton, "handle-close", + G_CALLBACK (request_method_close), self); + } + + if (!ensure_collection (self, &error)) { + g_clear_object (&self->request_skeleton); + g_dbus_method_invocation_take_error (invocation, error); + return TRUE; + } + + value = lookup_secret_value (self, arg_app_id, &n_value, &error); + if (error != NULL) { + g_clear_object (&self->request_skeleton); + g_dbus_method_invocation_take_error (invocation, error); + return TRUE; + } + + /* If secret is not found, create a new random key */ + if (value == NULL) { + value = create_secret_value (self, arg_app_id, &n_value, &error); + if (value == NULL) { + g_clear_object (&self->request_skeleton); + g_dbus_method_invocation_take_error (invocation, error); + return TRUE; + } + } + + /* Write the secret value to the file descriptor */ + stream = g_unix_output_stream_new (fd, TRUE); + if (!g_output_stream_write_all (stream, value, n_value, NULL, NULL, &error)) { + g_free (value); + g_object_unref (stream); + g_dbus_method_invocation_take_error (invocation, error); + return TRUE; + } + g_free (value); + g_object_unref (stream); + + g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}")); + gkd_exported_portal_complete_retrieve_secret (skeleton, + invocation, + NULL, + 0, + g_variant_builder_end (&builder)); + + return TRUE; +} diff --git a/daemon/dbus/gkd-secret-portal.h b/daemon/dbus/gkd-secret-portal.h new file mode 100644 index 00000000..732353dc --- /dev/null +++ b/daemon/dbus/gkd-secret-portal.h @@ -0,0 +1,33 @@ +/* + * gnome-keyring + * + * Copyright (C) 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 License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, see + * <http://www.gnu.org/licenses/>. + */ + +#ifndef __GKD_SECRET_PORTAL_H__ +#define __GKD_SECRET_PORTAL_H__ + +#include <glib-object.h> + +#define GKD_SECRET_TYPE_PORTAL (gkd_secret_portal_get_type ()) +G_DECLARE_FINAL_TYPE (GkdSecretPortal, gkd_secret_portal, GKD_SECRET, PORTAL, GObject); + +struct _GkdSecretPortalClass { + GObjectClass parent_class; +}; + +#endif /* __GKD_SECRET_PORTAL_H__ */ diff --git a/daemon/dbus/gkd-secret-service.c b/daemon/dbus/gkd-secret-service.c index 69a42b4b..a0cd9e80 100644 --- a/daemon/dbus/gkd-secret-service.c +++ b/daemon/dbus/gkd-secret-service.c @@ -26,6 +26,7 @@ #include "gkd-secret-error.h" #include "gkd-secret-lock.h" #include "gkd-secret-objects.h" +#include "gkd-secret-portal.h" #include "gkd-secret-prompt.h" #include "gkd-secret-property.h" #include "gkd-secret-secret.h" @@ -42,6 +43,7 @@ #include "egg/egg-unix-credentials.h" #include <gck/gck.h> +#include <gcrypt.h> #include "pkcs11/pkcs11i.h" @@ -126,6 +128,7 @@ struct _GkdSecretService { GDBusConnection *connection; GkdExportedService *skeleton; GkdExportedInternal *internal_skeleton; + GkdSecretPortal *portal; guint name_owner_id; guint filter_id; @@ -1034,6 +1037,8 @@ gkd_secret_service_constructor (GType type, g_signal_connect (self->internal_skeleton, "handle-unlock-with-master-password", G_CALLBACK (service_method_unlock_with_master_password), self); + self->portal = g_object_new (GKD_SECRET_TYPE_PORTAL, "service", self, NULL); + self->name_owner_id = g_dbus_connection_signal_subscribe (self->connection, NULL, "org.freedesktop.DBus", @@ -1092,6 +1097,8 @@ gkd_secret_service_dispose (GObject *obj) self->internal_session = NULL; } + g_clear_object (&self->portal); + G_OBJECT_CLASS (gkd_secret_service_parent_class)->dispose (obj); } diff --git a/daemon/dbus/gkd-secret-types.h b/daemon/dbus/gkd-secret-types.h index 1f756d23..4005108e 100644 --- a/daemon/dbus/gkd-secret-types.h +++ b/daemon/dbus/gkd-secret-types.h @@ -23,6 +23,9 @@ #define INTERNAL_SERVICE_INTERFACE "org.gnome.keyring.InternalUnsupportedGuiltRiddenInterface" +#define PORTAL_SERVICE_INTERFACE "org.freedesktop.impl.portal.Secret" +#define PORTAL_SERVICE_PATH "/org/freedesktop/portal/desktop" + #define SECRET_COLLECTION_INTERFACE "org.freedesktop.Secret.Collection" #define SECRET_ITEM_INTERFACE "org.freedesktop.Secret.Item" #define SECRET_PROMPT_INTERFACE "org.freedesktop.Secret.Prompt" diff --git a/daemon/dbus/org.freedesktop.impl.portal.Request.xml b/daemon/dbus/org.freedesktop.impl.portal.Request.xml new file mode 100644 index 00000000..4402c40a --- /dev/null +++ b/daemon/dbus/org.freedesktop.impl.portal.Request.xml @@ -0,0 +1,47 @@ +<?xml version="1.0"?> +<!-- + Copyright (C) 2016 Red Hat, Inc. + + This library 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 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. + + Author: Alexander Larsson <alexl@redhat.com> + Matthias Clasen <mclasen@redhat.com> +--> + +<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd"> + <!-- + org.freedesktop.impl.portal.Request: + @short_description: Shared request interface + + The Request interface is shared by all portal backend interfaces. + When a backend method is called, the backend exports a Request object + on the object path that was sent with the method call. The Request + will stay alive for the duration of the user interaction related to + the method call. + + The portal can abort the interaction calling + org.freedesktop.impl.portal.Request.Close() on the Request object. + --> + <interface name="org.freedesktop.impl.portal.Request"> + + <!-- + Close: + + Ends the user interaction to which this object refers. + Dialogs and other UIs related to the request should be closed. + --> + <method name="Close"> + </method> + </interface> +</node> diff --git a/daemon/dbus/org.freedesktop.impl.portal.Secret.xml b/daemon/dbus/org.freedesktop.impl.portal.Secret.xml new file mode 100644 index 00000000..c33d85e2 --- /dev/null +++ b/daemon/dbus/org.freedesktop.impl.portal.Secret.xml @@ -0,0 +1,65 @@ +<?xml version="1.0"?> +<!-- + Copyright (C) 2019 Red Hat, Inc. + + This library 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 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see <http://www.gnu.org/licenses/>. + + Author: Daiki Ueno <dueno@redhat.com> +--> + +<node name="/" xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd"> + <!-- + org.freedesktop.impl.portal.Secret: + @short_description: Secret portal backend interface + + The Secret portal allows sandboxed applications to retrieve a + per-application master secret. + --> + <interface name="org.freedesktop.impl.portal.Secret"> + + <!-- + RetrieveSecret: + @handle: Object path for the #org.freedesktop.impl.portal.Request object representing this call + @app_id: App id of the application + @fd: File descriptor for reading the secret + @options: Vardict with optional further information + @results: Vardict with the results of the call + + Retrieves a master secret for a sandboxed application. + + Supported keys in the @options vardict include: + <variablelist> + <varlistentry> + <term>token s</term> + <listitem><para> + An opaque string associated with the retrieve secret. + </para></listitem> + </varlistentry> + </variablelist> + --> + <method name="RetrieveSecret"> + <annotation name="org.gtk.GDBus.C.Name" value="retrieve_secret"/> + <annotation name="org.gtk.GDBus.C.UnixFD" value="true"/> + <arg type="o" name="handle" direction="in"/> + <arg type="s" name="app_id" direction="in"/> + <arg type="h" name="fd" direction="in"/> + <annotation name="org.qtproject.QtDBus.QtTypeName.In3" value="QVariantMap"/> + <arg type="a{sv}" name="options" direction="in"/> + <arg type="u" name="response" direction="out"/> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="QVariantMap"/> + <arg type="a{sv}" name="results" direction="out"/> + </method> + <property name="version" type="u" access="read"/> + </interface> +</node> diff --git a/daemon/dbus/test-dbus-portal.c b/daemon/dbus/test-dbus-portal.c new file mode 100644 index 00000000..aeb912fb --- /dev/null +++ b/daemon/dbus/test-dbus-portal.c @@ -0,0 +1,215 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* test-secret-portal.c: Test secret portal + + Copyright (C) 2013-2019 Red Hat, Inc + + The Gnome Keyring Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. + + The Gnome Keyring Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public + License along with the Gnome Library; see the file COPYING.LIB. If not, + <http://www.gnu.org/licenses/>. + + Author: Stef Walter <stefw@gnome.org>, Daiki Ueno +*/ + +#include "config.h" + +#include "test-service.h" + +#include "gkd-secret-types.h" + +#include "egg/egg-testing.h" + +#include <gcr/gcr-base.h> +#include <glib.h> +#include <glib/gstdio.h> +#include <gio/gio.h> +#include <glib-unix.h> +#include <gio/gunixfdlist.h> +#include <gio/gunixinputstream.h> +#include <fcntl.h> + +typedef struct { + TestService service; + guint8 buffer[128]; + gsize bytes_read; +} Test; + +static void +setup (Test *test, + gconstpointer unused) +{ + GVariant *retval; + GError *error = NULL; + + test->service.mock_prompter = gcr_mock_prompter_start (); + g_assert (test->service.mock_prompter != NULL); + + test_service_setup (&test->service); + + /* Unlock the test collection */ + retval = g_dbus_connection_call_sync (test->service.connection, + test->service.bus_name, + SECRET_SERVICE_PATH, + INTERNAL_SERVICE_INTERFACE, + "UnlockWithMasterPassword", + g_variant_new ("(o@(oayays))", + "/org/freedesktop/secrets/collection/test", + test_service_build_secret (&test->service, "booo")), + G_VARIANT_TYPE ("()"), + G_DBUS_CALL_FLAGS_NO_AUTO_START, + -1, NULL, &error); + g_assert_no_error (error); + g_variant_unref (retval); +} + +static void +teardown (Test *test, + gconstpointer unused) +{ + test_service_teardown (&test->service); + + gcr_mock_prompter_stop (); +} + +static void +setup_locked (Test *test, + gconstpointer unused) +{ + GVariant *element; + GVariant *retval; + GError *error = NULL; + const gchar *prompt; + GVariant *locked; + + /* Main setup */ + setup (test, unused); + + element = g_variant_new_object_path ("/org/freedesktop/secrets/collection/test"); + retval = g_dbus_connection_call_sync (test->service.connection, + test->service.bus_name, + SECRET_SERVICE_PATH, + SECRET_SERVICE_INTERFACE, + "Lock", + g_variant_new ("(@ao)", + g_variant_new_array (G_VARIANT_TYPE ("o"), &element, 1)), + G_VARIANT_TYPE ("(aoo)"), + G_DBUS_CALL_FLAGS_NO_AUTO_START, + -1, + NULL, + &error); + g_assert_no_error (error); + + /* Not expecting a prompt */ + g_variant_get (retval, "(@ao&o)", &locked, &prompt); + g_assert_cmpstr (prompt, ==, "/"); + g_variant_unref (locked); + g_variant_unref (retval); +} + +static void +call_retrieve_secret (Test *test) +{ + GUnixFDList *fd_list; + gint fds[2]; + gint fd_index; + GError *error = NULL; + gboolean ret; + GVariant *reply = NULL; + GInputStream *stream = NULL; + GVariantBuilder options; + + ret = g_unix_open_pipe (fds, FD_CLOEXEC, &error); + g_assert_true (ret); + g_assert_no_error (error); + + fd_list = g_unix_fd_list_new (); + fd_index = g_unix_fd_list_append (fd_list, fds[1], &error); + g_assert_no_error (error); + close (fds[1]); + + g_variant_builder_init (&options, G_VARIANT_TYPE ("a{sv}")); + reply = g_dbus_connection_call_with_unix_fd_list_sync (test->service.connection, + test->service.bus_name, + PORTAL_SERVICE_PATH, + PORTAL_SERVICE_INTERFACE, + "RetrieveSecret", + g_variant_new ("(osh@a{sv})", + "/org/gnome/keyring/Portal/Request", + "org.gnome.keyring.Test", + fd_index, + g_variant_builder_end (&options)), + G_VARIANT_TYPE ("(ua{sv})"), + G_DBUS_CALL_FLAGS_NONE, + 30000, + fd_list, NULL, + NULL, + &error); + g_object_unref (fd_list); + g_assert_no_error (error); + g_assert_nonnull (reply); + + stream = g_unix_input_stream_new (fds[0], TRUE); + ret = g_input_stream_read_all (stream, test->buffer, sizeof(test->buffer), + &test->bytes_read, NULL, &error); + g_assert_no_error (error); + g_assert_true (ret); + + g_object_unref (stream); +} + +static void +test_portal_retrieve_secret (Test *test, + gconstpointer unused) +{ + guint8 buffer[128]; + gsize bytes_read; + + call_retrieve_secret (test); + memcpy (buffer, test->buffer, sizeof(test->buffer)); + bytes_read = test->bytes_read; + + call_retrieve_secret (test); + g_assert_cmpmem (buffer, bytes_read, test->buffer, test->bytes_read); +} + +static void +test_portal_retrieve_secret_locked (Test *test, + gconstpointer unused) +{ + guint8 buffer[128]; + gsize bytes_read; + + gcr_mock_prompter_expect_password_ok ("booo", NULL); + + call_retrieve_secret (test); + memcpy (buffer, test->buffer, sizeof(test->buffer)); + bytes_read = test->bytes_read; + + call_retrieve_secret (test); + g_assert_cmpmem (buffer, bytes_read, test->buffer, test->bytes_read); +} + +int +main (int argc, char **argv) +{ +#if !GLIB_CHECK_VERSION(2,35,0) + g_type_init (); +#endif + g_test_init (&argc, &argv, NULL); + + g_test_add ("/secret-portal/portal-retrieve-secret", Test, NULL, + setup, test_portal_retrieve_secret, teardown); + g_test_add ("/secret-portal/portal-retrieve-secret-locked", Test, NULL, + setup_locked, test_portal_retrieve_secret_locked, teardown); + + return egg_tests_run_with_loop (); +} diff --git a/daemon/dbus/test-service.c b/daemon/dbus/test-service.c index 212e12f1..46c85be1 100644 --- a/daemon/dbus/test-service.c +++ b/daemon/dbus/test-service.c @@ -91,6 +91,7 @@ test_service_setup (TestService *test) env = gkd_test_launch_daemon (test->directory, args, &test->pid, "GNOME_KEYRING_TEST_SERVICE", test->bus_name, + "GNOME_KEYRING_TEST_LOGIN", "test", test->mock_prompter ? "GNOME_KEYRING_TEST_PROMPTER" : NULL, test->mock_prompter, NULL); g_strfreev (env); diff --git a/daemon/gnome-keyring.portal b/daemon/gnome-keyring.portal new file mode 100644 index 00000000..07eb3d24 --- /dev/null +++ b/daemon/gnome-keyring.portal @@ -0,0 +1,4 @@ +[portal] +DBusName=org.freedesktop.secrets +Interfaces=org.freedesktop.impl.portal.Secret +UseIn=gnome diff --git a/daemon/org.freedesktop.impl.portal.Secret.service.in b/daemon/org.freedesktop.impl.portal.Secret.service.in new file mode 100644 index 00000000..bc4d921c --- /dev/null +++ b/daemon/org.freedesktop.impl.portal.Secret.service.in @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=org.freedesktop.impl.portal.Secret +Exec=@bindir@/gnome-keyring-daemon --start --foreground --components=secrets |