[gnome-keyring/wip/dueno/ssh-agent-2: 7/7] ssh-agent: Use stock ssh-agent



commit c21dd0c27a58caa2b1e1654b0ddfc1734ed85bdd
Author: Stef Walter <stefw gnome org>
Date:   Fri Feb 16 16:26:35 2018 +0100

    ssh-agent: Use stock ssh-agent
    
    This patch removes our own implementation of ssh-agent and switches to
    using the ssh-agent program provided by OpenSSH.  We can't simply drop
    the ssh-agent functionality from gnome-keyring, as it enables the
    following:
    
     * Automatic loading and unlocking of keys
     * Prompting in the UI
    
    Instead we wrap the ssh-agent program as a subprocess and augment
    the protocol as we need.
    
    Signed-off-by: Stef Walter <stefw gnome org>
    Signed-off-by: Daiki Ueno <dueno src gnome org>

 configure.ac                                  |    7 +
 daemon/Makefile.am                            |    2 +-
 daemon/gkd-glue.c                             |   23 +-
 daemon/gkd-pkcs11.c                           |   47 +-
 daemon/gkd-pkcs11.h                           |    2 -
 daemon/ssh-agent/Makefile.am                  |   58 +-
 daemon/ssh-agent/gkd-ssh-agent-ops.c          | 1553 +++----------------------
 daemon/ssh-agent/gkd-ssh-agent-preload.c      |  277 +++++
 daemon/ssh-agent/gkd-ssh-agent-preload.h      |   50 +
 daemon/ssh-agent/gkd-ssh-agent-private.h      |  145 +---
 daemon/ssh-agent/gkd-ssh-agent-process.c      |  363 ++++++
 daemon/ssh-agent/gkd-ssh-agent-process.h      |   47 +
 daemon/ssh-agent/gkd-ssh-agent-proto.c        |  914 ---------------
 daemon/ssh-agent/gkd-ssh-agent-standalone.c   |  126 --
 daemon/ssh-agent/gkd-ssh-agent.c              |  648 ++++++-----
 daemon/ssh-agent/gkd-ssh-agent.h              |   49 +-
 daemon/ssh-agent/gkd-ssh-interaction.c        |  156 +++
 daemon/ssh-agent/gkd-ssh-interaction.h        |   39 +
 daemon/ssh-agent/gkd-ssh-openssh.c            |  112 ++
 daemon/ssh-agent/gkd-ssh-openssh.h            |   29 +
 daemon/ssh-agent/test-communication.c         |  449 -------
 daemon/ssh-agent/test-gkd-ssh-agent-common.h  |  293 +++++
 daemon/ssh-agent/test-gkd-ssh-agent-process.c |  232 ++++
 daemon/ssh-agent/test-gkd-ssh-agent.c         |  330 ++++++
 daemon/ssh-agent/test-gkd-ssh-openssh.c       |   86 ++
 daemon/ssh-agent/test-keytypes.c              |  197 ----
 po/POTFILES.in                                |    2 +
 27 files changed, 2616 insertions(+), 3620 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index a6e149c..fa8f143 100644
