[gcr/wip/dueno/ssh-agent: 2/5] gcr-ssh-agent: port from gnome-keyring




commit dc8e6b850373d2b6d15a9eab27e909c95e33730b
Author: Daiki Ueno <dueno src gnome org>
Date:   Sat Sep 26 11:34:34 2020 +0200

    gcr-ssh-agent: port from gnome-keyring
    
    This port the ssh-agent support provided as a sub-daemon in
    gnome-keyring, as a standalone binary, so that it can easily be
    managed through systemd.

 gcr/gcr-ssh-agent-interaction.c | 304 ++++++++++++++++++
 gcr/gcr-ssh-agent-interaction.h |  39 +++
 gcr/gcr-ssh-agent-preload.c     | 279 +++++++++++++++++
 gcr/gcr-ssh-agent-preload.h     |  50 +++
 gcr/gcr-ssh-agent-private.h     |  70 +++++
 gcr/gcr-ssh-agent-process.c     | 270 ++++++++++++++++
 gcr/gcr-ssh-agent-process.h     |  40 +++
 gcr/gcr-ssh-agent-service.c     | 662 ++++++++++++++++++++++++++++++++++++++++
 gcr/gcr-ssh-agent-service.h     |  53 ++++
 gcr/gcr-ssh-agent-util.c        | 220 +++++++++++++
 gcr/gcr-ssh-agent-util.h        |  50 +++
 gcr/gcr-ssh-agent.c             |  55 ++++
 gcr/meson.build                 |  23 ++
 meson.build                     |   5 +
 meson_options.txt               |   5 +
 15 files changed, 2125 insertions(+)
