summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaiki Ueno <ueno@gnu.org>2019-10-01 16:19:01 +0000
committerDaiki Ueno <ueno@gnu.org>2019-10-01 16:19:01 +0000
commit7f472501939bd4d1e0db65fd5d1dc0d57bdb63a1 (patch)
treee42677f62e0de7436e98323e7106f81cfc48ba35
parentd463f42495484437de8cec69bea9b6814ee32479 (diff)
parent4010e268e6b88668e4ff017538ec42096057591c (diff)
downloadgnome-keyring-7f472501939bd4d1e0db65fd5d1dc0d57bdb63a1.tar.gz
Merge branch 'wip/dueno/secret-portal' into 'master'
dbus: Implement secret portal backend See merge request GNOME/gnome-keyring!18
-rw-r--r--.gitignore4
-rw-r--r--daemon/.gitignore1
-rw-r--r--daemon/Makefile.am6
-rw-r--r--daemon/dbus/Makefile.am35
-rw-r--r--daemon/dbus/gkd-secret-portal.c438
-rw-r--r--daemon/dbus/gkd-secret-portal.h33
-rw-r--r--daemon/dbus/gkd-secret-service.c7
-rw-r--r--daemon/dbus/gkd-secret-types.h3
-rw-r--r--daemon/dbus/org.freedesktop.impl.portal.Request.xml47
-rw-r--r--daemon/dbus/org.freedesktop.impl.portal.Secret.xml65
-rw-r--r--daemon/dbus/test-dbus-portal.c215
-rw-r--r--daemon/dbus/test-service.c1
-rw-r--r--daemon/gnome-keyring.portal4
-rw-r--r--daemon/org.freedesktop.impl.portal.Secret.service.in3
14 files changed, 859 insertions, 3 deletions
diff --git a/.gitignore b/.gitignore
index f153d290..1f57c5a7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -126,6 +126,10 @@ depcomp
/daemon/dbus/gkd-internal-generated.h
/daemon/dbus/gkd-secrets-generated.c
/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
/pkcs11/gkm/tests/test-attributes
/pkcs11/gkm/tests/test-certificate
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