--- a/configure.ac
+++ b/configure.ac
@@ -348,6 +348,13 @@ AC_ARG_ENABLE([ssh-agent],
                [Don't include SSH agent in gnome-keyring]))
 
 if test "$enable_ssh_agent" != "no"; then
+       AC_PATH_PROG([SSH_AGENT], [ssh-agent], [no])
+       AC_PATH_PROG([SSH_ADD], [ssh-add], [no])
+       if test "$SSH_AGENT" = "no" -o "$SSH_ADD" = "no"; then
+               AC_MSG_ERROR([the ssh-agent and ssh-add commands were not found])
+       fi
+       AC_DEFINE_UNQUOTED(SSH_AGENT, "$SSH_AGENT", [The path to ssh-agent])
+       AC_DEFINE_UNQUOTED(SSH_ADD, "$SSH_ADD", [The path to ssh-add])
        AC_DEFINE(WITH_SSH, 1, [Whether to build SSH agent or not])
        ssh_status="yes"
 else
diff --git a/daemon/Makefile.am b/daemon/Makefile.am
index 3611d42..48c1652 100644
--- a/daemon/Makefile.am
+++ b/daemon/Makefile.am
@@ -16,9 +16,9 @@ gnome_keyring_daemon_SOURCES = \
        $(NULL)
 gnome_keyring_daemon_LDADD = \
        libgkd-dbus.la \
-       libgkd-login.la \
        libgkd-control.la \
        libgkd-ssh-agent.la \
+       libgkd-login.la \
        libgkm-wrap-layer.la \
        libgkm-rpc-layer.la \
        libgkm-secret-store.la \
diff --git a/daemon/gkd-glue.c b/daemon/gkd-glue.c
index 329a37e..816bbf2 100644
--- a/daemon/gkd-glue.c
+++ b/daemon/gkd-glue.c
@@ -24,20 +24,23 @@
 #include "gkd-util.h"
 
 #include "ssh-agent/gkd-ssh-agent.h"
+#include "ssh-agent/gkd-ssh-interaction.h"
 
 #include "egg/egg-cleanup.h"
 
+static GkdSshAgent *ssh_agent = NULL;
+
 static void
 pkcs11_ssh_cleanup (gpointer unused)
 {
-       gkd_ssh_agent_shutdown ();
+       g_clear_object (&ssh_agent);
 }
 
 static gboolean
 accept_ssh_client (GIOChannel *channel, GIOCondition cond, gpointer unused)
 {
        if (cond == G_IO_IN)
-               gkd_ssh_agent_accept ();
+               gkd_ssh_agent_accept (ssh_agent);
        return TRUE;
 }
 
@@ -46,15 +49,27 @@ gkd_daemon_startup_ssh (void)
 {
        GIOChannel *channel;
        const gchar *base_dir;
+       GTlsInteraction *interaction;
+       GkdSshAgentPreload *preload;
        int sock;
 
        base_dir = gkd_util_get_master_directory ();
        g_return_val_if_fail (base_dir, FALSE);
 
-       sock = gkd_ssh_agent_startup (base_dir);
-       if (sock == -1)
+       interaction = gkd_ssh_interaction_new ();
+       preload = gkd_ssh_agent_preload_new ("~/.ssh");
+
+       ssh_agent = gkd_ssh_agent_new (base_dir, interaction, preload);
+
+       g_object_unref (interaction);
+       g_object_unref (preload);
+
+       if (!gkd_ssh_agent_startup (ssh_agent))
                return FALSE;
 
+       sock = gkd_ssh_agent_get_fd (ssh_agent);
+       g_return_val_if_fail (sock != -1, FALSE);
+
        channel = g_io_channel_unix_new (sock);
        g_io_add_watch (channel, G_IO_IN | G_IO_HUP, accept_ssh_client, NULL);
        g_io_channel_unref (channel);
diff --git a/daemon/gkd-pkcs11.c b/daemon/gkd-pkcs11.c
index 71fdfe2..1e9db20 100644
--- a/daemon/gkd-pkcs11.c
+++ b/daemon/gkd-pkcs11.c
@@ -49,7 +49,6 @@ pkcs11_daemon_cleanup (gpointer unused)
 
        g_assert (pkcs11_roof);
 
-       gkd_ssh_agent_uninitialize ();
        gkm_rpc_layer_uninitialize ();
        rv = (pkcs11_roof->C_Finalize) (NULL);
 
@@ -67,7 +66,6 @@ gkd_pkcs11_initialize (void)
        CK_FUNCTION_LIST_PTR gnome2_store;
        CK_FUNCTION_LIST_PTR xdg_store;
        CK_C_INITIALIZE_ARGS init_args;
-       gboolean ret;
        CK_RV rv;
 
        /* Secrets */
@@ -113,10 +111,7 @@ gkd_pkcs11_initialize (void)
 
        egg_cleanup_register (pkcs11_daemon_cleanup, NULL);
 
-       ret = gkd_ssh_agent_initialize (pkcs11_roof) &&
-             gkm_rpc_layer_initialize (pkcs11_roof);
-
-       return ret;
+       return gkm_rpc_layer_initialize (pkcs11_roof);
 }
 
 static void
@@ -157,46 +152,6 @@ gkd_pkcs11_startup_pkcs11 (void)
        return TRUE;
 }
 
-static void
-pkcs11_ssh_cleanup (gpointer unused)
-{
-       gkd_ssh_agent_shutdown ();
-}
-
-static gboolean
-accept_ssh_client (GIOChannel *channel, GIOCondition cond, gpointer unused)
-{
-       if (cond == G_IO_IN)
-               gkd_ssh_agent_accept ();
-       return TRUE;
-}
-
-gboolean
-gkd_pkcs11_startup_ssh (void)
-{
-       GIOChannel *channel;
-       const gchar *base_dir;
-       int sock;
-
-       base_dir = gkd_util_get_master_directory ();
-       g_return_val_if_fail (base_dir, FALSE);
-
-       sock = gkd_ssh_agent_startup (base_dir);
-       if (sock == -1)
-               return FALSE;
-
-       channel = g_io_channel_unix_new (sock);
-       g_io_add_watch (channel, G_IO_IN | G_IO_HUP, accept_ssh_client, NULL);
-       g_io_channel_unref (channel);
-
-       /* gkm-ssh-agent sets the environment variable */
-       gkd_util_push_environment ("SSH_AUTH_SOCK", g_getenv ("SSH_AUTH_SOCK"));
-
-       egg_cleanup_register (pkcs11_ssh_cleanup, NULL);
-
-       return TRUE;
-}
-
 CK_FUNCTION_LIST_PTR
 gkd_pkcs11_get_functions (void)
 {
diff --git a/daemon/gkd-pkcs11.h b/daemon/gkd-pkcs11.h
index 1da7b09..38e3f15 100644
--- a/daemon/gkd-pkcs11.h
+++ b/daemon/gkd-pkcs11.h
@@ -29,8 +29,6 @@ gboolean               gkd_pkcs11_initialize           (void);
 
 gboolean               gkd_pkcs11_startup_pkcs11       (void);
 
-gboolean               gkd_pkcs11_startup_ssh          (void);
-
 CK_FUNCTION_LIST_PTR   gkd_pkcs11_get_functions        (void);
 
 CK_FUNCTION_LIST_PTR   gkd_pkcs11_get_base_functions   (void);
diff --git a/daemon/ssh-agent/Makefile.am b/daemon/ssh-agent/Makefile.am
index 4fb718e..593ce43 100644
--- a/daemon/ssh-agent/Makefile.am
+++ b/daemon/ssh-agent/Makefile.am
@@ -8,54 +8,48 @@ noinst_LTLIBRARIES += \
 libgkd_ssh_agent_la_SOURCES = \
        daemon/ssh-agent/gkd-ssh-agent.c \
        daemon/ssh-agent/gkd-ssh-agent.h \
+       daemon/ssh-agent/gkd-ssh-agent-process.h \
+       daemon/ssh-agent/gkd-ssh-agent-process.c \
+       daemon/ssh-agent/gkd-ssh-agent-preload.h \
+       daemon/ssh-agent/gkd-ssh-agent-preload.c \
        daemon/ssh-agent/gkd-ssh-agent-private.h \
        daemon/ssh-agent/gkd-ssh-agent-ops.c \
-       daemon/ssh-agent/gkd-ssh-agent-proto.c
+       daemon/ssh-agent/gkd-ssh-interaction.h \
+       daemon/ssh-agent/gkd-ssh-interaction.c \
+       daemon/ssh-agent/gkd-ssh-openssh.h \
+       daemon/ssh-agent/gkd-ssh-openssh.c \
+       $(NULL)
 libgkd_ssh_agent_la_CFLAGS = \
        $(DAEMON_CFLAGS)
 
-# ------------------------------------------------------------------------------
-# Standalone binary
-
-noinst_PROGRAMS += \
-       gkd-ssh-agent-standalone
-
-gkd_ssh_agent_standalone_SOURCES = \
-       daemon/ssh-agent/gkd-ssh-agent-standalone.c
-gkd_ssh_agent_standalone_CFLAGS = \
-       $(DAEMON_CFLAGS)
-gkd_ssh_agent_standalone_LDADD = \
-       libgkd-ssh-agent.la \
-       libegg-buffer.la \
-       libegg-secure.la \
-       libgkm.la \
-       $(DAEMON_LIBS)
-
-# ------------------------------------------------------------------------------
 # Tests
 
 ssh_agent_CFLAGS = \
-       $(GCK_CFLAGS)
+       $(DAEMON_CFLAGS)
 
 ssh_agent_LIBS = \
        libgkd-ssh-agent.la \
-       libgkm.la \
        libegg.la \
-       $(GLIB_LIBS) \
-       $(GCK_LIBS)
-       $(GTHREAD_LIBS)
+       $(DAEMON_LIBS)
 
 ssh_agent_TESTS = \
-       test-communication \
-       test-keytypes
+       test-gkd-ssh-openssh \
+       test-gkd-ssh-agent-process \
+       test-gkd-ssh-agent
+
+test_gkd_ssh_openssh_SOURCES = daemon/ssh-agent/test-gkd-ssh-openssh.c
+test_gkd_ssh_openssh_CFLAGS = $(ssh_agent_CFLAGS)
+test_gkd_ssh_openssh_LDADD = $(ssh_agent_LIBS)
+
+test_gkd_ssh_agent_process_SOURCES = daemon/ssh-agent/test-gkd-ssh-agent-process.c
+test_gkd_ssh_agent_process_CFLAGS = $(ssh_agent_CFLAGS)
+test_gkd_ssh_agent_process_LDADD = $(ssh_agent_LIBS)
 
-test_keytypes_SOURCES = daemon/ssh-agent/test-keytypes.c
-test_keytypes_CFLAGS = $(ssh_agent_CFLAGS)
-test_keytypes_LDADD = $(ssh_agent_LIBS)
+test_gkd_ssh_agent_SOURCES = daemon/ssh-agent/test-gkd-ssh-agent.c
+test_gkd_ssh_agent_CFLAGS = $(ssh_agent_CFLAGS)
+test_gkd_ssh_agent_LDADD = $(ssh_agent_LIBS) libgkd-login.la libgkm-wrap-layer.la
 
-test_communication_SOURCES = daemon/ssh-agent/test-communication.c
-test_communication_CFLAGS = $(ssh_agent_CFLAGS)
-test_communication_LDADD = $(ssh_agent_LIBS)
+EXTRA_DIST += daemon/ssh-agent/test-gkd-ssh-agent-common.h
 
 check_PROGRAMS += $(ssh_agent_TESTS)
 TESTS += $(ssh_agent_TESTS)
diff --git a/daemon/ssh-agent/gkd-ssh-agent-ops.c b/daemon/ssh-agent/gkd-ssh-agent-ops.c
index e1cba2d..0ecd474 100644
--- a/daemon/ssh-agent/gkd-ssh-agent-ops.c
+++ b/daemon/ssh-agent/gkd-ssh-agent-ops.c
@@ -2,6 +2,7 @@
 /* gkd-ssh-agent-ops.h - SSH agent operations
 
    Copyright (C) 2007 Stefan Walter
+   Copyright (C) 2014 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
@@ -17,1476 +18,226 @@
    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>
+   Author: Stef Walter <stef thewalter net>
 */
 
 #include "config.h"
 
+#include "gkd-ssh-agent-preload.h"
 #include "gkd-ssh-agent-private.h"
 
-#include <gck/gck.h>
-
-#include "pkcs11/pkcs11.h"
-#include "pkcs11/pkcs11i.h"
-
 #include "egg/egg-error.h"
-#include "egg/egg-secure-memory.h"
 
 #include <glib.h>
 
-#include <ctype.h>
-#include <stdarg.h>
-#include <string.h>
-#include <stdio.h>
-
-#define V1_LABEL "SSH1 RSA Key"
-
-typedef gboolean (*ObjectForeachFunc) (GckObject *object, gpointer user_data);
-
-EGG_SECURE_DECLARE (ssh_agent_ops);
-
 /* ---------------------------------------------------------------------------- */
 
-
-static void
-copy_attribute (GckAttributes *original,
-                CK_ATTRIBUTE_TYPE type,
-                GckBuilder *dest)
-{
-       const GckAttribute *attr;
-
-       g_assert (original);
-       g_assert (dest);
-
-       attr = gck_attributes_find (original, type);
-       if (attr)
-               gck_builder_add_attribute (dest, attr);
-}
-
 static gboolean
-login_session (GckSession *session)
+op_add_identity (GkdSshAgent *self,
+                EggBuffer *req,
+                EggBuffer *resp)
 {
-       gulong state;
-       GError *error = NULL;
-       gboolean ret = TRUE;
+       const guchar *blob;
+       gsize offset = 5;
+       gsize length;
+       GBytes *key = NULL;
+       gboolean ret;
 
-       state = gck_session_get_state (session);
+       /* 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_warning ("got unparseable add identity request for ssh-agent");
 
-       /* Log in the session if necessary */
-       if (state == CKS_RO_PUBLIC_SESSION || state == CKS_RW_PUBLIC_SESSION) {
-               if (!gck_session_login (session, CKU_USER, NULL, 0, NULL, &error)) {
-                       g_message ("couldn't log in to session: %s", egg_error_message (error));
-                       ret = FALSE;
-               }
+       ret = gkd_ssh_agent_relay (self, req, resp);
+       if (key) {
+               if (ret)
+                       gkd_ssh_agent_add_key (self, key);
+               g_bytes_unref (key);
        }
 
        return ret;
 }
 
-static GckAttributes*
-build_like_attributes (GckAttributes *attrs, CK_OBJECT_CLASS klass)
-{
-       GckBuilder builder = GCK_BUILDER_INIT;
-       gulong key_type;
-
-       g_assert (attrs);
-
-       /* Determine the key type */
-       if (!gck_attributes_find_ulong (attrs, CKA_KEY_TYPE, &key_type))
-               g_return_val_if_reached (NULL);
-
-       gck_builder_add_ulong (&builder, CKA_CLASS, klass);
-       copy_attribute (attrs, CKA_KEY_TYPE, &builder);
-       copy_attribute (attrs, CKA_TOKEN, &builder);
-
-       switch (key_type) {
-       case CKK_RSA:
-               copy_attribute (attrs, CKA_MODULUS, &builder);
-               copy_attribute (attrs, CKA_PUBLIC_EXPONENT, &builder);
-               break;
-
-       case CKK_DSA:
-               copy_attribute (attrs, CKA_PRIME, &builder);
-               copy_attribute (attrs, CKA_SUBPRIME, &builder);
-               copy_attribute (attrs, CKA_BASE, &builder);
-               copy_attribute (attrs, CKA_VALUE, &builder);
-               break;
-
-       case CKK_EC:
-               copy_attribute (attrs, CKA_EC_PARAMS, &builder);
-               copy_attribute (attrs, CKA_EC_POINT, &builder);
-               break;
-
-       default:
-               g_return_val_if_reached (NULL);
-               break;
-       }
-
-       return gck_attributes_ref_sink (gck_builder_end (&builder));
-}
-
-static void
-search_keys_like_attributes (GList *modules, GckSession *session, GckAttributes *attrs,
-                             CK_OBJECT_CLASS klass, ObjectForeachFunc func, gpointer user_data)
-{
-       GckAttributes *search;
-       GckEnumerator *en;
-       GError *error = NULL;
-       GList *keys, *l;
-       GckObject *object;
-
-       g_assert (modules || session);
-
-       search = build_like_attributes (attrs, klass);
-
-       /* In all slots */
-       if (modules) {
-               en = gck_modules_enumerate_objects (modules, search, GCK_SESSION_AUTHENTICATE | 
GCK_SESSION_READ_WRITE);
-
-               for (;;) {
-                       object = gck_enumerator_next (en, NULL, &error);
-                       if (!object) {
-                               if (error) {
-                                       g_warning ("couldn't enumerate matching keys: %s", egg_error_message 
(error));
-                                       g_clear_error (&error);
-                               }
-                               break;
-                       }
-
-                       if (!(func) (object, user_data))
-                               break;
-               }
-
-               g_object_unref (en);
-
-       }
-
-       /* Search in the session */
-       if (session){
-               keys = gck_session_find_objects (session, search, NULL, &error);
-
-               if (error) {
-                       g_warning ("couldn't find matching keys: %s", egg_error_message (error));
-                       g_clear_error (&error);
-
-               } else {
-                       for (l = keys; l; l = g_list_next (l)) {
-                               if (!(func) (l->data, user_data))
-                                       break;
-                       }
-
-                       gck_list_unref_free (keys);
-               }
-       }
-
-       gck_attributes_unref (search);
-}
-
-static gboolean
-list_all_matching (GckObject *object, gpointer user_data)
-{
-       GList** list = (GList**)user_data;
-       g_return_val_if_fail (GCK_IS_OBJECT (object), FALSE);
-       *list = g_list_prepend (*list, g_object_ref (object));
-
-       /* Keep going */
-       return TRUE;
-}
-
-static gboolean
-return_first_matching (GckObject *object, gpointer user_data)
-{
-       GckObject **result = (GckObject**)user_data;
-
-       g_return_val_if_fail (GCK_IS_OBJECT (object), FALSE);
-       g_return_val_if_fail (result != NULL, FALSE);
-       g_return_val_if_fail (*result == NULL, FALSE);
-       *result = g_object_ref (object);
-
-       /* We've seen enough */
-       return FALSE;
-}
-
-static gboolean
-return_private_matching (GckObject *object, gpointer user_data)
+static GHashTable *
+parse_identities_answer (EggBuffer *resp)
 {
-       GckObject **result = (GckObject**)user_data;
-       GckBuilder builder = GCK_BUILDER_INIT;
-       GckSession *session;
-       GckAttributes *attrs;
-       const GckAttribute *attr;
-       gboolean token;
-       GList *objects;
-       GError *error = NULL;
-
-       g_return_val_if_fail (GCK_IS_OBJECT (object), FALSE);
-       g_return_val_if_fail (result != NULL, FALSE);
-       g_return_val_if_fail (*result == NULL, FALSE);
-
-       /* Get the key identifier and token */
-       attrs = gck_object_get (object, NULL, &error, CKA_ID, CKA_TOKEN, GCK_INVALID);
-       if (error) {
-               g_warning ("error retrieving attributes for public key: %s", egg_error_message (error));
-               g_clear_error (&error);
-               return TRUE;
-       }
-
-       /* Dig out the key identifier and token */
-       attr = gck_attributes_find (attrs, CKA_ID);
-       g_return_val_if_fail (attr, FALSE);
-
-       if (!gck_attributes_find_boolean (attrs, CKA_TOKEN, &token))
-               token = FALSE;
-
-       session = gck_object_get_session (object);
-       g_return_val_if_fail (GCK_IS_SESSION (session), FALSE);
-
-       if (!login_session (session))
-               return FALSE;
-
-       gck_builder_add_attribute (&builder, attr);
-       gck_builder_add_ulong (&builder, CKA_CLASS, CKO_PRIVATE_KEY);
-       gck_builder_add_boolean (&builder, CKA_TOKEN, token);
-
-       /* Search for the matching private key */
-       objects = gck_session_find_objects (session, gck_builder_end (&builder), NULL, NULL);
-       gck_attributes_unref (attrs);
-
-       /* Keep searching, not found */
-       if (objects) {
-               *result = g_object_ref (objects->data);
-               gck_list_unref_free (objects);
-       }
-
-       g_object_unref (session);
-
-       /* Stop once we have a key */
-       return (*result == NULL);
-}
-
-static gboolean
-load_identity_v1_attributes (GckObject *object, gpointer user_data)
-{
-       GckAttributes *attrs;
-       GError *error = NULL;
-       GList **all_attrs;
-
-       g_return_val_if_fail (GCK_IS_OBJECT (object), FALSE);
-       g_return_val_if_fail (user_data, FALSE);
-
-       /*
-        * The encompassing search should have limited to the right label.
-        * In addition V1 keys are only RSA.
-        */
-
-       attrs = gck_object_get (object, NULL, &error, CKA_ID, CKA_LABEL, CKA_KEY_TYPE, CKA_MODULUS,
-                               CKA_PUBLIC_EXPONENT, CKA_CLASS, CKA_MODULUS_BITS, GCK_INVALID);
-       if (error) {
-               g_warning ("error retrieving attributes for public key: %s", egg_error_message (error));
-               g_clear_error (&error);
-               return TRUE;
-       }
-
-       all_attrs = (GList**)user_data;
-       *all_attrs = g_list_prepend (*all_attrs, attrs);
-
-       /* Note that we haven't reffed the object or session */
-
-       /* Keep going */
-       return TRUE;
-}
-
-static gboolean
-load_identity_v2_attributes (GckObject *object, gpointer user_data)
-{
-       GckAttributes *attrs;
-       const GckAttribute *attr;
-       GError *error = NULL;
-       gboolean valid = TRUE;
-       gboolean token;
-       GList **all_attrs;
-
-       g_return_val_if_fail (GCK_IS_OBJECT (object), FALSE);
-       g_return_val_if_fail (user_data, FALSE);
-
-       attrs = gck_object_get (object, NULL, &error, CKA_ID, CKA_LABEL, CKA_KEY_TYPE, CKA_MODULUS,
-                               CKA_PUBLIC_EXPONENT, CKA_PRIME, CKA_SUBPRIME, CKA_BASE,
-                               CKA_VALUE, CKA_CLASS, CKA_MODULUS_BITS, CKA_TOKEN,
-                               CKA_EC_POINT, CKA_EC_PARAMS, GCK_INVALID);
-       if (error) {
-               g_warning ("error retrieving attributes for public key: %s", egg_error_message (error));
-               g_clear_error (&error);
-               return TRUE;
-       }
-
-       /* Dig out the label, and see if it's not v1, skip if so */
-       attr = gck_attributes_find (attrs, CKA_LABEL);
-       if (attr != NULL) {
-               if (attr->length == strlen (V1_LABEL) &&
-                   strncmp ((gchar*)attr->value, V1_LABEL, attr->length) == 0)
-                       valid = FALSE;
-       }
-
-       /* Figure out if it's a token object or not */
-       if (!gck_attributes_find_boolean (attrs, CKA_TOKEN, &token))
-               token = FALSE;
-
-       all_attrs = (GList**)user_data;
-       if (valid == TRUE)
-               *all_attrs = g_list_prepend (*all_attrs, attrs);
-       else
-               gck_attributes_unref (attrs);
-
-       /* Note that we haven't reffed the object or session */
-
-       /* Keep going */
-       return TRUE;
-}
-
-static void
-remove_key_pair (GckSession *session, GckObject *priv, GckObject *pub)
-{
-       GError *error = NULL;
-
-       g_assert (GCK_IS_SESSION (session));
-
-       if (!login_session (session))
-               return;
-
-       if (priv != NULL) {
-               gck_object_destroy (priv, NULL, &error);
-
-               if (error) {
-                       if (!g_error_matches (error, GCK_ERROR, CKR_OBJECT_HANDLE_INVALID))
-                               g_warning ("couldn't remove ssh private key: %s", egg_error_message (error));
-                       g_clear_error (&error);
-               }
+       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 != GKD_SSH_RES_IDENTITIES_ANSWER ||
+           !egg_buffer_get_uint32 (resp, offset, &offset, &count)) {
+               g_warning ("got unexpected response back from ssh-agent when requesting identities");
+               return NULL;
        }
 
-       if (pub != NULL) {
-               gck_object_destroy (pub, NULL, &error);
+       answer = g_hash_table_new_full (g_bytes_hash, g_bytes_equal, (GDestroyNotify)g_bytes_unref, g_free);
 
-               if (error) {
-                       if (!g_error_matches (error, GCK_ERROR, CKR_OBJECT_HANDLE_INVALID))
-                               g_warning ("couldn't remove ssh public key: %s", egg_error_message (error));
-                       g_clear_error (&error);
+       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_warning ("got unparseable response back from ssh-agent when requesting identities");
+                       g_hash_table_unref (answer);
+                       return NULL;
                }
-       }
-}
-
-static void
-lock_key_pair (GckSession *session, GckObject *priv, GckObject *pub)
-{
-       GckBuilder builder = GCK_BUILDER_INIT;
-       GError *error = NULL;
-       GList *objects, *l;
-
-       g_assert (GCK_IS_SESSION (session));
-       g_assert (GCK_IS_OBJECT (priv));
-       g_assert (GCK_IS_OBJECT (pub));
-
-       if (!login_session (session))
-               return;
-
-       gck_builder_add_ulong (&builder, CKA_CLASS, CKO_G_CREDENTIAL);
-       gck_builder_add_ulong (&builder, CKA_G_OBJECT, gck_object_get_handle (priv));
-
-       /* Delete any authenticator objects */
-       objects = gck_session_find_objects (session, gck_builder_end (&builder), NULL, &error);
-
-       if (error) {
-               g_warning ("couldn't search for authenticator objects: %s", egg_error_message (error));
-               g_clear_error (&error);
-               return;
+               g_hash_table_insert (answer, g_bytes_new (blob, length), comment);
        }
 
-       /* Delete them all */
-       for (l = objects; l; l = g_list_next (l)) {
-               gck_object_destroy (l->data, NULL, &error);
-               if (error) {
-                       g_warning ("couldn't delete authenticator object: %s", egg_error_message (error));
-                       g_clear_error (&error);
-               }
-       }
+       return answer;
 }
 
-static void
-remove_by_public_key (GckSession *session, GckObject *pub, gboolean exclude_v1)
-{
-       GckBuilder builder = GCK_BUILDER_INIT;
-       GckAttributes *attrs;
-       GError *error = NULL;
-       GList *objects;
-       gboolean token;
-       gchar *label;
-
-       g_assert (GCK_IS_SESSION (session));
-       g_assert (GCK_IS_OBJECT (pub));
-
-       if (!login_session (session))
-               return;
-
-       attrs = gck_object_get (pub, NULL, &error, CKA_LABEL, CKA_ID, CKA_TOKEN, GCK_INVALID);
-
-       if (error) {
-               g_warning ("couldn't lookup attributes for key: %s", egg_error_message (error));
-               g_clear_error (&error);
-               return;
-       }
-
-       /* Skip over SSH V1 keys */
-       if (exclude_v1 && gck_attributes_find_string (attrs, CKA_LABEL, &label)) {
-               if (label && strcmp (label, V1_LABEL) == 0) {
-                       gck_attributes_unref (attrs);
-                       g_free (label);
-                       return;
-               }
-               g_free (label);
-       }
-
-       /* Lock token objects, remove session objects */
-       if (!gck_attributes_find_boolean (attrs, CKA_TOKEN, &token))
-               token = FALSE;
-
-       /* Search for exactly the same attributes but with a private key class */
-       gck_builder_add_all (&builder, attrs);
-       gck_builder_add_ulong (&builder, CKA_CLASS, CKO_PRIVATE_KEY);
-       gck_attributes_unref (attrs);
-
-       objects = gck_session_find_objects (session, gck_builder_end (&builder), NULL, &error);
-
-       if (error) {
-               g_warning ("couldn't search for related key: %s", egg_error_message (error));
-               g_clear_error (&error);
-               return;
-       }
-
-       /* Lock the token objects */
-       if (token && objects) {
-               lock_key_pair (session, objects->data, pub);
-       } else if (!token) {
-               remove_key_pair (session, objects->data, pub);
-       }
-
-       gck_list_unref_free (objects);
-}
 
 static gboolean
-create_key_pair (GckSession *session, GckAttributes *priv, GckAttributes *pub)
-{
-       GckObject *priv_key, *pub_key;
-       GError *error = NULL;
-
-       g_assert (GCK_IS_SESSION (session));
-       g_assert (priv);
-       g_assert (pub);
-
-       if (!login_session (session))
-               return FALSE;
-
-       priv_key = gck_session_create_object (session, priv, NULL, &error);
-       if (error) {
-               g_warning ("couldn't create session private key: %s", egg_error_message (error));
-               g_clear_error (&error);
-               return FALSE;
-       }
-
-       pub_key = gck_session_create_object (session, pub, NULL, &error);
-       if (error) {
-               g_warning ("couldn't create session public key: %s", egg_error_message (error));
-               g_clear_error (&error);
-
-               /* Failed, so remove private as well */
-               gck_object_destroy (priv_key, NULL, NULL);
-               g_object_unref (priv_key);
-
-               return FALSE;
-       }
-
-       g_object_unref (pub_key);
-       g_object_unref (priv_key);
-
-       return TRUE;
-}
-
-static void
-destroy_replaced_keys (GckSession *session, GList *keys)
+op_request_identities (GkdSshAgent *self,
+                      EggBuffer *req,
+                      EggBuffer *resp)
 {
-       GError *error = NULL;
+       GHashTable *answer;
+       GHashTableIter iter;
+       gsize length;
+       guint32 added;
+       GBytes *key;
+       GList *keys;
        GList *l;
+       GkdSshAgentPreload *preload;
 
-       g_assert (GCK_IS_SESSION (session));
-
-       for (l = keys; l; l = g_list_next (l)) {
-               if (!gck_object_destroy (l->data, NULL, &error)) {
-                       if (!g_error_matches (error, GCK_ERROR, CKR_OBJECT_HANDLE_INVALID))
-                               g_warning ("couldn't delete a SSH key we replaced: %s",
-                                          egg_error_message (error));
-                       g_clear_error (&error);
-               }
-       }
-}
-
-static gboolean
-replace_key_pair (GckSession *session,
-                  GckBuilder *priv,
-                  GckBuilder *pub)
-{
-       GList *priv_prev, *pub_prev;
-       GckAttributes *priv_atts, *pub_atts;
-
-       g_assert (GCK_IS_SESSION (session));
-       g_assert (priv != NULL);
-       g_assert (pub != NULL);
-
-       if (!login_session (session))
+       if (!gkd_ssh_agent_relay (self, req, resp))
                return FALSE;
 
-       gck_builder_set_boolean (priv, CKA_TOKEN, FALSE);
-       priv_atts = gck_attributes_ref_sink (gck_builder_end (priv));
-
-       gck_builder_set_boolean (pub, CKA_TOKEN, FALSE);
-       pub_atts = gck_attributes_ref_sink (gck_builder_end (pub));
-
-       /* Find the previous keys that match the same description */
-       priv_prev = pub_prev = NULL;
-       search_keys_like_attributes (NULL, session, priv_atts, CKO_PRIVATE_KEY, list_all_matching, 
&priv_prev);
-       search_keys_like_attributes (NULL, session, pub_atts, CKO_PUBLIC_KEY, list_all_matching, &pub_prev);
-
-       /* Now try and create the new keys */
-       if (create_key_pair (session, priv_atts, pub_atts)) {
-
-               /* Delete the old keys */
-               destroy_replaced_keys (session, priv_prev);
-               destroy_replaced_keys (session, pub_prev);
-       }
-
-       gck_attributes_unref (priv_atts);
-       gck_attributes_unref (pub_atts);
-       gck_list_unref_free (priv_prev);
-       gck_list_unref_free (pub_prev);
-
-       return TRUE;
-}
-
-static gboolean
-load_contraints (EggBuffer *buffer,
-                 gsize offset,
-                 gsize *next_offset,
-                 GckBuilder *priv,
-                 GckBuilder *pub)
-{
-       guchar constraint;
-       guint32 lifetime;
-
-       /*
-        * Constraints are a byte flag, and optional data depending
-        * on the constraint.
-        */
-
-       while (offset < egg_buffer_length (buffer)) {
-               if (!egg_buffer_get_byte (buffer, offset, &offset, &constraint))
-                       return FALSE;
-
-               switch (constraint) {
-               case GKD_SSH_FLAG_CONSTRAIN_LIFETIME:
-                       if (!egg_buffer_get_uint32 (buffer, offset, &offset, &lifetime))
-                               return FALSE;
-
-                       gck_builder_add_ulong (pub, CKA_G_DESTRUCT_AFTER, lifetime);
-                       gck_builder_add_ulong (priv, CKA_G_DESTRUCT_AFTER, lifetime);
-                       break;
-
-               case GKD_SSH_FLAG_CONSTRAIN_CONFIRM:
-                       /* We can't use prompting as access control on an insecure X desktop */
-                       g_message ("prompt constraints are not supported.");
-                       return FALSE;
+       /* Parse all the keys, and if it fails, just fall through */
+       answer = parse_identities_answer (resp);
+       if (!answer)
+               return TRUE;
 
-               default:
-                       g_message ("unsupported constraint or other unsupported data");
-                       return FALSE;
+       g_hash_table_iter_init (&iter, answer);
+       while (g_hash_table_iter_next (&iter, (gpointer *)&key, NULL))
+               gkd_ssh_agent_add_key (self, key);
+
+       added = 0;
+
+       /* Add any preloaded keys not already in answer */
+       preload = gkd_ssh_agent_get_preload (self);
+       keys = gkd_ssh_agent_preload_get_keys (preload);
+       for (l = keys; l != NULL; l = g_list_next (l)) {
+               GkdSshAgentKeyInfo *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++;
                }
        }
 
-       *next_offset = offset;
-       return TRUE;
-}
-
-/* -----------------------------------------------------------------------------
- * OPERATIONS
- */
-
-static gboolean
-op_add_identity (GkdSshAgentCall *call)
-{
-       GckBuilder pub;
-       GckBuilder priv;
-       GckSession *session;
-       gchar *stype = NULL;
-       gchar *comment = NULL;
-       gboolean ret;
-       gulong algo;
-       gsize offset;
-
-       if (!egg_buffer_get_string (call->req, 5, &offset, &stype, (EggBufferAllocator)g_realloc))
-               return FALSE;
-
-       algo = gkd_ssh_agent_proto_keytype_to_algo (stype);
-       if (algo == G_MAXULONG) {
-               g_warning ("unsupported algorithm from SSH: %s", stype);
-               g_free (stype);
-               return FALSE;
-       }
-
-       g_free (stype);
-       gck_builder_init_full (&pub, GCK_BUILDER_SECURE_MEMORY);
-       gck_builder_init_full (&priv, GCK_BUILDER_NONE);
+       g_list_free_full (keys, (GDestroyNotify)gkd_ssh_agent_key_info_free);
 
-       switch (algo) {
-       case CKK_RSA:
-               ret = gkd_ssh_agent_proto_read_pair_rsa (call->req, &offset, &priv, &pub);
-               break;
-       case CKK_DSA:
-               ret = gkd_ssh_agent_proto_read_pair_dsa (call->req, &offset, &priv, &pub);
-               break;
-       case CKK_EC:
-               ret = gkd_ssh_agent_proto_read_pair_ecdsa (call->req, &offset, &priv, &pub);
-               break;
-       default:
-               g_assert_not_reached ();
-               return FALSE;
-       }
-
-       if (!ret) {
-               g_warning ("couldn't read incoming SSH private key");
-               gck_builder_clear (&pub);
-               gck_builder_clear (&priv);
-               return FALSE;
-       }
-
-       /* Get the comment */
-       if (!egg_buffer_get_string (call->req, offset, &offset, &comment, (EggBufferAllocator)g_realloc)) {
-               gck_builder_clear (&pub);
-               gck_builder_clear (&priv);
-               return FALSE;
-       }
+       /* 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);
 
-       gck_builder_add_string (&pub, CKA_LABEL, comment);
-       gck_builder_add_string (&priv, CKA_LABEL, comment);
-       g_free (comment);
+       /* Set the correct total size of the payload */
+       egg_buffer_set_uint32 (resp, 0, resp->len - 4);
 
-       /* Any constraints on loading the key */
-       if (!load_contraints (call->req, offset, &offset, &priv, &pub)) {
-               gck_builder_clear (&pub);
-               gck_builder_clear (&priv);
-               return FALSE;
-       }
-
-       /*
-        * This is the session that owns these objects. Only
-        * one thread can use it at a time.
-        */
-
-       session = gkd_ssh_agent_checkout_main_session ();
-       g_return_val_if_fail (session, FALSE);
-
-       ret = replace_key_pair (session, &priv, &pub);
-
-       gkd_ssh_agent_checkin_main_session (session);
-
-       gck_builder_clear (&priv);
-       gck_builder_clear (&pub);
-
-       egg_buffer_add_byte (call->resp, ret ? GKD_SSH_RES_SUCCESS : GKD_SSH_RES_FAILURE);
        return TRUE;
 }
 
 static gboolean
-op_v1_add_identity (GkdSshAgentCall *call)
+op_sign_request (GkdSshAgent *self,
+                EggBuffer *req,
+                EggBuffer *resp)
 {
-       GckBuilder pub, priv;
-       GckSession *session;
-       gchar *comment = NULL;
-       gboolean ret;
+       const guchar *blob;
+       gsize length;
        gsize offset = 5;
-       guint32 unused;
-
-       if (!egg_buffer_get_uint32 (call->req, offset, &offset, &unused))
-               return FALSE;
-
-       gck_builder_init_full (&priv, GCK_BUILDER_SECURE_MEMORY);
-       gck_builder_init_full (&pub, GCK_BUILDER_NONE);
-
-       if (!gkd_ssh_agent_proto_read_pair_v1 (call->req, &offset, &priv, &pub)) {
-               g_warning ("couldn't read incoming SSH private key");
-               gck_builder_clear (&pub);
-               gck_builder_clear (&priv);
-               return FALSE;
-       }
-
-       /* Get the comment */
-       if (!egg_buffer_get_string (call->req, offset, &offset, &comment, (EggBufferAllocator)g_realloc)) {
-               gck_builder_clear (&pub);
-               gck_builder_clear (&priv);
-               return FALSE;
-       }
-
-       g_free (comment);
-
-       gck_builder_add_string (&priv, CKA_LABEL, V1_LABEL);
-       gck_builder_add_string (&pub, CKA_LABEL, V1_LABEL);
-
-       /* Any constraints on loading the key */
-       if (!load_contraints (call->req, offset, &offset, &priv, &pub)) {
-               gck_builder_clear (&pub);
-               gck_builder_clear (&priv);
-               return FALSE;
-       }
-
-       /*
-        * This is the session that owns these objects. Only
-        * one thread can use it at a time.
-        */
-
-       session = gkd_ssh_agent_checkout_main_session ();
-       g_return_val_if_fail (session, FALSE);
-
-       ret = replace_key_pair (session, &priv, &pub);
-
-       gkd_ssh_agent_checkin_main_session (session);
-
-       gck_builder_clear (&pub);
-       gck_builder_clear (&priv);
-
-       egg_buffer_add_byte (call->resp, ret ? GKD_SSH_RES_SUCCESS : GKD_SSH_RES_FAILURE);
-       return TRUE;
-}
-
-static gboolean
-op_request_identities (GkdSshAgentCall *call)
-{
-       GckBuilder builder = GCK_BUILDER_INIT;
-       GckEnumerator *en;
-       GckObject *obj;
-       GError *error = NULL;
-       GList *all_attrs, *l;
-       GckAttributes *attrs;
-       gsize blobpos;
-       gchar *comment;
-
-       /* TODO: Check SSH purpose */
-       gck_builder_add_ulong (&builder, CKA_CLASS, CKO_PUBLIC_KEY);
-
-       /* Find all the keys (we filter out v1 later) */
-       en = gck_modules_enumerate_objects (call->modules, gck_builder_end (&builder),
-                                           GCK_SESSION_AUTHENTICATE | GCK_SESSION_READ_WRITE);
-       g_return_val_if_fail (en, FALSE);
-
-       all_attrs = NULL;
-       while ((obj = gck_enumerator_next (en, NULL, &error))) {
-               load_identity_v2_attributes (obj, &all_attrs);
-               g_object_unref (obj);
-       }
-
-       g_object_unref (en);
-
-       if (error) {
-               g_warning ("couldn't enumerate ssh keys: %s", egg_error_message (error));
-               egg_buffer_add_byte (call->resp, GKD_SSH_RES_FAILURE);
-               g_clear_error (&error);
-               return TRUE;
-       }
-
-       egg_buffer_add_byte (call->resp, GKD_SSH_RES_IDENTITIES_ANSWER);
-       egg_buffer_add_uint32 (call->resp, g_list_length (all_attrs));
-
-       for (l = all_attrs; l; l = g_list_next (l)) {
-
-               attrs = l->data;
-
-               /* Dig out the label */
-               if (!gck_attributes_find_string (attrs, CKA_LABEL, &comment))
-                       comment = NULL;
-
-               /* Add a space for the key blob length */
-               blobpos = call->resp->len;
-               egg_buffer_add_uint32 (call->resp, 0);
-
-               /* Write out the key */
-               gkd_ssh_agent_proto_write_public (call->resp, attrs);
-
-               /* Write back the blob length */
-               egg_buffer_set_uint32 (call->resp, blobpos, (call->resp->len - blobpos) - 4);
-
-               /* And now a per key comment */
-               egg_buffer_add_string (call->resp, comment ? comment : "");
-
-               g_free (comment);
-               gck_attributes_unref (attrs);
-       }
-
-       g_list_free (all_attrs);
-
-       return TRUE;
-}
-
-static gboolean
-op_v1_request_identities (GkdSshAgentCall *call)
-{
-       GckBuilder builder = GCK_BUILDER_INIT;
-       GList *all_attrs, *l;
-       GckAttributes *attrs;
-       GError *error = NULL;
-       GckEnumerator *en;
-       GckObject *obj;
-
-       /* TODO: Check SSH purpose */
-       gck_builder_add_ulong (&builder, CKA_CLASS, CKO_PUBLIC_KEY);
-       gck_builder_add_boolean (&builder, CKA_TOKEN, FALSE);
-       gck_builder_add_string (&builder, CKA_LABEL, V1_LABEL);
-
-       /* Find all the keys not on token, and are V1 */
-       en = gck_modules_enumerate_objects (call->modules, gck_builder_end (&builder),
-                                           GCK_SESSION_AUTHENTICATE | GCK_SESSION_READ_WRITE);
-
-       all_attrs = NULL;
-       while ((obj = gck_enumerator_next (en, NULL, &error))) {
-               load_identity_v1_attributes (obj, &all_attrs);
-               g_object_unref (obj);
-       }
-       g_object_unref (en);
-
-       if (error) {
-               g_warning ("couldn't enumerate ssh keys: %s", egg_error_message (error));
-               egg_buffer_add_byte (call->resp, GKD_SSH_RES_FAILURE);
-               g_clear_error (&error);
-               return TRUE;
-       }
-
-       egg_buffer_add_byte (call->resp, GKD_SSH_RES_RSA_IDENTITIES_ANSWER);
-       egg_buffer_add_uint32 (call->resp, g_list_length (all_attrs));
-
-       for (l = all_attrs; l; l = g_list_next (l)) {
-
-               attrs = l->data;
-
-               /* Write out the key */
-               gkd_ssh_agent_proto_write_public_v1 (call->resp, attrs);
-
-               /* And now a per key comment */
-               egg_buffer_add_string (call->resp, "Public Key");
-
-               gck_attributes_unref (attrs);
-       }
+       GBytes *key;
 
-       g_list_free (all_attrs);
-
-       return TRUE;
-}
-
-/* XXX we should create it using asn1x ... */
-static const guchar SHA512_ASN[] = /* Object ID is 2.16.840.1.101.3.4.2.3  */
-       { 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48,
-         0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04,
-         0x40 };
-
-static const guchar SHA256_ASN[] = /* Object ID is 2.16.840.1.101.3.4.2.1  */
-       { 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48,
-         0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04,
-         0x20 };
-
-static const guchar SHA1_ASN[15] = /* Object ID is 1.3.14.3.2.26 */
-       { 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03,
-         0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 };
-
-static const guchar MD5_ASN[18] = /* Object ID is 1.2.840.113549.2.5 */
-       { 0x30, 0x20, 0x30, 0x0c, 0x06, 0x08, 0x2a, 0x86, 0x48,
-         0x86, 0xf7, 0x0d, 0x02, 0x05, 0x05, 0x00, 0x04, 0x10 };
-
-static guchar*
-make_pkcs1_sign_hash (GChecksumType algo, const guchar *data, gsize n_data,
-                      gsize *n_result)
-{
-       gsize n_algo, n_asn, n_hash;
-       GChecksum *checksum;
-       const guchar *asn;
-       guchar *hash;
-
-       g_assert (data);
-       g_assert (n_result);
-
-       n_algo = g_checksum_type_get_length (algo);
-       g_return_val_if_fail (n_algo > 0, FALSE);
-
-       if (algo == G_CHECKSUM_SHA1) {
-               asn = SHA1_ASN;
-               n_asn = sizeof (SHA1_ASN);
-       } else if (algo == G_CHECKSUM_SHA256) {
-               asn = SHA256_ASN;
-               n_asn = sizeof (SHA256_ASN);
-       } else if (algo == G_CHECKSUM_SHA512) {
-               asn = SHA512_ASN;
-               n_asn = sizeof (SHA512_ASN);
-       } else if (algo == G_CHECKSUM_MD5) {
-               asn = MD5_ASN;
-               n_asn = sizeof (MD5_ASN);
+       /* 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);
+               gkd_ssh_agent_ensure_key (self, key);
+               g_bytes_unref (key);
        } else {
-               g_assert_not_reached();
+               g_warning ("got unparseable sign request for ssh-agent");
        }
 
-       n_hash = n_algo + n_asn;
-       hash = g_malloc0 (n_hash);
-       memcpy (hash, asn, n_asn);
-
-       checksum = g_checksum_new (algo);
-       g_checksum_update (checksum, data, n_data);
-       g_checksum_get_digest (checksum, hash + n_asn, &n_algo);
-       g_checksum_free (checksum);
-
-       *n_result = n_hash;
-       return hash;
+       return gkd_ssh_agent_relay (self, req, resp);
 }
 
-static guchar*
-make_raw_sign_hash (GChecksumType algo, const guchar *data, gsize n_data,
-                    gsize *n_result)
-{
-       gsize n_hash;
-       GChecksum *checksum;
-       guchar *hash;
-
-       g_assert (data);
-       g_assert (n_result);
-
-       n_hash = g_checksum_type_get_length (algo);
-       g_return_val_if_fail (n_hash > 0, FALSE);
-
-       hash = g_malloc0 (n_hash);
-
-       checksum = g_checksum_new (algo);
-       g_checksum_update (checksum, data, n_data);
-       g_checksum_get_digest (checksum, hash, &n_hash);
-       g_checksum_free (checksum);
-
-       *n_result = n_hash;
-       return hash;
-}
-
-static guchar*
-unlock_and_sign (GckSession *session, GckObject *key, gulong mech_type, const guchar *input,
-                 gsize n_input, gsize *n_result, GError **err)
-{
-       GckBuilder builder = GCK_BUILDER_INIT;
-       GckAttributes *attrs;
-       GckObject *cred;
-       gboolean always;
-
-       /* First check if we should authenticate the key */
-       attrs = gck_object_get (key, NULL, err, CKA_ALWAYS_AUTHENTICATE, GCK_INVALID);
-       if (!attrs)
-               return NULL;
-
-       /* Authenticate the key if necessary, this allows long term */
-       if (!gck_attributes_find_boolean (attrs, CKA_ALWAYS_AUTHENTICATE, &always))
-               g_return_val_if_reached (NULL);
-
-       gck_attributes_unref (attrs);
-
-       if (always == TRUE) {
-               gck_builder_add_ulong (&builder, CKA_CLASS, CKO_G_CREDENTIAL);
-               gck_builder_add_boolean (&builder, CKA_TOKEN, FALSE);
-               gck_builder_add_empty (&builder, CKA_VALUE);
-               gck_builder_add_ulong (&builder, CKA_G_OBJECT, gck_object_get_handle (key));
-
-               cred = gck_session_create_object (session, gck_builder_end (&builder), NULL, err);
-
-               if (cred == NULL)
-                       return NULL;
-
-               g_object_unref (cred);
-       }
-
-       /* Do the magic */
-       return gck_session_sign (session, key, mech_type, input, n_input, n_result, NULL, err);
-}
-
-
 static gboolean
-op_sign_request (GkdSshAgentCall *call)
+op_remove_identity (GkdSshAgent *self,
+                   EggBuffer *req,
+                   EggBuffer *resp)
 {
-       GckBuilder builder = GCK_BUILDER_INIT;
-       GckAttributes *attrs;
-       GError *error = NULL;
-       GckObject *key = NULL;
-       const guchar *data;
-       const gchar *salgo;
-       GckSession *session;
-       guchar *result;
-       gsize n_data, n_result;
-       guint32 flags;
-       gsize offset;
-       gboolean ret = FALSE;
-       guint blobpos, sz;
-       guint8 *hash;
-       gulong algo, mech;
-       GChecksumType halgo;
-       gsize n_hash = 0;
-       GQuark oid = 0;
-       gint rv;
-
-       offset = 5;
-
-       /* The key packet size */
-       if (!egg_buffer_get_uint32 (call->req, offset, &offset, &sz))
-               return FALSE;
-
-       /* The key itself */
-       if (!gkd_ssh_agent_proto_read_public (call->req, &offset, &builder, &algo)) {
-               gck_builder_clear (&builder);
-               return FALSE;
-       }
-
-       attrs = gck_attributes_ref_sink (gck_builder_end (&builder));
-
-       /* Validate the key type / mechanism */
-       if (algo == CKK_RSA)
-               mech = CKM_RSA_PKCS;
-       else if (algo == CKK_DSA)
-               mech = CKM_DSA;
-       else if (algo == CKK_EC) {
-               mech = CKM_ECDSA;
-               oid = gkd_ssh_agent_proto_find_curve_oid (attrs);
-               if (!oid)
-                       return FALSE;
-       } else
-               g_return_val_if_reached (FALSE);
-
-       if (!egg_buffer_get_byte_array (call->req, offset, &offset, &data, &n_data) ||
-           !egg_buffer_get_uint32 (call->req, offset, &offset, &flags)) {
-               gck_attributes_unref (attrs);
-               return FALSE;
-       }
-
-       /* Lookup the key */
-       search_keys_like_attributes (call->modules, NULL, attrs, CKO_PUBLIC_KEY, return_private_matching, 
&key);
-       gck_attributes_unref (attrs);
-
-       if (!key) {
-               egg_buffer_add_byte (call->resp, GKD_SSH_RES_FAILURE);
-               return TRUE;
-       }
-
-       /* Usually we hash the data with SHA1 */
-       halgo = G_CHECKSUM_SHA1;
-       if (flags & GKD_SSH_FLAG_OLD_SIGNATURE) {
-               halgo = G_CHECKSUM_MD5;
-       }
-       switch (algo) {
-       case CKK_RSA:
-               /* draft-ietf-curdle-rsa-sha2-12 */
-               if (flags & GKD_SSH_FLAG_RSA_SHA2_256) {
-                       halgo = G_CHECKSUM_SHA256;
-               } else if (flags & GKD_SSH_FLAG_RSA_SHA2_512) {
-                       halgo = G_CHECKSUM_SHA512;
-               }
-               salgo = gkd_ssh_agent_proto_rsa_algo_to_keytype (halgo);
-               break;
-
-       case CKK_EC:
-               /* ECDSA is using SHA-2 hash algorithms based on key size */
-               rv = gkd_ssh_agent_proto_curve_oid_to_hash_algo (oid);
-               if (rv == -1) {
-                       egg_buffer_add_byte (call->resp, GKD_SSH_RES_FAILURE);
-                       return FALSE;
-               }
-               halgo = (GChecksumType) rv;
-               salgo = gkd_ssh_agent_proto_ecc_algo_to_keytype (oid);
-               break;
-
-       case CKK_DSA:
-               /* DSA is using default values */
-               salgo = gkd_ssh_agent_proto_dsa_algo_to_keytype ();
-               break;
-
-       default:
-               g_assert_not_reached ();
-       }
-
-       g_assert (salgo);
+       const guchar *blob;
+       gsize length;
+       gsize offset = 5;
+       GBytes *key = NULL;
+       gboolean ret;
 
-       /* Build the hash */
-       if (mech == CKM_RSA_PKCS)
-               hash = make_pkcs1_sign_hash (halgo, data, n_data, &n_hash);
+       /* 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
-               hash = make_raw_sign_hash (halgo, data, n_data, &n_hash);
-
-       session = gck_object_get_session (key);
-       g_return_val_if_fail (session, FALSE);
-
-       result = unlock_and_sign (session, key, mech, hash, n_hash, &n_result, &error);
-
-       g_object_unref (session);
-       g_object_unref (key);
-       g_free (hash);
-
-       if (error) {
-               if (!g_error_matches (error, GCK_ERROR, CKR_FUNCTION_CANCELED) &&
-                   !g_error_matches (error, GCK_ERROR, CKR_PIN_INCORRECT))
-                       g_message ("signing of the data failed: %s", egg_error_message (error));
-               g_clear_error (&error);
-               egg_buffer_add_byte (call->resp, GKD_SSH_RES_FAILURE);
-               return TRUE;
-       }
-
-       egg_buffer_add_byte (call->resp, GKD_SSH_RES_SIGN_RESPONSE);
-
-       /* Add a space for the sig blob length */
-       blobpos = call->resp->len;
-       egg_buffer_add_uint32 (call->resp, 0);
-
-       egg_buffer_add_string (call->resp, salgo);
-
-       switch (algo) {
-       case CKK_RSA:
-               ret = gkd_ssh_agent_proto_write_signature_rsa (call->resp, result, n_result);
-               break;
-
-       case CKK_DSA:
-               ret = gkd_ssh_agent_proto_write_signature_dsa (call->resp, result, n_result);
-               break;
+               g_warning ("got unparseable remove request for ssh-agent");
 
-       case CKK_EC:
-               ret = gkd_ssh_agent_proto_write_signature_ecdsa (call->resp, result, n_result);
-               break;
-
-       default:
-               g_assert_not_reached ();
-       }
-
-       g_free (result);
-       g_return_val_if_fail (ret, FALSE);
-
-       /* Write back the blob length */
-       egg_buffer_set_uint32 (call->resp, blobpos, (call->resp->len - blobpos) - 4);
-
-       return TRUE;
-}
-
-static gboolean
-op_v1_challenge (GkdSshAgentCall *call)
-{
-       GckBuilder builder = GCK_BUILDER_INIT;
-       gsize offset, n_data, n_result, n_hash;
-       GckSession *session;
-       GckAttributes *attrs;
-       guchar session_id[16];
-       guint8 hash[16];
-       const guchar *data;
-       guchar *result = NULL;
-       GChecksum *checksum;
-       GckObject *key = NULL;
-       guint32 resp_type;
-       GError *error = NULL;
-       guint i;
-       guchar b;
-
-       offset = 5;
-
-       if (!gkd_ssh_agent_proto_read_public_v1 (call->req, &offset, &builder)) {
-               gck_builder_clear (&builder);
-               return FALSE;
-       }
-
-       attrs = gck_attributes_ref_sink (gck_builder_end (&builder));
-
-       /* Read the entire challenge */
-       data = gkd_ssh_agent_proto_read_challenge_v1 (call->req, &offset, &n_data);
-
-       /* Only protocol 1.1 is supported */
-       if (call->req->len <= offset) {
-               gck_attributes_unref (attrs);
-               egg_buffer_add_byte (call->resp, GKD_SSH_RES_FAILURE);
-               return TRUE;
-       }
-
-       /* Read out the session id, raw, unbounded */
-       for (i = 0; i < 16; ++i) {
-               egg_buffer_get_byte (call->req, offset, &offset, &b);
-               session_id[i] = b;
-       }
-
-       /* And the response type */
-       egg_buffer_get_uint32 (call->req, offset, &offset, &resp_type);
-
-       /* Did parsing fail? */
-       if (egg_buffer_has_error (call->req) || data == NULL) {
-               gck_attributes_unref (attrs);
-               return FALSE;
-       }
-
-       /* Not supported request type */
-       if (resp_type != 1) {
-               gck_attributes_unref (attrs);
-               egg_buffer_add_byte (call->resp, GKD_SSH_RES_FAILURE);
-               return TRUE;
-       }
-
-       /* Lookup the key */
-       search_keys_like_attributes (call->modules, NULL, attrs, CKO_PUBLIC_KEY, return_private_matching, 
&key);
-       gck_attributes_unref (attrs);
-
-       /* Didn't find a key? */
-       if (key == NULL) {
-               egg_buffer_add_byte (call->resp, GKD_SSH_RES_FAILURE);
-               return TRUE;
-       }
-
-       session = gck_object_get_session (key);
-       g_return_val_if_fail (session, FALSE);
-
-       result = gck_session_decrypt (session, key, CKM_RSA_PKCS, data, n_data, &n_result, NULL, &error);
-
-       g_object_unref (session);
-       g_object_unref (key);
-
-       if (error) {
-               if (!g_error_matches (error, GCK_ERROR, CKR_FUNCTION_CANCELED))
-                       g_message ("decryption of the data failed: %s", egg_error_message (error));
-               g_clear_error (&error);
-               egg_buffer_add_byte (call->resp, GKD_SSH_RES_FAILURE);
-               return TRUE;
-       }
-
-       /* Now build up a hash of this and the session_id */
-       checksum = g_checksum_new (G_CHECKSUM_MD5);
-       g_checksum_update (checksum, result, n_result);
-       g_checksum_update (checksum, session_id, sizeof (session_id));
-       n_hash = sizeof (hash);
-       g_checksum_get_digest (checksum, hash, &n_hash);
-
-       egg_buffer_add_byte (call->resp, GKD_SSH_RES_RSA_RESPONSE);
-       egg_buffer_append (call->resp, hash, n_hash);
-
-       g_free (result);
-       return TRUE;
-}
-
-static gboolean
-op_remove_identity (GkdSshAgentCall *call)
-{
-       GckBuilder builder = GCK_BUILDER_INIT;
-       GckAttributes *attrs;
-       GckSession *session;
-       GckObject *key = NULL;
-       gsize offset;
-       guint sz;
-
-       offset = 5;
-
-       /* The key packet size */
-       if (!egg_buffer_get_uint32 (call->req, offset, &offset, &sz))
-               return FALSE;
-
-       /* The public key itself */
-       if (!gkd_ssh_agent_proto_read_public (call->req, &offset, &builder, NULL)) {
-               gck_builder_clear (&builder);
-               return FALSE;
+       /* Call out ssh-agent anyway to make sure that the key is removed */
+       ret = gkd_ssh_agent_relay (self, req, resp);
+       if (key) {
+               if (ret)
+                       gkd_ssh_agent_remove_key (self, key);
+               g_bytes_unref (key);
        }
-
-       attrs = gck_attributes_ref_sink (gck_builder_end (&builder));
-
-       /*
-        * This is the session that owns these objects. Only
-        * one thread can use it at a time.
-        */
-
-       session = gkd_ssh_agent_checkout_main_session ();
-       g_return_val_if_fail (session, FALSE);
-
-       search_keys_like_attributes (NULL, session, attrs, CKO_PUBLIC_KEY, return_first_matching, &key);
-       gck_attributes_unref (attrs);
-
-       if (key != NULL) {
-               remove_by_public_key (session, key, TRUE);
-               g_object_unref (key);
-       }
-
-       gkd_ssh_agent_checkin_main_session (session);
-
-       egg_buffer_add_byte (call->resp, GKD_SSH_RES_SUCCESS);
-
-       return TRUE;
-}
-
-static gboolean
-op_v1_remove_identity (GkdSshAgentCall *call)
-{
-       GckBuilder builder = GCK_BUILDER_INIT;
-       GckSession *session;
-       GckAttributes *attrs;
-       GckObject *key = NULL;
-       gsize offset;
-
-       offset = 5;
-
-       if (!gkd_ssh_agent_proto_read_public_v1 (call->req, &offset, &builder)) {
-               gck_builder_clear (&builder);
-               return FALSE;
-       }
-
-       attrs = gck_attributes_ref_sink (gck_builder_end (&builder));
-
-       /*
-        * This is the session that owns these objects. Only
-        * one thread can use it at a time.
-        */
-
-       session = gkd_ssh_agent_checkout_main_session ();
-       g_return_val_if_fail (session, FALSE);
-
-       search_keys_like_attributes (NULL, session, attrs, CKO_PUBLIC_KEY, return_first_matching, &key);
-       gck_attributes_unref (attrs);
-
-       if (key != NULL) {
-               remove_by_public_key (session, key, FALSE);
-               g_object_unref (key);
-       }
-
-       gkd_ssh_agent_checkin_main_session (session);
-
-       egg_buffer_add_byte (call->resp, GKD_SSH_RES_SUCCESS);
-       return TRUE;
-}
-
-static gboolean
-op_remove_all_identities (GkdSshAgentCall *call)
-{
-       GckBuilder builder = GCK_BUILDER_INIT;
-       GckSession *session;
-       GList *objects, *l;
-       GError *error = NULL;
-       GckAttributes *attrs;
-
-       /*
-        * This is the session that owns these objects. Only
-        * one thread can use it at a time.
-        */
-
-       session = gkd_ssh_agent_checkout_main_session ();
-       g_return_val_if_fail (session, FALSE);
-
-       /* Find all session SSH public keys */
-       gck_builder_add_ulong (&builder, CKA_CLASS, CKO_PUBLIC_KEY);
-       attrs = gck_attributes_ref_sink (gck_builder_end (&builder));
-
-       objects = gck_session_find_objects (session, attrs, NULL, &error);
-       gck_attributes_unref (attrs);
-
-       if (error) {
-               g_warning ("couldn't search for keys to remove: %s", egg_error_message (error));
-               g_clear_error (&error);
-
-       } else {
-               for (l = objects; l; l = g_list_next (l))
-                       remove_by_public_key (session, l->data, TRUE);
-               gck_list_unref_free (objects);
-       }
-
-       gkd_ssh_agent_checkin_main_session (session);
-
-       egg_buffer_add_byte (call->resp, GKD_SSH_RES_SUCCESS);
-       return TRUE;
-}
-
-static gboolean
-op_v1_remove_all_identities (GkdSshAgentCall *call)
-{
-       GckBuilder builder = GCK_BUILDER_INIT;
-       GckSession *session;
-       GList *objects, *l;
-       GError *error = NULL;
-       GckAttributes *attrs;
-
-       /*
-        * This is the session that owns these objects. Only
-        * one thread can use it at a time.
-        */
-
-       session = gkd_ssh_agent_checkout_main_session ();
-       g_return_val_if_fail (session, FALSE);
-
-       /* Find all session SSH v1 public keys */
-       gck_builder_add_ulong (&builder, CKA_CLASS, CKO_PUBLIC_KEY);
-       gck_builder_add_boolean (&builder, CKA_TOKEN, FALSE);
-       gck_builder_add_string (&builder, CKA_LABEL, V1_LABEL);
-       attrs = gck_attributes_ref_sink (gck_builder_end (&builder));
-
-       objects = gck_session_find_objects (session, attrs, NULL, &error);
-       gck_attributes_unref (attrs);
-
-       if (error) {
-               g_warning ("couldn't search for keys to remove: %s", egg_error_message (error));
-               g_clear_error (&error);
-
-       } else {
-               for (l = objects; l; l = g_list_next (l))
-                       remove_by_public_key (session, l->data, FALSE);
-               gck_list_unref_free (objects);
-       }
-
-       gkd_ssh_agent_checkin_main_session (session);
-
-       egg_buffer_add_byte (call->resp, GKD_SSH_RES_SUCCESS);
-       return TRUE;
+       return ret;
 }
 
 static gboolean
-op_not_implemented_success (GkdSshAgentCall *call)
+op_remove_all_identities (GkdSshAgent *self,
+                         EggBuffer *req,
+                         EggBuffer *resp)
 {
-       egg_buffer_add_byte (call->resp, GKD_SSH_RES_SUCCESS);
-       return TRUE;
-}
+       gboolean ret;
 
-static gboolean
-op_not_implemented_failure (GkdSshAgentCall *call)
-{
-       egg_buffer_add_byte (call->resp, GKD_SSH_RES_FAILURE);
-       return TRUE;
-}
+       ret = gkd_ssh_agent_relay (self, req, resp);
+       if (ret)
+               gkd_ssh_agent_clear_keys (self);
 
-static gboolean
-op_invalid (GkdSshAgentCall *call)
-{
-       /* Invalid request, disconnect immediately */
-       return FALSE;
+       return ret;
 }
 
 const GkdSshAgentOperation gkd_ssh_agent_operations[GKD_SSH_OP_MAX] = {
-     op_invalid,                                 /* 0 */
-     op_v1_request_identities,                   /* GKR_SSH_OP_REQUEST_RSA_IDENTITIES */
-     op_invalid,                                 /* 2 */
-     op_v1_challenge,                            /* GKR_SSH_OP_RSA_CHALLENGE */
-     op_invalid,                                 /* 4 */
-     op_invalid,                                 /* 5 */
-     op_invalid,                                 /* 6 */
-     op_v1_add_identity,                         /* GKR_SSH_OP_ADD_RSA_IDENTITY */
-     op_v1_remove_identity,                      /* GKR_SSH_OP_REMOVE_RSA_IDENTITY */
-     op_v1_remove_all_identities,                /* GKR_SSH_OP_REMOVE_ALL_RSA_IDENTITIES */
-     op_invalid,                                 /* 10 */
-     op_request_identities,                      /* GKR_SSH_OP_REQUEST_IDENTITIES */
-     op_invalid,                                 /* 12 */
-     op_sign_request,                            /* GKR_SSH_OP_SIGN_REQUEST */
-     op_invalid,                                 /* 14 */
-     op_invalid,                                 /* 15 */
-     op_invalid,                                 /* 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 */
-     op_not_implemented_failure,                 /* GKR_SSH_OP_ADD_SMARTCARD_KEY */
-     op_not_implemented_failure,                 /* GKR_SSH_OP_REMOVE_SMARTCARD_KEY */
-     op_not_implemented_success,                 /* GKR_SSH_OP_LOCK */
-     op_not_implemented_success,                 /* GKR_SSH_OP_UNLOCK */
-     op_v1_add_identity,                         /* GKR_SSH_OP_ADD_RSA_ID_CONSTRAINED */
-     op_add_identity,                            /* GKR_SSH_OP_ADD_ID_CONSTRAINED */
-     op_not_implemented_failure,                 /* GKR_SSH_OP_ADD_SMARTCARD_KEY_CONSTRAINED */
+       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/daemon/ssh-agent/gkd-ssh-agent-preload.c b/daemon/ssh-agent/gkd-ssh-agent-preload.c
new file mode 100644
index 0000000..be7a0ba
--- /dev/null
+++ b/daemon/ssh-agent/gkd-ssh-agent-preload.c
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2014 Stef 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 thewalter net>
+ */
+
+#include "config.h"
+
+#include "gkd-ssh-agent-preload.h"
+#include "gkd-ssh-openssh.h"
+
+#include "egg/egg-file-tracker.h"
+
+enum {
+       PROP_0,
+       PROP_PATH
+};
+
+struct _GkdSshAgentPreload
+{
+       GObject object;
+
+       gchar *path;
+       GHashTable *keys_by_filename;
+       GHashTable *keys_by_public_key;
+       EggFileTracker *file_tracker;
+       GMutex lock;
+};
+
+G_DEFINE_TYPE (GkdSshAgentPreload, gkd_ssh_agent_preload, G_TYPE_OBJECT);
+
+void
+gkd_ssh_agent_key_info_free (gpointer boxed)
+{
+       GkdSshAgentKeyInfo *info = boxed;
+       if (!info)
+               return;
+       g_bytes_unref (info->public_key);
+       g_free (info->comment);
+       g_free (info->filename);
+       g_free (info);
+}
+
+gpointer
+gkd_ssh_agent_key_info_copy (gpointer boxed)
+{
+       GkdSshAgentKeyInfo *info = boxed;
+       GkdSshAgentKeyInfo *copy = g_new0 (GkdSshAgentKeyInfo, 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
+gkd_ssh_agent_preload_init (GkdSshAgentPreload *self)
+{
+       self->keys_by_filename = g_hash_table_new (g_str_hash, g_str_equal);
+       self->keys_by_public_key = g_hash_table_new_full (g_bytes_hash, g_bytes_equal, NULL, 
gkd_ssh_agent_key_info_free);
+}
+
+static void
+gkd_ssh_agent_preload_constructed (GObject *object)
+{
+       GkdSshAgentPreload *self = GKD_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_OBJECT_CLASS (gkd_ssh_agent_preload_parent_class)->constructed (object);
+}
+
+static void
+gkd_ssh_agent_preload_set_property (GObject      *object,
+                                    guint         prop_id,
+                                    const GValue *value,
+                                    GParamSpec   *pspec)
+{
+       GkdSshAgentPreload *self = GKD_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
+gkd_ssh_agent_preload_finalize (GObject *object)
+{
+       GkdSshAgentPreload *self = GKD_SSH_AGENT_PRELOAD (object);
+
+       g_free (self->path);
+       g_clear_pointer (&self->keys_by_public_key, (GDestroyNotify) g_hash_table_unref);
+       g_clear_pointer (&self->keys_by_filename, (GDestroyNotify) g_hash_table_unref);
+       g_clear_object (&self->file_tracker);
+
+       g_mutex_clear (&self->lock);
+
+       G_OBJECT_CLASS (gkd_ssh_agent_preload_parent_class)->finalize (object);
+}
+
+static void
+gkd_ssh_agent_preload_class_init (GkdSshAgentPreloadClass *klass)
+{
+       GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+       gobject_class->constructed = gkd_ssh_agent_preload_constructed;
+       gobject_class->set_property = gkd_ssh_agent_preload_set_property;
+       gobject_class->finalize = gkd_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)
+{
+       GkdSshAgentPreload *self = GKD_SSH_AGENT_PRELOAD (user_data);
+       GkdSshAgentKeyInfo *info;
+
+       info = g_hash_table_lookup (self->keys_by_filename, path);
+       if (info) {
+               g_hash_table_remove (self->keys_by_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)
+{
+       GkdSshAgentPreload *self = GKD_SSH_AGENT_PRELOAD (user_data);
+       gchar *private_path;
+       GBytes *private_bytes;
+       GBytes *public_bytes;
+       GBytes *public_key;
+       GkdSshAgentKeyInfo *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 = gkd_ssh_openssh_parse_public_key (public_bytes, &comment);
+               if (public_key) {
+                       info = g_new0 (GkdSshAgentKeyInfo, 1);
+                       info->filename = private_path;
+                       private_path = NULL;
+                       info->public_key = public_key;
+                       info->comment = comment;
+                       g_hash_table_replace (self->keys_by_filename, info->filename, 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);
+}
+
+GkdSshAgentPreload *
+gkd_ssh_agent_preload_new (const gchar *path)
+{
+       g_return_val_if_fail (path, NULL);
+
+       return g_object_new (GKD_TYPE_SSH_AGENT_PRELOAD, "path", path, NULL);
+}
+
+GList *
+gkd_ssh_agent_preload_get_keys (GkdSshAgentPreload *self)
+{
+       GList *keys = NULL;
+       GHashTableIter iter;
+       GkdSshAgentKeyInfo *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, gkd_ssh_agent_key_info_copy (info));
+
+       g_mutex_unlock (&self->lock);
+
+       return keys;
+}
+
+GkdSshAgentKeyInfo *
+gkd_ssh_agent_preload_lookup_by_public_key (GkdSshAgentPreload *self,
+                                           GBytes *public_key)
+{
+       GkdSshAgentKeyInfo *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 = gkd_ssh_agent_key_info_copy (info);
+
+       g_mutex_unlock (&self->lock);
+
+       return info;
+}
diff --git a/daemon/ssh-agent/gkd-ssh-agent-preload.h b/daemon/ssh-agent/gkd-ssh-agent-preload.h
new file mode 100644
index 0000000..28e40e1
--- /dev/null
+++ b/daemon/ssh-agent/gkd-ssh-agent-preload.h
@@ -0,0 +1,50 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2014 Stef Walter
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Author: Stef Walter <stef thewalter net>
+ */
+
+#ifndef __GKD_SSH_AGENT_PRELOAD_H__
+#define __GKD_SSH_AGENT_PRELOAD_H__
+
+#include <glib-object.h>
+
+typedef struct {
+       gchar *filename;
+       GBytes *public_key;
+       gchar *comment;
+} GkdSshAgentKeyInfo;
+
+void                gkd_ssh_agent_key_info_free    (gpointer            boxed);
+gpointer            gkd_ssh_agent_key_info_copy    (gpointer            boxed);
+
+#define GKD_TYPE_SSH_AGENT_PRELOAD gkd_ssh_agent_preload_get_type ()
+G_DECLARE_FINAL_TYPE (GkdSshAgentPreload, gkd_ssh_agent_preload, GKD, SSH_AGENT_PRELOAD, GObject)
+
+GkdSshAgentPreload *gkd_ssh_agent_preload_new      (const gchar        *path);
+
+GList              *gkd_ssh_agent_preload_get_keys (GkdSshAgentPreload *self);
+
+GkdSshAgentKeyInfo *gkd_ssh_agent_preload_lookup_by_public_key
+                                                   (GkdSshAgentPreload *self,
+                                                    GBytes             *public_key);
+
+#endif /* __GKD_SSH_AGENT_PRELOAD_H__ */
+
diff --git a/daemon/ssh-agent/gkd-ssh-agent-private.h b/daemon/ssh-agent/gkd-ssh-agent-private.h
index a6c35a4..fe76dbc 100644
--- a/daemon/ssh-agent/gkd-ssh-agent-private.h
+++ b/daemon/ssh-agent/gkd-ssh-agent-private.h
@@ -23,21 +23,12 @@
 #ifndef GKDSSHPRIVATE_H_
 #define GKDSSHPRIVATE_H_
 
-#include "egg/egg-buffer.h"
-
-#include "pkcs11/pkcs11.h"
+#include "gkd-ssh-agent.h"
 
-#include <gck/gck.h>
+#include "egg/egg-buffer.h"
 
 #include <glib.h>
 
-typedef struct _GkdSshAgentCall {
-       int sock;
-       GList *modules;
-       EggBuffer *req;
-       EggBuffer *resp;
-} GkdSshAgentCall;
-
 /* -----------------------------------------------------------------------------
  * SSH OPERATIONS and CONSTANTS
  */
@@ -86,137 +77,29 @@ typedef struct _GkdSshAgentCall {
  * gkd-ssh-agent-ops.c
  */
 
-typedef gboolean (*GkdSshAgentOperation) (GkdSshAgentCall *call);
+typedef gboolean (*GkdSshAgentOperation) (GkdSshAgent *agent, EggBuffer *req, EggBuffer *resp);
 extern const GkdSshAgentOperation gkd_ssh_agent_operations[GKD_SSH_OP_MAX];
 
 /* -----------------------------------------------------------------------------
  * gkd-ssh-agent.c
  */
 
-gboolean              gkd_ssh_agent_initialize_with_module          (GckModule *module);
-
-GckSession*           gkd_ssh_agent_checkout_main_session           (void);
-
-void                  gkd_ssh_agent_checkin_main_session            (GckSession* session);
+#define GKD_SSH_INTERACTION_KEY_INFO_QDATA gkd_ssh_interaction_key_info_quark ()
+GQuark                gkd_ssh_interaction_key_info_quark            (void);
 
 /* -----------------------------------------------------------------------------
- * gkd-ssh-agent-proto.c
+ * gkd-ssh-agent-process.c
  */
 
-gulong                gkd_ssh_agent_proto_keytype_to_algo           (const gchar *salgo);
-
-const gchar*          gkd_ssh_agent_proto_rsa_algo_to_keytype       (GChecksumType halgo);
-
-const gchar*          gkd_ssh_agent_proto_dsa_algo_to_keytype       (void);
-
-const gchar*          gkd_ssh_agent_proto_ecc_algo_to_keytype       (GQuark oid);
-
-GQuark                gkd_ssh_agent_proto_curve_to_oid              (const gchar *salgo);
-
-const gchar*          gkd_ssh_agent_proto_oid_to_curve              (GQuark oid);
-
-gint                  gkd_ssh_agent_proto_curve_oid_to_hash_algo    (GQuark oid);
-
-GQuark                gkd_ssh_agent_proto_find_curve_oid            (GckAttributes *attrs);
-
-gboolean              gkd_ssh_agent_proto_read_mpi                  (EggBuffer *req,
-                                                                     gsize *offset,
-                                                                     GckBuilder *attrs,
-                                                                     CK_ATTRIBUTE_TYPE type);
-
-gboolean              gkd_ssh_agent_proto_read_mpi_v1               (EggBuffer *req,
-                                                                     gsize *offset,
-                                                                     GckBuilder *attrs,
-                                                                     CK_ATTRIBUTE_TYPE type);
-
-const guchar*         gkd_ssh_agent_proto_read_challenge_v1         (EggBuffer *req,
-                                                                     gsize *offset,
-                                                                     gsize *n_challenge);
-
-gboolean              gkd_ssh_agent_proto_read_string_to_der        (EggBuffer *req,
-                                                                     gsize *offset,
-                                                                     GckBuilder *attrs,
-                                                                     CK_ATTRIBUTE_TYPE type);
-
-gboolean              gkd_ssh_agent_proto_read_ecdsa_curve          (EggBuffer *req,
-                                                                     gsize *offset,
-                                                                     GckBuilder *attrs);
-
-gboolean              gkd_ssh_agent_proto_write_mpi                 (EggBuffer *resp,
-                                                                     const GckAttribute *attr);
-
-gboolean              gkd_ssh_agent_proto_write_mpi_v1              (EggBuffer *resp,
-                                                                     const GckAttribute *attr);
-
-gboolean              gkd_ssh_agent_proto_write_string              (EggBuffer *resp,
-                                                                     const GckAttribute *attr);
-
-gboolean              gkd_ssh_agent_proto_read_public               (EggBuffer *req,
-                                                                     gsize *offset,
-                                                                     GckBuilder *attrs,
-                                                                     gulong *algo);
-
-gboolean              gkd_ssh_agent_proto_read_public_rsa           (EggBuffer *req,
-                                                                     gsize *offset,
-                                                                     GckBuilder *attrs);
-
-gboolean              gkd_ssh_agent_proto_read_public_dsa           (EggBuffer *req,
-                                                                     gsize *offset,
-                                                                     GckBuilder *attrs);
-
-gboolean              gkd_ssh_agent_proto_read_public_ecdsa         (EggBuffer *req,
-                                                                     gsize *offset,
-                                                                     GckBuilder *attrs);
-
-gboolean              gkd_ssh_agent_proto_read_public_v1            (EggBuffer *req,
-                                                                     gsize *offset,
-                                                                     GckBuilder *attrs);
-
-gboolean              gkd_ssh_agent_proto_read_pair_rsa             (EggBuffer *req,
-                                                                     gsize *offset,
-                                                                     GckBuilder *priv,
-                                                                     GckBuilder *pub);
-
-gboolean              gkd_ssh_agent_proto_read_pair_dsa             (EggBuffer *req,
-                                                                     gsize *offset,
-                                                                     GckBuilder *priv,
-                                                                     GckBuilder *pub);
-
-gboolean              gkd_ssh_agent_proto_read_pair_ecdsa           (EggBuffer *req,
-                                                                     gsize *offset,
-                                                                     GckBuilder *priv,
-                                                                     GckBuilder *pub);
-
-gboolean              gkd_ssh_agent_proto_read_pair_v1              (EggBuffer *req,
-                                                                     gsize *offset,
-                                                                     GckBuilder *priv,
-                                                                     GckBuilder *pub);
-
-gboolean              gkd_ssh_agent_proto_write_public              (EggBuffer *resp,
-                                                                     GckAttributes *attrs);
-
-gboolean              gkd_ssh_agent_proto_write_public_rsa          (EggBuffer *resp,
-                                                                     GckAttributes *attrs);
-
-gboolean              gkd_ssh_agent_proto_write_public_dsa          (EggBuffer *resp,
-                                                                     GckAttributes *attrs);
-
-gboolean              gkd_ssh_agent_proto_write_public_ecdsa        (EggBuffer *resp,
-                                                                     GckAttributes *attrs);
-
-gboolean              gkd_ssh_agent_proto_write_public_v1           (EggBuffer *resp,
-                                                                     GckAttributes *attrs);
-
-gboolean              gkd_ssh_agent_proto_write_signature_rsa       (EggBuffer *resp,
-                                                                     CK_BYTE_PTR signature,
-                                                                     CK_ULONG n_signature);
+gboolean              gkd_ssh_agent_read_packet                     (gint fd,
+                                                                     EggBuffer *buffer);
 
-gboolean              gkd_ssh_agent_proto_write_signature_dsa       (EggBuffer *resp,
-                                                                     CK_BYTE_PTR signature,
-                                                                     CK_ULONG n_signature);
+gboolean              gkd_ssh_agent_write_packet                    (gint fd,
+                                                                     EggBuffer *buffer);
 
-gboolean              gkd_ssh_agent_proto_write_signature_ecdsa     (EggBuffer *resp,
-                                                                     CK_BYTE_PTR signature,
-                                                                     CK_ULONG n_signature);
+gboolean              gkd_ssh_agent_write_all                       (int fd,
+                                                                     const guchar *buf,
+                                                                     int len,
+                                                                     const gchar *where);
 
 #endif /*GKDSSHPRIVATE_H_*/
diff --git a/daemon/ssh-agent/gkd-ssh-agent-process.c b/daemon/ssh-agent/gkd-ssh-agent-process.c
new file mode 100644
index 0000000..6a6707b
--- /dev/null
+++ b/daemon/ssh-agent/gkd-ssh-agent-process.c
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2014 Stef 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 thewalter net>
+ */
+
+#include "config.h"
+
+#include "gkd-ssh-agent-process.h"
+#include "gkd-ssh-agent-private.h"
+
+#include <gio/gunixsocketaddress.h>
+#include <glib-unix.h>
+#include <glib/gstdio.h>
+
+enum {
+       PROP_0,
+       PROP_PATH
+};
+
+struct _GkdSshAgentProcess
+{
+       GObject object;
+       gchar *path;
+       GSocket *socket;
+       gint output;
+       GMutex lock;
+       GPid pid;
+       guint output_id;
+       guint child_id;
+       gboolean ready;
+};
+
+G_DEFINE_TYPE (GkdSshAgentProcess, gkd_ssh_agent_process, G_TYPE_OBJECT);
+
+static void
+gkd_ssh_agent_process_init (GkdSshAgentProcess *self)
+{
+       self->output = -1;
+       g_mutex_init (&self->lock);
+}
+
+static void
+gkd_ssh_agent_process_finalize (GObject *object)
+{
+       GkdSshAgentProcess *self = GKD_SSH_AGENT_PROCESS (object);
+
+       g_clear_object (&self->socket);
+       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 (gkd_ssh_agent_process_parent_class)->finalize (object);
+}
+
+static void
+gkd_ssh_agent_process_set_property (GObject      *object,
+                                    guint         prop_id,
+                                    const GValue *value,
+                                    GParamSpec   *pspec)
+{
+       GkdSshAgentProcess *self = GKD_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
+gkd_ssh_agent_process_class_init (GkdSshAgentProcessClass *klass)
+{
+       GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+       gobject_class->finalize = gkd_ssh_agent_process_finalize;
+       gobject_class->set_property = gkd_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));
+}
+
+static gboolean
+read_all (int fd, guchar *buf, int len)
+{
+       int all = len;
+       int res;
+
+       while (len > 0) {
+
+               res = read (fd, buf, len);
+
+               if (res < 0) {
+                       if (errno == EAGAIN || errno == EINTR)
+                               continue;
+                       g_warning ("couldn't read %u bytes from client: %s", all,
+                                  g_strerror (errno));
+                       return FALSE;
+               } else if (res == 0) {
+                       return FALSE;
+               } else  {
+                       len -= res;
+                       buf += res;
+               }
+       }
+
+       return TRUE;
+}
+
+static gboolean
+write_all (int fd,
+          const guchar *buf,
+          int len,
+          const gchar *where)
+{
+       int all = len;
+       int res;
+
+       while (len > 0) {
+
+               res = write (fd, buf, len);
+               if (res < 0) {
+                       if (errno == EAGAIN || errno == EINTR)
+                               continue;
+                       if (errno != EPIPE) {
+                               g_warning ("couldn't write %u bytes to %s: %s", all,
+                                          where, g_strerror (errno));
+                       }
+                       return FALSE;
+               } else if (res == 0) {
+                       g_warning ("couldn't write %u bytes to %s", all, where);
+                       return FALSE;
+               } else  {
+                       len -= res;
+                       buf += res;
+               }
+       }
+
+       return TRUE;
+}
+
+static void
+on_child_watch (GPid pid,
+                gint status,
+                gpointer user_data)
+{
+       GkdSshAgentProcess *self = GKD_SSH_AGENT_PROCESS (user_data);
+       GError *error = NULL;
+
+       if (pid != self->pid)
+               return;
+
+       g_mutex_lock (&self->lock);
+
+       self->pid = 0;
+
+       if (!g_spawn_check_exit_status (status, &error)) {
+               g_message ("ssh-agent: %s", error->message);
+               g_error_free (error);
+       }
+
+       g_mutex_unlock (&self->lock);
+}
+
+static gboolean
+on_output_watch (gint fd,
+                GIOCondition condition,
+                gpointer user_data)
+{
+       GkdSshAgentProcess *self = GKD_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 (GkdSshAgentProcess *self)
+{
+       const gchar *argv[] = { SSH_AGENT, "-D", "-a", self->path, NULL };
+       GError *error = 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)) {
+               g_warning ("couldn't run %s: %s", SSH_AGENT, error->message);
+               g_error_free (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;
+}
+
+gboolean
+gkd_ssh_agent_process_connect (GkdSshAgentProcess  *self)
+{
+       gboolean started = FALSE;
+       gboolean timedout = FALSE;
+       guint source;
+       GSocket *sock;
+       GSocketAddress *addr;
+       GError *error;
+
+       g_mutex_lock (&self->lock);
+
+       if (self->pid == 0 || kill (self->pid, 0) != 0) {
+               if (!agent_start_inlock (self)) {
+                       g_mutex_unlock (&self->lock);
+                       return FALSE;
+               }
+               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);
+               return FALSE;
+       }
+
+       error = NULL;
+       sock = g_socket_new (G_SOCKET_FAMILY_UNIX,
+                            G_SOCKET_TYPE_STREAM,
+                            G_SOCKET_PROTOCOL_DEFAULT,
+                            &error);
+       if (!sock) {
+               g_warning ("couldn't create socket: %s", error->message);
+               g_error_free (error);
+               g_mutex_unlock (&self->lock);
+               return FALSE;
+       }
+
+       addr = g_unix_socket_address_new (self->path);
+
+       error = NULL;
+       if (!g_socket_connect (sock, addr, NULL, &error)) {
+               g_warning ("couldn't connect to socket at: %s: %s",
+                          g_unix_socket_address_get_path (G_UNIX_SOCKET_ADDRESS (addr)),
+                          error->message);
+               g_error_free (error);
+               g_mutex_unlock (&self->lock);
+               g_object_unref (addr);
+               g_object_unref (sock);
+               return FALSE;
+       }
+
+       g_object_unref (addr);
+       self->socket = sock;
+       g_mutex_unlock (&self->lock);
+
+       return TRUE;
+}
+
+gboolean
+gkd_ssh_agent_read_packet (gint fd,
+                           EggBuffer *buffer)
+{
+       guint32 packet_size;
+
+       egg_buffer_reset (buffer);
+       egg_buffer_resize (buffer, 4);
+       if (!read_all (fd, buffer->buf, 4))
+               return FALSE;
+
+       if (!egg_buffer_get_uint32 (buffer, 0, NULL, &packet_size) ||
+           packet_size < 1) {
+               g_warning ("invalid packet size from client");
+               return FALSE;
+       }
+
+       egg_buffer_resize (buffer, packet_size + 4);
+       if (!read_all (fd, buffer->buf + 4, packet_size))
+               return FALSE;
+
+       return TRUE;
+}
+
+gboolean
+gkd_ssh_agent_write_packet (gint fd,
+                            EggBuffer *buffer)
+{
+       if (!egg_buffer_set_uint32 (buffer, 0, buffer->len - 4))
+               g_return_val_if_reached (FALSE);
+       return write_all (fd, buffer->buf, buffer->len, "client");
+}
+
+gboolean
+gkd_ssh_agent_process_call (GkdSshAgentProcess *self,
+                            EggBuffer          *req,
+                            EggBuffer          *resp)
+{
+       g_return_val_if_fail (self->socket != NULL, FALSE);
+       return gkd_ssh_agent_write_packet (g_socket_get_fd (self->socket), req) &&
+               gkd_ssh_agent_read_packet (g_socket_get_fd (self->socket), resp);
+}
+
+GkdSshAgentProcess *
+gkd_ssh_agent_process_new (const gchar *path)
+{
+       g_return_val_if_fail (path, NULL);
+
+       return g_object_new (GKD_TYPE_SSH_AGENT_PROCESS, "path", path, NULL);
+}
diff --git a/daemon/ssh-agent/gkd-ssh-agent-process.h b/daemon/ssh-agent/gkd-ssh-agent-process.h
new file mode 100644
index 0000000..805b6aa
--- /dev/null
+++ b/daemon/ssh-agent/gkd-ssh-agent-process.h
@@ -0,0 +1,47 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2014 Stef Walter
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Author: Stef Walter <stef thewalter net>
+ */
+
+#ifndef __GKD_SSH_AGENT_PROCESS_H__
+#define __GKD_SSH_AGENT_PROCESS_H__
+
+#include <glib-object.h>
+
+#include "egg/egg-buffer.h"
+
+#define GKD_TYPE_SSH_AGENT_PROCESS gkd_ssh_agent_process_get_type ()
+G_DECLARE_FINAL_TYPE(GkdSshAgentProcess, gkd_ssh_agent_process, GKD, SSH_AGENT_PROCESS, GObject)
+
+GkdSshAgentProcess *gkd_ssh_agent_process_new         (const gchar        *path);
+gboolean            gkd_ssh_agent_process_connect     (GkdSshAgentProcess *self);
+gboolean            gkd_ssh_agent_process_call        (GkdSshAgentProcess *self,
+                                                       EggBuffer          *req,
+                                                       EggBuffer          *resp);
+gboolean            gkd_ssh_agent_process_lookup_key  (GkdSshAgentProcess *self,
+                                                       GBytes             *key);
+void                gkd_ssh_agent_process_add_key     (GkdSshAgentProcess *self,
+                                                       GBytes             *key);
+void                gkd_ssh_agent_process_remove_key  (GkdSshAgentProcess *self,
+                                                       GBytes             *key);
+void                gkd_ssh_agent_process_clear_keys  (GkdSshAgentProcess *self);
+
+#endif /* __GKD_SSH_AGENT_PROCESS_H__ */
diff --git a/daemon/ssh-agent/gkd-ssh-agent.c b/daemon/ssh-agent/gkd-ssh-agent.c
index 2a53416..8ae33e0 100644
--- a/daemon/ssh-agent/gkd-ssh-agent.c
+++ b/daemon/ssh-agent/gkd-ssh-agent.c
@@ -22,430 +22,464 @@
 
 #include "config.h"
 
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/un.h>
+#include <gio/gunixsocketaddress.h>
+#include <glib/gstdio.h>
 
-#include <errno.h>
-#include <fcntl.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
+#include <gcr/gcr-base.h>
 
 #include "gkd-ssh-agent.h"
+#include "gkd-ssh-agent-preload.h"
 #include "gkd-ssh-agent-private.h"
+#include "gkd-ssh-agent-process.h"
+#include "daemon/login/gkd-login-interaction.h"
 
 #include "egg/egg-buffer.h"
 #include "egg/egg-error.h"
 #include "egg/egg-secure-memory.h"
 
-#ifndef HAVE_SOCKLEN_T
-typedef int socklen_t;
-#endif
-
-/* The loaded PKCS#11 modules */
-static GList *pkcs11_modules = NULL;
+#include <glib/gi18n-lib.h>
 
 EGG_SECURE_DECLARE (ssh_agent);
 
-static gboolean
-read_all (int fd, guchar *buf, int len)
+enum {
+       PROP_0,
+       PROP_PATH,
+       PROP_INTERACTION,
+       PROP_PRELOAD
+};
+
+struct _GkdSshAgent
 {
-       int all = len;
-       int res;
-
-       while (len > 0) {
-
-               res = read (fd, buf, len);
-
-               if (res < 0) {
-                       if (errno == EAGAIN || errno == EINTR)
-                               continue;
-                       g_warning ("couldn't read %u bytes from client: %s", all,
-                                  g_strerror (errno));
-                       return FALSE;
-               } else if (res == 0) {
-                       return FALSE;
-               } else  {
-                       len -= res;
-                       buf += res;
-               }
-       }
+       GObject object;
 
-       return TRUE;
+       gchar *path;
+       GTlsInteraction *interaction;
+       GkdSshAgentPreload *preload;
+
+       GkdSshAgentProcess *process;
+       GSocket *socket;
+       GList *clients;
+       GHashTable *keys;
+       GMutex lock;
+};
+
+G_DEFINE_TYPE (GkdSshAgent, gkd_ssh_agent, G_TYPE_OBJECT);
+
+typedef struct _Client {
+       GThread *thread;
+       GSocket *socket;
+       GkdSshAgent *self;
+} Client;
+
+static void
+gkd_ssh_agent_init (GkdSshAgent *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 gboolean
-write_all (int fd, const guchar *buf, int len)
+static void
+gkd_ssh_agent_constructed (GObject *object)
 {
-       int all = len;
-       int res;
-
-       while (len > 0) {
-
-               res = write (fd, buf, len);
-               if (res < 0) {
-                       if (errno == EAGAIN || errno == EINTR)
-                               continue;
-                       if (errno != EPIPE)
-                               g_warning ("couldn't write %u bytes to client: %s", all,
-                                          g_strerror (errno));
-                       return FALSE;
-               } else if (res == 0) {
-                       g_warning ("couldn't write %u bytes to client", all);
-                       return FALSE;
-               } else  {
-                       len -= res;
-                       buf += res;
-               }
-       }
+       GkdSshAgent *self = GKD_SSH_AGENT (object);
+       gchar *path;
 
-       return TRUE;
+       path = g_strdup_printf ("%s/.ssh", self->path);
+       self->process = gkd_ssh_agent_process_new (path);
+       g_free (path);
+
+       G_OBJECT_CLASS (gkd_ssh_agent_parent_class)->constructed (object);
 }
 
-static gboolean
-read_packet_with_size (GkdSshAgentCall *call)
+static void
+gkd_ssh_agent_set_property (GObject      *object,
+                            guint         prop_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
 {
-       int fd;
-       guint32 packet_size;
+       GkdSshAgent *self = GKD_SSH_AGENT (object);
+
+       switch (prop_id)
+       {
+       case PROP_PATH:
+               self->path = g_value_dup_string (value);
+               break;
+       case PROP_INTERACTION:
+               self->interaction = g_value_dup_object (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;
+       }
+}
 
-       fd = call->sock;
+static void
+gkd_ssh_agent_finalize (GObject *object)
+{
+       GkdSshAgent *self = GKD_SSH_AGENT (object);
 
-       egg_buffer_resize (call->req, 4);
-       if (!read_all (fd, call->req->buf, 4))
-               return FALSE;
+       gkd_ssh_agent_shutdown (self);
 
-       if (!egg_buffer_get_uint32 (call->req, 0, NULL, &packet_size) ||
-           packet_size < 1) {
-               g_warning ("invalid packet size from client");
-               return FALSE;
-       }
+       g_free (self->path);
+       g_object_set_qdata (G_OBJECT (self->interaction),
+                           GKD_SSH_INTERACTION_KEY_INFO_QDATA,
+                           NULL);
+       g_object_unref (self->interaction);
+       g_object_unref (self->preload);
+
+       g_clear_object (&self->process);
+       g_clear_object (&self->socket);
+
+       g_list_free (self->clients);
+       self->clients = NULL;
+
+       g_mutex_clear (&self->lock);
+       g_hash_table_unref (self->keys);
+
+       G_OBJECT_CLASS (gkd_ssh_agent_parent_class)->finalize (object);
+}
+
+static void
+gkd_ssh_agent_class_init (GkdSshAgentClass *klass)
+{
+       GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+       gobject_class->constructed = gkd_ssh_agent_constructed;
+       gobject_class->set_property = gkd_ssh_agent_set_property;
+       gobject_class->finalize = gkd_ssh_agent_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_INTERACTION,
+                                        g_param_spec_object ("interaction", "Interaction", "Interaction",
+                                                             G_TYPE_TLS_INTERACTION,
+                                                             G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
+       g_object_class_install_property (gobject_class, PROP_PRELOAD,
+                                        g_param_spec_object ("preload", "Preload", "Preload",
+                                                             GKD_TYPE_SSH_AGENT_PRELOAD,
+                                                             G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
+}
+
+gboolean
+gkd_ssh_agent_relay (GkdSshAgent *self, EggBuffer *req, EggBuffer *resp)
+{
+       return gkd_ssh_agent_process_call (self->process, req, resp);
+}
 
-       egg_buffer_resize (call->req, packet_size + 4);
-       if (!read_all (fd, call->req->buf + 4, packet_size))
+gboolean
+gkd_ssh_agent_handle (GkdSshAgent *self, EggBuffer *req, EggBuffer *resp)
+{
+       GkdSshAgentOperation func;
+       guchar op;
+
+       /* Decode the operation */
+       if (!egg_buffer_get_byte (req, 4, NULL, &op))
                return FALSE;
 
-       return TRUE;
+       /* Execute the right operation */
+       egg_buffer_reset (resp);
+       egg_buffer_add_uint32 (resp, 0);
+       if (op >= GKD_SSH_OP_MAX || gkd_ssh_agent_operations[op])
+               func = gkd_ssh_agent_operations[op];
+       else
+               func = gkd_ssh_agent_relay;
+       return func (self, req, resp);
 }
 
 static gpointer
 run_client_thread (gpointer data)
 {
-       gint *socket = data;
-       GkdSshAgentCall call;
+       Client *client = data;
+       GSocket *socket;
+       gint fd;
        EggBuffer req;
        EggBuffer resp;
-       guchar op;
 
-       memset (&call, 0, sizeof (call));
-       call.sock = g_atomic_int_get (socket);
-       g_assert (call.sock != -1);
+       socket = g_atomic_pointer_get (&client->socket);
+       g_assert (socket != NULL);
+
+       fd = g_socket_get_fd (socket);
 
        egg_buffer_init_full (&req, 128, egg_secure_realloc);
        egg_buffer_init_full (&resp, 128, (EggBufferAllocator)g_realloc);
-       call.req = &req;
-       call.resp = &resp;
-       call.modules = gck_list_ref_copy (pkcs11_modules);
 
        for (;;) {
-
-               egg_buffer_reset (call.req);
-
-               /* 1. Read in the request */
-               if (!read_packet_with_size (&call))
+               /* Read in the request */
+               if (!gkd_ssh_agent_read_packet (fd, &req))
                        break;
-
-               /* 2. Now decode the operation */
-               if (!egg_buffer_get_byte (call.req, 4, NULL, &op))
-                       break;
-               if (op >= GKD_SSH_OP_MAX)
-                       break;
-               g_assert (gkd_ssh_agent_operations[op]);
-
-               /* 3. Execute the right operation */
-               egg_buffer_reset (call.resp);
-               egg_buffer_add_uint32 (call.resp, 0);
-               if (!(gkd_ssh_agent_operations[op]) (&call))
+               /* Handle the request */
+               if (!gkd_ssh_agent_handle (client->self, &req, &resp))
                        break;
-               if (!egg_buffer_set_uint32 (call.resp, 0, call.resp->len - 4))
-                       break;
-
-               /* 4. Write the reply back out */
-               if (!write_all (call.sock, call.resp->buf, call.resp->len))
+               /* Write the reply back out */
+               if (!gkd_ssh_agent_write_packet (fd, &resp))
                        break;
        }
 
        egg_buffer_uninit (&req);
        egg_buffer_uninit (&resp);
-       gck_list_unref_free (call.modules);
-       call.modules = NULL;
 
-       close (call.sock);
-       g_atomic_int_set (socket, -1);
+       g_object_unref (socket);
+       g_atomic_pointer_set (&client->socket, NULL);
 
        return NULL;
 }
 
-/* --------------------------------------------------------------------------------------
- * SESSION MANAGEMENT
- */
-
-/* The main PKCS#11 session that owns objects, and the mutex/cond for waiting on it */
-static GckSession *pkcs11_main_session = NULL;
-static gboolean pkcs11_main_checked = FALSE;
-static GMutex *pkcs11_main_mutex = NULL;
-static GCond *pkcs11_main_cond = NULL;
-
-GckSession*
-gkd_ssh_agent_checkout_main_session (void)
-{
-       GckSession *result;
-
-       g_mutex_lock (pkcs11_main_mutex);
-
-               g_assert (GCK_IS_SESSION (pkcs11_main_session));
-               while (pkcs11_main_checked)
-                       g_cond_wait (pkcs11_main_cond, pkcs11_main_mutex);
-               pkcs11_main_checked = TRUE;
-               result = g_object_ref (pkcs11_main_session);
-
-       g_mutex_unlock (pkcs11_main_mutex);
-
-       return result;
-}
-
-void
-gkd_ssh_agent_checkin_main_session (GckSession *session)
-{
-       g_assert (GCK_IS_SESSION (session));
-
-       g_mutex_lock (pkcs11_main_mutex);
-
-               g_assert (session == pkcs11_main_session);
-               g_assert (pkcs11_main_checked);
-
-               g_object_unref (session);
-               pkcs11_main_checked = FALSE;
-               g_cond_signal (pkcs11_main_cond);
-
-       g_mutex_unlock (pkcs11_main_mutex);
-}
-
-/* --------------------------------------------------------------------------------------
- * MAIN THREAD
- */
-
-typedef struct _Client {
-       GThread *thread;
-       gint sock;
-} Client;
-
-/* Each client thread in this list */
-static GList *socket_clients = NULL;
-
-/* The main socket we listen on */
-static int socket_fd = -1;
-
-/* The path of the socket listening on */
-static char socket_path[1024] = { 0, };
-
 void
-gkd_ssh_agent_accept (void)
+gkd_ssh_agent_accept (GkdSshAgent *self)
 {
        Client *client;
-       struct sockaddr_un addr;
-       socklen_t addrlen;
-       GError *error = NULL;
        GList *l;
-       int new_fd;
-
-       g_return_if_fail (socket_fd != -1);
+       GError *error;
+       GSocket *socket;
 
        /* Cleanup any completed dispatch threads */
-       for (l = socket_clients; l; l = g_list_next (l)) {
+       for (l = self->clients; l; l = g_list_next (l)) {
                client = l->data;
-               if (g_atomic_int_get (&client->sock) == -1) {
+               if (g_atomic_pointer_get (&client->socket) == NULL) {
                        g_thread_join (client->thread);
                        g_slice_free (Client, client);
                        l->data = NULL;
                }
        }
-       socket_clients = g_list_remove_all (socket_clients, NULL);
-
-       addrlen = sizeof (addr);
-       new_fd = accept (socket_fd, (struct sockaddr*) &addr, &addrlen);
-       if (socket_fd < 0) {
-               g_warning ("cannot accept SSH agent connection: %s", strerror (errno));
+       self->clients = g_list_remove_all (self->clients, NULL);
+
+       error = NULL;
+       socket = g_socket_accept (self->socket, NULL, &error);
+       if (!socket) {
+               g_warning ("cannot accept SSH agent connection: %s",
+                          error->message);
+               g_error_free (error);
                return;
        }
 
        client = g_slice_new0 (Client);
-       client->sock = new_fd;
+       client->self = self;
+       client->socket = socket;
 
        /* And create a new thread/process */
-       client->thread = g_thread_new ("ssh-agent", run_client_thread, &client->sock);
-       if (!client->thread) {
-               g_warning ("couldn't create thread SSH agent connection: %s",
-                          egg_error_message (error));
-               g_slice_free (Client, client);
-               return;
-       }
-
-       socket_clients = g_list_append (socket_clients, client);
+       client->thread = g_thread_new ("ssh-agent", run_client_thread, client);
+       self->clients = g_list_append (self->clients, client);
 }
 
 void
-gkd_ssh_agent_shutdown (void)
+gkd_ssh_agent_shutdown (GkdSshAgent *self)
 {
        Client *client;
        GList *l;
 
-       if (socket_fd != -1)
-               close (socket_fd);
+       if (self->socket)
+               g_socket_close (self->socket, NULL);
 
-       if (*socket_path)
-               unlink (socket_path);
+       if (self->path)
+               g_unlink (self->path);
 
        /* Stop all of the dispatch threads */
-       for (l = socket_clients; l; l = g_list_next (l)) {
+       for (l = self->clients; l; l = g_list_next (l)) {
                client = l->data;
 
                /* Forcibly shutdown the connection */
-               if (client->sock != -1)
-                       shutdown (client->sock, SHUT_RDWR);
+               if (client->socket)
+                       g_socket_shutdown (client->socket, TRUE, TRUE, NULL);
                g_thread_join (client->thread);
 
                /* This is always closed by client thread */
-               g_assert (client->sock == -1);
+               g_assert (client->socket == NULL);
                g_slice_free (Client, client);
        }
 
-       g_list_free (socket_clients);
-       socket_clients = NULL;
+       g_list_free (self->clients);
+       self->clients = NULL;
 }
 
-void
-gkd_ssh_agent_uninitialize (void)
+gboolean
+gkd_ssh_agent_startup (GkdSshAgent *self)
 {
-       gboolean ret;
+       gchar *path;
+       GSocketAddress *addr;
+       GSocket *sock;
+       GError *error;
+
+       g_return_val_if_fail (!self->socket, FALSE);
+
+       error = NULL;
+       sock = g_socket_new (G_SOCKET_FAMILY_UNIX,
+                            G_SOCKET_TYPE_STREAM,
+                            G_SOCKET_PROTOCOL_DEFAULT,
+                            &error);
+       if (!sock) {
+               g_warning ("couldn't create socket: %s", error->message);
+               g_error_free (error);
+               return FALSE;
+       }
 
-       g_assert (pkcs11_main_mutex);
-       ret = g_mutex_trylock (pkcs11_main_mutex);
-       g_assert (ret);
+       path = g_strdup_printf ("%s/ssh", self->path);
+       g_unlink (path);
 
-               g_assert (GCK_IS_SESSION (pkcs11_main_session));
-               g_assert (!pkcs11_main_checked);
-               g_object_unref (pkcs11_main_session);
-               pkcs11_main_session = NULL;
+       addr = g_unix_socket_address_new (path);
+       g_free (path);
 
-       g_mutex_unlock (pkcs11_main_mutex);
-       g_mutex_clear (pkcs11_main_mutex);
-       g_free (pkcs11_main_mutex);
-       g_cond_clear (pkcs11_main_cond);
-       g_free (pkcs11_main_cond);
+       error = NULL;
+       if (!g_socket_bind (sock, addr, FALSE, &error)) {
+               g_warning ("couldn't bind to socket: %s: %s",
+                          g_unix_socket_address_get_path (G_UNIX_SOCKET_ADDRESS (addr)),
+                          error->message);
+               g_error_free (error);
+               g_object_unref (addr);
+               g_object_unref (sock);
+               return FALSE;
+       }
 
-       gck_list_unref_free (pkcs11_modules);
-       pkcs11_modules = NULL;
-}
+       error = NULL;
+       if (!g_socket_listen (sock, &error)) {
+               g_warning ("couldn't listen on socket: %s",
+                          error->message);
+               g_error_free (error);
+               g_object_unref (addr);
+               g_object_unref (sock);
+               return FALSE;
+       }
 
-int
-gkd_ssh_agent_initialize (CK_FUNCTION_LIST_PTR funcs)
-{
-       GckModule *module;
-       gboolean ret;
+       if (!gkd_ssh_agent_process_connect (self->process)) {
+               g_object_unref (addr);
+               g_object_unref (sock);
+               return FALSE;
+       }
 
-       g_return_val_if_fail (funcs, -1);
+       g_setenv ("SSH_AUTH_SOCK", g_unix_socket_address_get_path (G_UNIX_SOCKET_ADDRESS (addr)), TRUE);
+       g_object_unref (addr);
 
-       module = gck_module_new (funcs);
-       ret = gkd_ssh_agent_initialize_with_module (module);
-       g_object_unref (module);
-       return ret;
+       self->socket = sock;
+
+       return TRUE;
 }
 
-gboolean
-gkd_ssh_agent_initialize_with_module (GckModule *module)
+GkdSshAgent *
+gkd_ssh_agent_new (const gchar *path,
+                  GTlsInteraction *interaction,
+                  GkdSshAgentPreload *preload)
 {
-       GckSession *session = NULL;
-       GList *slots, *l;
-       GArray *mechs;
-       GError *error = NULL;
-
-       g_assert (GCK_IS_MODULE (module));
-
-       /* Find a good slot for our session keys */
-       slots = gck_module_get_slots (module, TRUE);
-       for (l = slots; session == NULL && l; l = g_list_next (l)) {
-
-               /* Check that it has the mechanisms we need */
-               mechs = gck_slot_get_mechanisms (l->data);
-               if (gck_mechanisms_check (mechs, CKM_RSA_PKCS, CKM_DSA, GCK_INVALID)) {
-
-                       /* Try and open a session */
-                       session = gck_slot_open_session (l->data, GCK_SESSION_AUTHENTICATE, NULL, &error);
-                       if (!session) {
-                               g_warning ("couldn't create pkcs#11 session: %s", egg_error_message (error));
-                               g_clear_error (&error);
-                       }
-               }
+       g_return_val_if_fail (path, NULL);
+       g_return_val_if_fail (interaction, NULL);
+       g_return_val_if_fail (preload, NULL);
+
+       return g_object_new (GKD_TYPE_SSH_AGENT,
+                            "path", path,
+                            "interaction", interaction,
+                            "preload", preload,
+                            NULL);
+}
 
-               g_array_unref (mechs);
-       }
+GkdSshAgentPreload *
+gkd_ssh_agent_get_preload (GkdSshAgent *self)
+{
+       return self->preload;
+}
 
-       gck_list_unref_free (slots);
+gint
+gkd_ssh_agent_get_fd (GkdSshAgent *self)
+{
+       g_return_val_if_fail (self->socket, -1);
+       return g_socket_get_fd (self->socket);
+}
 
-       if (!session) {
-               g_warning ("couldn't select a usable pkcs#11 slot for the ssh agent to use");
-               return FALSE;
-       }
+gboolean
+gkd_ssh_agent_lookup_key (GkdSshAgent *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;
+}
 
-       g_assert (!pkcs11_modules);
-       pkcs11_modules = g_list_append (NULL, g_object_ref (module));
+void
+gkd_ssh_agent_add_key (GkdSshAgent *self,
+                      GBytes *key)
+{
+       g_mutex_lock (&self->lock);
+       g_hash_table_add (self->keys, g_bytes_ref (key));
+       g_mutex_unlock (&self->lock);
+}
 
-       pkcs11_main_mutex = g_new0 (GMutex, 1);
-       g_mutex_init (pkcs11_main_mutex);
-       pkcs11_main_cond = g_new0 (GCond, 1);
-       g_cond_init (pkcs11_main_cond);
-       pkcs11_main_checked = FALSE;
-       pkcs11_main_session = session;
+void
+gkd_ssh_agent_remove_key (GkdSshAgent *self,
+                         GBytes *key)
+{
+       g_mutex_lock (&self->lock);
+       g_hash_table_remove (self->keys, key);
+       g_mutex_unlock (&self->lock);
+}
 
-       return TRUE;
+void
+gkd_ssh_agent_clear_keys (GkdSshAgent *self)
+{
+       g_mutex_lock (&self->lock);
+       g_hash_table_remove_all (self->keys);
+       g_mutex_unlock (&self->lock);
 }
 
-int
-gkd_ssh_agent_startup (const gchar *prefix)
+void
+gkd_ssh_agent_ensure_key (GkdSshAgent *self,
+                         GBytes *key)
 {
-       struct sockaddr_un addr;
-       int sock;
+       GcrSshAskpass *askpass;
+       GError *error = NULL;
+       gint status;
+       GkdSshAgentKeyInfo *info;
+       gchar *unique;
+       const gchar *label;
+       GHashTable *fields;
+       GTlsInteraction *interaction;
+
+       gchar *argv[] = {
+               SSH_ADD,
+               NULL,
+               NULL
+       };
+
+       if (gkd_ssh_agent_lookup_key (self, key))
+               return;
 
-       g_return_val_if_fail (prefix, -1);
+       info = gkd_ssh_agent_preload_lookup_by_public_key (self->preload, key);
+       if (!info)
+               return;
 
-       snprintf (socket_path, sizeof (socket_path), "%s/ssh", prefix);
-       unlink (socket_path);
+       argv[1] = info->filename;
 
-       sock = socket (AF_UNIX, SOCK_STREAM, 0);
-       if (sock < 0) {
-               g_warning ("couldn't create socket: %s", g_strerror (errno));
-               return -1;
-       }
+       fields = g_hash_table_new (g_str_hash, g_str_equal);
+       g_hash_table_insert (fields, "xdg:schema", "org.freedesktop.Secret.Generic");
+       unique = g_strdup_printf ("ssh-store:%s", info->filename);
+       g_hash_table_insert (fields, "unique", unique);
 
-       memset(&addr, 0, sizeof(addr));
-       addr.sun_family = AF_UNIX;
-       strncpy (addr.sun_path, socket_path, sizeof (addr.sun_path));
-       if (bind (sock, (struct sockaddr *) & addr, sizeof (addr)) < 0) {
-               g_warning ("couldn't bind to socket: %s: %s", socket_path, g_strerror (errno));
-               close (sock);
-               return -1;
-       }
+       label = info->comment[0] != '\0' ? info->comment : _("Unnamed");
 
-       if (listen (sock, 128) < 0) {
-               g_warning ("couldn't listen on socket: %s", g_strerror (errno));
-               close (sock);
-               return -1;
+       interaction = gkd_login_interaction_new (self->interaction, 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 | G_SPAWN_STDERR_TO_DEV_NULL,
+                          gcr_ssh_askpass_child_setup, askpass,
+                          NULL, NULL, &status, &error)) {
+               g_warning ("cannot run %s: %s", argv[0], error->message);
+       } else if (!g_spawn_check_exit_status (status, &error)) {
+               g_message ("the %s command failed: %s", argv[0], error->message);
+       } else {
+               gkd_ssh_agent_add_key (self, key);
        }
 
-       g_setenv ("SSH_AUTH_SOCK", socket_path, TRUE);
+       g_clear_error (&error);
+       g_object_unref (askpass);
+}
 
-       socket_fd = sock;
-       return sock;
+GQuark
+gkd_ssh_interaction_key_info_quark (void)
+{
+       return g_quark_from_static_string ("gkd-ssh-interaction-key-info");
 }
+
diff --git a/daemon/ssh-agent/gkd-ssh-agent.h b/daemon/ssh-agent/gkd-ssh-agent.h
index 03427d2..2b9480d 100644
--- a/daemon/ssh-agent/gkd-ssh-agent.h
+++ b/daemon/ssh-agent/gkd-ssh-agent.h
@@ -20,21 +20,50 @@
    Author: Stef Walter <stef memberwebs com>
 */
 
-#ifndef GKDSSHAGENT_H_
-#define GKDSSHAGENT_H_
+#ifndef __GKD_SSH_AGENT_H__
+#define __GKD_SSH_AGENT_H__
 
-#include <glib.h>
+#include <gio/gio.h>
+#include "gkd-ssh-agent-preload.h"
+#include "egg/egg-buffer.h"
 
-#include "pkcs11/pkcs11.h"
+#define GKD_TYPE_SSH_AGENT gkd_ssh_agent_get_type ()
+G_DECLARE_FINAL_TYPE (GkdSshAgent, gkd_ssh_agent, GKD, SSH_AGENT, GObject);
 
-int               gkd_ssh_agent_startup                 (const gchar *prefix);
+GkdSshAgent      *gkd_ssh_agent_new                     (const gchar *path,
+                                                        GTlsInteraction *interaction,
+                                                        GkdSshAgentPreload *preload);
 
-void              gkd_ssh_agent_accept                  (void);
+gboolean          gkd_ssh_agent_startup                 (GkdSshAgent *self);
 
-void              gkd_ssh_agent_shutdown                (void);
+void              gkd_ssh_agent_accept                  (GkdSshAgent *self);
 
-gboolean          gkd_ssh_agent_initialize              (CK_FUNCTION_LIST_PTR funcs);
+void              gkd_ssh_agent_shutdown                (GkdSshAgent *self);
 
-void              gkd_ssh_agent_uninitialize            (void);
+gboolean          gkd_ssh_agent_handle                  (GkdSshAgent *self,
+                                                         EggBuffer *req,
+                                                         EggBuffer *resp);
 
-#endif /* GKDSSHAGENT_H_ */
+gboolean          gkd_ssh_agent_relay                   (GkdSshAgent *self,
+                                                         EggBuffer *req,
+                                                         EggBuffer *resp);
+
+GkdSshAgentPreload *gkd_ssh_agent_get_preload           (GkdSshAgent *self);
+
+gint              gkd_ssh_agent_get_fd                  (GkdSshAgent *self);
+
+gboolean          gkd_ssh_agent_lookup_key              (GkdSshAgent *self,
+                                                         GBytes *key);
+
+void              gkd_ssh_agent_add_key                 (GkdSshAgent *self,
+                                                         GBytes *key);
+
+void              gkd_ssh_agent_remove_key              (GkdSshAgent *self,
+                                                         GBytes *key);
+
+void              gkd_ssh_agent_clear_keys              (GkdSshAgent *self);
+
+void              gkd_ssh_agent_ensure_key              (GkdSshAgent *self,
+                                                         GBytes *key);
+
+#endif /* __GKD_SSH_AGENT_H__ */
diff --git a/daemon/ssh-agent/gkd-ssh-interaction.c b/daemon/ssh-agent/gkd-ssh-interaction.c
new file mode 100644
index 0000000..3768153
--- /dev/null
+++ b/daemon/ssh-agent/gkd-ssh-interaction.c
@@ -0,0 +1,156 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gkd-ssh-interaction.c
+
+   Copyright (C) 2014 Stefan Walter
+
+   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,
+   see <http://www.gnu.org/licenses/>.
+
+   Author: Stef Walter <stefw gnome org>
+*/
+
+#include "config.h"
+
+#include "gkd-ssh-interaction.h"
+#include "gkd-ssh-agent-private.h"
+
+#include "daemon/login/gkd-login-interaction.h"
+
+#include <gcr/gcr-base.h>
+
+#include <glib/gi18n-lib.h>
+
+struct _GkdSshInteraction {
+       GTlsInteraction interaction;
+};
+
+G_DEFINE_TYPE (GkdSshInteraction, gkd_ssh_interaction, G_TYPE_TLS_INTERACTION);
+
+static void
+gkd_ssh_interaction_init (GkdSshInteraction *self)
+{
+}
+
+static void
+on_prompt_password (GObject *source_object,
+                   GAsyncResult *result,
+                   gpointer user_data)
+{
+       GTask *task = G_TASK (user_data);
+       GTlsPassword *password = g_task_get_task_data (task);
+       GkdLoginPassword *login_password = GKD_LOGIN_PASSWORD (password);
+       GcrPrompt *prompt = GCR_PROMPT (source_object);
+       GError *error = NULL;
+       const gchar *value;
+
+       value = gcr_prompt_password_finish (prompt, result, &error);
+       if (!value) {
+               g_task_return_error (task, error);
+               g_object_unref (task);
+               return;
+       }
+       g_tls_password_set_value (password, (const guchar *)value, strlen (value));
+       gkd_login_password_set_store_password (login_password,
+                                              gcr_prompt_get_choice_chosen (prompt));
+       g_object_unref (prompt);
+
+       g_task_return_boolean (task, TRUE);
+       g_object_unref (task);
+}
+
+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);
+       GkdLoginPassword *login_password = GKD_LOGIN_PASSWORD (password);
+       GError *error = NULL;
+       GcrPrompt *prompt;
+       const gchar *choice;
+       gchar *text;
+
+       prompt = gcr_system_prompt_open_finish (result, &error);
+       if (error != NULL) {
+               g_task_return_error (task, error);
+
+       } else {
+               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 = NULL;
+               if (gkd_login_password_get_login_available (login_password))
+                       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, g_object_ref 
(task));
+
+       g_object_unref (task);
+}
+
+static void
+gkd_ssh_interaction_ask_password_async (GTlsInteraction *interaction,
+                                        GTlsPassword *password,
+                                        GCancellable *cancellable,
+                                        GAsyncReadyCallback callback,
+                                        gpointer user_data)
+{
+       GTask *task;
+
+       task = g_task_new (interaction, cancellable, callback, user_data);
+       g_task_set_task_data (task, g_object_ref (password), g_object_unref);
+
+       gcr_system_prompt_open_async (60, cancellable, on_prompt_open,
+                                     g_object_ref (task));
+
+       g_object_unref (task);
+}
+
+static GTlsInteractionResult
+gkd_ssh_interaction_ask_password_finish (GTlsInteraction *interaction,
+                                         GAsyncResult *result,
+                                         GError **error)
+{
+       GTask *task = G_TASK (result);
+       if (!g_task_propagate_boolean (task, error))
+               return G_TLS_INTERACTION_FAILED;
+       return G_TLS_INTERACTION_HANDLED;
+}
+
+static void
+gkd_ssh_interaction_class_init (GkdSshInteractionClass *klass)
+{
+       GTlsInteractionClass *interaction_class = G_TLS_INTERACTION_CLASS (klass);
+
+       interaction_class->ask_password_async = gkd_ssh_interaction_ask_password_async;
+       interaction_class->ask_password_finish = gkd_ssh_interaction_ask_password_finish;
+}
+
+GTlsInteraction *
+gkd_ssh_interaction_new (void)
+{
+       return g_object_new (GKD_TYPE_SSH_INTERACTION, NULL);
+}
diff --git a/daemon/ssh-agent/gkd-ssh-interaction.h b/daemon/ssh-agent/gkd-ssh-interaction.h
new file mode 100644
index 0000000..4bbbfc9
--- /dev/null
+++ b/daemon/ssh-agent/gkd-ssh-interaction.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gkd-ssh-interaction.h
+
+   Copyright (C) 2014 Stefan Walter
+
+   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,
+   see <http://www.gnu.org/licenses/>.
+
+   Author: Stef Walter <stefw redhat com>
+*/
+
+#ifndef _GKD_SSH_INTERACTION_H__
+#define __GKD_SSH_INTERACTION_H__
+
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define GKD_TYPE_SSH_INTERACTION    (gkd_ssh_interaction_get_type ())
+G_DECLARE_FINAL_TYPE (GkdSshInteraction, gkd_ssh_interaction, GKD, SSH_INTERACTION, GTlsInteraction)
+
+GType               gkd_ssh_interaction_get_type               (void);
+
+GTlsInteraction *   gkd_ssh_interaction_new                    (void);
+
+G_END_DECLS
+
+#endif /* __GKD_SSH_INTERACTION_H__ */
diff --git a/daemon/ssh-agent/gkd-ssh-openssh.c b/daemon/ssh-agent/gkd-ssh-openssh.c
new file mode 100644
index 0000000..3d0c3e2
--- /dev/null
+++ b/daemon/ssh-agent/gkd-ssh-openssh.c
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2014 Stef 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 thewalter net>
+ */
+
+#include "config.h"
+
+#include "gkd-ssh-openssh.h"
+
+#include <string.h>
+
+GBytes *
+gkd_ssh_openssh_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;
+
+       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;
+
+       /* Find the first space */
+       at = memchr (data, ' ', n_data);
+       if (!at) {
+               g_message ("SSH public key missing space");
+               return NULL;
+       }
+
+       /* 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;
+
+       /* 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;
+       }
+
+       /* 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);
+}
diff --git a/daemon/ssh-agent/gkd-ssh-openssh.h b/daemon/ssh-agent/gkd-ssh-openssh.h
new file mode 100644
index 0000000..382e3c6
--- /dev/null
+++ b/daemon/ssh-agent/gkd-ssh-openssh.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2014 Stef 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 thewalter net>
+ */
+
+#ifndef GKD_SSH_OPENSSH_H_
+#define GKD_SSH_OPENSSH_H_
+
+#include <glib.h>
+
+GBytes *         gkd_ssh_openssh_parse_public_key      (GBytes *data,
+                                                        gchar **comment);
+
+#endif /* GKD_SSH_OPENSSH_H_ */
diff --git a/daemon/ssh-agent/test-gkd-ssh-agent-common.h b/daemon/ssh-agent/test-gkd-ssh-agent-common.h
new file mode 100644
index 0000000..a26d717
--- /dev/null
+++ b/daemon/ssh-agent/test-gkd-ssh-agent-common.h
@@ -0,0 +1,293 @@
+/* RSA private key blob decoded from pkcs11/ssh-store/fixtures/id_rsa_plain */
+static guint8 private_blob[4*6 + 0x101 + 0x1 + 0x101 + 0x80 + 0x81 + 0x81] = {
+       /* n */
+       0x00, 0x00, 0x01, 0x01, 0x00, 0xa0, 0x3e, 0x95, 0x2a, 0xa9, 0x21, 0x6b,
+       0x2e, 0xa9, 0x28, 0x74, 0x91, 0x8c, 0x01, 0x96, 0x59, 0xf1, 0x4f, 0x53,
+       0xcc, 0x5f, 0xb2, 0x2d, 0xa0, 0x9c, 0xec, 0x0f, 0xfc, 0x1d, 0x54, 0x1c,
+       0x3a, 0x33, 0xb7, 0x1d, 0xdc, 0xce, 0x13, 0xbe, 0xa7, 0x2f, 0xdf, 0x4e,
+       0x58, 0x42, 0x9d, 0x23, 0xf5, 0x8e, 0xc8, 0xe4, 0xad, 0x52, 0x19, 0x72,
+       0x7c, 0xda, 0x87, 0x67, 0xd4, 0x34, 0x51, 0x51, 0x81, 0x2e, 0x3e, 0x8d,
+       0x13, 0x81, 0xb6, 0xf6, 0xe0, 0x1e, 0xc4, 0xbb, 0xd9, 0x5d, 0x44, 0xeb,
+       0xe6, 0x68, 0x81, 0x5f, 0xa6, 0x04, 0x95, 0x96, 0x02, 0x1c, 0x34, 0x88,
+       0xfa, 0xe6, 0x43, 0x72, 0xaf, 0x9b, 0x7f, 0x03, 0xdc, 0xf0, 0x72, 0xa3,
+       0x96, 0x3b, 0xc8, 0xa3, 0xb9, 0x90, 0x81, 0xb6, 0x2e, 0x5a, 0x18, 0x2e,
+       0x3a, 0x2c, 0x27, 0x91, 0x78, 0xb3, 0x1d, 0xb1, 0x87, 0x4b, 0xb3, 0xdb,
+       0x05, 0xcd, 0xb6, 0x76, 0x35, 0x6f, 0x9c, 0x61, 0x7b, 0x6f, 0x95, 0x12,
+       0x4b, 0x26, 0xf4, 0xe0, 0x7e, 0x15, 0x76, 0x94, 0x91, 0x90, 0xb6, 0x7d,
+       0x0a, 0xd3, 0x36, 0x8f, 0x19, 0x18, 0x52, 0x50, 0x48, 0x57, 0x7c, 0x91,
+       0x48, 0x48, 0x7d, 0xb5, 0x03, 0x26, 0x69, 0x58, 0xb9, 0x9f, 0xaf, 0xbc,
+       0x73, 0x3e, 0x03, 0x72, 0xdc, 0xf6, 0xb1, 0xf2, 0x5b, 0x82, 0x0f, 0x69,
+       0x1c, 0xb1, 0x15, 0x07, 0x22, 0x46, 0x66, 0xfe, 0x65, 0x0a, 0x94, 0xda,
+       0xe4, 0x9d, 0x39, 0x70, 0x21, 0x83, 0x5e, 0xe5, 0xb2, 0x4b, 0x97, 0xfe,
+       0xaf, 0x32, 0x08, 0x8e, 0x47, 0xcb, 0x97, 0x83, 0x89, 0xc0, 0xb6, 0xdb,
+       0x6a, 0x14, 0x31, 0xd2, 0x53, 0xb5, 0x88, 0x30, 0x5f, 0x87, 0x50, 0x09,
+       0x4f, 0x13, 0x20, 0x25, 0xa1, 0xc5, 0xbd, 0xf1, 0xe1, 0x10, 0x95, 0xfa,
+       0x0e, 0xc3, 0xf7, 0xdf, 0xad, 0x90, 0x8b, 0xef, 0xfb,
+       /* e */
+       0x00, 0x00, 0x00, 0x01, 0x23,
+       /* d */
+       0x00, 0x00, 0x01, 0x01, 0x00, 0x9b, 0xaa, 0x82, 0x46, 0xb2, 0xed, 0x43,
+       0x8c, 0x69, 0xcf, 0x87, 0x2e, 0x4d, 0x7d, 0xe2, 0x83, 0x42, 0x2f, 0xcd,
+       0xbf, 0x38, 0x63, 0xf1, 0xcf, 0x39, 0x5a, 0x58, 0xab, 0xc4, 0xb8, 0x1b,
+       0x6b, 0xbd, 0x35, 0x8a, 0xb9, 0x3d, 0x37, 0xc0, 0x85, 0x27, 0x30, 0xb2,
+       0x81, 0x9f, 0xcb, 0xd9, 0xc9, 0xf8, 0x6b, 0x61, 0xcc, 0xf0, 0xab, 0x01,
+       0x80, 0x99, 0xc5, 0x5d, 0x8c, 0x50, 0x14, 0x7b, 0x0f, 0xc6, 0x85, 0xe8,
+       0x21, 0x93, 0xf3, 0x90, 0xbc, 0x75, 0xa9, 0x2b, 0x82, 0xb2, 0x60, 0x35,
+       0x9d, 0xff, 0x1e, 0x97, 0x6e, 0x13, 0x14, 0xf8, 0x1f, 0x4e, 0x99, 0x6f,
+       0x1f, 0x9d, 0xdb, 0x1e, 0xf3, 0xbb, 0x9f, 0xf5, 0x1f, 0xc5, 0x01, 0xa6,
+       0x3a, 0x2b, 0x72, 0x73, 0x29, 0x4a, 0x8c, 0xa2, 0x58, 0xe9, 0xce, 0x58,
+       0xca, 0xcb, 0xce, 0xaa, 0x92, 0x82, 0x1c, 0xd8, 0x57, 0x8b, 0x5e, 0x42,
+       0x79, 0x21, 0x0e, 0x63, 0x13, 0x0e, 0x03, 0xff, 0x2f, 0x7f, 0x64, 0xf6,
+       0x82, 0xe1, 0xfe, 0x0b, 0xc3, 0x1e, 0x4c, 0x50, 0x11, 0x3f, 0xc8, 0x8a,
+       0xba, 0xcc, 0xde, 0x24, 0xf7, 0xae, 0x96, 0x6c, 0x5e, 0x3b, 0x00, 0xfa,
+       0xf0, 0x0e, 0xac, 0x3a, 0xeb, 0xb1, 0xab, 0x8f, 0x3f, 0xdb, 0x80, 0xb3,
+       0x06, 0x91, 0x18, 0xe1, 0xf5, 0x3b, 0xec, 0x5d, 0x01, 0xcf, 0xd0, 0x1f,
+       0xaf, 0xe3, 0xd9, 0x12, 0xba, 0x7b, 0x0f, 0xee, 0x20, 0x29, 0x74, 0x57,
+       0xdc, 0x58, 0x75, 0xd4, 0xb0, 0xf4, 0xb4, 0xa4, 0x93, 0x48, 0x2b, 0x7b,
+       0x6b, 0x1d, 0x77, 0xbc, 0xf3, 0xfe, 0xbd, 0xad, 0xd6, 0x83, 0x05, 0x16,
+       0xca, 0xbe, 0x31, 0xa4, 0x39, 0x53, 0x29, 0xf3, 0xd3, 0x39, 0xb0, 0xa5,
+       0xef, 0xf0, 0xc9, 0x08, 0xd6, 0x63, 0x52, 0x0b, 0xcb, 0xfc, 0x1c, 0x21,
+       0xd3, 0xa9, 0x2f, 0x23, 0x92, 0x3d, 0x46, 0x8c, 0x4b,
+       /* iqmp */
+       0x00, 0x00, 0x00, 0x80, 0x15, 0x40, 0xcc, 0xa4, 0x83, 0xdf, 0x26, 0xbe,
+       0x55, 0x82, 0x85, 0x0f, 0x71, 0x3c, 0x19, 0xa8, 0x8b, 0x42, 0x80, 0xa5,
+       0x24, 0x5d, 0xad, 0xf5, 0x99, 0x33, 0xaf, 0x7c, 0xb2, 0x27, 0xae, 0x7b,
+       0x0b, 0x0b, 0xa0, 0x03, 0xfd, 0xae, 0x53, 0x6f, 0xf1, 0xdd, 0x83, 0x54,
+       0xde, 0xf2, 0xbd, 0x87, 0x2c, 0xa9, 0x4d, 0x7b, 0xa5, 0x6e, 0xdb, 0x5e,
+       0x89, 0xf4, 0x5c, 0x79, 0x22, 0xc3, 0xc4, 0x40, 0x50, 0xeb, 0xb7, 0xf4,
+       0x17, 0x78, 0x2f, 0x06, 0xa5, 0x3a, 0x65, 0x4d, 0x85, 0x98, 0x3e, 0xd8,
+       0x4d, 0x3b, 0xfc, 0xd8, 0x9b, 0xe5, 0xd1, 0x47, 0xb6, 0xe3, 0xda, 0x2e,
+       0xc5, 0x18, 0xce, 0x37, 0xd9, 0xd7, 0x9a, 0xbf, 0xba, 0xa9, 0xef, 0xf2,
+       0xaf, 0x9b, 0xc8, 0x46, 0x57, 0x11, 0x8c, 0xa9, 0x5f, 0x68, 0x8c, 0x43,
+       0x2f, 0xb5, 0x7a, 0x39, 0x38, 0x30, 0x79, 0xd5, 0x30, 0xa8, 0x2b, 0x98,
+       /* p */
+       0x00, 0x00, 0x00, 0x81, 0x00, 0xcc, 0x50, 0xb1, 0x2c, 0x5f, 0xe4, 0x02,
+       0x85, 0x7d, 0xce, 0x77, 0xd8, 0x27, 0xc1, 0xf6, 0xee, 0xe2, 0x2b, 0x7b,
+       0x29, 0x83, 0x95, 0xf1, 0x5e, 0x3d, 0xe5, 0xa9, 0x75, 0x62, 0xc6, 0x84,
+       0xc9, 0x97, 0x26, 0x70, 0xf4, 0x0d, 0x28, 0x6a, 0xc6, 0x88, 0x7c, 0xa3,
+       0x0d, 0x35, 0xa3, 0x8f, 0xdc, 0x34, 0x4c, 0x78, 0x6b, 0xcc, 0x5d, 0x99,
+       0x7e, 0x45, 0xb0, 0xdf, 0xe3, 0x77, 0x48, 0x77, 0xd8, 0xa9, 0x1c, 0x74,
+       0xf9, 0xbc, 0xcc, 0x82, 0xdb, 0x44, 0x10, 0x96, 0xda, 0x00, 0x23, 0xaa,
+       0x04, 0x93, 0xcc, 0x98, 0xec, 0x26, 0x8b, 0x7d, 0x08, 0xf4, 0x82, 0xdc,
+       0x9a, 0xc4, 0x8c, 0xc8, 0xe9, 0x3e, 0x5b, 0xd6, 0xc7, 0x28, 0xf4, 0x38,
+       0x3a, 0x3c, 0x08, 0x56, 0xbb, 0xa2, 0xca, 0xfb, 0x05, 0xa0, 0xb7, 0xe1,
+       0x70, 0x59, 0xb4, 0x86, 0x2b, 0x29, 0x89, 0xb5, 0x82, 0x2a, 0x79, 0x61,
+       0x51,
+       /* q */
+       0x00, 0x00, 0x00, 0x81, 0x00, 0xc8, 0xc7, 0xe6, 0x93, 0x90, 0x59, 0xe7,
+       0x54, 0x1b, 0xcf, 0x9c, 0xb0, 0x07, 0x80, 0x37, 0xcd, 0xdf, 0x65, 0xf4,
+       0x29, 0x1e, 0x4a, 0x93, 0x73, 0xd1, 0x7b, 0x47, 0x1d, 0x36, 0x87, 0x89,
+       0x1d, 0xbf, 0xd5, 0x1e, 0x02, 0xc2, 0xd1, 0x2b, 0xb3, 0x67, 0x07, 0x65,
+       0xf9, 0xbc, 0xcb, 0x74, 0x4c, 0x83, 0x68, 0xa8, 0x6d, 0x30, 0x68, 0x8f,
+       0xb5, 0xb9, 0x44, 0x86, 0xb8, 0xde, 0x4e, 0xfc, 0x02, 0x1e, 0x9c, 0x05,
+       0x3b, 0x23, 0x1b, 0xdf, 0x79, 0x58, 0x73, 0x51, 0x27, 0xf0, 0xbd, 0x83,
+       0x34, 0x38, 0xcb, 0xd0, 0x20, 0x12, 0xcd, 0x1a, 0x07, 0x6e, 0xf7, 0x0a,
+       0x92, 0x29, 0xff, 0x2f, 0xbf, 0x30, 0x2a, 0x69, 0x15, 0x4d, 0x8e, 0x6e,
+       0x17, 0x26, 0x7b, 0x43, 0xfe, 0x52, 0xd1, 0x83, 0x65, 0x19, 0x22, 0x8b,
+       0xd3, 0x6f, 0x97, 0x51, 0x11, 0x3f, 0x17, 0xfe, 0x05, 0xcc, 0xa4, 0x49,
+       0x8b
+};
+
+static void
+prepare_request_identities (EggBuffer *req)
+{
+       gboolean ret;
+
+       egg_buffer_reset (req);
+
+       ret = egg_buffer_add_uint32 (req, 1);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_byte (req, GKD_SSH_OP_REQUEST_IDENTITIES);
+       g_assert_true (ret);
+}
+
+static void
+check_identities_answer (EggBuffer *resp, gsize count)
+{
+       uint32_t length;
+       unsigned char code;
+       size_t offset;
+       gboolean ret;
+
+       offset = 0;
+       ret = egg_buffer_get_uint32 (resp, offset, &offset, &length);
+       g_assert_true (ret);
+       g_assert_cmpint (length, ==, resp->len - 4);
+
+       code = 0;
+       ret = egg_buffer_get_byte (resp, offset, &offset, &code);
+       g_assert_true (ret);
+       g_assert_cmpint (code, ==, GKD_SSH_RES_IDENTITIES_ANSWER);
+
+       ret = egg_buffer_get_uint32 (resp, offset, &offset, &length);
+       g_assert_true (ret);
+       g_assert_cmpint (length, ==, count);
+}
+
+static void
+prepare_add_identity (EggBuffer *req)
+{
+       gboolean ret;
+
+       egg_buffer_reset (req);
+
+       ret = egg_buffer_add_uint32 (req, 0);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_byte (req, GKD_SSH_OP_ADD_IDENTITY);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_string (req, "ssh-rsa");
+       g_assert_true (ret);
+
+       ret = egg_buffer_append (req, private_blob, G_N_ELEMENTS(private_blob));
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_string (req, "comment");
+       g_assert_true (ret);
+
+       ret = egg_buffer_set_uint32 (req, 0, req->len - 4);
+       g_assert_true (ret);
+}
+
+static GBytes *
+public_key_from_file (const gchar *path, gchar **comment)
+{
+       GBytes *public_bytes;
+       GBytes *public_key;
+
+       GError *error = NULL;
+       gchar *contents;
+       gsize length;
+
+       if (!g_file_get_contents (path, &contents, &length, &error)) {
+               g_message ("couldn't read file: %s: %s", path, error->message);
+               g_error_free (error);
+               return NULL;
+       }
+
+       public_bytes = g_bytes_new_take (contents, length);
+       public_key = gkd_ssh_openssh_parse_public_key (public_bytes, comment);
+       g_bytes_unref (public_bytes);
+
+       return public_key;
+}
+
+static void
+prepare_remove_identity (EggBuffer *req)
+{
+       GBytes *public_key;
+       gchar *comment;
+       gsize length;
+       const guchar *blob;
+       gboolean ret;
+
+       public_key = public_key_from_file (SRCDIR "/pkcs11/ssh-store/fixtures/id_rsa_plain.pub", &comment);
+       g_free (comment);
+       blob = g_bytes_get_data (public_key, &length);
+
+       egg_buffer_reset (req);
+       ret = egg_buffer_add_uint32 (req, 0);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_byte (req, GKD_SSH_OP_REMOVE_IDENTITY);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_byte_array (req, blob, length);
+       g_assert_true (ret);
+
+       ret = egg_buffer_set_uint32 (req, 0, req->len - 4);
+       g_assert_true (ret);
+
+       g_bytes_unref (public_key);
+}
+
+static void
+prepare_remove_all_identities (EggBuffer *req)
+{
+       gboolean ret;
+
+       egg_buffer_reset (req);
+       ret = egg_buffer_add_uint32 (req, 1);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_byte (req, GKD_SSH_OP_REMOVE_ALL_IDENTITIES);
+       g_assert_true (ret);
+}
+
+static void
+check_success (EggBuffer *resp)
+{
+       uint32_t length;
+       unsigned char code;
+       size_t offset;
+       gboolean ret;
+
+       offset = 0;
+       ret = egg_buffer_get_uint32 (resp, offset, &offset, &length);
+       g_assert_true (ret);
+       g_assert_cmpint (length, ==, resp->len - 4);
+
+       code = 0;
+       ret = egg_buffer_get_byte (resp, offset, &offset, &code);
+       g_assert_true (ret);
+       g_assert_cmpint (code, ==, GKD_SSH_RES_SUCCESS);
+}
+
+static void
+prepare_sign_request (EggBuffer *req)
+{
+       GBytes *public_key;
+       gchar *comment;
+       gsize length;
+       const guchar *blob;
+       gboolean ret;
+
+       public_key = public_key_from_file (SRCDIR "/pkcs11/ssh-store/fixtures/id_rsa_plain.pub", &comment);
+       g_free (comment);
+       blob = g_bytes_get_data (public_key, &length);
+
+       egg_buffer_reset (req);
+       ret = egg_buffer_add_uint32 (req, 0);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_byte (req, GKD_SSH_OP_SIGN_REQUEST);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_byte_array (req, blob, length);
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_string (req, "data");
+       g_assert_true (ret);
+
+       ret = egg_buffer_add_uint32 (req, 0);
+       g_assert_true (ret);
+
+       ret = egg_buffer_set_uint32 (req, 0, req->len - 4);
+       g_assert_true (ret);
+
+       g_bytes_unref (public_key);
+}
+
+static void
+check_sign_response (EggBuffer *resp)
+{
+       uint32_t length;
+       unsigned char code;
+       size_t offset;
+       gboolean ret;
+
+       offset = 0;
+       ret = egg_buffer_get_uint32 (resp, offset, &offset, &length);
+       g_assert_true (ret);
+       g_assert_cmpint (length, ==, resp->len - 4);
+
+       code = 0;
+       ret = egg_buffer_get_byte (resp, offset, &offset, &code);
+       g_assert_true (ret);
+       g_assert_cmpint (code, ==, GKD_SSH_RES_SIGN_RESPONSE);
+
+       ret = egg_buffer_get_uint32 (resp, offset, &offset, &length);
+       g_assert_true (ret);
+}
diff --git a/daemon/ssh-agent/test-gkd-ssh-agent-process.c b/daemon/ssh-agent/test-gkd-ssh-agent-process.c
new file mode 100644
index 0000000..c338cce
--- /dev/null
+++ b/daemon/ssh-agent/test-gkd-ssh-agent-process.c
@@ -0,0 +1,232 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+   Copyright (C) 2008 Stefan Walter
+
+   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 <stef memberwebs com>
+*/
+
+#include "config.h"
+
+#include "daemon/ssh-agent/gkd-ssh-agent-private.h"
+#include "daemon/ssh-agent/gkd-ssh-agent-process.h"
+#include "daemon/ssh-agent/gkd-ssh-openssh.h"
+#include "daemon/ssh-agent/test-gkd-ssh-agent-common.h"
+#include "egg/egg-testing.h"
+
+#include <glib.h>
+
+typedef struct {
+       gchar *directory;
+       EggBuffer req;
+       EggBuffer resp;
+       GkdSshAgentProcess *process;
+} Test;
+
+static void
+setup (Test *test, gconstpointer unused)
+{
+       gchar *path;
+
+       test->directory = egg_tests_create_scratch_directory (NULL, NULL);
+
+       egg_buffer_init_full (&test->req, 128, (EggBufferAllocator)g_realloc);
+       egg_buffer_init_full (&test->resp, 128, (EggBufferAllocator)g_realloc);
+
+       path = g_strdup_printf ("%s/.ssh.sock", test->directory);
+       test->process = gkd_ssh_agent_process_new (path);
+       g_free (path);
+       g_assert_nonnull (test->process);
+}
+
+static void
+teardown (Test *test, gconstpointer unused)
+{
+       g_clear_object (&test->process);
+
+       egg_buffer_uninit (&test->req);
+       egg_buffer_uninit (&test->resp);
+
+       egg_tests_remove_scratch_directory (test->directory);
+       free (test->directory);
+}
+
+static void
+test_connect (Test *test, gconstpointer unused)
+{
+       gboolean ret = gkd_ssh_agent_process_connect (test->process);
+       g_assert_true (ret);
+}
+
+static void
+call_request_identities (Test *test, gsize count)
+{
+       gboolean ret;
+
+       egg_buffer_reset (&test->req);
+       egg_buffer_reset (&test->resp);
+
+       prepare_request_identities (&test->req);
+       ret = gkd_ssh_agent_process_call (test->process, &test->req, &test->resp);
+       g_assert_true (ret);
+
+       check_identities_answer (&test->resp, count);
+}
+
+static void
+call_add_identity (Test *test)
+{
+       gboolean ret;
+
+       egg_buffer_reset (&test->req);
+       egg_buffer_reset (&test->resp);
+
+       prepare_add_identity (&test->req);
+       ret = gkd_ssh_agent_process_call (test->process, &test->req, &test->resp);
+       g_assert_true (ret);
+
+       check_success (&test->resp);
+}
+
+static void
+call_remove_identity (Test *test)
+{
+       gboolean ret;
+
+       egg_buffer_reset (&test->req);
+       egg_buffer_reset (&test->resp);
+
+       prepare_remove_identity (&test->req);
+       ret = gkd_ssh_agent_process_call (test->process, &test->req, &test->resp);
+       g_assert_true (ret);
+
+       check_success (&test->resp);
+}
+
+static void
+call_remove_all_identities (Test *test)
+{
+       gboolean ret;
+
+       egg_buffer_reset (&test->req);
+       egg_buffer_reset (&test->resp);
+
+       prepare_remove_all_identities (&test->req);
+       ret = gkd_ssh_agent_process_call (test->process, &test->req, &test->resp);
+       g_assert_true (ret);
+
+       check_success (&test->resp);
+}
+
+static void
+call_sign (Test *test)
+{
+       gboolean ret;
+
+       egg_buffer_reset (&test->req);
+       egg_buffer_reset (&test->resp);
+
+       prepare_sign_request (&test->req);
+       ret = gkd_ssh_agent_process_call (test->process, &test->req, &test->resp);
+       g_assert_true (ret);
+
+       check_sign_response (&test->resp);
+}
+
+static void
+test_list (Test *test, gconstpointer unused)
+{
+       gboolean ret;
+
+       ret = gkd_ssh_agent_process_connect (test->process);
+       g_assert_true (ret);
+
+       call_request_identities(test, 0);
+}
+
+static void
+test_add (Test *test, gconstpointer unused)
+{
+       gboolean ret;
+
+       ret = gkd_ssh_agent_process_connect (test->process);
+       g_assert_true (ret);
+
+       call_add_identity (test);
+       call_request_identities (test, 1);
+}
+
+static void
+test_remove (Test *test, gconstpointer unused)
+{
+       gboolean ret;
+
+       ret = gkd_ssh_agent_process_connect (test->process);
+       g_assert_true (ret);
+
+       call_add_identity (test);
+       call_request_identities (test, 1);
+
+       call_remove_identity (test);
+       call_request_identities (test, 0);
+}
+
+static void
+test_remove_all (Test *test, gconstpointer unused)
+{
+       gboolean ret;
+
+       ret = gkd_ssh_agent_process_connect (test->process);
+       g_assert_true (ret);
+
+       call_add_identity (test);
+       call_request_identities (test, 1);
+
+       call_remove_all_identities (test);
+       call_request_identities (test, 0);
+}
+
+static void
+test_sign (Test *test, gconstpointer unused)
+{
+       gboolean ret;
+
+       ret = gkd_ssh_agent_process_connect (test->process);
+       g_assert_true (ret);
+
+       call_add_identity (test);
+       call_request_identities (test, 1);
+
+       call_sign (test);
+
+       call_remove_all_identities (test);
+       call_request_identities (test, 0);
+}
+
+int
+main (int argc, char **argv)
+{
+       g_test_init (&argc, &argv, NULL);
+
+       g_test_add ("/ssh-agent/process/connect", Test, NULL, setup, test_connect, teardown);
+       g_test_add ("/ssh-agent/process/list", Test, NULL, setup, test_list, teardown);
+       g_test_add ("/ssh-agent/process/add", Test, NULL, setup, test_add, teardown);
+       g_test_add ("/ssh-agent/process/remove", Test, NULL, setup, test_remove, teardown);
+       g_test_add ("/ssh-agent/process/remove_all", Test, NULL, setup, test_remove_all, teardown);
+       g_test_add ("/ssh-agent/process/sign", Test, NULL, setup, test_sign, teardown);
+
+       return g_test_run ();
+}
diff --git a/daemon/ssh-agent/test-gkd-ssh-agent.c b/daemon/ssh-agent/test-gkd-ssh-agent.c
new file mode 100644
index 0000000..cd385c6
--- /dev/null
+++ b/daemon/ssh-agent/test-gkd-ssh-agent.c
@@ -0,0 +1,330 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+   Copyright (C) 2008 Stefan Walter
+
+   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 <stef memberwebs com>
+*/
+
+#include "config.h"
+
+#include "daemon/ssh-agent/gkd-ssh-agent-private.h"
+#include "daemon/ssh-agent/gkd-ssh-openssh.h"
+#include "daemon/ssh-agent/test-gkd-ssh-agent-common.h"
+#include "egg/egg-testing.h"
+#include "egg/mock-interaction.h"
+#include "pkcs11/gkm/gkm-mock.h"
+
+#include <glib.h>
+#include <glib/gstdio.h>
+
+typedef struct {
+       gchar *directory;
+       EggBuffer req;
+       EggBuffer resp;
+       GkdSshAgent *agent;
+       GMainContext *context;
+       GMainLoop *loop;
+       GSource *source;
+       GThread *thread;
+       GMutex lock;
+       GCond cond;
+} Test;
+
+/* define gkd_pkcs11_get_base_functions() for gkd-login.c */
+CK_FUNCTION_LIST_PTR
+gkd_pkcs11_get_base_functions (void);
+
+CK_FUNCTION_LIST_PTR
+gkd_pkcs11_get_base_functions (void)
+{
+       CK_FUNCTION_LIST_PTR funcs;
+       gkm_mock_C_GetFunctionList (&funcs);
+       return funcs;
+}
+
+static gboolean
+accept_client (GIOChannel *channel, GIOCondition cond, gpointer data)
+{
+       GkdSshAgent *self = GKD_SSH_AGENT (data);
+       if (cond == G_IO_IN)
+               gkd_ssh_agent_accept (self);
+       return TRUE;
+}
+
+static gpointer
+run_thread (gpointer data)
+{
+       Test *test = data;
+
+       g_mutex_lock (&test->lock);
+       g_cond_signal (&test->cond);
+       g_mutex_unlock (&test->lock);
+
+       g_main_loop_run (test->loop);
+       g_main_context_unref (test->context);
+       g_main_loop_unref (test->loop);
+
+       return NULL;
+}
+
+static void
+setup (Test *test, gconstpointer unused)
+{
+       GTlsInteraction *interaction;
+       GkdSshAgentPreload *preload;
+       gchar *sockets_path;
+       gchar *preload_path;
+       gchar *path;
+       GIOChannel *channel;
+       gint sock;
+       gboolean ret;
+
+       test->directory = egg_tests_create_scratch_directory (NULL, NULL);
+
+       sockets_path = g_build_filename (test->directory, "sockets", NULL);
+       g_mkdir (sockets_path, 0700);
+
+       preload_path = g_build_filename (test->directory, "preload", NULL);
+       g_mkdir (preload_path, 0700);
+
+       egg_tests_copy_scratch_file (preload_path, SRCDIR "/pkcs11/ssh-store/fixtures/id_rsa_plain");
+       egg_tests_copy_scratch_file (preload_path, SRCDIR "/pkcs11/ssh-store/fixtures/id_rsa_plain.pub");
+
+       path = g_build_filename (preload_path, "id_rsa_plain", NULL);
+       g_chmod (path, 0600);
+       g_free (path);
+
+       egg_buffer_init_full (&test->req, 128, (EggBufferAllocator)g_realloc);
+       egg_buffer_init_full (&test->resp, 128, (EggBufferAllocator)g_realloc);
+
+       interaction = mock_interaction_new ("password");
+       preload = gkd_ssh_agent_preload_new (preload_path);
+       g_free (preload_path);
+
+       test->agent = gkd_ssh_agent_new (sockets_path, interaction, preload);
+       g_free (sockets_path);
+
+       g_object_unref (interaction);
+       g_object_unref (preload);
+
+       ret = gkd_ssh_agent_startup (test->agent);
+       g_assert_true (ret);
+
+       sock = gkd_ssh_agent_get_fd (test->agent);
+       g_assert_cmpint (sock, != , -1);
+
+       test->context = g_main_context_new ();
+       test->loop = g_main_loop_new (test->context, FALSE);
+
+       channel = g_io_channel_unix_new (sock);
+       test->source = g_io_create_watch (channel, G_IO_IN | G_IO_HUP);
+       g_source_set_callback (test->source, (GSourceFunc)accept_client, test->agent, NULL);
+       g_source_attach (test->source, test->context);
+       g_io_channel_unref (channel);
+
+       g_mutex_init (&test->lock);
+       g_cond_init (&test->cond);
+
+       test->thread = g_thread_new ("ssh-agent", run_thread, test);
+
+       g_mutex_lock (&test->lock);
+       g_cond_wait (&test->cond, &test->lock);
+       g_mutex_unlock (&test->lock);
+}
+
+static void
+teardown (Test *test, gconstpointer unused)
+{
+       g_source_destroy (test->source);
+       g_source_unref (test->source);
+
+       g_main_loop_quit (test->loop);
+       g_thread_join (test->thread);
+
+       g_clear_object (&test->agent);
+
+       egg_buffer_uninit (&test->req);
+       egg_buffer_uninit (&test->resp);
+
+       egg_tests_remove_scratch_directory (test->directory);
+       g_free (test->directory);
+
+       g_cond_clear (&test->cond);
+       g_mutex_clear (&test->lock);
+}
+
+static void
+call_request_identities (Test *test, gsize count)
+{
+       gboolean ret;
+
+       egg_buffer_reset (&test->req);
+       egg_buffer_reset (&test->resp);
+
+       prepare_request_identities (&test->req);
+       ret = gkd_ssh_agent_handle (test->agent, &test->req, &test->resp);
+       g_assert_true (ret);
+
+       check_identities_answer (&test->resp, count);
+}
+
+static void
+call_add_identity (Test *test)
+{
+       gboolean ret;
+
+       egg_buffer_reset (&test->req);
+       egg_buffer_reset (&test->resp);
+
+       prepare_add_identity (&test->req);
+       ret = gkd_ssh_agent_handle (test->agent, &test->req, &test->resp);
+       g_assert_true (ret);
+
+       check_success (&test->resp);
+}
+
+static void
+call_remove (Test *test)
+{
+       gboolean ret;
+
+       egg_buffer_reset (&test->req);
+       egg_buffer_reset (&test->resp);
+
+       prepare_remove_identity (&test->req);
+       ret = gkd_ssh_agent_handle (test->agent, &test->req, &test->resp);
+       g_assert_true (ret);
+
+       check_success (&test->resp);
+}
+
+static void
+call_remove_all_identities (Test *test)
+{
+       gboolean ret;
+
+       egg_buffer_reset (&test->req);
+       egg_buffer_reset (&test->resp);
+
+       prepare_remove_all_identities (&test->req);
+       ret = gkd_ssh_agent_handle (test->agent, &test->req, &test->resp);
+       g_assert_true (ret);
+
+       check_success (&test->resp);
+}
+
+static void
+call_sign (Test *test)
+{
+       gboolean ret;
+
+       egg_buffer_reset (&test->req);
+       egg_buffer_reset (&test->resp);
+
+       prepare_sign_request (&test->req);
+       ret = gkd_ssh_agent_handle (test->agent, &test->req, &test->resp);
+       g_assert_true (ret);
+
+       check_sign_response (&test->resp);
+}
+
+static void
+test_startup_shutdown (Test *test, gconstpointer unused)
+{
+}
+
+static void
+test_list (Test *test, gconstpointer unused)
+{
+       call_request_identities (test, 1);
+}
+
+static void
+test_add (Test *test, gconstpointer unused)
+{
+       /* Adding an identity from the preloaded location doesn't
+        * change the total number of keys returned from
+        * GKD_SSH_OP_REQUEST_IDENTITIES */
+       call_add_identity (test);
+       call_request_identities (test, 1);
+}
+
+static void
+test_remove (Test *test, gconstpointer unused)
+{
+       /* Adding an identity from the preloaded location doesn't
+        * change the total number of keys returned from
+        * GKD_SSH_OP_REQUEST_IDENTITIES */
+       call_add_identity (test);
+       call_request_identities (test, 1);
+
+       /* Removing an identity from the preloaded location doesn't
+        * change the total number of keys returned from
+        * GKD_SSH_OP_REQUEST_IDENTITIES */
+       call_remove (test);
+       call_request_identities (test, 1);
+}
+
+static void
+test_remove_all (Test *test, gconstpointer unused)
+{
+       /* Adding an identity from the preloaded location doesn't
+        * change the total number of keys returned from
+        * GKD_SSH_OP_REQUEST_IDENTITIES */
+       call_add_identity (test);
+       call_request_identities (test, 1);
+
+       /* Removing an identity from the preloaded location doesn't
+        * change the total number of keys returned from
+        * GKD_SSH_OP_REQUEST_IDENTITIES */
+       call_remove_all_identities (test);
+       call_request_identities (test, 1);
+}
+
+static void
+test_sign_loaded (Test *test, gconstpointer unused)
+{
+       /* Adding an identity from the preloaded location doesn't
+        * change the total number of keys returned from
+        * GKD_SSH_OP_REQUEST_IDENTITIES */
+       call_add_identity (test);
+       call_request_identities (test, 1);
+
+       call_sign (test);
+}
+
+static void
+test_sign (Test *test, gconstpointer unused)
+{
+       call_sign (test);
+}
+
+int
+main (int argc, char **argv)
+{
+       g_test_init (&argc, &argv, NULL);
+
+       g_test_add ("/ssh-agent/startup_shutdown", Test, NULL, setup, test_startup_shutdown, teardown);
+       g_test_add ("/ssh-agent/list", Test, NULL, setup, test_list, teardown);
+       g_test_add ("/ssh-agent/add", Test, NULL, setup, test_add, teardown);
+       g_test_add ("/ssh-agent/remove", Test, NULL, setup, test_remove, teardown);
+       g_test_add ("/ssh-agent/remove_all", Test, NULL, setup, test_remove_all, teardown);
+       g_test_add ("/ssh-agent/sign_loaded", Test, NULL, setup, test_sign_loaded, teardown);
+       g_test_add ("/ssh-agent/sign", Test, NULL, setup, test_sign, teardown);
+
+       return g_test_run ();
+}
diff --git a/daemon/ssh-agent/test-gkd-ssh-openssh.c b/daemon/ssh-agent/test-gkd-ssh-openssh.c
new file mode 100644
index 0000000..e6249e7
--- /dev/null
+++ b/daemon/ssh-agent/test-gkd-ssh-openssh.c
@@ -0,0 +1,86 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+   Copyright (C) 2008 Stefan Walter
+
+   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 <stef memberwebs com>
+*/
+
+#include "config.h"
+
+#include "daemon/ssh-agent/gkd-ssh-openssh.h"
+
+#include <glib.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+static struct {
+       const char *filename;
+       const char *encoded;
+} PUBLIC_FILES[] = {
+       { SRCDIR "/pkcs11/ssh-store/fixtures/id_rsa_test.pub",
+         
"AAAAB3NzaC1yc2EAAAABIwAAAQEAoD6VKqkhay6pKHSRjAGWWfFPU8xfsi2gnOwP/B1UHDoztx3czhO+py/fTlhCnSP1jsjkrVIZcnzah2fUNFFRgS4+jROBtvbgHsS72V1E6+ZogV+mBJWWAhw0iPrmQ3Kvm38D3PByo5Y7yKO5kIG2LloYLjosJ5F4sx2xh0uz2wXNtnY1b5xhe2+VEksm9OB+FXaUkZC2fQrTNo8ZGFJQSFd8kUhIfbUDJmlYuZ+vvHM+A3Lc9rHyW4IPaRyxFQciRmb+ZQqU2uSdOXAhg17lskuX/q8yCI5Hy5eDicC222oUMdJTtYgwX4dQCU8TICWhxb3x4RCV+g7D99+tkIvv+w=="
 },
+       { SRCDIR "/pkcs11/ssh-store/fixtures/id_dsa_test.pub",
+         
"AAAAB3NzaC1kc3MAAACBANHNmw2YHEodUj4Ae27i8Rm8uoLnpS68QEiCJx8bv9P1o0AaD0w55sH+TBzlo7vtAEDlAzIOBY3PMpy5WarELTIeXmFPzKfHL8tuxMbOPaN/wDkDZNnJZsqlyRwlQKStPcAlvLBNuMjA53u2ndMTVghtUHXETQzwxKhXf7TmvfLBAAAAFQDnF/Y8MgFCP0PpRC5ZAQo1dyDEwwAAAIEAr4iOpTeZx8i1QgQpRl+dmbBAtHTXbPiophzNJBge9lixqF0T3egN2B9wGGnumIXmnst9RPPjuu+cHCLfxhXHzLlW8MLwoiF6ZQOx9M8WcfWIl5oiGyr2e969woRf5OcMGQPOQBdws6MEtemRqq5gu6dqDqVl3xfhSZSP9LpqAI8AAACAUjiuQ3qGErsCz++qd0qrR++QA185XGXAPZqQEHcr4iKSlO17hSUYA03kOWtDaeRtJOlxjIjl9iLo3juKGFgxUfo2StScOSO2saTWFGjA4MybHCK1+mIYXRcYrq314yK2Tmbql/UGDWpcCCGXLWpSFHTaXTbJjPd6VL+TO9/8tFk="
 }
+};
+
+#define COMMENT "A public key comment"
+
+static void
+test_parse_public (void)
+{
+       GBytes *input_bytes, *output_bytes;
+       gchar *comment;
+       guchar *data;
+       const guchar *blob;
+       gsize n_data;
+       gchar *encoded;
+       gsize i;
+
+       for (i = 0; i < G_N_ELEMENTS (PUBLIC_FILES); ++i) {
+               if (!g_file_get_contents (PUBLIC_FILES[i].filename, (gchar **)&data, &n_data, NULL))
+                       g_assert_not_reached ();
+
+               input_bytes = g_bytes_new_take (data, n_data);
+               output_bytes = gkd_ssh_openssh_parse_public_key (input_bytes, &comment);
+               g_bytes_unref (input_bytes);
+               g_assert (output_bytes);
+
+               blob = g_bytes_get_data (output_bytes, &n_data);
+               encoded = g_base64_encode (blob, n_data);
+               g_bytes_unref (output_bytes);
+               g_assert_cmpstr (encoded, ==, PUBLIC_FILES[i].encoded);
+               g_free (encoded);
+
+               g_assert_cmpstr (comment, ==, COMMENT);
+               g_free (comment);
+       }
+}
+
+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_func ("/ssh-agent/openssh/parse_public", test_parse_public);
+
+       return g_test_run ();
+}
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 3ed5e3d..3b2b359 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -7,6 +7,8 @@ daemon/gnome-keyring-pkcs11.desktop.in.in
 daemon/gnome-keyring-secrets.desktop.in.in
 daemon/gnome-keyring-ssh.desktop.in.in
 daemon/login/gkd-login.c
+daemon/ssh-agent/gkd-ssh-interaction.c
+daemon/org.gnome.keyring.service.in
 egg/dotlock.c
 egg/egg-oid.c
 pkcs11/gkm/gkm-certificate.c


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