---
diff --git a/gcr/gcr-ssh-agent-interaction.c b/gcr/gcr-ssh-agent-interaction.c
new file mode 100644
index 0000000..25a962f
--- /dev/null
+++ b/gcr/gcr-ssh-agent-interaction.c
@@ -0,0 +1,304 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2018 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/>.
+ *
+ * Author: Daiki Ueno
+ */
+
+#include "config.h"
+
+#include "gcr-ssh-agent-interaction.h"
+
+#include "gcr-ssh-agent-private.h"
+#include "gcr-prompt.h"
+#include "gcr-system-prompt.h"
+
+#include "egg/egg-secure-memory.h"
+#include <glib/gi18n-lib.h>
+#include <libsecret/secret.h>
+
+static const SecretSchema schema = {
+       "org.freedesktop.Secret.Generic", SECRET_SCHEMA_NONE,
+       {
+               { "unique", SECRET_SCHEMA_ATTRIBUTE_STRING },
+               { NULL, 0 }
+       }
+};
+
+enum {
+       PROP_0,
+       PROP_PROMPTER_NAME,
+       PROP_LABEL,
+       PROP_FIELDS
+};
+
+struct _GcrSshAgentInteraction {
+       GTlsInteraction interaction;
+
+       gchar *prompter_name;
+       gchar *label;
+       GHashTable *fields;
+};
+
+G_DEFINE_TYPE (GcrSshAgentInteraction, gcr_ssh_agent_interaction, G_TYPE_TLS_INTERACTION);
+
+EGG_SECURE_DECLARE (gcr_ssh_agent_interaction);
+
+static void
+gcr_ssh_agent_interaction_init (GcrSshAgentInteraction *self)
+{
+}
+
+static void
+on_store_password_ready (GObject *source_object,
+                        GAsyncResult *res,
+                        gpointer user_data)
+{
+       GTask *task = G_TASK (user_data);
+       GError *error = NULL;
+
+       if (secret_password_store_finish (res, &error)) {
+               g_task_return_int (task, G_TLS_INTERACTION_HANDLED);
+       } else {
+               g_task_return_error (task, error);
+       }
+       g_object_unref (task);
+}
+
+static void
+on_prompt_password (GObject *source_object,
+                   GAsyncResult *result,
+                   gpointer user_data)
+{
+       GTask *task = G_TASK (user_data);
+       GcrSshAgentInteraction *self = g_task_get_source_object (task);
+       GTlsPassword *password = G_TLS_PASSWORD (g_task_get_task_data (task));
+       GcrPrompt *prompt = GCR_PROMPT (source_object);
+       GError *error = NULL;
+       const gchar *value;
+       gchar *secure_value;
+
+       value = gcr_prompt_password_finish (prompt, result, &error);
+       if (!value) {
+               g_object_unref (prompt);
+               if (error)
+                       g_task_return_error (task, error);
+               else
+                       g_task_return_new_error (task, G_IO_ERROR, G_IO_ERROR_CANCELLED, "cancelled");
+               g_object_unref (task);
+               return;
+       }
+
+       secure_value = egg_secure_strndup (value, strlen (value));
+       g_tls_password_set_value_full (password,
+                                      (guchar *) secure_value,
+                                      strlen (secure_value),
+                                      (GDestroyNotify) egg_secure_free);
+
+       if (gcr_prompt_get_choice_chosen (prompt)) {
+               gchar *label;
+
+               label = g_strdup_printf (_("Unlock password for: %s"), self->label);
+               secret_password_storev (&schema,
+                                       self->fields,
+                                       NULL,
+                                       label,
+                                       secure_value,
+                                       g_task_get_cancellable (task),
+                                       on_store_password_ready,
+                                       task);
+               g_free (label);
+       } else {
+               g_task_return_int (task, G_TLS_INTERACTION_HANDLED);
+               g_object_unref (task);
+       }
+       g_object_unref (prompt);
+}
+
+static void
+on_prompt_open (GObject *source_object,
+                GAsyncResult *result,
+                gpointer user_data)
+{
+       GTask *task = G_TASK (user_data);
+       GTlsPassword *password = g_task_get_task_data (task);
+       GError *error = NULL;
+       GcrPrompt *prompt;
+       const gchar *choice;
+       gchar *text;
+
+       prompt = gcr_system_prompt_open_finish (result, &error);
+       if (!prompt) {
+               g_task_return_error (task, error);
+               g_object_unref (task);
+               return;
+       }
+
+       gcr_prompt_set_title (prompt, _("Unlock private key"));
+       gcr_prompt_set_message (prompt, _("Enter password to unlock the private key"));
+
+       /* TRANSLATORS: The private key is locked */
+       text = g_strdup_printf (_("An application wants access to the private key “%s”, but it is locked"),
+                               g_tls_password_get_description (password));
+       gcr_prompt_set_description (prompt, text);
+       g_free (text);
+
+       choice = _("Automatically unlock this key whenever I’m logged in");
+       gcr_prompt_set_choice_label (prompt, choice);
+       gcr_prompt_set_continue_label (prompt, _("Unlock"));
+
+       if (g_tls_password_get_flags (password) & G_TLS_PASSWORD_RETRY)
+               gcr_prompt_set_warning (prompt, _("The unlock password was incorrect"));
+
+       gcr_prompt_password_async (prompt, g_task_get_cancellable (task), on_prompt_password, task);
+}
+
+static void
+on_lookup_password_ready (GObject *source_object,
+                         GAsyncResult *res,
+                         gpointer user_data)
+{
+       GTask *task = G_TASK (user_data);
+       GcrSshAgentInteraction *self = g_task_get_source_object (task);
+       GTlsPassword *password = G_TLS_PASSWORD (g_task_get_task_data (task));
+       GError *error = NULL;
+
+       gchar *value = secret_password_lookup_finish (res, &error);
+       if (value) {
+               /* If password is found in the login keyring, return it */
+               g_tls_password_set_value_full (password,
+                                              (guchar *)value, strlen (value),
+                                              (GDestroyNotify)egg_secure_free);
+               g_task_return_int (task, G_TLS_INTERACTION_HANDLED);
+               g_object_unref (task);
+       } else {
+               /* Otherwise, prompt a password */
+               gcr_system_prompt_open_for_prompter_async (self->prompter_name, 60,
+                                                          g_task_get_cancellable (task),
+                                                          on_prompt_open,
+                                                          task);
+       }
+}
+
+static void
+gcr_ssh_agent_interaction_ask_password_async (GTlsInteraction *interaction,
+                                             GTlsPassword *password,
+                                             GCancellable *cancellable,
+                                             GAsyncReadyCallback callback,
+                                             gpointer user_data)
+{
+       GcrSshAgentInteraction *self = GCR_SSH_AGENT_INTERACTION (interaction);
+       GTask *task;
+
+       task = g_task_new (interaction, cancellable, callback, user_data);
+       g_task_set_task_data (task, g_object_ref (password), g_object_unref);
+
+       secret_password_lookupv (&schema,
+                                self->fields,
+                                cancellable,
+                                on_lookup_password_ready,
+                                task);
+}
+
+static GTlsInteractionResult
+gcr_ssh_agent_interaction_ask_password_finish (GTlsInteraction *interaction,
+                                              GAsyncResult *res,
+                                              GError **error)
+{
+       GTask *task = G_TASK (res);
+       GTlsInteractionResult result;
+
+       result = g_task_propagate_int (task, error);
+       if (result == -1)
+               return G_TLS_INTERACTION_FAILED;
+       return result;
+}
+
+static void
+gcr_ssh_agent_interaction_set_property (GObject *object,
+                                       guint prop_id,
+                                       const GValue *value,
+                                       GParamSpec *pspec)
+{
+       GcrSshAgentInteraction *self = GCR_SSH_AGENT_INTERACTION (object);
+
+       switch (prop_id) {
+       case PROP_PROMPTER_NAME:
+               self->prompter_name = g_value_dup_string (value);
+               break;
+       case PROP_LABEL:
+               self->label = g_value_dup_string (value);
+               break;
+       case PROP_FIELDS:
+               self->fields = g_value_dup_boxed (value);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+gcr_ssh_agent_interaction_finalize (GObject *object)
+{
+       GcrSshAgentInteraction *self = GCR_SSH_AGENT_INTERACTION (object);
+
+       g_free (self->prompter_name);
+       g_free (self->label);
+       g_hash_table_unref (self->fields);
+
+       G_OBJECT_CLASS (gcr_ssh_agent_interaction_parent_class)->finalize (object);
+}
+
+static void
+gcr_ssh_agent_interaction_class_init (GcrSshAgentInteractionClass *klass)
+{
+       GTlsInteractionClass *interaction_class = G_TLS_INTERACTION_CLASS (klass);
+       GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+       interaction_class->ask_password_async = gcr_ssh_agent_interaction_ask_password_async;
+       interaction_class->ask_password_finish = gcr_ssh_agent_interaction_ask_password_finish;
+
+       gobject_class->set_property = gcr_ssh_agent_interaction_set_property;
+       gobject_class->finalize = gcr_ssh_agent_interaction_finalize;
+
+       g_object_class_install_property (gobject_class, PROP_PROMPTER_NAME,
+                                        g_param_spec_string ("prompter-name", "Prompter-name", 
"Prompter-name",
+                                                             NULL,
+                                                             G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
+       g_object_class_install_property (gobject_class, PROP_LABEL,
+                                        g_param_spec_string ("label", "Label", "Label",
+                                                             "",
+                                                             G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
+       g_object_class_install_property (gobject_class, PROP_FIELDS,
+                                        g_param_spec_boxed ("fields", "Fields", "Fields",
+                                                            G_TYPE_HASH_TABLE,
+                                                            G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
+}
+
+GTlsInteraction *
+gcr_ssh_agent_interaction_new (const gchar *prompter_name,
+                              const gchar *label,
+                              GHashTable *fields)
+{
+       return g_object_new (GCR_TYPE_SSH_AGENT_INTERACTION,
+                            "prompter-name", prompter_name,
+                            "label", label,
+                            "fields", fields,
+                            NULL);
+}
diff --git a/gcr/gcr-ssh-agent-interaction.h b/gcr/gcr-ssh-agent-interaction.h
new file mode 100644
index 0000000..276e4c6
--- /dev/null
+++ b/gcr/gcr-ssh-agent-interaction.h
@@ -0,0 +1,39 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2018 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/>.
+ *
+ * Author: Daiki Ueno
+ */
+
+#ifndef GCR_SSH_AGENT_INTERACTION_H
+#define GCR_SSH_AGENT_INTERACTION_H
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GCR_TYPE_SSH_AGENT_INTERACTION gcr_ssh_agent_interaction_get_type ()
+G_DECLARE_FINAL_TYPE (GcrSshAgentInteraction, gcr_ssh_agent_interaction, GCR, SSH_AGENT_INTERACTION, 
GTlsInteraction)
+
+GTlsInteraction *gcr_ssh_agent_interaction_new (const gchar *prompter_name,
+                                               const gchar *label,
+                                               GHashTable *fields);
+
+G_END_DECLS
+
+#endif /* GCR_SSH_AGENT_INTERACTION_H */
diff --git a/gcr/gcr-ssh-agent-preload.c b/gcr/gcr-ssh-agent-preload.c
new file mode 100644
index 0000000..4e5bc66
--- /dev/null
+++ b/gcr/gcr-ssh-agent-preload.c
@@ -0,0 +1,279 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2014 Stef Walter
+ * Copyright (C) 2018 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/>.
+ *
+ * Author: Stef Walter <stef thewalter net>, Daiki Ueno
+ */
+
+#include "config.h"
+
+#include "gcr-ssh-agent-preload.h"
+
+#include "gcr-ssh-agent-util.h"
+#include "egg/egg-file-tracker.h"
+#include <string.h>
+
+enum {
+       PROP_0,
+       PROP_PATH
+};
+
+struct _GcrSshAgentPreload
+{
+       GObject object;
+
+       gchar *path;
+       GHashTable *keys_by_public_filename;
+       GHashTable *keys_by_public_key;
+       EggFileTracker *file_tracker;
+       GMutex lock;
+};
+
+G_DEFINE_TYPE (GcrSshAgentPreload, gcr_ssh_agent_preload, G_TYPE_OBJECT);
+
+void
+gcr_ssh_agent_key_info_free (gpointer boxed)
+{
+       GcrSshAgentKeyInfo *info = boxed;
+       if (!info)
+               return;
+       g_bytes_unref (info->public_key);
+       g_free (info->comment);
+       g_free (info->filename);
+       g_free (info);
+}
+
+gpointer
+gcr_ssh_agent_key_info_copy (gpointer boxed)
+{
+       GcrSshAgentKeyInfo *info = boxed;
+       GcrSshAgentKeyInfo *copy = g_new0 (GcrSshAgentKeyInfo, 1);
+       copy->public_key = g_bytes_ref (info->public_key);
+       copy->comment = g_strdup (info->comment);
+       copy->filename = g_strdup (info->filename);
+       return copy;
+}
+
+static void file_load_inlock   (EggFileTracker *tracker,
+                                const gchar *path,
+                                gpointer user_data);
+static void file_remove_inlock (EggFileTracker *tracker,
+                                const gchar *path,
+                                gpointer user_data);
+
+static void
+gcr_ssh_agent_preload_init (GcrSshAgentPreload *self)
+{
+       self->keys_by_public_filename = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+       self->keys_by_public_key = g_hash_table_new_full (g_bytes_hash, g_bytes_equal, NULL, 
gcr_ssh_agent_key_info_free);
+}
+
+static void
+gcr_ssh_agent_preload_constructed (GObject *object)
+{
+       GcrSshAgentPreload *self = GCR_SSH_AGENT_PRELOAD (object);
+
+       self->file_tracker = egg_file_tracker_new (self->path, "*.pub", NULL);
+       g_signal_connect (self->file_tracker, "file-added", G_CALLBACK (file_load_inlock), self);
+       g_signal_connect (self->file_tracker, "file-removed", G_CALLBACK (file_remove_inlock), self);
+       g_signal_connect (self->file_tracker, "file-changed", G_CALLBACK (file_load_inlock), self);
+
+       G_OBJECT_CLASS (gcr_ssh_agent_preload_parent_class)->constructed (object);
+}
+
+static void
+gcr_ssh_agent_preload_set_property (GObject *object,
+                                    guint prop_id,
+                                    const GValue *value,
+                                    GParamSpec *pspec)
+{
+       GcrSshAgentPreload *self = GCR_SSH_AGENT_PRELOAD (object);
+
+       switch (prop_id) {
+       case PROP_PATH:
+               self->path = g_value_dup_string (value);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+gcr_ssh_agent_preload_finalize (GObject *object)
+{
+       GcrSshAgentPreload *self = GCR_SSH_AGENT_PRELOAD (object);
+
+       g_free (self->path);
+       g_clear_pointer (&self->keys_by_public_key, g_hash_table_unref);
+       g_clear_pointer (&self->keys_by_public_filename, g_hash_table_unref);
+       g_clear_object (&self->file_tracker);
+
+       g_mutex_clear (&self->lock);
+
+       G_OBJECT_CLASS (gcr_ssh_agent_preload_parent_class)->finalize (object);
+}
+
+static void
+gcr_ssh_agent_preload_class_init (GcrSshAgentPreloadClass *klass)
+{
+       GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+       gobject_class->constructed = gcr_ssh_agent_preload_constructed;
+       gobject_class->set_property = gcr_ssh_agent_preload_set_property;
+       gobject_class->finalize = gcr_ssh_agent_preload_finalize;
+       g_object_class_install_property (gobject_class, PROP_PATH,
+                g_param_spec_string ("path", "Path", "Path",
+                                     "",
+                                     G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
+}
+
+static gchar *
+private_path_for_public (const gchar *public_path)
+{
+       if (g_str_has_suffix (public_path, ".pub"))
+               return g_strndup (public_path, strlen (public_path) - 4);
+
+       return NULL;
+}
+
+static GBytes *
+file_get_contents (const gchar *path,
+                   gboolean must_be_present)
+{
+       GError *error = NULL;
+       gchar *contents;
+       gsize length;
+
+       if (!g_file_get_contents (path, &contents, &length, &error)) {
+               if (must_be_present || error->code != G_FILE_ERROR_NOENT)
+                       g_message ("couldn't read file: %s: %s", path, error->message);
+               g_error_free (error);
+               return NULL;
+       }
+
+       return g_bytes_new_take (contents, length);
+}
+
+static void
+file_remove_inlock (EggFileTracker *tracker,
+                    const gchar *path,
+                    gpointer user_data)
+{
+       GcrSshAgentPreload *self = GCR_SSH_AGENT_PRELOAD (user_data);
+       GcrSshAgentKeyInfo *info;
+
+       info = g_hash_table_lookup (self->keys_by_public_filename, path);
+       if (info) {
+               g_hash_table_remove (self->keys_by_public_filename, path);
+               g_hash_table_remove (self->keys_by_public_key, info->public_key);
+       }
+}
+
+static void
+file_load_inlock (EggFileTracker *tracker,
+                  const gchar *path,
+                  gpointer user_data)
+{
+       GcrSshAgentPreload *self = GCR_SSH_AGENT_PRELOAD (user_data);
+       gchar *private_path;
+       GBytes *private_bytes;
+       GBytes *public_bytes;
+       GBytes *public_key;
+       GcrSshAgentKeyInfo *info;
+       gchar *comment;
+
+       file_remove_inlock (tracker, path, user_data);
+
+       private_path = private_path_for_public (path);
+
+       private_bytes = file_get_contents (private_path, FALSE);
+       if (!private_bytes) {
+               g_debug ("no private key present for public key: %s", path);
+               g_free (private_path);
+               return;
+       }
+
+       public_bytes = file_get_contents (path, TRUE);
+       if (public_bytes) {
+               public_key = _gcr_ssh_agent_parse_public_key (public_bytes, &comment);
+               if (public_key) {
+                       info = g_new0 (GcrSshAgentKeyInfo, 1);
+                       info->filename = private_path;
+                       private_path = NULL;
+                       info->public_key = public_key;
+                       info->comment = comment;
+                       g_hash_table_replace (self->keys_by_public_filename, g_strdup (path), info);
+                       g_hash_table_replace (self->keys_by_public_key, info->public_key, info);
+               } else {
+                       g_message ("failed to parse ssh public key: %s", path);
+               }
+
+               g_bytes_unref (public_bytes);
+       }
+
+       g_bytes_unref (private_bytes);
+       g_free (private_path);
+}
+
+GcrSshAgentPreload *
+gcr_ssh_agent_preload_new (const gchar *path)
+{
+       g_return_val_if_fail (path, NULL);
+
+       return g_object_new (GCR_TYPE_SSH_AGENT_PRELOAD, "path", path, NULL);
+}
+
+GList *
+gcr_ssh_agent_preload_get_keys (GcrSshAgentPreload *self)
+{
+       GList *keys = NULL;
+       GHashTableIter iter;
+       GcrSshAgentKeyInfo *info;
+
+       g_mutex_lock (&self->lock);
+
+       egg_file_tracker_refresh (self->file_tracker, FALSE);
+
+       g_hash_table_iter_init (&iter, self->keys_by_public_key);
+       while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&info))
+               keys = g_list_prepend (keys, gcr_ssh_agent_key_info_copy (info));
+
+       g_mutex_unlock (&self->lock);
+
+       return keys;
+}
+
+GcrSshAgentKeyInfo *
+gcr_ssh_agent_preload_lookup_by_public_key (GcrSshAgentPreload *self,
+                                           GBytes *public_key)
+{
+       GcrSshAgentKeyInfo *info;
+
+       g_mutex_lock (&self->lock);
+
+       egg_file_tracker_refresh (self->file_tracker, FALSE);
+
+       info = g_hash_table_lookup (self->keys_by_public_key, public_key);
+       if (info)
+               info = gcr_ssh_agent_key_info_copy (info);
+
+       g_mutex_unlock (&self->lock);
+
+       return info;
+}
diff --git a/gcr/gcr-ssh-agent-preload.h b/gcr/gcr-ssh-agent-preload.h
new file mode 100644
index 0000000..1ea5258
--- /dev/null
+++ b/gcr/gcr-ssh-agent-preload.h
@@ -0,0 +1,50 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2014 Stef Walter
+ * Copyright (C) 2018 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/>.
+ *
+ * Author: Stef Walter <stef thewalter net>, Daiki Ueno
+ */
+
+#ifndef GCR_SSH_AGENT_PRELOAD_H
+#define GCR_SSH_AGENT_PRELOAD_H
+
+#include <glib-object.h>
+
+typedef struct {
+       gchar *filename;
+       GBytes *public_key;
+       gchar *comment;
+} GcrSshAgentKeyInfo;
+
+void                gcr_ssh_agent_key_info_free    (gpointer boxed);
+gpointer            gcr_ssh_agent_key_info_copy    (gpointer boxed);
+
+#define GCR_TYPE_SSH_AGENT_PRELOAD gcr_ssh_agent_preload_get_type ()
+G_DECLARE_FINAL_TYPE (GcrSshAgentPreload, gcr_ssh_agent_preload, GCR, SSH_AGENT_PRELOAD, GObject)
+
+GcrSshAgentPreload *gcr_ssh_agent_preload_new      (const gchar *path);
+
+GList              *gcr_ssh_agent_preload_get_keys (GcrSshAgentPreload *self);
+
+GcrSshAgentKeyInfo *gcr_ssh_agent_preload_lookup_by_public_key
+                                                   (GcrSshAgentPreload *self,
+                                                    GBytes *public_key);
+
+#endif /* GCR_SSH_AGENT_PRELOAD_H */
+
diff --git a/gcr/gcr-ssh-agent-private.h b/gcr/gcr-ssh-agent-private.h
new file mode 100644
index 0000000..5b21510
--- /dev/null
+++ b/gcr/gcr-ssh-agent-private.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gkd-ssh-agent-private.h - Private SSH agent declarations
+
+   Copyright (C) 2007 Stefan Walter
+
+   Gnome keyring is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   Gnome keyring 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
+   General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Author: Stef Walter <stef memberwebs com>
+*/
+
+#ifndef GCRSSHPRIVATE_H_
+#define GCRSSHPRIVATE_H_
+
+/* -----------------------------------------------------------------------------
+ * SSH OPERATIONS and CONSTANTS
+ */
+
+/* Requests from client to daemon */
+#define GCR_SSH_OP_REQUEST_RSA_IDENTITIES               1
+#define GCR_SSH_OP_RSA_CHALLENGE                        3
+#define GCR_SSH_OP_ADD_RSA_IDENTITY                     7
+#define GCR_SSH_OP_REMOVE_RSA_IDENTITY                  8
+#define GCR_SSH_OP_REMOVE_ALL_RSA_IDENTITIES            9
+#define GCR_SSH_OP_REQUEST_IDENTITIES                   11
+#define GCR_SSH_OP_SIGN_REQUEST                         13
+#define GCR_SSH_OP_ADD_IDENTITY                         17
+#define GCR_SSH_OP_REMOVE_IDENTITY                      18
+#define GCR_SSH_OP_REMOVE_ALL_IDENTITIES                19
+#define GCR_SSH_OP_ADD_SMARTCARD_KEY                    20
+#define GCR_SSH_OP_REMOVE_SMARTCARD_KEY                 21
+#define GCR_SSH_OP_LOCK                                 22
+#define GCR_SSH_OP_UNLOCK                               23
+#define GCR_SSH_OP_ADD_RSA_ID_CONSTRAINED               24
+#define GCR_SSH_OP_ADD_ID_CONSTRAINED                   25
+#define GCR_SSH_OP_ADD_SMARTCARD_KEY_CONSTRAINED        26
+
+#define GCR_SSH_OP_MAX                                  27
+
+/* Responses from daemon to client */
+#define GCR_SSH_RES_RSA_IDENTITIES_ANSWER               2
+#define GCR_SSH_RES_RSA_RESPONSE                        4
+#define GCR_SSH_RES_FAILURE                             5
+#define GCR_SSH_RES_SUCCESS                             6
+#define GCR_SSH_RES_IDENTITIES_ANSWER                   12
+#define GCR_SSH_RES_SIGN_RESPONSE                       14
+#define GCR_SSH_RES_EXTENDED_FAILURE                    30
+#define GCR_SSH_RES_SSHCOM_FAILURE                      102
+
+
+#define        GCR_SSH_FLAG_CONSTRAIN_LIFETIME                 1
+#define        GCR_SSH_FLAG_CONSTRAIN_CONFIRM                  2
+
+#define GCR_SSH_DSA_SIGNATURE_PADDING                   20
+#define        GCR_SSH_FLAG_OLD_SIGNATURE                      0x01
+#define        GCR_SSH_FLAG_RSA_SHA2_256                       0x02
+#define        GCR_SSH_FLAG_RSA_SHA2_512                       0x04
+
+#endif /*GCRSSHPRIVATE_H_*/
diff --git a/gcr/gcr-ssh-agent-process.c b/gcr/gcr-ssh-agent-process.c
new file mode 100644
index 0000000..2eb43fb
--- /dev/null
+++ b/gcr/gcr-ssh-agent-process.c
@@ -0,0 +1,270 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2014 Stef Walter
+ * Copyright (C) 2018 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/>.
+ *
+ * Author: Stef Walter <stef thewalter net>, Daiki Ueno
+ */
+
+#include "config.h"
+
+#include "gcr-ssh-agent-process.h"
+#include "gcr-ssh-agent-private.h"
+#include "gcr-ssh-agent-util.h"
+
+#include <gio/gunixsocketaddress.h>
+#include <glib-unix.h>
+#include <glib/gstdio.h>
+
+enum {
+       PROP_0,
+       PROP_PATH
+};
+
+enum {
+       CLOSED,
+       LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+struct _GcrSshAgentProcess
+{
+       GObject object;
+       gchar *path;
+       gint output;
+       GMutex lock;
+       GPid pid;
+       guint output_id;
+       guint child_id;
+       gboolean ready;
+};
+
+G_DEFINE_TYPE (GcrSshAgentProcess, gcr_ssh_agent_process, G_TYPE_OBJECT);
+
+static void
+gcr_ssh_agent_process_init (GcrSshAgentProcess *self)
+{
+       self->output = -1;
+       g_mutex_init (&self->lock);
+}
+
+static void
+gcr_ssh_agent_process_finalize (GObject *object)
+{
+       GcrSshAgentProcess *self = GCR_SSH_AGENT_PROCESS (object);
+
+       if (self->output != -1)
+               close (self->output);
+       if (self->output_id)
+               g_source_remove (self->output_id);
+       if (self->child_id)
+               g_source_remove (self->child_id);
+       if (self->pid)
+               kill (self->pid, SIGTERM);
+       g_unlink (self->path);
+       g_free (self->path);
+       g_mutex_clear (&self->lock);
+
+       G_OBJECT_CLASS (gcr_ssh_agent_process_parent_class)->finalize (object);
+}
+
+static void
+gcr_ssh_agent_process_set_property (GObject *object,
+                                    guint prop_id,
+                                    const GValue *value,
+                                    GParamSpec *pspec)
+{
+       GcrSshAgentProcess *self = GCR_SSH_AGENT_PROCESS (object);
+
+       switch (prop_id) {
+       case PROP_PATH:
+               self->path = g_value_dup_string (value);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+gcr_ssh_agent_process_class_init (GcrSshAgentProcessClass *klass)
+{
+       GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+       gobject_class->finalize = gcr_ssh_agent_process_finalize;
+       gobject_class->set_property = gcr_ssh_agent_process_set_property;
+       g_object_class_install_property (gobject_class, PROP_PATH,
+                g_param_spec_string ("path", "Path", "Path",
+                                     "",
+                                     G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
+       signals[CLOSED] = g_signal_new_class_handler ("closed",
+                                                     G_TYPE_FROM_CLASS (klass),
+                                                     G_SIGNAL_RUN_LAST,
+                                                     NULL, NULL, NULL, NULL,
+                                                     G_TYPE_NONE, 0);
+}
+
+static void
+on_child_watch (GPid pid,
+                gint status,
+                gpointer user_data)
+{
+       GcrSshAgentProcess *self = GCR_SSH_AGENT_PROCESS (user_data);
+       GError *error = NULL;
+
+       if (pid != self->pid)
+               return;
+
+       g_mutex_lock (&self->lock);
+
+       self->pid = 0;
+       self->output_id = 0;
+       self->child_id = 0;
+
+       if (!g_spawn_check_exit_status (status, &error)) {
+               g_message ("ssh-agent: %s", error->message);
+               g_error_free (error);
+       }
+
+       g_spawn_close_pid (pid);
+
+       g_mutex_unlock (&self->lock);
+
+       g_signal_emit (self, signals[CLOSED], 0);
+}
+
+static gboolean
+on_output_watch (gint fd,
+                GIOCondition condition,
+                gpointer user_data)
+{
+       GcrSshAgentProcess *self = GCR_SSH_AGENT_PROCESS (user_data);
+       guint8 buf[1024];
+       gssize len;
+
+       if (condition & G_IO_IN) {
+               self->ready = TRUE;
+
+               len = read (fd, buf, sizeof (buf));
+               if (len < 0) {
+                       if (errno != EAGAIN && errno != EINTR)
+                               g_message ("couldn't read from ssh-agent stdout: %m");
+                       condition |= G_IO_ERR;
+               }
+       }
+
+       if (condition & G_IO_HUP || condition & G_IO_ERR)
+               return FALSE;
+
+       return TRUE;
+}
+
+static gboolean
+agent_start_inlock (GcrSshAgentProcess *self,
+                   GError **error)
+{
+       const gchar *argv[] = { SSH_AGENT_EXECUTABLE, "-D", "-a", self->path, NULL };
+       GPid pid;
+
+       if (!g_spawn_async_with_pipes ("/", (gchar **)argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD,
+                                      NULL, NULL, &pid, NULL, &self->output, NULL, error))
+               return FALSE;
+
+       self->ready = FALSE;
+       self->output_id = g_unix_fd_add (self->output,
+                                        G_IO_IN | G_IO_HUP | G_IO_ERR,
+                                        on_output_watch, self);
+
+       self->pid = pid;
+       self->child_id = g_child_watch_add (self->pid, on_child_watch, self);
+
+       return TRUE;
+}
+
+static gboolean
+on_timeout (gpointer user_data)
+{
+       gboolean *timedout = user_data;
+       *timedout = TRUE;
+       return TRUE;
+}
+
+GSocketConnection *
+gcr_ssh_agent_process_connect (GcrSshAgentProcess *self,
+                              GCancellable *cancellable,
+                              GError **error)
+{
+       gboolean started = FALSE;
+       gboolean timedout = FALSE;
+       guint source;
+       GSocketClient *client;
+       GSocketAddress *address;
+       GSocketConnection *connection;
+
+       g_mutex_lock (&self->lock);
+
+       if (self->pid == 0) {
+               if (!agent_start_inlock (self, error)) {
+                       g_mutex_unlock (&self->lock);
+                       return NULL;
+               }
+               started = TRUE;
+       }
+
+       if (started && !self->ready) {
+               source = g_timeout_add_seconds (5, on_timeout, &timedout);
+               while (!self->ready && !timedout)
+                       g_main_context_iteration (NULL, FALSE);
+               g_source_remove (source);
+       }
+
+       if (!self->ready) {
+               g_mutex_unlock (&self->lock);
+               g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                            "ssh-agent process is not ready");
+               return NULL;
+       }
+
+       address = g_unix_socket_address_new (self->path);
+       client = g_socket_client_new ();
+
+       connection = g_socket_client_connect (client,
+                                             G_SOCKET_CONNECTABLE (address),
+                                             cancellable,
+                                             error);
+       g_object_unref (address);
+       g_object_unref (client);
+
+       g_mutex_unlock (&self->lock);
+
+       return connection;
+}
+
+GcrSshAgentProcess *
+gcr_ssh_agent_process_new (const gchar *path)
+{
+       g_return_val_if_fail (path, NULL);
+
+       return g_object_new (GCR_TYPE_SSH_AGENT_PROCESS, "path", path, NULL);
+}
+
+GPid
+gcr_ssh_agent_process_get_pid (GcrSshAgentProcess *self)
+{
+       return self->pid;
+}
diff --git a/gcr/gcr-ssh-agent-process.h b/gcr/gcr-ssh-agent-process.h
new file mode 100644
index 0000000..590fae2
--- /dev/null
+++ b/gcr/gcr-ssh-agent-process.h
@@ -0,0 +1,40 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2014 Stef Walter
+ * Copyright (C) 2018 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/>.
+ *
+ * Author: Stef Walter <stef thewalter net>, Daiki Ueno
+ */
+
+#ifndef GCR_SSH_AGENT_PROCESS_H
+#define GCR_SSH_AGENT_PROCESS_H
+
+#include <gio/gio.h>
+
+#include "egg/egg-buffer.h"
+
+#define GCR_TYPE_SSH_AGENT_PROCESS gcr_ssh_agent_process_get_type ()
+G_DECLARE_FINAL_TYPE(GcrSshAgentProcess, gcr_ssh_agent_process, GCR, SSH_AGENT_PROCESS, GObject)
+
+GcrSshAgentProcess *gcr_ssh_agent_process_new         (const gchar *path);
+GSocketConnection  *gcr_ssh_agent_process_connect     (GcrSshAgentProcess *self,
+                                                       GCancellable *cancellable,
+                                                       GError **error);
+GPid                gcr_ssh_agent_process_get_pid     (GcrSshAgentProcess *self);
+
+#endif /* GCR_SSH_AGENT_PROCESS_H */
diff --git a/gcr/gcr-ssh-agent-service.c b/gcr/gcr-ssh-agent-service.c
new file mode 100644
index 0000000..f343400
--- /dev/null
+++ b/gcr/gcr-ssh-agent-service.c
@@ -0,0 +1,662 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2007 Stefan Walter
+ * Copyright (C) 2018 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/>.
+ *
+ * Author: Stef Walter <stef thewalter net>, Daiki Ueno
+ */
+
+#include "config.h"
+
+#include "gcr-ssh-agent-service.h"
+
+#include "gcr-ssh-agent-interaction.h"
+#include "gcr-ssh-agent-preload.h"
+#include "gcr-ssh-agent-private.h"
+#include "gcr-ssh-agent-process.h"
+#include "gcr-ssh-agent-util.h"
+
+#include "egg/egg-buffer.h"
+#include "egg/egg-error.h"
+#include "egg/egg-secure-memory.h"
+
+#include <gio/gunixsocketaddress.h>
+#include <glib/gstdio.h>
+#include <gcr/gcr-base.h>
+
+#include <glib/gi18n-lib.h>
+
+EGG_SECURE_DECLARE (ssh_agent);
+
+typedef gboolean (*GcrSshAgentOperation) (GcrSshAgentService *agent, GSocketConnection *connection, 
EggBuffer *req, EggBuffer *resp, GCancellable *cancellable, GError **error);
+static const GcrSshAgentOperation operations[GCR_SSH_OP_MAX];
+
+enum {
+       PROP_0,
+       PROP_PATH,
+       PROP_PRELOAD
+};
+
+struct _GcrSshAgentService
+{
+       GObject object;
+       gchar *path;
+       GcrSshAgentPreload *preload;
+       GcrSshAgentProcess *process;
+       GSocketAddress *address;
+       GSocketListener *listener;
+       GHashTable *keys;
+       GMutex lock;
+       GCancellable *cancellable;
+};
+
+G_DEFINE_TYPE (GcrSshAgentService, gcr_ssh_agent_service, G_TYPE_OBJECT);
+
+static void
+gcr_ssh_agent_service_init (GcrSshAgentService *self)
+{
+       self->keys = g_hash_table_new_full (g_bytes_hash, g_bytes_equal,
+                                           (GDestroyNotify)g_bytes_unref, NULL);
+       g_mutex_init (&self->lock);
+}
+
+static void
+gcr_ssh_agent_service_constructed (GObject *object)
+{
+       GcrSshAgentService *self = GCR_SSH_AGENT_SERVICE (object);
+       gchar *path;
+
+       path = g_strdup_printf ("%s/.ssh", self->path);
+       self->process = gcr_ssh_agent_process_new (path);
+       g_free (path);
+
+       self->listener = G_SOCKET_LISTENER (g_threaded_socket_service_new (-1));
+       self->cancellable = g_cancellable_new ();
+
+       G_OBJECT_CLASS (gcr_ssh_agent_service_parent_class)->constructed (object);
+}
+
+static void
+gcr_ssh_agent_service_set_property (GObject *object,
+                            guint prop_id,
+                            const GValue *value,
+                            GParamSpec *pspec)
+{
+       GcrSshAgentService *self = GCR_SSH_AGENT_SERVICE (object);
+
+       switch (prop_id) {
+       case PROP_PATH:
+               self->path = g_value_dup_string (value);
+               break;
+       case PROP_PRELOAD:
+               self->preload = g_value_dup_object (value);
+               break;
+       default:
+               G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+               break;
+       }
+}
+
+static void
+gcr_ssh_agent_service_finalize (GObject *object)
+{
+       GcrSshAgentService *self = GCR_SSH_AGENT_SERVICE (object);
+
+       g_free (self->path);
+       g_object_unref (self->preload);
+
+       g_object_unref (self->process);
+       g_object_unref (self->listener);
+       g_clear_object (&self->address);
+       g_mutex_clear (&self->lock);
+       g_hash_table_unref (self->keys);
+       g_object_unref (self->cancellable);
+
+       G_OBJECT_CLASS (gcr_ssh_agent_service_parent_class)->finalize (object);
+}
+
+static void
+gcr_ssh_agent_service_class_init (GcrSshAgentServiceClass *klass)
+{
+       GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+       gobject_class->constructed = gcr_ssh_agent_service_constructed;
+       gobject_class->set_property = gcr_ssh_agent_service_set_property;
+       gobject_class->finalize = gcr_ssh_agent_service_finalize;
+       g_object_class_install_property (gobject_class, PROP_PATH,
+                g_param_spec_string ("path", "Path", "Path",
+                                     "",
+                                     G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
+       g_object_class_install_property (gobject_class, PROP_PRELOAD,
+                g_param_spec_object ("preload", "Preload", "Preload",
+                                     GCR_TYPE_SSH_AGENT_PRELOAD,
+                                     G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
+}
+
+static gboolean
+relay_request (GcrSshAgentService *self,
+              GSocketConnection *connection,
+              EggBuffer *req,
+              EggBuffer *resp,
+              GCancellable *cancellable,
+              GError **error)
+{
+       return _gcr_ssh_agent_call (connection, req, resp, cancellable, error);
+}
+
+static gboolean
+handle_request (GcrSshAgentService *self,
+               GSocketConnection *connection,
+               EggBuffer *req,
+               EggBuffer *resp,
+               GCancellable *cancellable,
+               GError **error)
+{
+       GcrSshAgentOperation func;
+       guchar op;
+
+       egg_buffer_reset (resp);
+       egg_buffer_add_uint32 (resp, 0);
+
+       /* Decode the operation; on failure, just pass through */
+       if (egg_buffer_get_byte (req, 4, NULL, &op) &&
+           op <= GCR_SSH_OP_MAX && operations[op] != NULL)
+               func = operations[op];
+       else
+               func = relay_request;
+
+       return func (self, connection, req, resp, cancellable, error);
+}
+
+static void
+add_key (GcrSshAgentService *self,
+        GBytes *key)
+{
+       g_mutex_lock (&self->lock);
+       g_hash_table_add (self->keys, g_bytes_ref (key));
+       g_mutex_unlock (&self->lock);
+}
+
+static void
+remove_key (GcrSshAgentService *self,
+           GBytes *key)
+{
+       g_mutex_lock (&self->lock);
+       g_hash_table_remove (self->keys, key);
+       g_mutex_unlock (&self->lock);
+}
+
+static void
+clear_keys (GcrSshAgentService *self)
+{
+       g_mutex_lock (&self->lock);
+       g_hash_table_remove_all (self->keys);
+       g_mutex_unlock (&self->lock);
+}
+
+static void
+ensure_key (GcrSshAgentService *self,
+           GBytes *key)
+{
+       GcrSshAskpass *askpass;
+       GError *error = NULL;
+       gint status;
+       GcrSshAgentKeyInfo *info;
+       gchar *unique;
+       const gchar *label;
+       GHashTable *fields;
+       GTlsInteraction *interaction;
+       gchar *standard_error;
+
+       gchar *argv[] = {
+               SSH_ADD_EXECUTABLE,
+               NULL,
+               NULL
+       };
+
+       if (gcr_ssh_agent_service_lookup_key (self, key))
+               return;
+
+       info = gcr_ssh_agent_preload_lookup_by_public_key (self->preload, key);
+       if (!info)
+               return;
+
+       argv[1] = info->filename;
+
+       fields = g_hash_table_new (g_str_hash, g_str_equal);
+       unique = g_strdup_printf ("ssh-store:%s", info->filename);
+       g_hash_table_insert (fields, "unique", unique);
+
+       label = info->comment[0] != '\0' ? info->comment : _("Unnamed");
+
+       interaction = gcr_ssh_agent_interaction_new (NULL, label, fields);
+       askpass = gcr_ssh_askpass_new (interaction);
+       g_object_unref (interaction);
+
+       if (!g_spawn_sync (NULL, argv, NULL,
+                          G_SPAWN_STDOUT_TO_DEV_NULL,
+                          gcr_ssh_askpass_child_setup, askpass,
+                          NULL, &standard_error, &status, &error)) {
+               g_warning ("couldn't run %s: %s", argv[0], error->message);
+               g_error_free (error);
+       } else if (!g_spawn_check_exit_status (status, &error)) {
+               g_message ("the %s command failed: %s", argv[0], error->message);
+               g_printerr ("%s", _gcr_ssh_agent_canon_error (standard_error));
+               g_free (standard_error);
+       } else {
+               add_key (self, key);
+       }
+
+       g_hash_table_unref (fields);
+       g_free (unique);
+       gcr_ssh_agent_key_info_free (info);
+       g_object_unref (askpass);
+}
+
+static gboolean
+on_run (GThreadedSocketService *service,
+       GSocketConnection *connection,
+       GObject *source_object,
+       gpointer user_data)
+{
+       GcrSshAgentService *self = g_object_ref (GCR_SSH_AGENT_SERVICE (user_data));
+       EggBuffer req;
+       EggBuffer resp;
+       GError *error;
+       GSocketConnection *agent_connection;
+       gboolean ret;
+
+       egg_buffer_init_full (&req, 128, egg_secure_realloc);
+       egg_buffer_init_full (&resp, 128, (EggBufferAllocator)g_realloc);
+
+       error = NULL;
+       agent_connection = gcr_ssh_agent_process_connect (self->process, self->cancellable, &error);
+       if (!agent_connection) {
+               g_warning ("couldn't connect to ssh-agent: %s", error->message);
+               g_error_free (error);
+               goto out;
+       }
+
+       while (TRUE) {
+               /* Read in the request */
+               error = NULL;
+               if (!_gcr_ssh_agent_read_packet (connection, &req, self->cancellable, &error)) {
+                       if (error->code != G_IO_ERROR_CANCELLED &&
+                           error->code != G_IO_ERROR_CONNECTION_CLOSED)
+                               g_message ("couldn't read from client: %s", error->message);
+                       g_error_free (error);
+                       break;
+               }
+
+               /* Handle the request */
+               error = NULL;
+               while (!(ret = handle_request (self, agent_connection, &req, &resp, self->cancellable, 
&error))) {
+                       if (gcr_ssh_agent_process_get_pid (self->process) != 0) {
+                               if (error->code != G_IO_ERROR_CANCELLED)
+                                       g_message ("couldn't handle client request: %s", error->message);
+                               g_error_free (error);
+                               goto out;
+                       }
+
+                       /* Reconnect to the ssh-agent */
+                       g_clear_object (&agent_connection);
+                       g_clear_error (&error);
+                       agent_connection = gcr_ssh_agent_process_connect (self->process, self->cancellable, 
&error);
+                       if (!agent_connection) {
+                               if (error->code != G_IO_ERROR_CANCELLED)
+                                       g_message ("couldn't connect to ssh-agent: %s", error->message);
+                               g_error_free (error);
+                               goto out;
+                       }
+               }
+
+               /* Write the reply back out */
+               error = NULL;
+               if (!_gcr_ssh_agent_write_packet (connection, &resp, self->cancellable, &error)) {
+                       if (error->code != G_IO_ERROR_CANCELLED)
+                               g_message ("couldn't write to client: %s", error->message);
+                       g_error_free (error);
+                       break;
+               }
+       }
+
+ out:
+       egg_buffer_uninit (&req);
+       egg_buffer_uninit (&resp);
+
+       g_object_unref (agent_connection);
+       g_object_unref (self);
+
+       return TRUE;
+}
+
+static void
+on_closed (GcrSshAgentProcess *process,
+          gpointer user_data)
+{
+       GcrSshAgentService *self = GCR_SSH_AGENT_SERVICE (user_data);
+       clear_keys (self);
+}
+
+gboolean
+gcr_ssh_agent_service_start (GcrSshAgentService *self)
+{
+       gchar *path;
+       GError *error;
+
+       path = g_strdup_printf ("%s/ssh", self->path);
+       g_unlink (path);
+       self->address = g_unix_socket_address_new (path);
+       g_free (path);
+
+       error = NULL;
+       if (!g_socket_listener_add_address (self->listener,
+                                           self->address,
+                                           G_SOCKET_TYPE_STREAM,
+                                           G_SOCKET_PROTOCOL_DEFAULT,
+                                           NULL,
+                                           NULL,
+                                           &error)) {
+               g_warning ("couldn't listen on %s: %s",
+                          g_unix_socket_address_get_path (G_UNIX_SOCKET_ADDRESS (self->address)),
+                          error->message);
+               g_error_free (error);
+               return FALSE;
+       }
+
+       g_signal_connect (self->listener, "run", G_CALLBACK (on_run), self);
+       g_signal_connect (self->process, "closed", G_CALLBACK (on_closed), self);
+
+       g_setenv ("SSH_AUTH_SOCK", g_unix_socket_address_get_path (G_UNIX_SOCKET_ADDRESS (self->address)), 
TRUE);
+
+       g_socket_service_start (G_SOCKET_SERVICE (self->listener));
+
+       return TRUE;
+}
+
+void
+gcr_ssh_agent_service_stop (GcrSshAgentService *self)
+{
+       if (self->address)
+               g_unlink (g_unix_socket_address_get_path (G_UNIX_SOCKET_ADDRESS (self->address)));
+
+       g_cancellable_cancel (self->cancellable);
+       g_socket_service_stop (G_SOCKET_SERVICE (self->listener));
+}
+
+GcrSshAgentService *
+gcr_ssh_agent_service_new (const gchar *path,
+                          GcrSshAgentPreload *preload)
+{
+       g_return_val_if_fail (path, NULL);
+       g_return_val_if_fail (preload, NULL);
+
+       return g_object_new (GCR_TYPE_SSH_AGENT_SERVICE,
+                            "path", path,
+                            "preload", preload,
+                            NULL);
+}
+
+GcrSshAgentPreload *
+gcr_ssh_agent_service_get_preload (GcrSshAgentService *self)
+{
+       return self->preload;
+}
+
+GcrSshAgentProcess *
+gcr_ssh_agent_service_get_process (GcrSshAgentService *self)
+{
+       return self->process;
+}
+
+gboolean
+gcr_ssh_agent_service_lookup_key (GcrSshAgentService *self,
+                         GBytes *key)
+{
+       gboolean ret;
+       g_mutex_lock (&self->lock);
+       ret = g_hash_table_contains (self->keys, key);
+       g_mutex_unlock (&self->lock);
+       return ret;
+}
+
+/* ---------------------------------------------------------------------------- */
+
+static gboolean
+op_add_identity (GcrSshAgentService *self,
+                GSocketConnection *connection,
+                EggBuffer *req,
+                EggBuffer *resp,
+                GCancellable *cancellable,
+                GError **error)
+{
+       const guchar *blob;
+       gsize offset = 5;
+       gsize length;
+       GBytes *key = NULL;
+       gboolean ret;
+
+       /* If parsing the request fails, just pass through */
+       ret = egg_buffer_get_byte_array (req, offset, &offset, &blob, &length);
+       if (ret)
+               key = g_bytes_new (blob, length);
+       else
+               g_message ("got unparseable add identity request for ssh-agent");
+
+       ret = relay_request (self, connection, req, resp, cancellable, error);
+       if (key) {
+               if (ret)
+                       add_key (self, key);
+               g_bytes_unref (key);
+       }
+
+       return ret;
+}
+
+static GHashTable *
+parse_identities_answer (EggBuffer *resp)
+{
+       GHashTable *answer;
+       const guchar *blob;
+       gchar *comment;
+       gsize length;
+       gsize offset = 4;
+       guint32 count;
+       guchar op;
+       guint32 i;
+
+       if (!egg_buffer_get_byte (resp, offset, &offset, &op) ||
+           op != GCR_SSH_RES_IDENTITIES_ANSWER ||
+           !egg_buffer_get_uint32 (resp, offset, &offset, &count)) {
+               g_message ("got unexpected response back from ssh-agent when requesting identities");
+               return NULL;
+       }
+
+       answer = g_hash_table_new_full (g_bytes_hash, g_bytes_equal, (GDestroyNotify)g_bytes_unref, g_free);
+
+       for (i = 0; i < count; i++) {
+               if (!egg_buffer_get_byte_array (resp, offset, &offset, &blob, &length) ||
+                   !egg_buffer_get_string (resp, offset, &offset, &comment, g_realloc)) {
+                       g_message ("got unparseable response back from ssh-agent when requesting identities");
+                       g_hash_table_unref (answer);
+                       return NULL;
+               }
+               g_hash_table_insert (answer, g_bytes_new (blob, length), comment);
+       }
+
+       return answer;
+}
+
+
+static gboolean
+op_request_identities (GcrSshAgentService *self,
+                      GSocketConnection *connection,
+                      EggBuffer *req,
+                      EggBuffer *resp,
+                      GCancellable *cancellable,
+                      GError **error)
+{
+       GHashTable *answer;
+       GHashTableIter iter;
+       gsize length;
+       guint32 added;
+       GBytes *key;
+       GList *keys;
+       GList *l;
+       GcrSshAgentPreload *preload;
+
+       if (!relay_request (self, connection, req, resp, cancellable, error))
+               return FALSE;
+
+       /* Parse all the keys, and if it fails, just fall through */
+       answer = parse_identities_answer (resp);
+       if (!answer)
+               return TRUE;
+
+       g_hash_table_iter_init (&iter, answer);
+       while (g_hash_table_iter_next (&iter, (gpointer *)&key, NULL))
+               add_key (self, key);
+
+       added = 0;
+
+       /* Add any preloaded keys not already in answer */
+       preload = gcr_ssh_agent_service_get_preload (self);
+       keys = gcr_ssh_agent_preload_get_keys (preload);
+       for (l = keys; l != NULL; l = g_list_next (l)) {
+               GcrSshAgentKeyInfo *info = l->data;
+               if (!g_hash_table_contains (answer, info->public_key)) {
+                       const guchar *blob = g_bytes_get_data (info->public_key, &length);
+                       egg_buffer_add_byte_array (resp, blob, length);
+                       egg_buffer_add_string (resp, info->comment);
+                       added++;
+               }
+       }
+
+       g_list_free_full (keys, (GDestroyNotify)gcr_ssh_agent_key_info_free);
+
+       /* Set the correct amount of keys including the ones we added */
+       egg_buffer_set_uint32 (resp, 5, added + g_hash_table_size (answer));
+       g_hash_table_unref (answer);
+
+       /* Set the correct total size of the payload */
+       egg_buffer_set_uint32 (resp, 0, resp->len - 4);
+
+       return TRUE;
+}
+
+static gboolean
+op_sign_request (GcrSshAgentService *self,
+                GSocketConnection *connection,
+                EggBuffer *req,
+                EggBuffer *resp,
+                GCancellable *cancellable,
+                GError **error)
+{
+       const guchar *blob;
+       gsize length;
+       gsize offset = 5;
+       GBytes *key;
+
+       /* If parsing the request fails, just pass through */
+       if (egg_buffer_get_byte_array (req, offset, &offset, &blob, &length)) {
+               key = g_bytes_new (blob, length);
+               ensure_key (self, key);
+               g_bytes_unref (key);
+       } else {
+               g_message ("got unparseable sign request for ssh-agent");
+       }
+
+       return relay_request (self, connection, req, resp, cancellable, error);
+}
+
+static gboolean
+op_remove_identity (GcrSshAgentService *self,
+                   GSocketConnection *connection,
+                   EggBuffer *req,
+                   EggBuffer *resp,
+                   GCancellable *cancellable,
+                   GError **error)
+{
+       const guchar *blob;
+       gsize length;
+       gsize offset = 5;
+       GBytes *key = NULL;
+       gboolean ret;
+
+       /* If parsing the request fails, just pass through */
+       ret = egg_buffer_get_byte_array (req, offset, &offset, &blob, &length);
+       if (ret)
+               key = g_bytes_new (blob, length);
+       else
+               g_message ("got unparseable remove request for ssh-agent");
+
+       /* Call out ssh-agent anyway to make sure that the key is removed */
+       ret = relay_request (self, connection, req, resp, cancellable, error);
+       if (key) {
+               if (ret)
+                       remove_key (self, key);
+               g_bytes_unref (key);
+       }
+       return ret;
+}
+
+static gboolean
+op_remove_all_identities (GcrSshAgentService *self,
+                         GSocketConnection *connection,
+                         EggBuffer *req,
+                         EggBuffer *resp,
+                         GCancellable *cancellable,
+                         GError **error)
+{
+       gboolean ret;
+
+       ret = relay_request (self, connection, req, resp, cancellable, error);
+       if (ret)
+               clear_keys (self);
+
+       return ret;
+}
+
+static const GcrSshAgentOperation operations[GCR_SSH_OP_MAX] = {
+       NULL,                                 /* 0 */
+       NULL,                                 /* GKR_SSH_OP_REQUEST_RSA_IDENTITIES */
+       NULL,                                 /* 2 */
+       NULL,                                 /* GKR_SSH_OP_RSA_CHALLENGE */
+       NULL,                                 /* 4 */
+       NULL,                                 /* 5 */
+       NULL,                                 /* 6 */
+       NULL,                                 /* GKR_SSH_OP_ADD_RSA_IDENTITY */
+       NULL,                                 /* GKR_SSH_OP_REMOVE_RSA_IDENTITY */
+       NULL,                                 /* GKR_SSH_OP_REMOVE_ALL_RSA_IDENTITIES */
+       NULL,                                 /* 10 */
+       op_request_identities,                /* GKR_SSH_OP_REQUEST_IDENTITIES */
+       NULL,                                 /* 12 */
+       op_sign_request,                      /* GKR_SSH_OP_SIGN_REQUEST */
+       NULL,                                 /* 14 */
+       NULL,                                 /* 15 */
+       NULL,                                 /* 16 */
+       op_add_identity,                      /* GKR_SSH_OP_ADD_IDENTITY */
+       op_remove_identity,                   /* GKR_SSH_OP_REMOVE_IDENTITY */
+       op_remove_all_identities,             /* GKR_SSH_OP_REMOVE_ALL_IDENTITIES */
+       NULL,                                 /* GKR_SSH_OP_ADD_SMARTCARD_KEY */
+       NULL,                                 /* GKR_SSH_OP_REMOVE_SMARTCARD_KEY */
+       NULL,                                 /* GKR_SSH_OP_LOCK */
+       NULL,                                 /* GKR_SSH_OP_UNLOCK */
+       NULL,                                 /* GKR_SSH_OP_ADD_RSA_ID_CONSTRAINED */
+       op_add_identity,                      /* GKR_SSH_OP_ADD_ID_CONSTRAINED */
+       NULL,                                 /* GKR_SSH_OP_ADD_SMARTCARD_KEY_CONSTRAINED */
+};
diff --git a/gcr/gcr-ssh-agent-service.h b/gcr/gcr-ssh-agent-service.h
new file mode 100644
index 0000000..0949d56
--- /dev/null
+++ b/gcr/gcr-ssh-agent-service.h
@@ -0,0 +1,53 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2007 Stefan Walter
+ * Copyright (C) 2018 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/>.
+ *
+ * Author: Stef Walter <stef thewalter net>, Daiki Ueno
+ */
+
+#ifndef GCR_SSH_AGENT_SERVICE_H
+#define GCR_SSH_AGENT_SERVICE_H
+
+#include <gio/gio.h>
+#include "gcr-ssh-agent-preload.h"
+#include "gcr-ssh-agent-process.h"
+#include "egg/egg-buffer.h"
+
+#define GCR_TYPE_SSH_AGENT_SERVICE gcr_ssh_agent_service_get_type ()
+G_DECLARE_FINAL_TYPE (GcrSshAgentService, gcr_ssh_agent_service, GCR, SSH_AGENT_SERVICE, GObject);
+
+GcrSshAgentService *gcr_ssh_agent_service_new  (const gchar        *path,
+                                                GcrSshAgentPreload *preload);
+
+gboolean            gcr_ssh_agent_service_start
+                                               (GcrSshAgentService *self);
+
+void                gcr_ssh_agent_service_stop (GcrSshAgentService *self);
+
+GcrSshAgentPreload *gcr_ssh_agent_service_get_preload
+                                               (GcrSshAgentService *self);
+
+GcrSshAgentProcess *gcr_ssh_agent_service_get_process
+                                               (GcrSshAgentService *self);
+
+gboolean            gcr_ssh_agent_service_lookup_key
+                                               (GcrSshAgentService *self,
+                                                GBytes             *key);
+
+#endif /* GCR_SSH_AGENT_SERVICE_H */
diff --git a/gcr/gcr-ssh-agent-util.c b/gcr/gcr-ssh-agent-util.c
new file mode 100644
index 0000000..0ac91eb
--- /dev/null
+++ b/gcr/gcr-ssh-agent-util.c
@@ -0,0 +1,220 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2014 Stef Walter
+ * Copyright (C) 2018 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/>.
+ *
+ * Author: Stef Walter <stef thewalter net>, Daiki Ueno
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include "gcr-ssh-agent-util.h"
+
+gboolean
+_gcr_ssh_agent_read_packet (GSocketConnection *connection,
+                           EggBuffer *buffer,
+                           GCancellable *cancellable,
+                           GError **error)
+{
+       GInputStream *stream;
+       guint32 packet_size;
+       gsize bytes_read;
+
+       stream = g_io_stream_get_input_stream (G_IO_STREAM (connection));
+
+       egg_buffer_reset (buffer);
+       egg_buffer_resize (buffer, 4);
+
+       if (!g_input_stream_read_all (stream, buffer->buf, 4, &bytes_read, cancellable, error))
+               return FALSE;
+
+       if (bytes_read < 4) {
+               g_set_error (error, G_IO_ERROR, G_IO_ERROR_CONNECTION_CLOSED,
+                            "connection closed by peer");
+               return FALSE;
+       }
+
+       if (!egg_buffer_get_uint32 (buffer, 0, NULL, &packet_size) ||
+           packet_size < 1) {
+               g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                            "invalid packet size %u",
+                            packet_size);
+               return FALSE;
+       }
+
+       egg_buffer_resize (buffer, packet_size + 4);
+       if (!g_input_stream_read_all (stream, buffer->buf + 4, packet_size, &bytes_read, cancellable, error))
+               return FALSE;
+
+       return TRUE;
+}
+
+gboolean
+_gcr_ssh_agent_write_packet (GSocketConnection *connection,
+                            EggBuffer *buffer,
+                            GCancellable *cancellable,
+                            GError **error)
+{
+       GOutputStream *stream;
+       gsize bytes_written;
+
+       stream = g_io_stream_get_output_stream (G_IO_STREAM (connection));
+       if (!egg_buffer_set_uint32 (buffer, 0, buffer->len - 4)) {
+               g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+                            "cannot read packet length");
+               return FALSE;
+       }
+       return g_output_stream_write_all (stream, buffer->buf, buffer->len, &bytes_written, cancellable, 
error);
+}
+
+gboolean
+_gcr_ssh_agent_call (GSocketConnection *connection,
+                    EggBuffer*req,
+                    EggBuffer *resp,
+                    GCancellable *cancellable,
+                    GError **error)
+{
+       return _gcr_ssh_agent_write_packet (connection, req, cancellable, error) &&
+               _gcr_ssh_agent_read_packet (connection, resp, cancellable, error);
+}
+
+GBytes *
+_gcr_ssh_agent_parse_public_key (GBytes *input,
+                                gchar **comment)
+{
+       const guchar *at;
+       guchar *decoded;
+       gsize n_decoded;
+       gint state;
+       guint save;
+       const guchar *data;
+       gsize n_data;
+       const guchar *keytype;
+       gsize n_keytype;
+
+       g_return_val_if_fail (input, NULL);
+
+       data = g_bytes_get_data (input, &n_data);
+
+       /* Look for a key line */
+       for (;;) {
+               /* Eat space at the front */
+               while (n_data > 0 && g_ascii_isspace (data[0])) {
+                       ++data;
+                       --n_data;
+               }
+
+               /* Not a comment or blank line? Then parse... */
+               if (data[0] != '#')
+                       break;
+
+               /* Skip to the next line */
+               at = memchr (data, '\n', n_data);
+               if (!at)
+                       return NULL;
+               at += 1;
+               n_data -= (at - data);
+               data = at;
+       }
+
+       /* Limit to use only the first line */
+       at = memchr (data, '\n', n_data);
+       if (at != NULL)
+               n_data = at - data;
+
+       keytype = data;
+
+       /* Find the first space */
+       at = memchr (data, ' ', n_data);
+       if (!at) {
+               g_message ("SSH public key missing space");
+               return NULL;
+       }
+
+       n_keytype = at - data;
+
+       /* Skip more whitespace */
+       n_data -= (at - data);
+       data = at;
+       while (n_data > 0 && (data[0] == ' ' || data[0] == '\t')) {
+               ++data;
+               --n_data;
+       }
+
+       /* Find the next whitespace, or the end */
+       at = memchr (data, ' ', n_data);
+       if (at == NULL)
+               at = data + n_data;
+
+       /* Check if the chunk is the base64 key */
+       if ((at - data) % 4 != 0) {
+               g_message ("SSH public key missing key data");
+               return NULL;
+       }
+
+       /* Decode the base64 key */
+       save = state = 0;
+       decoded = g_malloc (n_data * 3 / 4);
+       n_decoded = g_base64_decode_step ((gchar*)data, at - data, decoded, &state, &save);
+
+       if (!n_decoded) {
+               g_free (decoded);
+               return NULL;
+       }
+
+       /* Check if the key type is prefixed to the decoded blob */
+       if (!(n_decoded > n_keytype + 4 &&
+             egg_buffer_decode_uint32 (decoded) == n_keytype &&
+             memcmp (keytype, decoded + 4, n_keytype) == 0)) {
+               g_message ("SSH public key missing key type");
+               g_free (decoded);
+               return NULL;
+       }
+
+       /* Skip more whitespace */
+       n_data -= (at - data);
+       data = at;
+       while (n_data > 0 && (data[0] == ' ' || data[0] == '\t')) {
+               ++data;
+               --n_data;
+       }
+
+       /* If there's data left, its the comment */
+       if (comment)
+               *comment = n_data ? g_strndup ((gchar*)data, n_data) : g_strdup ("");
+
+       return g_bytes_new_take (decoded, n_decoded);
+}
+
+gchar *
+_gcr_ssh_agent_canon_error (gchar *str)
+{
+       gchar *start = str;
+       gchar *end = str + strlen (str) + 1;
+
+       for (;;) {
+               start = strchr (start, '\r');
+               if (!start)
+                       break;
+               memmove (start, start + 1, end - (start + 1));
+       }
+
+       return str;
+}
diff --git a/gcr/gcr-ssh-agent-util.h b/gcr/gcr-ssh-agent-util.h
new file mode 100644
index 0000000..84393c5
--- /dev/null
+++ b/gcr/gcr-ssh-agent-util.h
@@ -0,0 +1,50 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2018 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/>.
+ *
+ * Author: Daiki Ueno
+ */
+
+#include <gio/gio.h>
+#include "egg/egg-buffer.h"
+
+#ifndef GCR_SSH_AGENT_UTIL_H
+#define GCR_SSH_AGENT_UTIL_H
+
+gboolean _gcr_ssh_agent_read_packet      (GSocketConnection  *connection,
+                                          EggBuffer          *buffer,
+                                          GCancellable       *cancellable,
+                                          GError            **error);
+
+gboolean _gcr_ssh_agent_write_packet     (GSocketConnection  *connection,
+                                          EggBuffer          *buffer,
+                                          GCancellable       *cancellable,
+                                          GError            **error);
+
+gboolean _gcr_ssh_agent_call             (GSocketConnection  *connection,
+                                          EggBuffer          *req,
+                                          EggBuffer          *resp,
+                                          GCancellable       *cancellable,
+                                          GError            **error);
+
+GBytes  *_gcr_ssh_agent_parse_public_key (GBytes             *input,
+                                          gchar             **comment);
+
+gchar   *_gcr_ssh_agent_canon_error      (gchar *str);
+
+#endif /* GCR_SSH_AGENT_UTIL_H */
diff --git a/gcr/gcr-ssh-agent.c b/gcr/gcr-ssh-agent.c
new file mode 100644
index 0000000..1c0fe77
--- /dev/null
+++ b/gcr/gcr-ssh-agent.c
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2021 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/>.
+ *
+ * Auther: Daiki Ueno
+ */
+
+#include "gcr-ssh-agent-preload.h"
+#include "gcr-ssh-agent-service.h"
+
+int
+main (int argc,
+      char **argv)
+{
+       const gchar *base_dir;
+       GcrSshAgentPreload *preload;
+       GcrSshAgentService *service;
+       GMainLoop *loop;
+
+       if (argc != 2) {
+               g_printerr ("Usage: gcr-ssh-agent <base-dir>");
+               return EXIT_FAILURE;
+       }
+
+       base_dir = argv[1];
+       if (!g_path_is_absolute (base_dir)) {
+               g_printerr ("gcr-ssh-agent: base-dir must be specified as an absolute path");
+               return EXIT_FAILURE;
+       }
+
+       preload = gcr_ssh_agent_preload_new ("~/.ssh");
+       service = gcr_ssh_agent_service_new (base_dir, preload);
+       g_object_unref (preload);
+
+       if (!gcr_ssh_agent_service_start (service))
+               return EXIT_FAILURE;
+
+       loop = g_main_loop_new (NULL, FALSE);
+       g_main_loop_run (loop);
+       g_main_loop_unref (loop);
+
+       return EXIT_SUCCESS;
+}
diff --git a/gcr/meson.build b/gcr/meson.build
index 06c3a63..b6bcd0a 100644
--- a/gcr/meson.build
+++ b/gcr/meson.build
@@ -238,6 +238,29 @@ gcr_ssh_askpass = executable('gcr-ssh-askpass',
   install_dir: get_option('libexecdir'),
 )
 
+if get_option('ssh_agent')
+  # gcr-ssh-agent binary
+  gcr_ssh_agent_sources = [
+    'gcr-ssh-agent.c',
+    'gcr-ssh-agent-interaction.c',
+    'gcr-ssh-agent-preload.c',
+    'gcr-ssh-agent-process.c',
+    'gcr-ssh-agent-service.c',
+    'gcr-ssh-agent-util.c',
+  ]
+  gcr_ssh_agent = executable('gcr-ssh-agent',
+    gcr_ssh_agent_sources,
+    dependencies: [ gcr_base_deps, gcr_base_dep, libsecret_dep ],
+    c_args: [
+      '-DGCR_COMPILATION',
+      '-DGCR_API_SUBJECT_TO_CHANGE',
+    ],
+    include_directories: config_h_dir,
+    install: true,
+    install_dir: get_option('libexecdir'),
+  )
+endif
+
 # Services
 gcr_service_files = [
   'org.gnome.keyring.SystemPrompter.service',
diff --git a/meson.build b/meson.build
index dffd3dd..98b8f35 100644
--- a/meson.build
+++ b/meson.build
@@ -51,6 +51,9 @@ p11_system_config_modules = p11kit_dep.get_pkgconfig_variable('p11_system_config
 if p11_system_config_modules == ''
   error('Couldn\'t find location for pkcs11 module config')
 endif
+libsecret_dep = dependency('libsecret-1', version: '>= 0.20', required: get_option('ssh_agent'))
+ssh_add_path = find_program('ssh-add', required: get_option('ssh_agent')).path()
+ssh_agent_path = find_program('ssh-agent', required: get_option('ssh_agent')).path()
 
 if get_option('gtk')
   gtk_min_version = '3.22'
@@ -69,6 +72,8 @@ conf.set('HAVE_TIMEGM', cc.has_function('timegm'))
 conf.set('HAVE_MLOCK', cc.has_function('mlock'))
 conf.set_quoted('GPG_EXECUTABLE', gpg_path)
 conf.set_quoted('LIBGCRYPT_VERSION', libgcrypt_dep.version())
+conf.set_quoted('SSH_ADD_EXECUTABLE', ssh_add_path)
+conf.set_quoted('SSH_AGENT_EXECUTABLE', ssh_agent_path)
 config_file = configure_file(
   output: 'config.h',
   configuration: conf,
diff --git a/meson_options.txt b/meson_options.txt
index ae0f524..17683d9 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -18,3 +18,8 @@ option('gpg_path',
   value: '',
   description: 'Path to gpg, autodetected if not set',
 )
+option('ssh_agent',
+  type: 'boolean',
+  value: true,
+  description: 'Build ssh-agent binary',
+)


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]