summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStef Walter <stefw@gnome.org>2018-02-23 16:37:01 +0100
committerDaiki Ueno <dueno@src.gnome.org>2018-02-25 07:16:09 +0100
commit90fb7fec727081d28946827e31f4330715dad283 (patch)
tree8594d803d7a0162911f940c0516f184a133afebf
parentd0d059cb6e1de1a925f6853877648b1fc79807a8 (diff)
downloadgnome-keyring-wip/dueno/ssh-agent-2.tar.gz
ssh-agent: Use stock ssh-agentwip/dueno/ssh-agent-2
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> https://bugzilla.gnome.org/show_bug.cgi?id=775981
-rw-r--r--configure.ac7
-rw-r--r--daemon/Makefile.am2
-rw-r--r--daemon/gkd-glue.c38
-rw-r--r--daemon/gkd-pkcs11.c49
-rw-r--r--daemon/gkd-pkcs11.h2
-rw-r--r--daemon/ssh-agent/Makefile.am76
-rw-r--r--daemon/ssh-agent/gkd-ssh-agent-ops.c1492
-rw-r--r--daemon/ssh-agent/gkd-ssh-agent-preload.c279
-rw-r--r--daemon/ssh-agent/gkd-ssh-agent-preload.h50
-rw-r--r--daemon/ssh-agent/gkd-ssh-agent-private.h152
-rw-r--r--daemon/ssh-agent/gkd-ssh-agent-process.c291
-rw-r--r--daemon/ssh-agent/gkd-ssh-agent-process.h45
-rw-r--r--daemon/ssh-agent/gkd-ssh-agent-proto.c914
-rw-r--r--daemon/ssh-agent/gkd-ssh-agent-service.c661
-rw-r--r--daemon/ssh-agent/gkd-ssh-agent-service.h54
-rw-r--r--daemon/ssh-agent/gkd-ssh-agent-standalone.c126
-rw-r--r--daemon/ssh-agent/gkd-ssh-agent-util.c163
-rw-r--r--daemon/ssh-agent/gkd-ssh-agent-util.h43
-rw-r--r--daemon/ssh-agent/gkd-ssh-agent.c451
-rw-r--r--daemon/ssh-agent/gkd-ssh-agent.h40
-rw-r--r--daemon/ssh-agent/test-common.c347
-rw-r--r--daemon/ssh-agent/test-common.h94
-rw-r--r--daemon/ssh-agent/test-communication.c449
-rw-r--r--daemon/ssh-agent/test-gkd-ssh-agent-preload.c166
-rw-r--r--daemon/ssh-agent/test-gkd-ssh-agent-process.c217
-rw-r--r--daemon/ssh-agent/test-gkd-ssh-agent-service.c617
-rw-r--r--daemon/ssh-agent/test-gkd-ssh-agent-util.c84
-rw-r--r--daemon/ssh-agent/test-keytypes.c197
28 files changed, 3182 insertions, 3924 deletions
diff --git a/configure.ac b/configure.ac
index 34a50a21..e74deccb 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 3611d42c..48c1652a 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 329a37ed..44ee2136 100644
--- a/daemon/gkd-glue.c
+++ b/daemon/gkd-glue.c
@@ -23,46 +23,44 @@
#include "gkd-glue.h"
#include "gkd-util.h"
-#include "ssh-agent/gkd-ssh-agent.h"
+#include "ssh-agent/gkd-ssh-agent-service.h"
+#include "ssh-agent/gkd-ssh-agent-interaction.h"
#include "egg/egg-cleanup.h"
static void
-pkcs11_ssh_cleanup (gpointer unused)
+pkcs11_ssh_cleanup (gpointer data)
{
- 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;
+ GkdSshAgentService *service = GKD_SSH_AGENT_SERVICE (data);
+ gkd_ssh_agent_service_stop (service);
+ g_object_unref (service);
}
gboolean
gkd_daemon_startup_ssh (void)
{
- GIOChannel *channel;
const gchar *base_dir;
- int sock;
+ GTlsInteraction *interaction;
+ GkdSshAgentPreload *preload;
+ GkdSshAgentService *service;
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;
+ interaction = gkd_ssh_agent_interaction_new (NULL);
+ preload = gkd_ssh_agent_preload_new ("~/.ssh");
+
+ service = gkd_ssh_agent_service_new (base_dir, interaction, preload);
+ g_object_unref (interaction);
+ g_object_unref (preload);
- 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);
+ if (!gkd_ssh_agent_service_start (service))
+ return FALSE;
/* 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);
+ egg_cleanup_register (pkcs11_ssh_cleanup, service);
return TRUE;
}
diff --git a/daemon/gkd-pkcs11.c b/daemon/gkd-pkcs11.c
index 71fdfe21..4d8ecc0b 100644
--- a/daemon/gkd-pkcs11.c
+++ b/daemon/gkd-pkcs11.c
@@ -32,7 +32,7 @@
#include "pkcs11/gnome2-store/gkm-gnome2-store.h"
#include "pkcs11/xdg-store/gkm-xdg-store.h"
-#include "ssh-agent/gkd-ssh-agent.h"
+#include "ssh-agent/gkd-ssh-agent-service.h"
#include <string.h>
@@ -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 1da7b091..38e3f152 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 8ed821bd..ff7793ad 100644
--- a/daemon/ssh-agent/Makefile.am
+++ b/daemon/ssh-agent/Makefile.am
@@ -6,63 +6,73 @@ noinst_LTLIBRARIES += \
libgkd-ssh-agent.la
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-interaction.c \
daemon/ssh-agent/gkd-ssh-agent-interaction.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-agent-service.c \
+ daemon/ssh-agent/gkd-ssh-agent-service.h \
+ daemon/ssh-agent/gkd-ssh-agent-util.c \
+ daemon/ssh-agent/gkd-ssh-agent-util.h \
+ $(NULL)
libgkd_ssh_agent_la_CFLAGS = \
$(DAEMON_CFLAGS)
-# ------------------------------------------------------------------------------
-# Standalone binary
+# Tests
+
+noinst_LTLIBRARIES += \
+ libgkd-ssh-agent-test.la
-noinst_PROGRAMS += \
- gkd-ssh-agent-standalone
+libgkd_ssh_agent_test_la_SOURCES = \
+ daemon/ssh-agent/test-common.c \
+ daemon/ssh-agent/test-common.h
-gkd_ssh_agent_standalone_SOURCES = \
- daemon/ssh-agent/gkd-ssh-agent-standalone.c
-gkd_ssh_agent_standalone_CFLAGS = \
+libgkd_ssh_agent_test_la_CFLAGS = \
$(DAEMON_CFLAGS)
-gkd_ssh_agent_standalone_LDADD = \
- libgkd-ssh-agent.la \
+
+libgkd_ssh_agent_test_la_LIBADD = \
libegg-buffer.la \
- libegg-secure.la \
- libgkm.la \
+ libgkm-wrap-layer.la \
$(DAEMON_LIBS)
-# ------------------------------------------------------------------------------
-# Tests
-
ssh_agent_CFLAGS = \
- $(GCK_CFLAGS)
+ $(DAEMON_CFLAGS)
ssh_agent_LIBS = \
libgkd-ssh-agent.la \
- libgkm.la \
+ libgkd-ssh-agent-test.la \
libegg.la \
- $(GLIB_LIBS) \
- $(GCK_LIBS)
- $(GTHREAD_LIBS)
+ $(DAEMON_LIBS)
ssh_agent_TESTS = \
- test-communication \
- test-keytypes \
- test-gkd-ssh-agent-interaction
+ test-gkd-ssh-agent-interaction \
+ test-gkd-ssh-agent-preload \
+ test-gkd-ssh-agent-process \
+ test-gkd-ssh-agent-service \
+ test-gkd-ssh-agent-util
+
+test_gkd_ssh_agent_preload_SOURCES = daemon/ssh-agent/test-gkd-ssh-agent-preload.c
+test_gkd_ssh_agent_preload_CFLAGS = $(ssh_agent_CFLAGS)
+test_gkd_ssh_agent_preload_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_service_SOURCES = daemon/ssh-agent/test-gkd-ssh-agent-service.c
+test_gkd_ssh_agent_service_CFLAGS = $(ssh_agent_CFLAGS)
+test_gkd_ssh_agent_service_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)
+test_gkd_ssh_agent_util_SOURCES = daemon/ssh-agent/test-gkd-ssh-agent-util.c
+test_gkd_ssh_agent_util_CFLAGS = $(ssh_agent_CFLAGS)
+test_gkd_ssh_agent_util_LDADD = $(ssh_agent_LIBS)
test_gkd_ssh_agent_interaction_SOURCES = daemon/ssh-agent/test-gkd-ssh-agent-interaction.c
test_gkd_ssh_agent_interaction_CFLAGS = $(ssh_agent_CFLAGS)
-test_gkd_ssh_agent_interaction_LDADD = $(ssh_agent_LIBS) libgkd-login.la libgkm-wrap-layer.la
+test_gkd_ssh_agent_interaction_LDADD = $(ssh_agent_LIBS) libgkd-login.la
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
deleted file mode 100644
index e1cba2d5..00000000
--- a/daemon/ssh-agent/gkd-ssh-agent-ops.c
+++ /dev/null
@@ -1,1492 +0,0 @@
-/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
-/* gkd-ssh-agent-ops.h - SSH agent operations
-
- Copyright (C) 2007 Stefan Walter
-
- Gnome keyring is free software; you can redistribute it and/or
- modify it under the terms of the GNU General Public License as
- published by the Free Software Foundation; either version 2 of the
- License, or (at your option) any later version.
-
- Gnome keyring is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
- Author: Stef Walter <stef@memberwebs.com>
-*/
-
-#include "config.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)
-{
- gulong state;
- GError *error = NULL;
- gboolean ret = TRUE;
-
- state = gck_session_get_state (session);
-
- /* 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;
- }
- }
-
- 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)
-{
- 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);
- }
- }
-
- if (pub != NULL) {
- gck_object_destroy (pub, NULL, &error);
-
- 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);
- }
- }
-}
-
-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;
- }
-
- /* 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);
- }
- }
-}
-
-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)
-{
- GError *error = NULL;
- GList *l;
-
- 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))
- 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;
-
- default:
- g_message ("unsupported constraint or other unsupported data");
- return FALSE;
- }
- }
-
- *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);
-
- 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;
- }
-
- gck_builder_add_string (&pub, CKA_LABEL, comment);
- gck_builder_add_string (&priv, CKA_LABEL, comment);
- g_free (comment);
-
- /* 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)
-{
- GckBuilder pub, priv;
- GckSession *session;
- gchar *comment = NULL;
- gboolean ret;
- 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);
- }
-
- 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);
- } else {
- g_assert_not_reached();
- }
-
- 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;
-}
-
-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)
-{
- 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);
-
- /* Build the hash */
- if (mech == CKM_RSA_PKCS)
- hash = make_pkcs1_sign_hash (halgo, data, n_data, &n_hash);
- 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;
-
- 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;
- }
-
- 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;
-}
-
-static gboolean
-op_not_implemented_success (GkdSshAgentCall *call)
-{
- egg_buffer_add_byte (call->resp, GKD_SSH_RES_SUCCESS);
- return TRUE;
-}
-
-static gboolean
-op_not_implemented_failure (GkdSshAgentCall *call)
-{
- egg_buffer_add_byte (call->resp, GKD_SSH_RES_FAILURE);
- return TRUE;
-}
-
-static gboolean
-op_invalid (GkdSshAgentCall *call)
-{
- /* Invalid request, disconnect immediately */
- return FALSE;
-}
-
-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 */
-};
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 00000000..acc8cd8e
--- /dev/null
+++ b/daemon/ssh-agent/gkd-ssh-agent-preload.c
@@ -0,0 +1,279 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2014 Stef Walter
+ * Copyright (C) 2018 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Stef Walter <stef@thewalter.net>, Daiki Ueno
+ */
+
+#include "config.h"
+
+#include "gkd-ssh-agent-preload.h"
+#include "gkd-ssh-agent-util.h"
+
+#include "egg/egg-file-tracker.h"
+#include <string.h>
+
+enum {
+ PROP_0,
+ PROP_PATH
+};
+
+struct _GkdSshAgentPreload
+{
+ GObject object;
+
+ gchar *path;
+ GHashTable *keys_by_public_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_public_filename = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+ self->keys_by_public_key = g_hash_table_new_full (g_bytes_hash, g_bytes_equal, NULL, 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_signal_connect (self->file_tracker, "file-changed", G_CALLBACK (file_load_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_public_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_public_filename, path);
+ if (info) {
+ g_hash_table_remove (self->keys_by_public_filename, path);
+ g_hash_table_remove (self->keys_by_public_key, info->public_key);
+ }
+}
+
+static void
+file_load_inlock (EggFileTracker *tracker,
+ const gchar *path,
+ gpointer user_data)
+{
+ 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_agent_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_public_filename, g_strdup (path), info);
+ g_hash_table_replace (self->keys_by_public_key, info->public_key, info);
+ } else {
+ g_message ("failed to parse ssh public key: %s", path);
+ }
+
+ g_bytes_unref (public_bytes);
+ }
+
+ g_bytes_unref (private_bytes);
+ g_free (private_path);
+}
+
+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 00000000..c626c503
--- /dev/null
+++ b/daemon/ssh-agent/gkd-ssh-agent-preload.h
@@ -0,0 +1,50 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2014 Stef Walter
+ * Copyright (C) 2018 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Stef Walter <stef@thewalter.net>, Daiki Ueno
+ */
+
+#ifndef __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 a6c35a44..97c5a093 100644
--- a/daemon/ssh-agent/gkd-ssh-agent-private.h
+++ b/daemon/ssh-agent/gkd-ssh-agent-private.h
@@ -23,21 +23,6 @@
#ifndef GKDSSHPRIVATE_H_
#define GKDSSHPRIVATE_H_
-#include "egg/egg-buffer.h"
-
-#include "pkcs11/pkcs11.h"
-
-#include <gck/gck.h>
-
-#include <glib.h>
-
-typedef struct _GkdSshAgentCall {
- int sock;
- GList *modules;
- EggBuffer *req;
- EggBuffer *resp;
-} GkdSshAgentCall;
-
/* -----------------------------------------------------------------------------
* SSH OPERATIONS and CONSTANTS
*/
@@ -82,141 +67,4 @@ typedef struct _GkdSshAgentCall {
#define GKD_SSH_FLAG_RSA_SHA2_256 0x02
#define GKD_SSH_FLAG_RSA_SHA2_512 0x04
-/* -----------------------------------------------------------------------------
- * gkd-ssh-agent-ops.c
- */
-
-typedef gboolean (*GkdSshAgentOperation) (GkdSshAgentCall *call);
-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);
-
-/* -----------------------------------------------------------------------------
- * gkd-ssh-agent-proto.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_proto_write_signature_dsa (EggBuffer *resp,
- CK_BYTE_PTR signature,
- CK_ULONG n_signature);
-
-gboolean gkd_ssh_agent_proto_write_signature_ecdsa (EggBuffer *resp,
- CK_BYTE_PTR signature,
- CK_ULONG n_signature);
-
#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 00000000..b6585d70
--- /dev/null
+++ b/daemon/ssh-agent/gkd-ssh-agent-process.c
@@ -0,0 +1,291 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2014 Stef Walter
+ * Copyright (C) 2018 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Stef Walter <stef@thewalter.net>, Daiki Ueno
+ */
+
+#include "config.h"
+
+#include "gkd-ssh-agent-process.h"
+#include "gkd-ssh-agent-private.h"
+#include "gkd-ssh-agent-util.h"
+
+#include <gio/gunixsocketaddress.h>
+#include <glib-unix.h>
+#include <glib/gstdio.h>
+
+enum {
+ PROP_0,
+ PROP_PATH
+};
+
+enum {
+ CLOSED,
+ LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0 };
+
+struct _GkdSshAgentProcess
+{
+ GObject object;
+ gchar *path;
+ GSocketConnection *connection;
+ 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->connection);
+ 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));
+ signals[CLOSED] = g_signal_new_class_handler ("closed",
+ G_TYPE_FROM_CLASS (klass),
+ G_SIGNAL_RUN_LAST,
+ NULL, NULL, NULL, NULL,
+ G_TYPE_NONE, 0);
+}
+
+static void
+on_child_watch (GPid pid,
+ gint status,
+ gpointer user_data)
+{
+ GkdSshAgentProcess *self = GKD_SSH_AGENT_PROCESS (user_data);
+ GError *error = NULL;
+
+ if (pid != self->pid)
+ return;
+
+ g_mutex_lock (&self->lock);
+
+ self->pid = 0;
+ self->output_id = 0;
+ self->child_id = 0;
+
+ if (!g_spawn_check_exit_status (status, &error)) {
+ g_message ("ssh-agent: %s", error->message);
+ g_error_free (error);
+ }
+
+ g_spawn_close_pid (pid);
+
+ g_mutex_unlock (&self->lock);
+
+ g_signal_emit (self, signals[CLOSED], 0);
+}
+
+static gboolean
+on_output_watch (gint fd,
+ GIOCondition condition,
+ gpointer user_data)
+{
+ 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,
+ GError **error)
+{
+ const gchar *argv[] = { SSH_AGENT, "-D", "-a", self->path, NULL };
+ GPid pid;
+
+ if (!g_spawn_async_with_pipes ("/", (gchar **)argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD,
+ NULL, NULL, &pid, NULL, &self->output, NULL, error))
+ return FALSE;
+
+ self->ready = FALSE;
+ self->output_id = g_unix_fd_add (self->output,
+ G_IO_IN | G_IO_HUP | G_IO_ERR,
+ on_output_watch, self);
+
+ self->pid = pid;
+ self->child_id = g_child_watch_add (self->pid, on_child_watch, self);
+
+ return TRUE;
+}
+
+static gboolean
+on_timeout (gpointer user_data)
+{
+ gboolean *timedout = user_data;
+ *timedout = TRUE;
+ return TRUE;
+}
+
+gboolean
+gkd_ssh_agent_process_connect (GkdSshAgentProcess *self,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean started = FALSE;
+ gboolean timedout = FALSE;
+ guint source;
+ GSocketClient *client;
+ GSocketAddress *address;
+ GSocketConnection *connection;
+
+ g_mutex_lock (&self->lock);
+
+ if (self->pid == 0) {
+ if (!agent_start_inlock (self, error)) {
+ g_mutex_unlock (&self->lock);
+ return 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);
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "ssh-agent process is not ready");
+ return FALSE;
+ }
+
+ address = g_unix_socket_address_new (self->path);
+ client = g_socket_client_new ();
+
+ connection = g_socket_client_connect (client,
+ G_SOCKET_CONNECTABLE (address),
+ cancellable,
+ error);
+ g_object_unref (address);
+ g_object_unref (client);
+ if (!connection) {
+ g_mutex_unlock (&self->lock);
+ return FALSE;
+ }
+
+ g_clear_object (&self->connection);
+ self->connection = connection;
+
+ g_mutex_unlock (&self->lock);
+
+ return TRUE;
+}
+
+gboolean
+gkd_ssh_agent_process_call (GkdSshAgentProcess *self,
+ EggBuffer*req,
+ EggBuffer *resp,
+ GCancellable *cancellable,
+ GError **error)
+{
+ g_return_val_if_fail (self->connection != NULL, FALSE);
+ return _gkd_ssh_agent_write_packet (self->connection, req, cancellable, error) &&
+ _gkd_ssh_agent_read_packet (self->connection, resp, cancellable, error);
+}
+
+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);
+}
+
+GPid
+gkd_ssh_agent_process_get_pid (GkdSshAgentProcess *self)
+{
+ return self->pid;
+}
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 00000000..350bc959
--- /dev/null
+++ b/daemon/ssh-agent/gkd-ssh-agent-process.h
@@ -0,0 +1,45 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2014 Stef Walter
+ * Copyright (C) 2018 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Stef Walter <stef@thewalter.net>, Daiki Ueno
+ */
+
+#ifndef __GKD_SSH_AGENT_PROCESS_H__
+#define __GKD_SSH_AGENT_PROCESS_H__
+
+#include <gio/gio.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,
+ GCancellable *cancellable,
+ GError **error);
+gboolean gkd_ssh_agent_process_call (GkdSshAgentProcess *self,
+ EggBuffer *req,
+ EggBuffer *resp,
+ GCancellable *cancellable,
+ GError **error);
+GPid gkd_ssh_agent_process_get_pid (GkdSshAgentProcess *self);
+
+#endif /* __GKD_SSH_AGENT_PROCESS_H__ */
diff --git a/daemon/ssh-agent/gkd-ssh-agent-proto.c b/daemon/ssh-agent/gkd-ssh-agent-proto.c
deleted file mode 100644
index 522733d1..00000000
--- a/daemon/ssh-agent/gkd-ssh-agent-proto.c
+++ /dev/null
@@ -1,914 +0,0 @@
-/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
-/* gkd-ssh-agent-proto.c - SSH agent protocol helpers
-
- Copyright (C) 2007 Stefan Walter
-
- Gnome keyring is free software; you can redistribute it and/or
- modify it under the terms of the GNU General Public License as
- published by the Free Software Foundation; either version 2 of the
- License, or (at your option) any later version.
-
- Gnome keyring is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
- Author: Stef Walter <stef@memberwebs.com>
-*/
-
-#include "config.h"
-
-#include "gkd-ssh-agent-private.h"
-
-#include "gkm/gkm-data-der.h"
-
-#include "egg/egg-buffer.h"
-
-#include <gck/gck.h>
-
-#include <glib.h>
-
-#include <string.h>
-
-/* -----------------------------------------------------------------------------
- * QUARKS
- */
-
-static GQuark OID_ANSI_SECP256R1;
-static GQuark OID_ANSI_SECP384R1;
-static GQuark OID_ANSI_SECP521R1;
-
-static void
-init_quarks (void)
-{
- static volatile gsize quarks_inited = 0;
-
- if (g_once_init_enter (&quarks_inited)) {
-
- #define QUARK(name, value) \
- name = g_quark_from_static_string(value)
-
- QUARK (OID_ANSI_SECP256R1, "1.2.840.10045.3.1.7");
- QUARK (OID_ANSI_SECP384R1, "1.3.132.0.34");
- QUARK (OID_ANSI_SECP521R1, "1.3.132.0.35");
-
- #undef QUARK
-
- g_once_init_leave (&quarks_inited, 1);
- }
-}
-
-gulong
-gkd_ssh_agent_proto_keytype_to_algo (const gchar *salgo)
-{
- g_return_val_if_fail (salgo, G_MAXULONG);
- if (strcmp (salgo, "ssh-rsa") == 0 ||
- strcmp (salgo, "rsa-sha2-256") == 0 ||
- strcmp (salgo, "rsa-sha2-512") == 0)
- return CKK_RSA;
- else if (strcmp (salgo, "ssh-dss") == 0)
- return CKK_DSA;
- else if (strcmp (salgo, "ecdsa-sha2-nistp256") == 0 ||
- strcmp (salgo, "ecdsa-sha2-nistp384") == 0 ||
- strcmp (salgo, "ecdsa-sha2-nistp521") == 0)
- return CKK_EC;
- return G_MAXULONG;
-}
-
-GQuark
-gkd_ssh_agent_proto_curve_to_oid (const gchar *salgo)
-{
- g_return_val_if_fail (salgo, 0);
-
- init_quarks ();
-
- if (g_str_equal (salgo, "nistp256"))
- return OID_ANSI_SECP256R1;
- else if (g_str_equal (salgo, "nistp384"))
- return OID_ANSI_SECP384R1;
- else if (g_str_equal (salgo, "nistp521"))
- return OID_ANSI_SECP521R1;
-
- return 0;
-}
-
-const gchar*
-gkd_ssh_agent_proto_oid_to_curve (GQuark oid)
-{
- g_return_val_if_fail (oid, NULL);
-
- init_quarks ();
-
- if (oid == OID_ANSI_SECP256R1)
- return "nistp256";
- else if (oid == OID_ANSI_SECP384R1)
- return "nistp384";
- else if (oid == OID_ANSI_SECP521R1)
- return "nistp521";
-
- return NULL;
-}
-
-gint
-gkd_ssh_agent_proto_curve_oid_to_hash_algo (GQuark oid)
-{
- g_return_val_if_fail (oid, -1);
-
- init_quarks ();
-
- /* from rfc5656 */
- if (oid == OID_ANSI_SECP256R1)
- return G_CHECKSUM_SHA256;
- else if (oid == OID_ANSI_SECP384R1)
- return G_CHECKSUM_SHA384;
- else if (oid == OID_ANSI_SECP521R1)
- return G_CHECKSUM_SHA512;
-
- return -1;
-}
-
-const gchar*
-gkd_ssh_agent_proto_dsa_algo_to_keytype (void)
-{
- return "ssh-dss";
-}
-
-const gchar*
-gkd_ssh_agent_proto_rsa_algo_to_keytype (GChecksumType halgo)
-{
- if (halgo == G_CHECKSUM_SHA256)
- return "rsa-sha2-256";
- else if (halgo == G_CHECKSUM_SHA512)
- return "rsa-sha2-512";
-
- return "ssh-rsa";
-}
-
-const gchar*
-gkd_ssh_agent_proto_ecc_algo_to_keytype (GQuark oid)
-{
- g_return_val_if_fail (oid != 0, NULL);
-
- init_quarks ();
-
- if (oid == OID_ANSI_SECP256R1)
- return "ecdsa-sha2-nistp256";
- else if (oid == OID_ANSI_SECP384R1)
- return "ecdsa-sha2-nistp384";
- else if (oid == OID_ANSI_SECP521R1)
- return "ecdsa-sha2-nistp521";
-
- return NULL;
-}
-
-GQuark
-gkd_ssh_agent_proto_find_curve_oid (GckAttributes *attrs)
-{
- GBytes *bytes;
- const GckAttribute *attr;
- GQuark oid;
-
- g_assert (attrs);
-
- attr = gck_attributes_find (attrs, CKA_EC_PARAMS);
- if (attr == NULL)
- g_return_val_if_reached (0);
-
- bytes = g_bytes_new (attr->value, attr->length);
-
- oid = gkm_data_der_oid_from_ec_params (bytes);
-
- g_bytes_unref (bytes);
-
- return oid;
-}
-
-gboolean
-gkd_ssh_agent_proto_read_mpi (EggBuffer *req, gsize *offset,
- GckBuilder *builder,
- CK_ATTRIBUTE_TYPE type)
-{
- const guchar *data;
- gsize len;
-
- if (!egg_buffer_get_byte_array (req, *offset, offset, &data, &len))
- return FALSE;
-
- /* Convert to unsigned format */
- if (len >= 2 && data[0] == 0 && (data[1] & 0x80)) {
- ++data;
- --len;
- }
-
- gck_builder_add_data (builder, type, data, len);
- return TRUE;
-}
-
-gboolean
-gkd_ssh_agent_proto_read_mpi_v1 (EggBuffer *req,
- gsize *offset,
- GckBuilder *attrs,
- CK_ATTRIBUTE_TYPE type)
-{
- const guchar *data;
- gsize bytes;
- guint16 bits;
-
- /* Get the number of bits */
- if (!egg_buffer_get_uint16 (req, *offset, offset, &bits))
- return FALSE;
-
- /* Figure out the number of binary bytes following */
- bytes = (bits + 7) / 8;
- if (bytes > 8 * 1024)
- return FALSE;
-
- /* Pull these out directly */
- if (req->len < *offset + bytes)
- return FALSE;
- data = req->buf + *offset;
- *offset += bytes;
-
- gck_builder_add_data (attrs, type, data, bytes);
- return TRUE;
-}
-
-gboolean
-gkd_ssh_agent_proto_read_string_to_der (EggBuffer *req,
- gsize *offset,
- GckBuilder *attrs,
- CK_ATTRIBUTE_TYPE type)
-{
- const guchar *data, *q_data;
- gsize len, q_len;
- GBytes *bytes;
-
- if (!egg_buffer_get_byte_array (req, *offset, offset, &data, &len))
- return FALSE;
-
- bytes = gkm_data_der_encode_ecdsa_q_str (data, len);
-
- q_data = g_bytes_get_data (bytes, &q_len);
-
- gck_builder_add_data (attrs, type, q_data, q_len);
- return TRUE;
-}
-
-gboolean
-gkd_ssh_agent_proto_write_mpi (EggBuffer *resp,
- const GckAttribute *attr)
-{
- const guchar *value;
- guchar *data;
- gsize n_extra;
-
- g_assert (resp);
- g_assert (attr);
-
- /* Convert from unsigned format */
- n_extra = 0;
- value = attr->value;
- if (attr->length && (value[0] & 0x80))
- ++n_extra;
-
- data = egg_buffer_add_byte_array_empty (resp, attr->length + n_extra);
- if (data == NULL)
- return FALSE;
-
- memset (data, 0, n_extra);
- memcpy (data + n_extra, attr->value, attr->length);
- return TRUE;
-}
-
-gboolean
-gkd_ssh_agent_proto_write_mpi_v1 (EggBuffer *resp,
- const GckAttribute *attr)
-{
- guchar *data;
-
- g_return_val_if_fail (attr->length * 8 < G_MAXUSHORT, FALSE);
-
- if (!egg_buffer_add_uint16 (resp, attr->length * 8))
- return FALSE;
-
- data = egg_buffer_add_empty (resp, attr->length);
- if (data == NULL)
- return FALSE;
- memcpy (data, attr->value, attr->length);
- return TRUE;
-}
-
-gboolean
-gkd_ssh_agent_proto_write_string (EggBuffer *resp,
- const GckAttribute *attr)
-{
- guchar *data;
-
- g_assert (resp);
- g_assert (attr);
-
- data = egg_buffer_add_byte_array_empty (resp, attr->length);
- if (data == NULL)
- return FALSE;
-
- memcpy (data, attr->value, attr->length);
- return TRUE;
-}
-
-const guchar*
-gkd_ssh_agent_proto_read_challenge_v1 (EggBuffer *req, gsize *offset, gsize *n_challenge)
-{
- const guchar *data;
- gsize bytes;
- guint16 bits;
-
- /* Get the number of bits */
- if (!egg_buffer_get_uint16 (req, *offset, offset, &bits))
- return FALSE;
-
- /* Figure out the number of binary bytes following */
- bytes = (bits + 7) / 8;
- if (bytes > 8 * 1024)
- return FALSE;
-
- /* Pull these out directly */
- if (req->len < *offset + bytes)
- return FALSE;
- data = req->buf + *offset;
- *offset += bytes;
- *n_challenge = bytes;
- return data;
-}
-
-gboolean
-gkd_ssh_agent_proto_read_public (EggBuffer *req,
- gsize *offset,
- GckBuilder *attrs,
- gulong *algo)
-{
- gboolean ret;
- gchar *stype;
- gulong alg;
-
- g_assert (req);
- g_assert (offset);
-
- /* The string algorithm */
- if (!egg_buffer_get_string (req, *offset, offset, &stype, (EggBufferAllocator)g_realloc))
- return FALSE;
-
- alg = gkd_ssh_agent_proto_keytype_to_algo (stype);
- if (alg == G_MAXULONG) {
- g_warning ("unsupported algorithm from SSH: %s", stype);
- g_free (stype);
- return FALSE;
- }
-
- g_free (stype);
- switch (alg) {
- case CKK_RSA:
- ret = gkd_ssh_agent_proto_read_public_rsa (req, offset, attrs);
- break;
- case CKK_DSA:
- ret = gkd_ssh_agent_proto_read_public_dsa (req, offset, attrs);
- break;
- case CKK_EC:
- ret = gkd_ssh_agent_proto_read_public_ecdsa (req, offset, attrs);
- break;
- default:
- g_assert_not_reached ();
- return FALSE;
- }
-
- if (!ret) {
- g_warning ("couldn't read incoming SSH public key");
- return FALSE;
- }
-
- if (algo)
- *algo = alg;
- return ret;
-}
-
-gboolean
-gkd_ssh_agent_proto_read_pair_rsa (EggBuffer *req,
- gsize *offset,
- GckBuilder *priv_attrs,
- GckBuilder *pub_attrs)
-{
- const GckAttribute *attr;
-
- g_assert (req);
- g_assert (offset);
- g_assert (priv_attrs);
- g_assert (pub_attrs);
-
- if (!gkd_ssh_agent_proto_read_mpi (req, offset, priv_attrs, CKA_MODULUS) ||
- !gkd_ssh_agent_proto_read_mpi (req, offset, priv_attrs, CKA_PUBLIC_EXPONENT) ||
- !gkd_ssh_agent_proto_read_mpi (req, offset, priv_attrs, CKA_PRIVATE_EXPONENT) ||
- !gkd_ssh_agent_proto_read_mpi (req, offset, priv_attrs, CKA_COEFFICIENT) ||
- !gkd_ssh_agent_proto_read_mpi (req, offset, priv_attrs, CKA_PRIME_1) ||
- !gkd_ssh_agent_proto_read_mpi (req, offset, priv_attrs, CKA_PRIME_2))
- return FALSE;
-
- /* Copy attributes to the public key */
- attr = gck_builder_find (priv_attrs, CKA_MODULUS);
- gck_builder_add_attribute (pub_attrs, attr);
- attr = gck_builder_find (priv_attrs, CKA_PUBLIC_EXPONENT);
- gck_builder_add_attribute (pub_attrs, attr);
-
- /* Add in your basic other required attributes */
- gck_builder_add_ulong (priv_attrs, CKA_CLASS, CKO_PRIVATE_KEY);
- gck_builder_add_ulong (priv_attrs, CKA_KEY_TYPE, CKK_RSA);
- gck_builder_add_ulong (pub_attrs, CKA_CLASS, CKO_PUBLIC_KEY);
- gck_builder_add_ulong (pub_attrs, CKA_KEY_TYPE, CKK_RSA);
-
- return TRUE;
-}
-
-gboolean
-gkd_ssh_agent_proto_read_pair_v1 (EggBuffer *req,
- gsize *offset,
- GckBuilder *priv_attrs,
- GckBuilder *pub_attrs)
-{
- const GckAttribute *attr;
-
- g_assert (req);
- g_assert (offset);
- g_assert (priv_attrs);
- g_assert (pub_attrs);
-
- if (!gkd_ssh_agent_proto_read_mpi_v1 (req, offset, priv_attrs, CKA_MODULUS) ||
- !gkd_ssh_agent_proto_read_mpi_v1 (req, offset, priv_attrs, CKA_PUBLIC_EXPONENT) ||
- !gkd_ssh_agent_proto_read_mpi_v1 (req, offset, priv_attrs, CKA_PRIVATE_EXPONENT) ||
- !gkd_ssh_agent_proto_read_mpi_v1 (req, offset, priv_attrs, CKA_COEFFICIENT) ||
- !gkd_ssh_agent_proto_read_mpi_v1 (req, offset, priv_attrs, CKA_PRIME_1) ||
- !gkd_ssh_agent_proto_read_mpi_v1 (req, offset, priv_attrs, CKA_PRIME_2))
- return FALSE;
-
- /* Copy attributes to the public key */
- attr = gck_builder_find (priv_attrs, CKA_MODULUS);
- gck_builder_add_attribute (pub_attrs, attr);
- attr = gck_builder_find (priv_attrs, CKA_PUBLIC_EXPONENT);
- gck_builder_add_attribute (pub_attrs, attr);
-
- /* Add in your basic other required attributes */
- gck_builder_add_ulong (priv_attrs, CKA_CLASS, CKO_PRIVATE_KEY);
- gck_builder_add_ulong (priv_attrs, CKA_KEY_TYPE, CKK_RSA);
- gck_builder_add_ulong (pub_attrs, CKA_CLASS, CKO_PUBLIC_KEY);
- gck_builder_add_ulong (pub_attrs, CKA_KEY_TYPE, CKK_RSA);
-
- return TRUE;
-}
-
-gboolean
-gkd_ssh_agent_proto_read_public_rsa (EggBuffer *req,
- gsize *offset,
- GckBuilder *attrs)
-{
- g_assert (req);
- g_assert (offset);
- g_assert (attrs);
-
- if (!gkd_ssh_agent_proto_read_mpi (req, offset, attrs, CKA_PUBLIC_EXPONENT) ||
- !gkd_ssh_agent_proto_read_mpi (req, offset, attrs, CKA_MODULUS))
- return FALSE;
-
- /* Add in your basic other required attributes */
- gck_builder_add_ulong (attrs, CKA_CLASS, CKO_PUBLIC_KEY);
- gck_builder_add_ulong (attrs, CKA_KEY_TYPE, CKK_RSA);
-
- return TRUE;
-}
-
-gboolean
-gkd_ssh_agent_proto_read_public_v1 (EggBuffer *req,
- gsize *offset,
- GckBuilder *attrs)
-{
- guint32 bits;
-
- g_assert (req);
- g_assert (offset);
- g_assert (attrs);
-
- if (!egg_buffer_get_uint32 (req, *offset, offset, &bits))
- return FALSE;
-
- if (!gkd_ssh_agent_proto_read_mpi_v1 (req, offset, attrs, CKA_PUBLIC_EXPONENT) ||
- !gkd_ssh_agent_proto_read_mpi_v1 (req, offset, attrs, CKA_MODULUS))
- return FALSE;
-
- /* Add in your basic other required attributes */
- gck_builder_add_ulong (attrs, CKA_CLASS, CKO_PUBLIC_KEY);
- gck_builder_add_ulong (attrs, CKA_KEY_TYPE, CKK_RSA);
-
- return TRUE;
-}
-
-gboolean
-gkd_ssh_agent_proto_read_pair_dsa (EggBuffer *req,
- gsize *offset,
- GckBuilder *priv_attrs,
- GckBuilder *pub_attrs)
-{
- const GckAttribute *attr;
-
- g_assert (req);
- g_assert (offset);
- g_assert (priv_attrs);
- g_assert (pub_attrs);
-
- if (!gkd_ssh_agent_proto_read_mpi (req, offset, priv_attrs, CKA_PRIME) ||
- !gkd_ssh_agent_proto_read_mpi (req, offset, priv_attrs, CKA_SUBPRIME) ||
- !gkd_ssh_agent_proto_read_mpi (req, offset, priv_attrs, CKA_BASE) ||
- !gkd_ssh_agent_proto_read_mpi (req, offset, pub_attrs, CKA_VALUE) ||
- !gkd_ssh_agent_proto_read_mpi (req, offset, priv_attrs, CKA_VALUE))
- return FALSE;
-
- /* Copy attributes to the public key */
- attr = gck_builder_find (priv_attrs, CKA_PRIME);
- gck_builder_add_attribute (pub_attrs, attr);
- attr = gck_builder_find (priv_attrs, CKA_SUBPRIME);
- gck_builder_add_attribute (pub_attrs, attr);
- attr = gck_builder_find (priv_attrs, CKA_BASE);
- gck_builder_add_attribute (pub_attrs, attr);
-
- /* Add in your basic other required attributes */
- gck_builder_add_ulong (priv_attrs, CKA_CLASS, CKO_PRIVATE_KEY);
- gck_builder_add_ulong (priv_attrs, CKA_KEY_TYPE, CKK_DSA);
- gck_builder_add_ulong (pub_attrs, CKA_CLASS, CKO_PUBLIC_KEY);
- gck_builder_add_ulong (pub_attrs, CKA_KEY_TYPE, CKK_DSA);
-
- return TRUE;
-}
-
-gboolean
-gkd_ssh_agent_proto_read_public_dsa (EggBuffer *req,
- gsize *offset,
- GckBuilder *attrs)
-{
- g_assert (req);
- g_assert (offset);
- g_assert (attrs);
-
- if (!gkd_ssh_agent_proto_read_mpi (req, offset, attrs, CKA_PRIME) ||
- !gkd_ssh_agent_proto_read_mpi (req, offset, attrs, CKA_SUBPRIME) ||
- !gkd_ssh_agent_proto_read_mpi (req, offset, attrs, CKA_BASE) ||
- !gkd_ssh_agent_proto_read_mpi (req, offset, attrs, CKA_VALUE))
- return FALSE;
-
- /* Add in your basic other required attributes */
- gck_builder_add_ulong (attrs, CKA_CLASS, CKO_PUBLIC_KEY);
- gck_builder_add_ulong (attrs, CKA_KEY_TYPE, CKK_DSA);
-
- return TRUE;
-}
-
-gboolean
-gkd_ssh_agent_proto_read_ecdsa_curve (EggBuffer *req,
- gsize *offset,
- GckBuilder *attrs)
-{
- GBytes *params;
- gchar *curve_name;
- const guchar *params_data;
- GQuark oid;
- gsize params_len;
-
- g_assert (req);
- g_assert (offset);
- g_assert (attrs);
-
- /* first part is the curve name (nistp* part of key name) and needs
- * to be converted to CKA_EC_PARAMS
- */
- if (!egg_buffer_get_string (req, *offset, offset, &curve_name,
- (EggBufferAllocator)g_realloc))
- return FALSE;
-
- oid = gkd_ssh_agent_proto_curve_to_oid (curve_name);
- g_return_val_if_fail (oid, FALSE);
-
- params = gkm_data_der_get_ec_params (oid);
- g_return_val_if_fail (params != NULL, FALSE);
-
- params_data = g_bytes_get_data (params, &params_len);
- gck_builder_add_data (attrs, CKA_EC_PARAMS, params_data, params_len);
-
- return TRUE;
-}
-
-gboolean
-gkd_ssh_agent_proto_read_pair_ecdsa (EggBuffer *req,
- gsize *offset,
- GckBuilder *priv_attrs,
- GckBuilder *pub_attrs)
-{
- const GckAttribute *attr;
-
- g_assert (req);
- g_assert (offset);
- g_assert (priv_attrs);
- g_assert (pub_attrs);
-
- if (!gkd_ssh_agent_proto_read_ecdsa_curve (req, offset, priv_attrs) ||
- !gkd_ssh_agent_proto_read_string_to_der (req, offset, priv_attrs, CKA_EC_POINT) ||
- !gkd_ssh_agent_proto_read_mpi (req, offset, priv_attrs, CKA_VALUE))
- return FALSE;
-
- /* Copy attributes to the public key */
- attr = gck_builder_find (priv_attrs, CKA_EC_POINT);
- gck_builder_add_attribute (pub_attrs, attr);
- attr = gck_builder_find (priv_attrs, CKA_EC_PARAMS);
- gck_builder_add_attribute (pub_attrs, attr);
-
- /* Add in your basic other required attributes */
- gck_builder_add_ulong (priv_attrs, CKA_CLASS, CKO_PRIVATE_KEY);
- gck_builder_add_ulong (priv_attrs, CKA_KEY_TYPE, CKK_EC);
- gck_builder_add_ulong (pub_attrs, CKA_CLASS, CKO_PUBLIC_KEY);
- gck_builder_add_ulong (pub_attrs, CKA_KEY_TYPE, CKK_EC);
-
- return TRUE;
-}
-
-gboolean
-gkd_ssh_agent_proto_read_public_ecdsa (EggBuffer *req,
- gsize *offset,
- GckBuilder *attrs)
-{
- g_assert (req);
- g_assert (offset);
- g_assert (attrs);
-
- if (!gkd_ssh_agent_proto_read_ecdsa_curve (req, offset, attrs) ||
- !gkd_ssh_agent_proto_read_string_to_der (req, offset, attrs, CKA_EC_POINT))
- return FALSE;
-
- /* Add in your basic other required attributes */
- gck_builder_add_ulong (attrs, CKA_CLASS, CKO_PUBLIC_KEY);
- gck_builder_add_ulong (attrs, CKA_KEY_TYPE, CKK_EC);
-
- return TRUE;
-}
-
-gboolean
-gkd_ssh_agent_proto_write_public (EggBuffer *resp, GckAttributes *attrs)
-{
- gboolean ret = FALSE;
- gulong algo;
-
- g_assert (resp);
- g_assert (attrs);
-
- if (!gck_attributes_find_ulong (attrs, CKA_KEY_TYPE, &algo))
- g_return_val_if_reached (FALSE);
-
- switch (algo) {
- case CKK_RSA:
- ret = gkd_ssh_agent_proto_write_public_rsa (resp, attrs);
- break;
-
- case CKK_DSA:
- ret = gkd_ssh_agent_proto_write_public_dsa (resp, attrs);
- break;
-
- case CKK_EC:
- ret = gkd_ssh_agent_proto_write_public_ecdsa (resp, attrs);
- break;
-
- default:
- g_return_val_if_reached (FALSE);
- break;
- }
-
- return ret;
-}
-
-gboolean
-gkd_ssh_agent_proto_write_public_rsa (EggBuffer *resp, GckAttributes *attrs)
-{
- const GckAttribute *attr;
- const gchar *salgo;
-
- g_assert (resp);
- g_assert (attrs);
-
- /* write algorithm identification */
- salgo = gkd_ssh_agent_proto_rsa_algo_to_keytype (G_CHECKSUM_SHA1);
- g_assert (salgo);
- egg_buffer_add_string (resp, salgo);
-
- attr = gck_attributes_find (attrs, CKA_PUBLIC_EXPONENT);
- g_return_val_if_fail (attr, FALSE);
-
- if (!gkd_ssh_agent_proto_write_mpi (resp, attr))
- return FALSE;
-
- attr = gck_attributes_find (attrs, CKA_MODULUS);
- g_return_val_if_fail (attr, FALSE);
-
- if (!gkd_ssh_agent_proto_write_mpi (resp, attr))
- return FALSE;
-
- return TRUE;
-}
-
-gboolean
-gkd_ssh_agent_proto_write_public_dsa (EggBuffer *resp, GckAttributes *attrs)
-{
- const GckAttribute *attr;
- const gchar *salgo;
-
- g_assert (resp);
- g_assert (attrs);
-
- /* write algorithm identification */
- salgo = gkd_ssh_agent_proto_dsa_algo_to_keytype ();
- g_assert (salgo);
- egg_buffer_add_string (resp, salgo);
-
- attr = gck_attributes_find (attrs, CKA_PRIME);
- g_return_val_if_fail (attr, FALSE);
-
- if (!gkd_ssh_agent_proto_write_mpi (resp, attr))
- return FALSE;
-
- attr = gck_attributes_find (attrs, CKA_SUBPRIME);
- g_return_val_if_fail (attr, FALSE);
-
- if (!gkd_ssh_agent_proto_write_mpi (resp, attr))
- return FALSE;
-
- attr = gck_attributes_find (attrs, CKA_BASE);
- g_return_val_if_fail (attr, FALSE);
-
- if (!gkd_ssh_agent_proto_write_mpi (resp, attr))
- return FALSE;
-
- attr = gck_attributes_find (attrs, CKA_VALUE);
- g_return_val_if_fail (attr, FALSE);
-
- if (!gkd_ssh_agent_proto_write_mpi (resp, attr))
- return FALSE;
-
- return TRUE;
-}
-
-gboolean
-gkd_ssh_agent_proto_write_public_ecdsa (EggBuffer *resp, GckAttributes *attrs)
-{
- const GckAttribute *attr;
- GQuark oid;
- const gchar *curve;
- guchar *data;
- const guchar *q_data;
- GBytes *bytes, *q;
- gboolean rv;
- gsize q_len;
- const gchar *salgo;
-
- g_assert (resp);
- g_assert (attrs);
-
- /* decode curve name from EC_PARAMS */
- oid = gkd_ssh_agent_proto_find_curve_oid (attrs);
- g_return_val_if_fail (oid, FALSE);
-
- /* write algorithm identification */
- salgo = gkd_ssh_agent_proto_ecc_algo_to_keytype (oid);
- g_assert (salgo);
- egg_buffer_add_string (resp, salgo);
-
- curve = gkd_ssh_agent_proto_oid_to_curve (oid);
- g_return_val_if_fail (curve != NULL, FALSE);
-
- data = egg_buffer_add_byte_array_empty (resp, strlen (curve));
- if (data == NULL)
- return FALSE;
-
- memcpy (data, curve, strlen(curve));
-
- /* decode DER-encoded value Q */
- attr = gck_attributes_find (attrs, CKA_EC_POINT);
- g_return_val_if_fail (attr, FALSE);
-
- bytes = g_bytes_new_static (attr->value, attr->length);
- rv = gkm_data_der_decode_ecdsa_q (bytes, &q);
- g_return_val_if_fail (rv, FALSE);
- g_bytes_unref (bytes);
-
- q_data = g_bytes_get_data (q, &q_len);
-
- data = egg_buffer_add_byte_array_empty (resp, q_len);
- if (data == NULL)
- return FALSE;
-
- memcpy (data, q_data, q_len);
- g_bytes_unref (q);
-
- return TRUE;
-}
-
-gboolean
-gkd_ssh_agent_proto_write_public_v1 (EggBuffer *resp, GckAttributes *attrs)
-{
- const GckAttribute *attr;
- gulong bits;
-
- g_assert (resp);
- g_assert (attrs);
-
- /* This is always an RSA key. */
-
- /* Write out the number of bits of the key */
- if (!gck_attributes_find_ulong (attrs, CKA_MODULUS_BITS, &bits))
- g_return_val_if_reached (FALSE);
- egg_buffer_add_uint32 (resp, bits);
-
- /* Write out the exponent */
- attr = gck_attributes_find (attrs, CKA_PUBLIC_EXPONENT);
- g_return_val_if_fail (attr, FALSE);
-
- if (!gkd_ssh_agent_proto_write_mpi_v1 (resp, attr))
- return FALSE;
-
- /* Write out the modulus */
- attr = gck_attributes_find (attrs, CKA_MODULUS);
- g_return_val_if_fail (attr, FALSE);
-
- if (!gkd_ssh_agent_proto_write_mpi_v1 (resp, attr))
- return FALSE;
-
- return TRUE;
-}
-
-gboolean
-gkd_ssh_agent_proto_write_signature_rsa (EggBuffer *resp, CK_BYTE_PTR signature, CK_ULONG n_signature)
-{
- return egg_buffer_add_byte_array (resp, signature, n_signature);
-}
-
-gboolean
-gkd_ssh_agent_proto_write_signature_dsa (EggBuffer *resp, CK_BYTE_PTR signature, CK_ULONG n_signature)
-{
- g_return_val_if_fail (n_signature == 40, FALSE);
- return egg_buffer_add_byte_array (resp, signature, n_signature);
-}
-
-static gboolean
-gkd_ssh_agent_buffer_put_rfc_mpi (EggBuffer *buffer, const guchar *val,
- gsize len)
-{
- gsize pad = 0;
-
- /*
- * From RFC 4251:
- * If the most significant bit would be set for a positive number,
- * the number MUST be preceded by a zero byte.
- */
- if ((val[0] & 0x80))
- pad = 1;
-
- if (!egg_buffer_add_uint32 (buffer, len + pad))
- return 0;
- if (pad && !egg_buffer_add_byte (buffer, 0x00))
- return 0;
- return egg_buffer_append (buffer, val, len);
-}
-
-gboolean
-gkd_ssh_agent_proto_write_signature_ecdsa (EggBuffer *resp, CK_BYTE_PTR signature, CK_ULONG n_signature)
-{
- gboolean rv;
- gsize mpi_size;
- gsize pads = 0;
-
- g_return_val_if_fail ((n_signature % 2) == 0, FALSE);
-
- /* PKCS#11 lists the MPIs concatenated, SSH-agent expects the size headers */
- mpi_size = n_signature/2;
-
- /*
- * From RFC 4251, Section 5:
- * If the most significant bit would be set for a positive number,
- * the number MUST be preceded by a zero byte.
- */
- pads = ((signature[0] & 0x80) == 0x80) + ((signature[mpi_size] & 0x80) == 0x80);
-
- /* First we need header for the whole signature blob
- * (including 2 length headers and potential "padding")
- */
- egg_buffer_add_uint32 (resp, n_signature + 8 + pads);
-
- rv = gkd_ssh_agent_buffer_put_rfc_mpi (resp, signature, mpi_size);
- g_return_val_if_fail (rv, FALSE);
-
- rv = gkd_ssh_agent_buffer_put_rfc_mpi (resp, signature + mpi_size, mpi_size);
- return rv;
-}
diff --git a/daemon/ssh-agent/gkd-ssh-agent-service.c b/daemon/ssh-agent/gkd-ssh-agent-service.c
new file mode 100644
index 00000000..8b9a8f10
--- /dev/null
+++ b/daemon/ssh-agent/gkd-ssh-agent-service.c
@@ -0,0 +1,661 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2007 Stefan Walter
+ * Copyright (C) 2018 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Stef Walter <stef@thewalter.net>, Daiki Ueno
+ */
+
+#include "config.h"
+
+#include <gio/gunixsocketaddress.h>
+#include <glib/gstdio.h>
+
+#include <gcr/gcr-base.h>
+
+#include "gkd-ssh-agent-service.h"
+#include "gkd-ssh-agent-preload.h"
+#include "gkd-ssh-agent-private.h"
+#include "gkd-ssh-agent-process.h"
+#include "gkd-ssh-agent-util.h"
+#include "daemon/login/gkd-login-interaction.h"
+
+#include "egg/egg-buffer.h"
+#include "egg/egg-error.h"
+#include "egg/egg-secure-memory.h"
+
+#include <glib/gi18n-lib.h>
+
+EGG_SECURE_DECLARE (ssh_agent);
+
+typedef gboolean (*GkdSshAgentOperation) (GkdSshAgentService *agent, EggBuffer *req, EggBuffer *resp, GCancellable *cancellable, GError **error);
+static const GkdSshAgentOperation operations[GKD_SSH_OP_MAX];
+
+enum {
+ PROP_0,
+ PROP_PATH,
+ PROP_INTERACTION,
+ PROP_PRELOAD
+};
+
+struct _GkdSshAgentService
+{
+ GObject object;
+ gchar *path;
+ GTlsInteraction *interaction;
+ GkdSshAgentPreload *preload;
+ GkdSshAgentProcess *process;
+ GSocketAddress *address;
+ GSocketListener *listener;
+ GHashTable *keys;
+ GMutex lock;
+ GCancellable *cancellable;
+};
+
+G_DEFINE_TYPE (GkdSshAgentService, gkd_ssh_agent_service, G_TYPE_OBJECT);
+
+static void
+gkd_ssh_agent_service_init (GkdSshAgentService *self)
+{
+ self->keys = g_hash_table_new_full (g_bytes_hash, g_bytes_equal,
+ (GDestroyNotify)g_bytes_unref, NULL);
+ g_mutex_init (&self->lock);
+}
+
+static void
+gkd_ssh_agent_service_constructed (GObject *object)
+{
+ GkdSshAgentService *self = GKD_SSH_AGENT_SERVICE (object);
+ gchar *path;
+
+ path = g_strdup_printf ("%s/.ssh", self->path);
+ self->process = gkd_ssh_agent_process_new (path);
+ g_free (path);
+
+ self->listener = G_SOCKET_LISTENER (g_threaded_socket_service_new (-1));
+ self->cancellable = g_cancellable_new ();
+
+ G_OBJECT_CLASS (gkd_ssh_agent_service_parent_class)->constructed (object);
+}
+
+static void
+gkd_ssh_agent_service_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GkdSshAgentService *self = GKD_SSH_AGENT_SERVICE (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;
+ }
+}
+
+static void
+gkd_ssh_agent_service_finalize (GObject *object)
+{
+ GkdSshAgentService *self = GKD_SSH_AGENT_SERVICE (object);
+
+ g_free (self->path);
+ g_object_unref (self->interaction);
+ g_object_unref (self->preload);
+
+ g_object_unref (self->process);
+ g_object_unref (self->listener);
+ g_clear_object (&self->address);
+ g_mutex_clear (&self->lock);
+ g_hash_table_unref (self->keys);
+ g_object_unref (self->cancellable);
+
+ G_OBJECT_CLASS (gkd_ssh_agent_service_parent_class)->finalize (object);
+}
+
+static void
+gkd_ssh_agent_service_class_init (GkdSshAgentServiceClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ gobject_class->constructed = gkd_ssh_agent_service_constructed;
+ gobject_class->set_property = gkd_ssh_agent_service_set_property;
+ gobject_class->finalize = gkd_ssh_agent_service_finalize;
+ g_object_class_install_property (gobject_class, PROP_PATH,
+ g_param_spec_string ("path", "Path", "Path",
+ "",
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_WRITABLE));
+ g_object_class_install_property (gobject_class, PROP_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));
+}
+
+static gboolean
+relay_request (GkdSshAgentService *self,
+ EggBuffer *req,
+ EggBuffer *resp,
+ GCancellable *cancellable,
+ GError **error)
+{
+ return gkd_ssh_agent_process_call (self->process, req, resp, cancellable, error);
+}
+
+static gboolean
+handle_request (GkdSshAgentService *self,
+ EggBuffer *req,
+ EggBuffer *resp,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GkdSshAgentOperation func;
+ guchar op;
+
+ egg_buffer_reset (resp);
+ egg_buffer_add_uint32 (resp, 0);
+
+ /* Decode the operation; on failure, just pass through */
+ if (egg_buffer_get_byte (req, 4, NULL, &op) &&
+ op <= GKD_SSH_OP_MAX && operations[op] != NULL)
+ func = operations[op];
+ else
+ func = relay_request;
+
+ return func (self, req, resp, cancellable, error);
+}
+
+static void
+add_key (GkdSshAgentService *self,
+ GBytes *key)
+{
+ g_mutex_lock (&self->lock);
+ g_hash_table_add (self->keys, g_bytes_ref (key));
+ g_mutex_unlock (&self->lock);
+}
+
+static void
+remove_key (GkdSshAgentService *self,
+ GBytes *key)
+{
+ g_mutex_lock (&self->lock);
+ g_hash_table_remove (self->keys, key);
+ g_mutex_unlock (&self->lock);
+}
+
+static void
+clear_keys (GkdSshAgentService *self)
+{
+ g_mutex_lock (&self->lock);
+ g_hash_table_remove_all (self->keys);
+ g_mutex_unlock (&self->lock);
+}
+
+static void
+ensure_key (GkdSshAgentService *self,
+ GBytes *key)
+{
+ 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_service_lookup_key (self, key))
+ return;
+
+ info = gkd_ssh_agent_preload_lookup_by_public_key (self->preload, key);
+ if (!info)
+ return;
+
+ argv[1] = info->filename;
+
+ fields = g_hash_table_new (g_str_hash, g_str_equal);
+ 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);
+
+ label = info->comment[0] != '\0' ? info->comment : _("Unnamed");
+
+ 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 ("couldn't run %s: %s", argv[0], error->message);
+ g_error_free (error);
+ } else if (!g_spawn_check_exit_status (status, &error)) {
+ g_message ("the %s command failed: %s", argv[0], error->message);
+ g_error_free (error);
+ } else {
+ add_key (self, key);
+ }
+
+ g_hash_table_unref (fields);
+ g_free (unique);
+ gkd_ssh_agent_key_info_free (info);
+ g_object_unref (askpass);
+}
+
+static gboolean
+on_run (GThreadedSocketService *service,
+ GSocketConnection *connection,
+ GObject *source_object,
+ gpointer user_data)
+{
+ GkdSshAgentService *self = g_object_ref (GKD_SSH_AGENT_SERVICE (user_data));
+ EggBuffer req;
+ EggBuffer resp;
+ GError *error;
+ gboolean ret;
+
+ egg_buffer_init_full (&req, 128, egg_secure_realloc);
+ egg_buffer_init_full (&resp, 128, (EggBufferAllocator)g_realloc);
+
+ error = NULL;
+ if (!gkd_ssh_agent_process_connect (self->process, self->cancellable, &error)) {
+ g_warning ("couldn't connect to ssh-agent: %s", error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ while (TRUE) {
+ /* Read in the request */
+ error = NULL;
+ if (!_gkd_ssh_agent_read_packet (connection, &req, self->cancellable, &error)) {
+ if (error->code != G_IO_ERROR_CANCELLED)
+ g_message ("couldn't read from client: %s", error->message);
+ g_error_free (error);
+ break;
+ }
+
+ /* Handle the request */
+ error = NULL;
+ while (!(ret = handle_request (self, &req, &resp, self->cancellable, &error))) {
+ if (gkd_ssh_agent_process_get_pid (self->process) != 0) {
+ if (error->code != G_IO_ERROR_CANCELLED)
+ g_message ("couldn't handle client request: %s", error->message);
+ g_error_free (error);
+ goto out;
+ }
+
+ /* Reconnect to the ssh-agent */
+ g_clear_error (&error);
+ if (!gkd_ssh_agent_process_connect (self->process, self->cancellable, &error)) {
+ if (error->code != G_IO_ERROR_CANCELLED)
+ g_message ("couldn't connect to ssh-agent: %s", error->message);
+ g_error_free (error);
+ goto out;
+ }
+ }
+
+ /* Write the reply back out */
+ error = NULL;
+ if (!_gkd_ssh_agent_write_packet (connection, &resp, self->cancellable, &error)) {
+ if (error->code != G_IO_ERROR_CANCELLED)
+ g_message ("couldn't write to client: %s", error->message);
+ g_error_free (error);
+ break;
+ }
+ }
+
+ out:
+ egg_buffer_uninit (&req);
+ egg_buffer_uninit (&resp);
+
+ g_object_unref (self);
+
+ return TRUE;
+}
+
+static void
+on_closed (GkdSshAgentProcess *process,
+ gpointer user_data)
+{
+ GkdSshAgentService *self = GKD_SSH_AGENT_SERVICE (user_data);
+ clear_keys (self);
+}
+
+gboolean
+gkd_ssh_agent_service_start (GkdSshAgentService *self)
+{
+ gchar *path;
+ GError *error;
+
+ path = g_strdup_printf ("%s/ssh", self->path);
+ g_unlink (path);
+ self->address = g_unix_socket_address_new (path);
+ g_free (path);
+
+ error = NULL;
+ if (!g_socket_listener_add_address (self->listener,
+ self->address,
+ G_SOCKET_TYPE_STREAM,
+ G_SOCKET_PROTOCOL_DEFAULT,
+ NULL,
+ NULL,
+ &error)) {
+ g_warning ("couldn't listen on %s: %s",
+ g_unix_socket_address_get_path (G_UNIX_SOCKET_ADDRESS (self->address)),
+ error->message);
+ g_error_free (error);
+ return FALSE;
+ }
+
+ g_signal_connect (self->listener, "run", G_CALLBACK (on_run), self);
+ g_signal_connect (self->process, "closed", G_CALLBACK (on_closed), self);
+
+ g_setenv ("SSH_AUTH_SOCK", g_unix_socket_address_get_path (G_UNIX_SOCKET_ADDRESS (self->address)), TRUE);
+
+ g_socket_service_start (G_SOCKET_SERVICE (self->listener));
+
+ return TRUE;
+}
+
+void
+gkd_ssh_agent_service_stop (GkdSshAgentService *self)
+{
+ if (self->address)
+ g_unlink (g_unix_socket_address_get_path (G_UNIX_SOCKET_ADDRESS (self->address)));
+
+ g_cancellable_cancel (self->cancellable);
+ g_socket_service_stop (G_SOCKET_SERVICE (self->listener));
+}
+
+GkdSshAgentService *
+gkd_ssh_agent_service_new (const gchar *path,
+ GTlsInteraction *interaction,
+ GkdSshAgentPreload *preload)
+{
+ 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_SERVICE,
+ "path", path,
+ "interaction", interaction,
+ "preload", preload,
+ NULL);
+}
+
+GkdSshAgentPreload *
+gkd_ssh_agent_service_get_preload (GkdSshAgentService *self)
+{
+ return self->preload;
+}
+
+GkdSshAgentProcess *
+gkd_ssh_agent_service_get_process (GkdSshAgentService *self)
+{
+ return self->process;
+}
+
+gboolean
+gkd_ssh_agent_service_lookup_key (GkdSshAgentService *self,
+ GBytes *key)
+{
+ gboolean ret;
+ g_mutex_lock (&self->lock);
+ ret = g_hash_table_contains (self->keys, key);
+ g_mutex_unlock (&self->lock);
+ return ret;
+}
+
+/* ---------------------------------------------------------------------------- */
+
+static gboolean
+op_add_identity (GkdSshAgentService *self,
+ EggBuffer *req,
+ EggBuffer *resp,
+ GCancellable *cancellable,
+ GError **error)
+{
+ const guchar *blob;
+ gsize offset = 5;
+ gsize length;
+ GBytes *key = NULL;
+ gboolean ret;
+
+ /* If parsing the request fails, just pass through */
+ ret = egg_buffer_get_byte_array (req, offset, &offset, &blob, &length);
+ if (ret)
+ key = g_bytes_new (blob, length);
+ else
+ g_message ("got unparseable add identity request for ssh-agent");
+
+ ret = relay_request (self, req, resp, cancellable, error);
+ if (key) {
+ if (ret)
+ add_key (self, key);
+ g_bytes_unref (key);
+ }
+
+ return ret;
+}
+
+static GHashTable *
+parse_identities_answer (EggBuffer *resp)
+{
+ GHashTable *answer;
+ const guchar *blob;
+ gchar *comment;
+ gsize length;
+ gsize offset = 4;
+ guint32 count;
+ guchar op;
+ guint32 i;
+
+ if (!egg_buffer_get_byte (resp, offset, &offset, &op) ||
+ op != GKD_SSH_RES_IDENTITIES_ANSWER ||
+ !egg_buffer_get_uint32 (resp, offset, &offset, &count)) {
+ g_message ("got unexpected response back from ssh-agent when requesting identities");
+ return NULL;
+ }
+
+ answer = g_hash_table_new_full (g_bytes_hash, g_bytes_equal, (GDestroyNotify)g_bytes_unref, g_free);
+
+ for (i = 0; i < count; i++) {
+ if (!egg_buffer_get_byte_array (resp, offset, &offset, &blob, &length) ||
+ !egg_buffer_get_string (resp, offset, &offset, &comment, g_realloc)) {
+ g_message ("got unparseable response back from ssh-agent when requesting identities");
+ g_hash_table_unref (answer);
+ return NULL;
+ }
+ g_hash_table_insert (answer, g_bytes_new (blob, length), comment);
+ }
+
+ return answer;
+}
+
+
+static gboolean
+op_request_identities (GkdSshAgentService *self,
+ EggBuffer *req,
+ EggBuffer *resp,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GHashTable *answer;
+ GHashTableIter iter;
+ gsize length;
+ guint32 added;
+ GBytes *key;
+ GList *keys;
+ GList *l;
+ GkdSshAgentPreload *preload;
+
+ if (!relay_request (self, req, resp, cancellable, error))
+ return FALSE;
+
+ /* Parse all the keys, and if it fails, just fall through */
+ answer = parse_identities_answer (resp);
+ if (!answer)
+ return TRUE;
+
+ g_hash_table_iter_init (&iter, answer);
+ while (g_hash_table_iter_next (&iter, (gpointer *)&key, NULL))
+ add_key (self, key);
+
+ added = 0;
+
+ /* Add any preloaded keys not already in answer */
+ preload = gkd_ssh_agent_service_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++;
+ }
+ }
+
+ g_list_free_full (keys, (GDestroyNotify)gkd_ssh_agent_key_info_free);
+
+ /* Set the correct amount of keys including the ones we added */
+ egg_buffer_set_uint32 (resp, 5, added + g_hash_table_size (answer));
+ g_hash_table_unref (answer);
+
+ /* Set the correct total size of the payload */
+ egg_buffer_set_uint32 (resp, 0, resp->len - 4);
+
+ return TRUE;
+}
+
+static gboolean
+op_sign_request (GkdSshAgentService *self,
+ EggBuffer *req,
+ EggBuffer *resp,
+ GCancellable *cancellable,
+ GError **error)
+{
+ const guchar *blob;
+ gsize length;
+ gsize offset = 5;
+ GBytes *key;
+
+ /* If parsing the request fails, just pass through */
+ if (egg_buffer_get_byte_array (req, offset, &offset, &blob, &length)) {
+ key = g_bytes_new (blob, length);
+ ensure_key (self, key);
+ g_bytes_unref (key);
+ } else {
+ g_message ("got unparseable sign request for ssh-agent");
+ }
+
+ return relay_request (self, req, resp, cancellable, error);
+}
+
+static gboolean
+op_remove_identity (GkdSshAgentService *self,
+ EggBuffer *req,
+ EggBuffer *resp,
+ GCancellable *cancellable,
+ GError **error)
+{
+ const guchar *blob;
+ gsize length;
+ gsize offset = 5;
+ GBytes *key = NULL;
+ gboolean ret;
+
+ /* If parsing the request fails, just pass through */
+ ret = egg_buffer_get_byte_array (req, offset, &offset, &blob, &length);
+ if (ret)
+ key = g_bytes_new (blob, length);
+ else
+ g_message ("got unparseable remove request for ssh-agent");
+
+ /* Call out ssh-agent anyway to make sure that the key is removed */
+ ret = relay_request (self, req, resp, cancellable, error);
+ if (key) {
+ if (ret)
+ remove_key (self, key);
+ g_bytes_unref (key);
+ }
+ return ret;
+}
+
+static gboolean
+op_remove_all_identities (GkdSshAgentService *self,
+ EggBuffer *req,
+ EggBuffer *resp,
+ GCancellable *cancellable,
+ GError **error)
+{
+ gboolean ret;
+
+ ret = relay_request (self, req, resp, cancellable, error);
+ if (ret)
+ clear_keys (self);
+
+ return ret;
+}
+
+static const GkdSshAgentOperation operations[GKD_SSH_OP_MAX] = {
+ NULL, /* 0 */
+ NULL, /* GKR_SSH_OP_REQUEST_RSA_IDENTITIES */
+ NULL, /* 2 */
+ NULL, /* GKR_SSH_OP_RSA_CHALLENGE */
+ NULL, /* 4 */
+ NULL, /* 5 */
+ NULL, /* 6 */
+ NULL, /* GKR_SSH_OP_ADD_RSA_IDENTITY */
+ NULL, /* GKR_SSH_OP_REMOVE_RSA_IDENTITY */
+ NULL, /* GKR_SSH_OP_REMOVE_ALL_RSA_IDENTITIES */
+ NULL, /* 10 */
+ op_request_identities, /* GKR_SSH_OP_REQUEST_IDENTITIES */
+ NULL, /* 12 */
+ op_sign_request, /* GKR_SSH_OP_SIGN_REQUEST */
+ NULL, /* 14 */
+ NULL, /* 15 */
+ NULL, /* 16 */
+ op_add_identity, /* GKR_SSH_OP_ADD_IDENTITY */
+ op_remove_identity, /* GKR_SSH_OP_REMOVE_IDENTITY */
+ op_remove_all_identities, /* GKR_SSH_OP_REMOVE_ALL_IDENTITIES */
+ NULL, /* GKR_SSH_OP_ADD_SMARTCARD_KEY */
+ NULL, /* GKR_SSH_OP_REMOVE_SMARTCARD_KEY */
+ NULL, /* GKR_SSH_OP_LOCK */
+ NULL, /* GKR_SSH_OP_UNLOCK */
+ NULL, /* GKR_SSH_OP_ADD_RSA_ID_CONSTRAINED */
+ op_add_identity, /* GKR_SSH_OP_ADD_ID_CONSTRAINED */
+ NULL, /* GKR_SSH_OP_ADD_SMARTCARD_KEY_CONSTRAINED */
+};
diff --git a/daemon/ssh-agent/gkd-ssh-agent-service.h b/daemon/ssh-agent/gkd-ssh-agent-service.h
new file mode 100644
index 00000000..9d3b2f1a
--- /dev/null
+++ b/daemon/ssh-agent/gkd-ssh-agent-service.h
@@ -0,0 +1,54 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2007 Stefan Walter
+ * Copyright (C) 2018 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Stef Walter <stef@thewalter.net>, Daiki Ueno
+ */
+
+#ifndef __GKD_SSH_AGENT_SERVICE_H__
+#define __GKD_SSH_AGENT_SERVICE_H__
+
+#include <gio/gio.h>
+#include "gkd-ssh-agent-preload.h"
+#include "gkd-ssh-agent-process.h"
+#include "egg/egg-buffer.h"
+
+#define GKD_TYPE_SSH_AGENT_SERVICE gkd_ssh_agent_service_get_type ()
+G_DECLARE_FINAL_TYPE (GkdSshAgentService, gkd_ssh_agent_service, GKD, SSH_AGENT_SERVICE, GObject);
+
+GkdSshAgentService *gkd_ssh_agent_service_new (const gchar *path,
+ GTlsInteraction *interaction,
+ GkdSshAgentPreload *preload);
+
+gboolean gkd_ssh_agent_service_start
+ (GkdSshAgentService *self);
+
+void gkd_ssh_agent_service_stop (GkdSshAgentService *self);
+
+GkdSshAgentPreload *gkd_ssh_agent_service_get_preload
+ (GkdSshAgentService *self);
+
+GkdSshAgentProcess *gkd_ssh_agent_service_get_process
+ (GkdSshAgentService *self);
+
+gboolean gkd_ssh_agent_service_lookup_key
+ (GkdSshAgentService *self,
+ GBytes *key);
+
+#endif /* __GKD_SSH_AGENT_SERVICE_H__ */
diff --git a/daemon/ssh-agent/gkd-ssh-agent-standalone.c b/daemon/ssh-agent/gkd-ssh-agent-standalone.c
deleted file mode 100644
index e865a5e1..00000000
--- a/daemon/ssh-agent/gkd-ssh-agent-standalone.c
+++ /dev/null
@@ -1,126 +0,0 @@
-/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
-/* gkd-ssh-agent-standalone.c - Test standalone SSH agent
-
- Copyright (C) 2007 Stefan Walter
-
- Gnome keyring is free software; you can redistribute it and/or
- modify it under the terms of the GNU General Public License as
- published by the Free Software Foundation; either version 2 of the
- License, or (at your option) any later version.
-
- Gnome keyring is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
- Author: Stef Walter <stef@memberwebs.com>
-*/
-
-#include "config.h"
-
-#include "gkd-ssh-agent.h"
-#include "gkd-ssh-agent-private.h"
-
-#include "egg/egg-error.h"
-#include "egg/egg-secure-memory.h"
-
-#include <gck/gck.h>
-
-#include <glib.h>
-#include <glib-object.h>
-
-#include <pwd.h>
-#include <string.h>
-#include <unistd.h>
-
-EGG_SECURE_DEFINE_GLIB_GLOBALS ();
-
-static gboolean
-accept_client (GIOChannel *channel, GIOCondition cond, gpointer unused)
-{
- gkd_ssh_agent_accept ();
- return TRUE;
-}
-
-static gboolean
-authenticate_slot (GckModule *module, GckSlot *slot, gchar *label, gchar **password, gpointer unused)
-{
- gchar *prompt = g_strdup_printf ("Enter token password (%s): ", label);
- char *result = getpass (prompt);
- g_free (prompt);
- *password = g_strdup (result);
- memset (result, 0, strlen (result));
- return TRUE;
-}
-
-static gboolean
-authenticate_object (GckModule *module, GckObject *object, gchar *label, gchar **password)
-{
- gchar *prompt = g_strdup_printf ("Enter object password (%s): ", label);
- char *result = getpass (prompt);
- g_free (prompt);
- *password = g_strdup (result);
- memset (result, 0, strlen (result));
- return TRUE;
-}
-
-int
-main(int argc, char *argv[])
-{
- GckModule *module;
- GError *error = NULL;
- GIOChannel *channel;
- GMainLoop *loop;
- gboolean ret;
- int sock;
-
-#if !GLIB_CHECK_VERSION(2,35,0)
- g_type_init ();
-#endif
-
- if (argc <= 1) {
- g_message ("specify pkcs11 module on the command line");
- return 1;
- }
-
- module = gck_module_initialize (argv[1], NULL, &error);
- if (!module) {
- g_message ("couldn't load pkcs11 module: %s", egg_error_message (error));
- g_clear_error (&error);
- return 1;
- }
-
-
- g_signal_connect (module, "authenticate-slot", G_CALLBACK (authenticate_slot), NULL);
- g_signal_connect (module, "authenticate-object", G_CALLBACK (authenticate_object), NULL);
-
- ret = gkd_ssh_agent_initialize_with_module (module);
- g_object_unref (module);
-
- if (ret == FALSE)
- return 1;
-
- sock = gkd_ssh_agent_startup ("/tmp");
- if (sock == -1)
- return 1;
-
- channel = g_io_channel_unix_new (sock);
- g_io_add_watch (channel, G_IO_IN | G_IO_HUP, accept_client, NULL);
- g_io_channel_unref (channel);
-
- g_print ("SSH_AUTH_SOCK=%s\n", g_getenv ("SSH_AUTH_SOCK"));
-
- /* Run a main loop */
- loop = g_main_loop_new (NULL, FALSE);
- g_main_loop_run (loop);
- g_main_loop_unref (loop);
-
- gkd_ssh_agent_shutdown ();
- gkd_ssh_agent_uninitialize ();
-
- return 0;
-}
diff --git a/daemon/ssh-agent/gkd-ssh-agent-util.c b/daemon/ssh-agent/gkd-ssh-agent-util.c
new file mode 100644
index 00000000..464cd1c5
--- /dev/null
+++ b/daemon/ssh-agent/gkd-ssh-agent-util.c
@@ -0,0 +1,163 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2014 Stef Walter
+ * Copyright (C) 2018 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Stef Walter <stef@thewalter.net>, Daiki Ueno
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include "gkd-ssh-agent-util.h"
+
+gboolean
+_gkd_ssh_agent_read_packet (GSocketConnection *connection,
+ EggBuffer *buffer,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GInputStream *stream;
+ guint32 packet_size;
+ gsize bytes_read;
+
+ stream = g_io_stream_get_input_stream (G_IO_STREAM (connection));
+
+ egg_buffer_reset (buffer);
+ egg_buffer_resize (buffer, 4);
+
+ if (!g_input_stream_read_all (stream, buffer->buf, 4, &bytes_read, cancellable, error))
+ return FALSE;
+
+ if (!egg_buffer_get_uint32 (buffer, 0, NULL, &packet_size) ||
+ packet_size < 1) {
+ g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "invalid packet size %u",
+ packet_size);
+ return FALSE;
+ }
+
+ egg_buffer_resize (buffer, packet_size + 4);
+ if (!g_input_stream_read_all (stream, buffer->buf + 4, packet_size, &bytes_read, cancellable, error))
+ return FALSE;
+
+ return TRUE;
+}
+
+gboolean
+_gkd_ssh_agent_write_packet (GSocketConnection *connection,
+ EggBuffer *buffer,
+ GCancellable *cancellable,
+ GError **error)
+{
+ GOutputStream *stream;
+ gsize bytes_written;
+
+ stream = g_io_stream_get_output_stream (G_IO_STREAM (connection));
+ if (!egg_buffer_set_uint32 (buffer, 0, buffer->len - 4))
+ g_return_val_if_reached (FALSE);
+ return g_output_stream_write_all (stream, buffer->buf, buffer->len, &bytes_written, cancellable, error);
+}
+
+GBytes *
+_gkd_ssh_agent_parse_public_key (GBytes *input,
+ gchar **comment)
+{
+ const guchar *at;
+ guchar *decoded;
+ gsize n_decoded;
+ gint state;
+ guint save;
+ const guchar *data;
+ gsize n_data;
+
+ 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-agent-util.h b/daemon/ssh-agent/gkd-ssh-agent-util.h
new file mode 100644
index 00000000..8b16b3e0
--- /dev/null
+++ b/daemon/ssh-agent/gkd-ssh-agent-util.h
@@ -0,0 +1,43 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Daiki Ueno
+ */
+
+#include <gio/gio.h>
+#include "egg/egg-buffer.h"
+
+#ifndef __GKD_SSH_AGENT_UTIL_H__
+#define __GKD_SSH_AGENT_UTIL_H__ 1
+
+gboolean _gkd_ssh_agent_read_packet (GSocketConnection *connection,
+ EggBuffer *buffer,
+ GCancellable *cancellable,
+ GError **error);
+
+gboolean _gkd_ssh_agent_write_packet (GSocketConnection *connection,
+ EggBuffer *buffer,
+ GCancellable *cancellable,
+ GError **error);
+
+GBytes *_gkd_ssh_agent_parse_public_key (GBytes *input,
+ gchar **comment);
+
+
+#endif /* __GKD_SSH_AGENT_UTIL_H__ */
diff --git a/daemon/ssh-agent/gkd-ssh-agent.c b/daemon/ssh-agent/gkd-ssh-agent.c
deleted file mode 100644
index 2a53416f..00000000
--- a/daemon/ssh-agent/gkd-ssh-agent.c
+++ /dev/null
@@ -1,451 +0,0 @@
-/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
-/* gkd-ssh-agent.c - handles SSH i/o from the clients
-
- Copyright (C) 2007 Stefan Walter
-
- Gnome keyring is free software; you can redistribute it and/or
- modify it under the terms of the GNU General Public License as
- published by the Free Software Foundation; either version 2 of the
- License, or (at your option) any later version.
-
- Gnome keyring is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
- Author: Stef Walter <stef@memberwebs.com>
-*/
-
-#include "config.h"
-
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/un.h>
-
-#include <errno.h>
-#include <fcntl.h>
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-
-#include "gkd-ssh-agent.h"
-#include "gkd-ssh-agent-private.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;
-
-EGG_SECURE_DECLARE (ssh_agent);
-
-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)
-{
- 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;
- }
- }
-
- return TRUE;
-}
-
-static gboolean
-read_packet_with_size (GkdSshAgentCall *call)
-{
- int fd;
- guint32 packet_size;
-
- fd = call->sock;
-
- egg_buffer_resize (call->req, 4);
- if (!read_all (fd, call->req->buf, 4))
- return FALSE;
-
- if (!egg_buffer_get_uint32 (call->req, 0, NULL, &packet_size) ||
- packet_size < 1) {
- g_warning ("invalid packet size from client");
- return FALSE;
- }
-
- egg_buffer_resize (call->req, packet_size + 4);
- if (!read_all (fd, call->req->buf + 4, packet_size))
- return FALSE;
-
- return TRUE;
-}
-
-static gpointer
-run_client_thread (gpointer data)
-{
- gint *socket = data;
- GkdSshAgentCall call;
- EggBuffer req;
- EggBuffer resp;
- guchar op;
-
- memset (&call, 0, sizeof (call));
- call.sock = g_atomic_int_get (socket);
- g_assert (call.sock != -1);
-
- 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))
- 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))
- 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))
- 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);
-
- 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)
-{
- Client *client;
- struct sockaddr_un addr;
- socklen_t addrlen;
- GError *error = NULL;
- GList *l;
- int new_fd;
-
- g_return_if_fail (socket_fd != -1);
-
- /* Cleanup any completed dispatch threads */
- for (l = socket_clients; l; l = g_list_next (l)) {
- client = l->data;
- if (g_atomic_int_get (&client->sock) == -1) {
- 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));
- return;
- }
-
- client = g_slice_new0 (Client);
- client->sock = new_fd;
-
- /* 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);
-}
-
-void
-gkd_ssh_agent_shutdown (void)
-{
- Client *client;
- GList *l;
-
- if (socket_fd != -1)
- close (socket_fd);
-
- if (*socket_path)
- unlink (socket_path);
-
- /* Stop all of the dispatch threads */
- for (l = socket_clients; l; l = g_list_next (l)) {
- client = l->data;
-
- /* Forcibly shutdown the connection */
- if (client->sock != -1)
- shutdown (client->sock, SHUT_RDWR);
- g_thread_join (client->thread);
-
- /* This is always closed by client thread */
- g_assert (client->sock == -1);
- g_slice_free (Client, client);
- }
-
- g_list_free (socket_clients);
- socket_clients = NULL;
-}
-
-void
-gkd_ssh_agent_uninitialize (void)
-{
- gboolean ret;
-
- g_assert (pkcs11_main_mutex);
- ret = g_mutex_trylock (pkcs11_main_mutex);
- g_assert (ret);
-
- g_assert (GCK_IS_SESSION (pkcs11_main_session));
- g_assert (!pkcs11_main_checked);
- g_object_unref (pkcs11_main_session);
- pkcs11_main_session = NULL;
-
- 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);
-
- gck_list_unref_free (pkcs11_modules);
- pkcs11_modules = NULL;
-}
-
-int
-gkd_ssh_agent_initialize (CK_FUNCTION_LIST_PTR funcs)
-{
- GckModule *module;
- gboolean ret;
-
- g_return_val_if_fail (funcs, -1);
-
- module = gck_module_new (funcs);
- ret = gkd_ssh_agent_initialize_with_module (module);
- g_object_unref (module);
- return ret;
-}
-
-gboolean
-gkd_ssh_agent_initialize_with_module (GckModule *module)
-{
- 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_array_unref (mechs);
- }
-
- gck_list_unref_free (slots);
-
- if (!session) {
- g_warning ("couldn't select a usable pkcs#11 slot for the ssh agent to use");
- return FALSE;
- }
-
- g_assert (!pkcs11_modules);
- pkcs11_modules = g_list_append (NULL, g_object_ref (module));
-
- 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;
-
- return TRUE;
-}
-
-int
-gkd_ssh_agent_startup (const gchar *prefix)
-{
- struct sockaddr_un addr;
- int sock;
-
- g_return_val_if_fail (prefix, -1);
-
- snprintf (socket_path, sizeof (socket_path), "%s/ssh", prefix);
- unlink (socket_path);
-
- sock = socket (AF_UNIX, SOCK_STREAM, 0);
- if (sock < 0) {
- g_warning ("couldn't create socket: %s", g_strerror (errno));
- return -1;
- }
-
- 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;
- }
-
- if (listen (sock, 128) < 0) {
- g_warning ("couldn't listen on socket: %s", g_strerror (errno));
- close (sock);
- return -1;
- }
-
- g_setenv ("SSH_AUTH_SOCK", socket_path, TRUE);
-
- socket_fd = sock;
- return sock;
-}
diff --git a/daemon/ssh-agent/gkd-ssh-agent.h b/daemon/ssh-agent/gkd-ssh-agent.h
deleted file mode 100644
index 03427d2e..00000000
--- a/daemon/ssh-agent/gkd-ssh-agent.h
+++ /dev/null
@@ -1,40 +0,0 @@
-/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
-/* gkd-ssh-agent.c - handles SSH i/o from the clients
-
- Copyright (C) 2007 Stefan Walter
-
- Gnome keyring is free software; you can redistribute it and/or
- modify it under the terms of the GNU General Public License as
- published by the Free Software Foundation; either version 2 of the
- License, or (at your option) any later version.
-
- Gnome keyring is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-
- Author: Stef Walter <stef@memberwebs.com>
-*/
-
-#ifndef GKDSSHAGENT_H_
-#define GKDSSHAGENT_H_
-
-#include <glib.h>
-
-#include "pkcs11/pkcs11.h"
-
-int gkd_ssh_agent_startup (const gchar *prefix);
-
-void gkd_ssh_agent_accept (void);
-
-void gkd_ssh_agent_shutdown (void);
-
-gboolean gkd_ssh_agent_initialize (CK_FUNCTION_LIST_PTR funcs);
-
-void gkd_ssh_agent_uninitialize (void);
-
-#endif /* GKDSSHAGENT_H_ */
diff --git a/daemon/ssh-agent/test-common.c b/daemon/ssh-agent/test-common.c
new file mode 100644
index 00000000..76b1c7b4
--- /dev/null
+++ b/daemon/ssh-agent/test-common.c
@@ -0,0 +1,347 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Daiki Ueno
+ */
+
+#include "config.h"
+
+#include "test-common.h"
+
+#include "gkd-ssh-agent-private.h"
+#include "gkd-ssh-agent-util.h"
+#include "pkcs11/gkm/gkm-mock.h"
+
+/* RSA private key blob decoded from pkcs11/ssh-store/fixtures/id_rsa_plain */
+static const 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
+};
+
+/* 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;
+}
+
+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);
+}
+
+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);
+}
+
+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);
+}
+
+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_agent_parse_public_key (public_bytes, comment);
+ g_bytes_unref (public_bytes);
+
+ return public_key;
+}
+
+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);
+}
+
+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);
+}
+
+void
+check_response (EggBuffer *resp, unsigned char expected)
+{
+ 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 (expected, ==, code);
+}
+
+void
+check_success (EggBuffer *resp)
+{
+ check_response (resp, GKD_SSH_RES_SUCCESS);
+}
+
+void
+check_failure (EggBuffer *resp)
+{
+ check_response (resp, GKD_SSH_RES_FAILURE);
+}
+
+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);
+}
+
+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-common.h b/daemon/ssh-agent/test-common.h
new file mode 100644
index 00000000..f4849fd6
--- /dev/null
+++ b/daemon/ssh-agent/test-common.h
@@ -0,0 +1,94 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Daiki Ueno
+ */
+
+#include <glib.h>
+#include "egg/egg-buffer.h"
+
+void prepare_request_identities (EggBuffer *req);
+void prepare_add_identity (EggBuffer *req);
+void prepare_remove_identity (EggBuffer *req);
+void prepare_remove_all_identities (EggBuffer *req);
+void prepare_sign_request (EggBuffer *req);
+
+void check_identities_answer (EggBuffer *resp, gsize count);
+void check_sign_response (EggBuffer *resp);
+void check_response (EggBuffer *resp, unsigned char expected);
+void check_success (EggBuffer *resp);
+void check_failure (EggBuffer *resp);
+
+GBytes *public_key_from_file (const gchar *path, gchar **comment);
+
+#define DEFINE_CALL_FUNCS(Test, Call) \
+static inline void \
+call_request_identities (Test *test, gsize count) \
+{ \
+ egg_buffer_reset (&test->req); \
+ egg_buffer_reset (&test->resp); \
+ \
+ prepare_request_identities (&test->req); \
+ Call (test); \
+ check_identities_answer (&test->resp, count); \
+} \
+ \
+static inline void \
+call_add_identity (Test *test) \
+{ \
+ egg_buffer_reset (&test->req); \
+ egg_buffer_reset (&test->resp); \
+ \
+ prepare_add_identity (&test->req); \
+ Call (test); \
+ check_success (&test->resp); \
+} \
+ \
+static inline void \
+call_remove_identity (Test *test) \
+{ \
+ egg_buffer_reset (&test->req); \
+ egg_buffer_reset (&test->resp); \
+ \
+ prepare_remove_identity (&test->req); \
+ Call (test); \
+ check_success (&test->resp); \
+} \
+ \
+static inline void \
+call_remove_all_identities (Test *test) \
+{ \
+ egg_buffer_reset (&test->req); \
+ egg_buffer_reset (&test->resp); \
+ \
+ prepare_remove_all_identities (&test->req); \
+ Call (test); \
+ check_success (&test->resp); \
+} \
+ \
+static inline void \
+call_sign (Test *test) \
+{ \
+ egg_buffer_reset (&test->req); \
+ egg_buffer_reset (&test->resp); \
+ \
+ prepare_sign_request (&test->req); \
+ Call (test); \
+ check_sign_response (&test->resp); \
+}
diff --git a/daemon/ssh-agent/test-communication.c b/daemon/ssh-agent/test-communication.c
deleted file mode 100644
index 3d452f00..00000000
--- a/daemon/ssh-agent/test-communication.c
+++ /dev/null
@@ -1,449 +0,0 @@
-/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
-/* test-communication.c: Communication with ssh-agent
-
- Copyright (C) 2017 Red Hat, Inc.
-
- The Gnome Keyring Library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Library General Public License as
- published by the Free Software Foundation; either version 2 of the
- License, or (at your option) any later version.
-
- The Gnome Keyring Library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Library General Public License for more details.
-
- You should have received a copy of the GNU Library General Public
- License along with the Gnome Library; see the file COPYING.LIB. If not,
- <http://www.gnu.org/licenses/>.
-
- Author: Jakub Jelen <jjelen@redhat.com>
-*/
-
-#include "config.h"
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-
-#include <glib.h>
-
-#include "pkcs11/pkcs11.h"
-#include "gkd-ssh-agent-private.h"
-
-/* 4 bytes: length of message
- * 1 byte: Operation ID (0x11 = SSH2_AGENTC_ADD_IDENTITY),
- * 4 bytes: length of key type (ssh-rsa)
- * | message size ||OP|| key type size|| -------- ssh-rsa ---------*/
-#define RSA_PAIR "\x00\x00\x03\xd3\x11\x00\x00\x00\x07\x73\x73\x68\x2d\x72\x73\x61" \
- "\x00\x00\x01\x01\x00\xa0\x3e\x95\x2a\xa9\x21\x6b\x2e\xa9\x28\x74" \
- "\x91\x8c\x01\x96\x59\xf1\x4f\x53\xcc\x5f\xb2\x2d\xa0\x9c\xec\x0f" \
- "\xfc\x1d\x54\x1c\x3a\x33\xb7\x1d\xdc\xce\x13\xbe\xa7\x2f\xdf\x4e" \
- "\x58\x42\x9d\x23\xf5\x8e\xc8\xe4\xad\x52\x19\x72\x7c\xda\x87\x67" \
- "\xd4\x34\x51\x51\x81\x2e\x3e\x8d\x13\x81\xb6\xf6\xe0\x1e\xc4\xbb" \
- "\xd9\x5d\x44\xeb\xe6\x68\x81\x5f\xa6\x04\x95\x96\x02\x1c\x34\x88" \
- "\xfa\xe6\x43\x72\xaf\x9b\x7f\x03\xdc\xf0\x72\xa3\x96\x3b\xc8\xa3" \
- "\xb9\x90\x81\xb6\x2e\x5a\x18\x2e\x3a\x2c\x27\x91\x78\xb3\x1d\xb1" \
- "\x87\x4b\xb3\xdb\x05\xcd\xb6\x76\x35\x6f\x9c\x61\x7b\x6f\x95\x12" \
- "\x4b\x26\xf4\xe0\x7e\x15\x76\x94\x91\x90\xb6\x7d\x0a" \
- "\xd3\x36\x8f\x19\x18\x52\x50\x48\x57\x7c\x91\x48\x48\x7d\xb5\x03" \
- "\x26\x69\x58\xb9\x9f\xaf\xbc\x73\x3e\x03\x72\xdc\xf6\xb1\xf2\x5b" \
- "\x82\x0f\x69\x1c\xb1\x15\x07\x22\x46\x66\xfe\x65\x0a" \
- "\x94\xda\xe4\x9d\x39\x70\x21\x83\x5e\xe5\xb2\x4b\x97\xfe\xaf\x32" \
- "\x08\x8e\x47\xcb\x97\x83\x89\xc0\xb6\xdb\x6a\x14\x31\xd2\x53\xb5" \
- "\x88\x30\x5f\x87\x50\x09\x4f\x13\x20\x25\xa1\xc5\xbd\xf1\xe1\x10" \
- "\x95\xfa\x0e\xc3\xf7\xdf\xad\x90\x8b\xef\xfb\x00\x00\x00\x01\x23" \
- "\x00\x00\x01\x01\x00\x9b\xaa\x82\x46\xb2\xed\x43\x8c\x69\xcf\x87" \
- "\x2e\x4d\x7d\xe2\x83\x42\x2f\xcd\xbf\x38\x63\xf1\xcf\x39\x5a\x58" \
- "\xab\xc4\xb8\x1b\x6b\xbd\x35\x8a\xb9\x3d\x37\xc0\x85\x27\x30\xb2" \
- "\x81\x9f\xcb\xd9\xc9\xf8\x6b\x61\xcc\xf0\xab\x01\x80\x99\xc5\x5d" \
- "\x8c\x50\x14\x7b\x0f\xc6\x85\xe8\x21\x93\xf3\x90\xbc\x75\xa9\x2b" \
- "\x82\xb2\x60\x35\x9d\xff\x1e\x97\x6e\x13\x14\xf8\x1f\x4e\x99\x6f" \
- "\x1f\x9d\xdb\x1e\xf3\xbb\x9f\xf5\x1f\xc5\x01\xa6\x3a\x2b\x72\x73" \
- "\x29\x4a\x8c\xa2\x58\xe9\xce\x58\xca\xcb\xce\xaa\x92\x82\x1c\xd8" \
- "\x57\x8b\x5e\x42\x79\x21\x0e\x63\x13\x0e\x03\xff\x2f\x7f\x64\xf6" \
- "\x82\xe1\xfe\x0b\xc3\x1e\x4c\x50\x11\x3f\xc8\x8a\xba\xcc\xde\x24" \
- "\xf7\xae\x96\x6c\x5e\x3b\x00\xfa\xf0\x0e\xac\x3a\xeb\xb1\xab\x8f" \
- "\x3f\xdb\x80\xb3\x06\x91\x18\xe1\xf5\x3b\xec\x5d\x01\xcf\xd0\x1f" \
- "\xaf\xe3\xd9\x12\xba\x7b\x0f\xee\x20\x29\x74\x57\xdc\x58\x75\xd4" \
- "\xb0\xf4\xb4\xa4\x93\x48\x2b\x7b\x6b\x1d\x77\xbc\xf3\xfe\xbd\xad" \
- "\xd6\x83\x05\x16\xca\xbe\x31\xa4\x39\x53\x29\xf3\xd3\x39\xb0\xa5" \
- "\xef\xf0\xc9\x08\xd6\x63\x52\x0b\xcb\xfc\x1c\x21\xd3\xa9\x2f\x23" \
- "\x92\x3d\x46\x8c\x4b\x00\x00\x00\x80\x15\x40\xcc\xa4\x83\xdf\x26" \
- "\xbe\x55\x82\x85\x0f\x71\x3c\x19\xa8\x8b\x42\x80\xa5\x24\x5d\xad" \
- "\xf5\x99\x33\xaf\x7c\xb2\x27\xae\x7b\x0b\x0b\xa0\x03\xfd\xae\x53" \
- "\x6f\xf1\xdd\x83\x54\xde\xf2\xbd\x87\x2c\xa9\x4d\x7b\xa5\x6e\xdb" \
- "\x5e\x89\xf4\x5c\x79\x22\xc3\xc4\x40\x50\xeb\xb7\xf4\x17\x78\x2f" \
- "\x06\xa5\x3a\x65\x4d\x85\x98\x3e\xd8\x4d\x3b\xfc\xd8\x9b\xe5\xd1" \
- "\x47\xb6\xe3\xda\x2e\xc5\x18\xce\x37\xd9\xd7\x9a\xbf\xba\xa9\xef" \
- "\xf2\xaf\x9b\xc8\x46\x57\x11\x8c\xa9\x5f\x68\x8c\x43\x2f\xb5\x7a" \
- "\x39\x38\x30\x79\xd5\x30\xa8\x2b\x98\x00\x00\x00\x81\x00\xcc\x50" \
- "\xb1\x2c\x5f\xe4\x02\x85\x7d\xce\x77\xd8\x27\xc1\xf6\xee\xe2\x2b" \
- "\x7b\x29\x83\x95\xf1\x5e\x3d\xe5\xa9\x75\x62\xc6\x84\xc9\x97\x26" \
- "\x70\xf4\x0d\x28\x6a\xc6\x88\x7c\xa3\x0d\x35\xa3\x8f\xdc\x34\x4c" \
- "\x78\x6b\xcc\x5d\x99\x7e\x45\xb0\xdf\xe3\x77\x48\x77\xd8\xa9\x1c" \
- "\x74\xf9\xbc\xcc\x82\xdb\x44\x10\x96\xda\x00\x23\xaa\x04\x93\xcc" \
- "\x98\xec\x26\x8b\x7d\x08\xf4\x82\xdc\x9a\xc4\x8c\xc8\xe9\x3e\x5b" \
- "\xd6\xc7\x28\xf4\x38\x3a\x3c\x08\x56\xbb\xa2\xca\xfb\x05\xa0\xb7" \
- "\xe1\x70\x59\xb4\x86\x2b\x29\x89\xb5\x82\x2a\x79\x61\x51\x00\x00" \
- "\x00\x81\x00\xc8\xc7\xe6\x93\x90\x59\xe7\x54\x1b\xcf\x9c\xb0\x07" \
- "\x80\x37\xcd\xdf\x65\xf4\x29\x1e\x4a\x93\x73\xd1\x7b\x47\x1d\x36" \
- "\x87\x89\x1d\xbf\xd5\x1e\x02\xc2\xd1\x2b\xb3\x67\x07\x65\xf9\xbc" \
- "\xcb\x74\x4c\x83\x68\xa8\x6d\x30\x68\x8f\xb5\xb9\x44\x86\xb8\xde" \
- "\x4e\xfc\x02\x1e\x9c\x05\x3b\x23\x1b\xdf\x79\x58\x73\x51\x27\xf0" \
- "\xbd\x83\x34\x38\xcb\xd0\x20\x12\xcd\x1a\x07\x6e\xf7\x0a" \
- "\x92\x29\xff\x2f\xbf\x30\x2a\x69\x15\x4d\x8e\x6e\x17\x26\x7b\x43" \
- "\xfe\x52\xd1\x83\x65\x19\x22\x8b\xd3\x6f\x97\x51\x11\x3f\x17\xfe" \
- "\x05\xcc\xa4\x49\x8b\x00\x00\x00\x26\x70\x6b\x63\x73\x31\x31\x2f" \
- "\x73\x73\x68\x2d\x73\x74\x6f\x72\x65\x2f\x66\x69\x78\x74\x75\x72" \
- "\x65\x73\x2f\x69\x64\x5f\x72\x73\x61\x5f\x70\x6c\x61\x69\x6e"
-
-#define DSA_PAIR "\x00\x00\x01\xf5\x11\x00\x00\x00\x07\x73\x73\x68\x2d\x64\x73\x73" \
- "\x00\x00\x00\x81\x00\xc2\xc9\x98\xa3\xeb\x5e\x3c\x71\xbe\x86\xa7" \
- "\x65\xda\xcd\x52\x6b\xfb\x3a\xdc\x7d\x29\x1b\x37\x53\x32\x79\x1e" \
- "\x61\x0a" \
- "\x00\x02\x4e\xa7\x27\xd4\x3e\x11\x86\xe7\xfb\xf6\xe5\x9e\xee\x5b" \
- "\xf2\x62\xe3\xf2\x5c\xd7\x9d\x7d\xd7\xb4\x88\x53\xfb\x15\xff\x64" \
- "\xd5\x3f\x62\xde\xd8\x72\x62\x3b\x35\xb1\xc7\x70\xa5\xba\xb1\x9c" \
- "\x05\x67\x98\xde\x12\x36\xff\xef\x7c\x55\xcf\xa7\xac\x34\x10\x33" \
- "\x6a\x98\x03\xb0\x83\xe3\xea\xc2\xe7\xbb\x8e\xe8\x8f\x54\x9c\x8f" \
- "\x05\xcb\x12\x3d\x96\x4a\x4c\xe3\x03\x09\x2f\xf6\x7d\x58\x9b\x14" \
- "\x25\xe0\xb3\x00\x00\x00\x15\x00\xe5\xba\x9e\x03\x42\x83\xa7\x78" \
- "\x0a" \
- "\x7f\x86\x1d\x0b\x44\x6c\xd5\x6d\x66\x94\x5d\x00\x00\x00\x80\x3f" \
- "\x62\xe1\x10\x7f\xb3\x50\x26\x2b\x8b\x4c\x62\xdf\x69\x7d\x6c\xc9" \
- "\xe5\xa8\x30\x81\x77\x0d\xb3\x38\xc6\xde\x1b\x65\xc3\x46\xde\x34" \
- "\x5b\x83\x9b\x88\x4d\x7e\x3c\xf3\x69\xfc\x20\xc5\xb9\x8a\xce\x8c" \
- "\x3e\x2c\xec\x29\x78\xb3\xf5\x3e\x8a\x8a\x78\x0c\xab\x0f\x70\xbc" \
- "\x1b\x76\xbd\xc2\xa1\x61\x3a\x84\x4b\x8e\x74\x7d\x23\x93\x37\x2f" \
- "\xb7\xc9\x19\xe5\x89\xad\x9b\x73\xa2\xa8\x45\xb6\x01\x18\xbc\xad" \
- "\x4c\x15\x27\xdf\x9b\x45\xb9\x1f\x45\xea\xcd\xac\x37\x87\xfc\xf4" \
- "\x33\xc0\x25\x9f\xec\xbc\xe9\xf6\x53\xd3\x35\x58\xc2\x0e\xdb\x00" \
- "\x00\x00\x80\x0d\x5b\xe1\xed\xb8\x95\x95\x99\xf5\xd1\x44\x32\x75" \
- "\x82\x83\x0a" \
- "\xf8\x65\xe2\xc4\x3b\xc1\x2c\x16\xc5\x48\x37\xfb\xe2\x46\x08\x9e" \
- "\xdd\xef\x50\x19\xb8\x30\xaa\x6b\x1d\xb9\x82\xdb\xb4\xcb\x47\x29" \
- "\x9b\xe8\x83\x87\xd4\x43\x69\x61\x3d\xcc\x7d\xf6\x49\xba\xc4\x13" \
- "\xaa\xa6\x49\x28\xf6\xda\xe6\x7c\x0b\xbc\xf3\xfd\x97\x33\x7a\xd1" \
- "\xb2\x40\xb6\xa8\x96\x52\xca\x9f\xc0\x71\x21\xe8\x9c\x7e\xaa\x83" \
- "\x20\x82\x6f\x22\xea\x88\x09\x24\xed\xb3\x0e\x59\x22\xe5\x3c\x1f" \
- "\xd6\x29\x4d\xfa\xd8\xa7\x1d\xd3\x8b\xff\x60\xa6\x3c\xf4\x4c\x80" \
- "\x00\x00\x00\x15\x00\xb2\x9f\x51\x2e\xb5\xc6\xa1\x53\x39\x8e\x5f" \
- "\x1b\x5f\xab\x2f\x09\x4f\x1a\xf2\xfd\x00\x00\x00\x26\x70\x6b\x63" \
- "\x73\x31\x31\x2f\x73\x73\x68\x2d\x73\x74\x6f\x72\x65\x2f\x66\x69" \
- "\x78\x74\x75\x72\x65\x73\x2f\x69\x64\x5f\x64\x73\x61\x5f\x70\x6c" \
- "\x61\x69\x6e"
-
-#define ECDSA_PAIR "\x00\x00\x00\xba\x11\x00\x00\x00\x13\x65\x63\x64\x73\x61\x2d\x73" \
- "\x68\x61\x32\x2d\x6e\x69\x73\x74\x70\x32\x35\x36\x00\x00\x00\x08" \
- "\x6e\x69\x73\x74\x70\x32\x35\x36\x00\x00\x00\x41\x04\xa8\xeb\x59" \
- "\xa5\xb6\x01\xd8\x39\xac\x23\x73\xc3\x19\x74\x40\xad\x2d\xd7\x2d" \
- "\xfe\x06\x84\xe4\x2b\xe1\x5c\x57\x24\x72\x2f\xec\xbf\x0e\xc3\x67" \
- "\x56\x95\xce\xfd\x9d\x1d\x86\x4a\x74\xb6\x42\xc5\xc6\x45\x59\x01" \
- "\x38\x03\xc7\xe5\x97\x5f\xbd\x52\xeb\x23\x5c\xcb\x9c\x00\x00\x00" \
- "\x21\x00\xc6\x16\xa3\x20\xe3\x83\x9b\xc6\x94\x6e\x43\x2e\x8e\x84" \
- "\x9a\x7c\xd7\x2b\x83\x86\x7e\x70\x3e\xd8\x6a\xcb\xf6\x9d\xf1\x7e" \
- "\xfb\xbe\x00\x00\x00\x28\x70\x6b\x63\x73\x31\x31\x2f\x73\x73\x68" \
- "\x2d\x73\x74\x6f\x72\x65\x2f\x66\x69\x78\x74\x75\x72\x65\x73\x2f" \
- "\x69\x64\x5f\x65\x63\x64\x73\x61\x5f\x70\x6c\x61\x69\x6e"
-
-
-typedef struct {
- unsigned char *rsakey;
- gsize rsakey_len;
- unsigned char *dsakey;
- gsize dsakey_len;
- unsigned char *ecdsakey;
- gsize ecdsakey_len;
-} Test;
-
-static void
-setup (Test *test, gconstpointer unused)
-{
- test->rsakey = (unsigned char *) RSA_PAIR;
- test->rsakey_len = sizeof(RSA_PAIR);
- test->dsakey = (unsigned char *) DSA_PAIR;
- test->dsakey_len = sizeof(DSA_PAIR);
- test->ecdsakey = (unsigned char *) ECDSA_PAIR;
- test->ecdsakey_len = sizeof(ECDSA_PAIR);
-}
-
-static void
-teardown (Test *test, gconstpointer unused)
-{
-}
-
-/* Reads private key from SSH client
- * Writes the public key
- * Reads the written public key */
-static void
-test_read_write_rsa (Test *test, gconstpointer unused)
-{
- GckAttributes *priv_attrs, *pub_attrs, *new_pub_attrs;
- const GckAttribute *priv_attr, *pub_attr, *new_pub_attr;
- GckBuilder priv, pub, new_pub;
- EggBuffer buffer, resp;
- gsize offset = 5; /* skipping message number (4B length, 1B message) */
- gchar *stype;
- gboolean ret;
- gulong value, algo;
-
- /* Prepare intercepted message from ssh-add */
- egg_buffer_init_static (&buffer, test->rsakey, test->rsakey_len);
- egg_buffer_init (&resp, 128);
-
- /* check the key type */
- ret = egg_buffer_get_string (&buffer, 5, &offset, &stype, (EggBufferAllocator)g_realloc);
- g_assert (ret);
- g_assert_cmpstr (stype, ==, "ssh-rsa");
-
- /* parse the key to PKCS11 structures */
- gck_builder_init (&pub);
- gck_builder_init (&priv);
- ret = gkd_ssh_agent_proto_read_pair_rsa (&buffer, &offset, &priv, &pub);
- g_assert (ret);
-
- /* Finish */
- pub_attrs = gck_builder_end (&pub);
- g_assert (pub_attrs);
- priv_attrs = gck_builder_end (&priv);
- g_assert (priv_attrs);
-
- /* private looks reasonable */
- gck_attributes_find_ulong (priv_attrs, CKA_CLASS, &value);
- g_assert_cmpuint (value, ==, CKO_PRIVATE_KEY);
- gck_attributes_find_ulong (priv_attrs, CKA_KEY_TYPE, &value);
- g_assert_cmpuint (value, ==, CKK_RSA);
- /* public looks reasonable */
- gck_attributes_find_ulong (pub_attrs, CKA_CLASS, &value);
- g_assert_cmpuint (value, ==, CKO_PUBLIC_KEY);
- gck_attributes_find_ulong (pub_attrs, CKA_KEY_TYPE, &value);
- g_assert_cmpuint (value, ==, CKK_RSA);
- /* public exponent should be same */
- priv_attr = gck_attributes_find (priv_attrs, CKA_PUBLIC_EXPONENT);
- pub_attr = gck_attributes_find (pub_attrs, CKA_PUBLIC_EXPONENT);
- g_assert_cmpmem (priv_attr->value, priv_attr->length, pub_attr->value, pub_attr->length);
-
- /* try to write the public key */
- ret = gkd_ssh_agent_proto_write_public (&resp, pub_attrs);
- g_assert (ret);
- g_assert (egg_buffer_length(&resp) != 0);
-
- /* Read the written public key */
- gck_builder_init (&new_pub);
- offset = 0; /* in this case we do not have message length and operation */
- ret = gkd_ssh_agent_proto_read_public (&resp, &offset, &new_pub, &algo);
- g_assert (ret);
- g_assert_cmpuint (algo, ==, CKK_RSA);
- new_pub_attrs = gck_builder_end (&new_pub);
- g_assert (new_pub_attrs);
-
- /* check that the parameters in old and new public key match */
- new_pub_attr = gck_attributes_find (new_pub_attrs, CKA_PUBLIC_EXPONENT);
- pub_attr = gck_attributes_find (pub_attrs, CKA_PUBLIC_EXPONENT);
- g_assert_cmpmem (new_pub_attr->value, new_pub_attr->length, pub_attr->value, pub_attr->length);
-
- new_pub_attr = gck_attributes_find (new_pub_attrs, CKA_MODULUS);
- pub_attr = gck_attributes_find (pub_attrs, CKA_MODULUS);
- g_assert_cmpmem (new_pub_attr->value, new_pub_attr->length, pub_attr->value, pub_attr->length);
-
- /* cleanup */
- g_free (stype);
- gck_attributes_unref (priv_attrs);
- gck_attributes_unref (pub_attrs);
- gck_attributes_unref (new_pub_attrs);
- egg_buffer_uninit (&buffer);
- egg_buffer_uninit (&resp);
-}
-
-static void
-test_read_write_dsa (Test *test, gconstpointer unused)
-{
- GckAttributes *priv_attrs, *pub_attrs, *new_pub_attrs;
- const GckAttribute *priv_attr, *pub_attr, *new_pub_attr;
- GckBuilder priv, pub, new_pub;
- EggBuffer buffer, resp;
- gsize offset = 5; /* skipping message number (4B length, 1B message) */
- gchar *stype;
- gboolean ret;
- gulong value, algo;
-
- /* Prepare intercepted message from ssh-add */
- egg_buffer_init_static (&buffer, test->dsakey, test->dsakey_len);
- egg_buffer_init (&resp, 128);
-
- /* check the key type */
- ret = egg_buffer_get_string (&buffer, 5, &offset, &stype, (EggBufferAllocator)g_realloc);
- g_assert (ret);
- g_assert_cmpstr (stype, ==, "ssh-dss");
-
- /* parse the key to PKCS11 structures */
- gck_builder_init (&pub);
- gck_builder_init (&priv);
- ret = gkd_ssh_agent_proto_read_pair_dsa (&buffer, &offset, &priv, &pub);
- g_assert (ret);
-
- /* Finish */
- pub_attrs = gck_builder_end (&pub);
- g_assert (pub_attrs);
- priv_attrs = gck_builder_end (&priv);
- g_assert (priv_attrs);
-
- /* private looks reasonable */
- gck_attributes_find_ulong (priv_attrs, CKA_CLASS, &value);
- g_assert_cmpuint (value, ==, CKO_PRIVATE_KEY);
- gck_attributes_find_ulong (priv_attrs, CKA_KEY_TYPE, &value);
- g_assert_cmpuint (value, ==, CKK_DSA);
- /* public looks reasonable */
- gck_attributes_find_ulong (pub_attrs, CKA_CLASS, &value);
- g_assert_cmpuint (value, ==, CKO_PUBLIC_KEY);
- gck_attributes_find_ulong (pub_attrs, CKA_KEY_TYPE, &value);
- g_assert_cmpuint (value, ==, CKK_DSA);
- /* public parts should be same */
- priv_attr = gck_attributes_find (priv_attrs, CKA_PRIME);
- pub_attr = gck_attributes_find (pub_attrs, CKA_PRIME);
- g_assert_cmpmem (priv_attr->value, priv_attr->length, pub_attr->value, pub_attr->length);
-
- priv_attr = gck_attributes_find (priv_attrs, CKA_SUBPRIME);
- pub_attr = gck_attributes_find (pub_attrs, CKA_SUBPRIME);
- g_assert_cmpmem (priv_attr->value, priv_attr->length, pub_attr->value, pub_attr->length);
-
- priv_attr = gck_attributes_find (priv_attrs, CKA_BASE);
- pub_attr = gck_attributes_find (pub_attrs, CKA_BASE);
- g_assert_cmpmem (priv_attr->value, priv_attr->length, pub_attr->value, pub_attr->length);
-
- /* try to write the public key */
- ret = gkd_ssh_agent_proto_write_public (&resp, pub_attrs);
- g_assert (ret);
- g_assert (egg_buffer_length(&resp) != 0);
-
- /* Read the written public key */
- gck_builder_init (&new_pub);
- offset = 0; /* in this case we do not have message length and operation */
- ret = gkd_ssh_agent_proto_read_public (&resp, &offset, &new_pub, &algo);
- g_assert (ret);
- g_assert_cmpuint (algo, ==, CKK_DSA);
- new_pub_attrs = gck_builder_end (&new_pub);
- g_assert (new_pub_attrs);
-
- /* check that the parameters in old and new public key match */
- new_pub_attr = gck_attributes_find (new_pub_attrs, CKA_PRIME);
- pub_attr = gck_attributes_find (pub_attrs, CKA_PRIME);
- g_assert_cmpmem (new_pub_attr->value, new_pub_attr->length, pub_attr->value, pub_attr->length);
-
- new_pub_attr = gck_attributes_find (new_pub_attrs, CKA_SUBPRIME);
- pub_attr = gck_attributes_find (pub_attrs, CKA_SUBPRIME);
- g_assert_cmpmem (new_pub_attr->value, new_pub_attr->length, pub_attr->value, pub_attr->length);
-
- new_pub_attr = gck_attributes_find (new_pub_attrs, CKA_BASE);
- pub_attr = gck_attributes_find (pub_attrs, CKA_BASE);
- g_assert_cmpmem (new_pub_attr->value, new_pub_attr->length, pub_attr->value, pub_attr->length);
-
- /* cleanup */
- g_free (stype);
- gck_attributes_unref (priv_attrs);
- gck_attributes_unref (pub_attrs);
- gck_attributes_unref (new_pub_attrs);
- egg_buffer_uninit (&buffer);
- egg_buffer_uninit (&resp);
-}
-
-static void
-test_read_write_ecdsa (Test *test, gconstpointer unused)
-{
- GckAttributes *priv_attrs, *pub_attrs, *new_pub_attrs;
- const GckAttribute *priv_attr, *pub_attr, *new_pub_attr;
- GckBuilder priv, pub, new_pub;
- EggBuffer buffer, resp;
- gsize offset = 5; /* skipping message number (4B length, 1B message) */
- gchar *stype;
- gboolean ret;
- gulong value, algo;
-
- /* Prepare intercepted message from ssh-add */
- egg_buffer_init_static (&buffer, test->ecdsakey, test->ecdsakey_len);
- egg_buffer_init (&resp, 128);
-
- /* check the key type */
- ret = egg_buffer_get_string (&buffer, 5, &offset, &stype, (EggBufferAllocator)g_realloc);
- g_assert (ret);
- g_assert_cmpstr (stype, ==, "ecdsa-sha2-nistp256");
-
- /* parse the key to PKCS11 structures */
- gck_builder_init (&pub);
- gck_builder_init (&priv);
- ret = gkd_ssh_agent_proto_read_pair_ecdsa (&buffer, &offset, &priv, &pub);
- g_assert (ret);
-
- /* Finish */
- pub_attrs = gck_builder_end (&pub);
- g_assert (pub_attrs);
- priv_attrs = gck_builder_end (&priv);
- g_assert (priv_attrs);
-
- /* private looks reasonable */
- gck_attributes_find_ulong (priv_attrs, CKA_CLASS, &value);
- g_assert_cmpuint (value, ==, CKO_PRIVATE_KEY);
- gck_attributes_find_ulong (priv_attrs, CKA_KEY_TYPE, &value);
- g_assert_cmpuint (value, ==, CKK_ECDSA);
- /* public looks reasonable */
- gck_attributes_find_ulong (pub_attrs, CKA_CLASS, &value);
- g_assert_cmpuint (value, ==, CKO_PUBLIC_KEY);
- gck_attributes_find_ulong (pub_attrs, CKA_KEY_TYPE, &value);
- g_assert_cmpuint (value, ==, CKK_ECDSA);
- /* public parts should be same */
- priv_attr = gck_attributes_find (priv_attrs, CKA_EC_PARAMS);
- pub_attr = gck_attributes_find (pub_attrs, CKA_EC_PARAMS);
- g_assert_cmpmem (priv_attr->value, priv_attr->length, pub_attr->value, pub_attr->length);
-
- priv_attr = gck_attributes_find (priv_attrs, CKA_EC_POINT);
- pub_attr = gck_attributes_find (pub_attrs, CKA_EC_POINT);
- g_assert_cmpmem (priv_attr->value, priv_attr->length, pub_attr->value, pub_attr->length);
-
- /* try to write the public key */
- ret = gkd_ssh_agent_proto_write_public (&resp, pub_attrs);
- g_assert (ret);
- g_assert (egg_buffer_length(&resp) != 0);
-
- /* Read the written public key */
- gck_builder_init (&new_pub);
- offset = 0; /* in this case we do not have message length and operation */
- ret = gkd_ssh_agent_proto_read_public (&resp, &offset, &new_pub, &algo);
- g_assert (ret);
- g_assert_cmpuint (algo, ==, CKK_ECDSA);
- new_pub_attrs = gck_builder_end (&new_pub);
- g_assert (new_pub_attrs);
-
- /* check that the parameters in old and new public key match */
- new_pub_attr = gck_attributes_find (new_pub_attrs, CKA_EC_PARAMS);
- pub_attr = gck_attributes_find (pub_attrs, CKA_EC_PARAMS);
- g_assert_cmpmem (new_pub_attr->value, new_pub_attr->length, pub_attr->value, pub_attr->length);
-
- new_pub_attr = gck_attributes_find (new_pub_attrs, CKA_EC_POINT);
- pub_attr = gck_attributes_find (pub_attrs, CKA_EC_POINT);
- g_assert_cmpmem (new_pub_attr->value, new_pub_attr->length, pub_attr->value, pub_attr->length);
-
- /* cleanup */
- g_free (stype);
- gck_attributes_unref (priv_attrs);
- gck_attributes_unref (pub_attrs);
- egg_buffer_uninit (&buffer);
- egg_buffer_uninit (&resp);
-}
-
-/* gkd_ssh_agent_proto_write_signature_rsa (); XXX next test */
-
-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 ("/daemon/ssh-agent/communication/rsa_pair", Test, NULL, setup, test_read_write_rsa, teardown);
- g_test_add ("/daemon/ssh-agent/communication/dsa_pair", Test, NULL, setup, test_read_write_dsa, teardown);
- g_test_add ("/daemon/ssh-agent/communication/ecdsa_pair", Test, NULL, setup, test_read_write_ecdsa, teardown);
-
- return g_test_run ();
-}
diff --git a/daemon/ssh-agent/test-gkd-ssh-agent-preload.c b/daemon/ssh-agent/test-gkd-ssh-agent-preload.c
new file mode 100644
index 00000000..bce2197a
--- /dev/null
+++ b/daemon/ssh-agent/test-gkd-ssh-agent-preload.c
@@ -0,0 +1,166 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Daiki Ueno
+ */
+
+#include "config.h"
+
+#include "gkd-ssh-agent-preload.h"
+#include "egg/egg-testing.h"
+
+#include <glib/gstdio.h>
+#include <unistd.h>
+
+typedef struct {
+ gchar *directory;
+ GkdSshAgentPreload *preload;
+} Test;
+
+static void
+setup (Test *test, gconstpointer unused)
+{
+ test->directory = egg_tests_create_scratch_directory (NULL, NULL);
+
+ egg_tests_copy_scratch_file (test->directory, SRCDIR "/pkcs11/ssh-store/fixtures/id_rsa_plain");
+ egg_tests_copy_scratch_file (test->directory, SRCDIR "/pkcs11/ssh-store/fixtures/id_rsa_plain.pub");
+
+ test->preload = gkd_ssh_agent_preload_new (test->directory);
+}
+
+static void
+teardown (Test *test, gconstpointer unused)
+{
+ g_object_unref (test->preload);
+
+ egg_tests_remove_scratch_directory (test->directory);
+ g_free (test->directory);
+}
+
+static void
+test_list (Test *test, gconstpointer unused)
+{
+ GList *keys;
+
+ keys = gkd_ssh_agent_preload_get_keys (test->preload);
+ g_assert_cmpint (1, ==, g_list_length (keys));
+ g_list_free_full (keys, (GDestroyNotify)gkd_ssh_agent_key_info_free);
+}
+
+static void
+test_added (Test *test, gconstpointer unused)
+{
+ GList *keys;
+
+ keys = gkd_ssh_agent_preload_get_keys (test->preload);
+ g_assert_cmpint (1, ==, g_list_length (keys));
+ g_list_free_full (keys, (GDestroyNotify)gkd_ssh_agent_key_info_free);
+
+ /* Mtime must change so wait between tests */
+ sleep (1);
+
+ egg_tests_copy_scratch_file (test->directory, SRCDIR "/pkcs11/ssh-store/fixtures/id_ecdsa_plain");
+ egg_tests_copy_scratch_file (test->directory, SRCDIR "/pkcs11/ssh-store/fixtures/id_ecdsa_plain.pub");
+
+ keys = gkd_ssh_agent_preload_get_keys (test->preload);
+ g_assert_cmpint (2, ==, g_list_length (keys));
+ g_list_free_full (keys, (GDestroyNotify)gkd_ssh_agent_key_info_free);
+}
+
+static void
+test_removed (Test *test, gconstpointer unused)
+{
+ GList *keys;
+ gchar *path;
+
+ keys = gkd_ssh_agent_preload_get_keys (test->preload);
+ g_assert_cmpint (1, ==, g_list_length (keys));
+ g_list_free_full (keys, (GDestroyNotify)gkd_ssh_agent_key_info_free);
+
+ /* Mtime must change so wait between tests */
+ sleep (1);
+
+ path = g_build_filename (test->directory, "id_rsa_plain.pub", NULL);
+ g_unlink (path);
+ g_free (path);
+
+ path = g_build_filename (test->directory, "id_rsa_plain", NULL);
+ g_unlink (path);
+ g_free (path);
+
+ keys = gkd_ssh_agent_preload_get_keys (test->preload);
+ g_assert_cmpint (0, ==, g_list_length (keys));
+ g_list_free_full (keys, (GDestroyNotify)gkd_ssh_agent_key_info_free);
+}
+
+static void
+test_changed (Test *test, gconstpointer unused)
+{
+ GList *keys;
+ gchar *path;
+ gchar *contents;
+ gsize length;
+ GError *error;
+ gchar *p;
+ gboolean ret;
+
+ keys = gkd_ssh_agent_preload_get_keys (test->preload);
+ g_assert_cmpint (1, ==, g_list_length (keys));
+ g_list_free_full (keys, (GDestroyNotify)gkd_ssh_agent_key_info_free);
+
+ /* Mtime must change so wait between tests */
+ sleep (1);
+
+ path = g_build_filename (test->directory, "id_rsa_plain.pub", NULL);
+ error = NULL;
+ ret = g_file_get_contents (path, &contents, &length, &error);
+ g_assert_true (ret);
+ g_assert_no_error (error);
+
+#define COMMENT "comment"
+ contents = g_realloc (contents, length + strlen (COMMENT) + 1);
+ p = strchr (contents, '\n');
+ g_assert_nonnull (p);
+ memcpy (p, " " COMMENT "\n", strlen (COMMENT) + 2);
+ error = NULL;
+ ret = g_file_set_contents (path, contents, length + strlen (COMMENT), &error);
+ g_assert_true (ret);
+ g_assert_no_error (error);
+ g_free (path);
+ g_free (contents);
+
+ keys = gkd_ssh_agent_preload_get_keys (test->preload);
+ g_assert_cmpint (1, ==, g_list_length (keys));
+ g_assert_cmpstr (COMMENT, ==, ((GkdSshAgentKeyInfo *)keys->data)->comment);
+ g_list_free_full (keys, (GDestroyNotify)gkd_ssh_agent_key_info_free);
+#undef COMMENT
+}
+
+int
+main (int argc, char **argv)
+{
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add ("/ssh-agent/preload/list", Test, NULL, setup, test_list, teardown);
+ g_test_add ("/ssh-agent/preload/added", Test, NULL, setup, test_added, teardown);
+ g_test_add ("/ssh-agent/preload/removed", Test, NULL, setup, test_removed, teardown);
+ g_test_add ("/ssh-agent/preload/changed", Test, NULL, setup, test_changed, teardown);
+
+ return g_test_run ();
+}
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 00000000..329eda10
--- /dev/null
+++ b/daemon/ssh-agent/test-gkd-ssh-agent-process.c
@@ -0,0 +1,217 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Daiki Ueno
+ */
+
+#include "config.h"
+
+#include "gkd-ssh-agent-private.h"
+#include "gkd-ssh-agent-process.h"
+#include "gkd-ssh-agent-util.h"
+#include "test-common.h"
+#include "egg/egg-testing.h"
+
+#include <glib.h>
+
+typedef struct {
+ gchar *directory;
+ EggBuffer req;
+ EggBuffer resp;
+ GkdSshAgentProcess *process;
+ GMainLoop *loop;
+} 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
+connect_to_process (Test *test)
+{
+ GError *error;
+ gboolean ret;
+
+ error = NULL;
+ ret = gkd_ssh_agent_process_connect (test->process, NULL, &error);
+ g_assert_true (ret);
+ g_assert_no_error (error);
+}
+
+static void
+test_connect (Test *test, gconstpointer unused)
+{
+ connect_to_process (test);
+}
+
+static void
+call (Test *test)
+{
+ GError *error;
+ gboolean ret;
+
+ error = NULL;
+ ret = gkd_ssh_agent_process_call (test->process, &test->req, &test->resp, NULL, &error);
+ g_assert_true (ret);
+ g_assert_no_error (error);
+}
+
+DEFINE_CALL_FUNCS(Test, call)
+
+static void
+test_list (Test *test, gconstpointer unused)
+{
+ connect_to_process (test);
+ call_request_identities(test, 0);
+}
+
+static void
+test_add (Test *test, gconstpointer unused)
+{
+ connect_to_process (test);
+ call_add_identity (test);
+ call_request_identities (test, 1);
+}
+
+static void
+test_remove (Test *test, gconstpointer unused)
+{
+ connect_to_process (test);
+ 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)
+{
+ connect_to_process (test);
+ 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)
+{
+ connect_to_process (test);
+ call_add_identity (test);
+ call_request_identities (test, 1);
+
+ call_sign (test);
+
+ call_remove_all_identities (test);
+ call_request_identities (test, 0);
+}
+
+static gpointer
+kill_thread (gpointer data)
+{
+ Test *test = data;
+ GPid pid;
+
+ pid = gkd_ssh_agent_process_get_pid (test->process);
+ g_assert_cmpint (-1, !=, pid);
+
+ kill (pid, SIGTERM);
+
+ return NULL;
+}
+
+static void
+on_closed (GkdSshAgentProcess *self, gpointer data)
+{
+ GMainLoop *loop = data;
+
+ g_main_loop_quit (loop);
+ g_main_loop_unref (loop);
+}
+
+static void
+test_restart (Test *test, gconstpointer unused)
+{
+ GPid pid;
+ GMainLoop *loop;
+ GThread *thread;
+
+ connect_to_process (test);
+
+ pid = gkd_ssh_agent_process_get_pid (test->process);
+ g_assert_cmpint (0, !=, pid);
+
+ thread = g_thread_new ("kill", kill_thread, test);
+
+ loop = g_main_loop_new (NULL, FALSE);
+ g_signal_connect (test->process, "closed", G_CALLBACK (on_closed), loop);
+ g_main_loop_run (loop);
+
+ g_thread_join (thread);
+
+ pid = gkd_ssh_agent_process_get_pid (test->process);
+ g_assert_cmpint (0, ==, pid);
+
+ connect_to_process (test);
+
+ pid = gkd_ssh_agent_process_get_pid (test->process);
+ g_assert_cmpint (0, !=, pid);
+}
+
+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);
+ g_test_add ("/ssh-agent/process/restart", Test, NULL, setup, test_restart, teardown);
+
+ return g_test_run ();
+}
diff --git a/daemon/ssh-agent/test-gkd-ssh-agent-service.c b/daemon/ssh-agent/test-gkd-ssh-agent-service.c
new file mode 100644
index 00000000..d02d1639
--- /dev/null
+++ b/daemon/ssh-agent/test-gkd-ssh-agent-service.c
@@ -0,0 +1,617 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2018 Red Hat, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Daiki Ueno
+ */
+
+#include "config.h"
+
+#include "gkd-ssh-agent-service.h"
+#include "gkd-ssh-agent-private.h"
+#include "gkd-ssh-agent-util.h"
+#include "test-common.h"
+#include "egg/egg-testing.h"
+#include "egg/mock-interaction.h"
+
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <gio/gunixsocketaddress.h>
+
+typedef struct {
+ gchar *directory;
+ EggBuffer req;
+ EggBuffer resp;
+ GkdSshAgentService *service;
+ GMainLoop *loop;
+ GSocketConnection *connection;
+ GThread *thread;
+ GMutex lock;
+ GCond cond;
+} Test;
+
+static gpointer
+server_thread (gpointer data)
+{
+ Test *test = data;
+ GMainContext *context;
+ gboolean ret;
+
+ context = g_main_context_new ();
+ test->loop = g_main_loop_new (context, FALSE);
+
+ g_main_context_push_thread_default (context);
+
+ ret = gkd_ssh_agent_service_start (test->service);
+ g_assert_true (ret);
+
+ g_mutex_lock (&test->lock);
+ g_cond_signal (&test->cond);
+ g_mutex_unlock (&test->lock);
+
+ g_main_loop_run (test->loop);
+
+ g_main_context_pop_thread_default (context);
+
+ g_main_context_unref (context);
+ g_main_loop_unref (test->loop);
+
+ return NULL;
+}
+
+static void
+connect_to_server (Test *test)
+{
+ const gchar *envvar;
+ GSocketClient *client;
+ GSocketAddress *address;
+ GError *error;
+
+ envvar = g_getenv ("SSH_AUTH_SOCK");
+ g_assert_nonnull (envvar);
+ address = g_unix_socket_address_new (envvar);
+
+ client = g_socket_client_new ();
+
+ error = NULL;
+ test->connection = g_socket_client_connect (client,
+ G_SOCKET_CONNECTABLE (address),
+ NULL,
+ &error);
+ g_assert_nonnull (test->connection);
+ g_assert_no_error (error);
+
+ g_object_unref (address);
+ g_object_unref (client);
+}
+
+static void
+setup (Test *test, gconstpointer unused)
+{
+ GTlsInteraction *interaction;
+ GkdSshAgentPreload *preload;
+ gchar *sockets_path;
+ gchar *preload_path;
+ gchar *path;
+
+ 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->service = gkd_ssh_agent_service_new (sockets_path, interaction, preload);
+ g_free (sockets_path);
+
+ g_object_unref (interaction);
+ g_object_unref (preload);
+
+ g_mutex_init (&test->lock);
+ g_cond_init (&test->cond);
+
+ test->thread = g_thread_new ("ssh-agent", server_thread, test);
+
+ /* Wait until the server is up */
+ 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_main_loop_quit (test->loop);
+ g_thread_join (test->thread);
+
+ g_clear_object (&test->connection);
+
+ gkd_ssh_agent_service_stop (test->service);
+ g_object_unref (test->service);
+
+ 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 (Test *test)
+{
+ GError *error;
+ gboolean ret;
+
+ error = NULL;
+ ret = _gkd_ssh_agent_write_packet (test->connection, &test->req, NULL, &error);
+ g_assert_true (ret);
+ g_assert_no_error (error);
+
+ error = NULL;
+ ret = _gkd_ssh_agent_read_packet (test->connection, &test->resp, NULL, &error);
+ g_assert_true (ret);
+ g_assert_no_error (error);
+}
+
+static void
+call_error_or_failure (Test *test, gint dom, gint code)
+{
+ GError *error;
+ gboolean ret;
+
+ error = NULL;
+ ret = _gkd_ssh_agent_write_packet (test->connection, &test->req, NULL, &error);
+ g_assert_true (ret);
+ g_assert_no_error (error);
+
+ error = NULL;
+ ret = _gkd_ssh_agent_read_packet (test->connection, &test->resp, NULL, &error);
+ if (ret)
+ check_failure (&test->resp);
+ else {
+ g_assert_false (ret);
+ g_assert_error (error, dom, code);
+ }
+}
+
+DEFINE_CALL_FUNCS(Test, call)
+
+static void
+call_unparseable_add (Test *test)
+{
+ egg_buffer_reset (&test->req);
+ egg_buffer_reset (&test->resp);
+
+ prepare_add_identity (&test->req);
+ egg_buffer_set_uint32 (&test->req, 5, 0x80000000);
+ call_error_or_failure (test, G_IO_ERROR, G_IO_ERROR_FAILED);
+}
+
+static void
+call_unparseable_remove (Test *test)
+{
+ egg_buffer_reset (&test->req);
+ egg_buffer_reset (&test->resp);
+
+ prepare_remove_identity (&test->req);
+ egg_buffer_set_uint32 (&test->req, 5, 0x80000000);
+ call_error_or_failure (test, G_IO_ERROR, G_IO_ERROR_FAILED);
+}
+
+static void
+call_unparseable_sign (Test *test)
+{
+ egg_buffer_reset (&test->req);
+ egg_buffer_reset (&test->resp);
+
+ prepare_sign_request (&test->req);
+ egg_buffer_set_uint32 (&test->req, 5, 0x80000000);
+ call_error_or_failure (test, G_IO_ERROR, G_IO_ERROR_FAILED);
+}
+
+static void
+prepare_sign_request_unknown (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_ecdsa_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
+call_sign_unknown (Test *test)
+{
+ egg_buffer_reset (&test->req);
+ egg_buffer_reset (&test->resp);
+
+ prepare_sign_request_unknown (&test->req);
+ call (test);
+ check_failure (&test->resp);
+}
+
+static void
+call_empty (Test *test)
+{
+ GError *error;
+ gboolean ret;
+
+ egg_buffer_reset (&test->req);
+ egg_buffer_reset (&test->resp);
+
+ ret = egg_buffer_add_uint32 (&test->req, 0);
+ g_assert_true (ret);
+
+ error = NULL;
+ ret = _gkd_ssh_agent_write_packet (test->connection, &test->req, NULL, &error);
+ g_assert_true (ret);
+ g_assert_no_error (error);
+
+ error = NULL;
+ ret = _gkd_ssh_agent_read_packet (test->connection, &test->resp, NULL, &error);
+ g_assert_false (ret);
+ g_assert_error (error, G_IO_ERROR, G_IO_ERROR_FAILED);
+}
+
+static void
+call_unknown (Test *test)
+{
+ GError *error;
+ gboolean ret;
+
+ egg_buffer_reset (&test->req);
+ egg_buffer_reset (&test->resp);
+
+ ret = egg_buffer_add_uint32 (&test->req, 0);
+ g_assert_true (ret);
+
+ ret = egg_buffer_add_byte (&test->req, 255);
+ g_assert_true (ret);
+
+ error = NULL;
+ ret = _gkd_ssh_agent_write_packet (test->connection, &test->req, NULL, &error);
+ g_assert_true (ret);
+ g_assert_no_error (error);
+
+ error = NULL;
+ ret = _gkd_ssh_agent_read_packet (test->connection, &test->resp, NULL, &error);
+ g_assert_true (ret);
+ g_assert_no_error (error);
+
+ check_failure (&test->resp);
+}
+
+static void
+call_lock (Test *test)
+{
+ gboolean ret;
+
+ egg_buffer_reset (&test->req);
+ egg_buffer_reset (&test->resp);
+
+ ret = egg_buffer_add_uint32 (&test->req, 0);
+ g_assert_true (ret);
+
+ ret = egg_buffer_add_byte (&test->req, GKD_SSH_OP_LOCK);
+ g_assert_true (ret);
+
+ ret = egg_buffer_add_string (&test->req, "password");
+ g_assert_true (ret);
+
+ ret = egg_buffer_set_uint32 (&test->req, 0, test->req.len - 4);
+ g_assert_true (ret);
+
+ call (test);
+
+ check_success (&test->resp);
+}
+
+static void
+call_unlock (Test *test)
+{
+ gboolean ret;
+
+ egg_buffer_reset (&test->req);
+ egg_buffer_reset (&test->resp);
+
+ ret = egg_buffer_add_uint32 (&test->req, 0);
+ g_assert_true (ret);
+
+ ret = egg_buffer_add_byte (&test->req, GKD_SSH_OP_UNLOCK);
+ g_assert_true (ret);
+
+ ret = egg_buffer_add_string (&test->req, "password");
+ g_assert_true (ret);
+
+ ret = egg_buffer_set_uint32 (&test->req, 0, test->req.len - 4);
+ g_assert_true (ret);
+
+ call (test);
+
+ check_success (&test->resp);
+}
+
+static void
+test_startup_shutdown (Test *test, gconstpointer unused)
+{
+}
+
+static void
+test_list (Test *test, gconstpointer unused)
+{
+ connect_to_server (test);
+
+ call_request_identities (test, 1);
+}
+
+static void
+test_add (Test *test, gconstpointer unused)
+{
+ connect_to_server (test);
+
+ /* 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_unparseable_add (Test *test, gconstpointer unused)
+{
+ connect_to_server (test);
+
+ call_unparseable_add (test);
+}
+
+static void
+test_unparseable_remove (Test *test, gconstpointer unused)
+{
+ connect_to_server (test);
+
+ call_unparseable_remove (test); /* This closes the connection */
+}
+
+static void
+test_unparseable_sign (Test *test, gconstpointer unused)
+{
+ connect_to_server (test);
+
+ call_unparseable_sign (test); /* This closes the connection */
+}
+
+static void
+test_remove (Test *test, gconstpointer unused)
+{
+ connect_to_server (test);
+
+ /* 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_identity (test);
+ call_request_identities (test, 1);
+}
+
+static void
+test_remove_all (Test *test, gconstpointer unused)
+{
+ connect_to_server (test);
+
+ /* 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)
+{
+ connect_to_server (test);
+
+ /* 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)
+{
+ connect_to_server (test);
+
+ call_sign (test);
+}
+
+static void
+test_sign_unknown (Test *test, gconstpointer unused)
+{
+ connect_to_server (test);
+
+ call_sign_unknown (test);
+}
+
+static gpointer
+kill_thread (gpointer data)
+{
+ Test *test = data;
+ GkdSshAgentProcess *process;
+ GPid pid;
+
+ process = gkd_ssh_agent_service_get_process (test->service);
+ pid = gkd_ssh_agent_process_get_pid (process);
+ g_assert_cmpint (-1, !=, pid);
+
+ kill (pid, SIGTERM);
+
+ return NULL;
+}
+
+static void
+on_closed (GkdSshAgentProcess *self, gpointer data)
+{
+ GMainLoop *loop = data;
+
+ g_main_loop_quit (loop);
+ g_main_loop_unref (loop);
+}
+
+static void
+test_restart (Test *test, gconstpointer unused)
+{
+ GkdSshAgentProcess *process;
+ GThread *thread;
+ GMainLoop *loop;
+ GBytes *public_key;
+ gchar *comment;
+
+ connect_to_server (test);
+
+ public_key = public_key_from_file (SRCDIR "/pkcs11/ssh-store/fixtures/id_rsa_plain.pub", &comment);
+ g_free (comment);
+
+ call_add_identity (test);
+ call_request_identities (test, 1);
+
+ g_assert_true (gkd_ssh_agent_service_lookup_key (test->service, public_key));
+
+ thread = g_thread_new ("kill", kill_thread, test);
+
+ loop = g_main_loop_new (NULL, FALSE);
+
+ process = gkd_ssh_agent_service_get_process (test->service);
+ g_signal_connect (process, "closed", G_CALLBACK (on_closed), loop);
+ g_main_loop_run (loop);
+
+ g_thread_join (thread);
+
+ g_assert_false (gkd_ssh_agent_service_lookup_key (test->service, public_key));
+
+ call_add_identity (test);
+ call_request_identities (test, 1);
+
+ g_assert_true (gkd_ssh_agent_service_lookup_key (test->service, public_key));
+ g_bytes_unref (public_key);
+}
+
+static void
+test_empty (Test *test, gconstpointer unused)
+{
+ connect_to_server (test);
+
+ call_empty (test);
+}
+
+static void
+test_unknown (Test *test, gconstpointer unused)
+{
+ connect_to_server (test);
+
+ call_unknown (test);
+}
+
+static void
+test_lock (Test *test, gconstpointer unused)
+{
+ connect_to_server (test);
+
+ call_lock (test);
+ call_unlock (test);
+}
+
+int
+main (int argc, char **argv)
+{
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add ("/ssh-agent/service/startup_shutdown", Test, NULL, setup, test_startup_shutdown, teardown);
+ g_test_add ("/ssh-agent/service/list", Test, NULL, setup, test_list, teardown);
+ g_test_add ("/ssh-agent/service/add", Test, NULL, setup, test_add, teardown);
+ g_test_add ("/ssh-agent/service/remove", Test, NULL, setup, test_remove, teardown);
+ g_test_add ("/ssh-agent/service/remove_all", Test, NULL, setup, test_remove_all, teardown);
+ g_test_add ("/ssh-agent/service/sign_loaded", Test, NULL, setup, test_sign_loaded, teardown);
+ g_test_add ("/ssh-agent/service/sign", Test, NULL, setup, test_sign, teardown);
+ g_test_add ("/ssh-agent/service/sign_unknown", Test, NULL, setup, test_sign_unknown, teardown);
+ g_test_add ("/ssh-agent/service/empty", Test, NULL, setup, test_empty, teardown);
+ g_test_add ("/ssh-agent/service/unknown", Test, NULL, setup, test_unknown, teardown);
+ g_test_add ("/ssh-agent/service/unparseable_add", Test, NULL, setup, test_unparseable_add, teardown);
+ g_test_add ("/ssh-agent/service/unparseable_remove", Test, NULL, setup, test_unparseable_remove, teardown);
+ g_test_add ("/ssh-agent/service/unparseable_sign", Test, NULL, setup, test_unparseable_sign, teardown);
+ g_test_add ("/ssh-agent/service/restart", Test, NULL, setup, test_restart, teardown);
+ g_test_add ("/ssh-agent/service/lock", Test, NULL, setup, test_lock, teardown);
+
+ return g_test_run ();
+}
diff --git a/daemon/ssh-agent/test-gkd-ssh-agent-util.c b/daemon/ssh-agent/test-gkd-ssh-agent-util.c
new file mode 100644
index 00000000..89c10d26
--- /dev/null
+++ b/daemon/ssh-agent/test-gkd-ssh-agent-util.c
@@ -0,0 +1,84 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2008 Stefan 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, see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Author: Stef Walter <stef@thewalter.net>
+ */
+
+#include "config.h"
+
+#include "gkd-ssh-agent-util.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_agent_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)
+{
+ g_test_init (&argc, &argv, NULL);
+
+ g_test_add_func ("/ssh-agent/util/parse_public", test_parse_public);
+
+ return g_test_run ();
+}
diff --git a/daemon/ssh-agent/test-keytypes.c b/daemon/ssh-agent/test-keytypes.c
deleted file mode 100644
index e8ed127b..00000000
--- a/daemon/ssh-agent/test-keytypes.c
+++ /dev/null
@@ -1,197 +0,0 @@
-/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
-/* test-keytypes.c: Parsing and generating key types from SSH
-
- Copyright (C) 2017 Red Hat, Inc.
-
- The Gnome Keyring Library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Library General Public License as
- published by the Free Software Foundation; either version 2 of the
- License, or (at your option) any later version.
-
- The Gnome Keyring Library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Library General Public License for more details.
-
- You should have received a copy of the GNU Library General Public
- License along with the Gnome Library; see the file COPYING.LIB. If not,
- <http://www.gnu.org/licenses/>.
-
- Author: Jakub Jelen <jjelen@redhat.com>
-*/
-
-#include "config.h"
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-
-#include <glib.h>
-
-#include "pkcs11/pkcs11.h"
-#include "gkd-ssh-agent-private.h"
-
-#define GKD_SSH_OID_ANSI_SECP256R1 "1.2.840.10045.3.1.7"
-#define GKD_SSH_OID_ANSI_SECP384R1 "1.3.132.0.34"
-#define GKD_SSH_OID_ANSI_SECP521R1 "1.3.132.0.35"
-
-struct alg {
- gchar *name;
- CK_KEY_TYPE id;
- gchar *curve_oid;
- GChecksumType hash;
-};
-
-/* known algorithms */
-static struct alg algs_known[] = {
- { "ssh-rsa", CKK_RSA, NULL, 0 },
- { "rsa-sha2-256", CKK_RSA, NULL, G_CHECKSUM_SHA256 },
- { "rsa-sha2-512", CKK_RSA, NULL, G_CHECKSUM_SHA512 },
- { "ssh-dss", CKK_DSA, NULL, 0},
- { "ecdsa-sha2-nistp256", CKK_EC, GKD_SSH_OID_ANSI_SECP256R1, 0 },
- { "ecdsa-sha2-nistp384", CKK_EC, GKD_SSH_OID_ANSI_SECP384R1, 0 },
- { "ecdsa-sha2-nistp521", CKK_EC, GKD_SSH_OID_ANSI_SECP521R1, 0 },
-
- /* terminator */
- { NULL, 0, 0 }
-};
-
-/* unknown algorithms */
-static struct alg algs_parse_unknown[] = {
- /* no certificates */
- { "ssh-rsa-cert-v01@openssh.com", G_MAXULONG, NULL, 0 },
- { "ssh-dss-cert-v01@openssh.com", G_MAXULONG, NULL, 0 },
- { "ecdsa-sha2-nistp256-cert-v01@openssh.com", G_MAXULONG, NULL, 0 },
- { "ecdsa-sha2-nistp384-cert-v01@openssh.com", G_MAXULONG, NULL, 0 },
- { "ecdsa-sha2-nistp521-cert-v01@openssh.com", G_MAXULONG, NULL, 0 },
- /* no new signatures/algorithms */
- { "ssh-ed25519", G_MAXULONG, NULL, 0 },
- { "ssh-ed25519-cert-v01@openssh.com", G_MAXULONG, NULL, 0 },
-
- /* terminator */
- { NULL, 0, 0 }
-};
-
-static struct alg curves[] = {
- { "ecdsa-sha2-nistp256", CKK_EC, GKD_SSH_OID_ANSI_SECP256R1 },
- { "ecdsa-sha2-nistp384", CKK_EC, GKD_SSH_OID_ANSI_SECP384R1 },
- { "ecdsa-sha2-nistp521", CKK_EC, GKD_SSH_OID_ANSI_SECP521R1 },
-
- /* terminator */
- { NULL, 0, 0 }
-};
-
-typedef struct {
- const struct alg *algs_known;
- const struct alg *algs_parse_unknown;
- const struct alg *curves;
-} Test;
-
-static void
-setup (Test *test, gconstpointer unused)
-{
- test->algs_known = algs_known;
- test->algs_parse_unknown = algs_parse_unknown;
- test->curves = curves;
-}
-
-static void
-teardown (Test *test, gconstpointer unused)
-{
-}
-
-static void
-test_parse (Test *test, gconstpointer unused)
-{
- const struct alg *a;
- gulong alg_id;
-
- /* known */
- for (a = test->algs_known; a->name != NULL; a++) {
- alg_id = gkd_ssh_agent_proto_keytype_to_algo (a->name);
- g_assert_cmpuint (a->id, ==, alg_id);
- }
-
- g_assert_cmpuint (a->id, ==, 0);
-
- /* we do not recognize nor fail with the unknown */
- for (a = test->algs_parse_unknown; a->name != NULL; a++) {
- alg_id = gkd_ssh_agent_proto_keytype_to_algo (a->name);
- g_assert_cmpuint (a->id, ==, alg_id);
- }
-
- g_assert_cmpuint (a->id, ==, 0);
-}
-
-static void
-test_generate (Test *test, gconstpointer unused)
-{
- const struct alg *a;
-
- for (a = test->algs_known; a->name != NULL; a++) {
- const gchar *alg_name = NULL;
- GQuark oid;
- switch (a->id) {
- case CKK_RSA:
- alg_name = gkd_ssh_agent_proto_rsa_algo_to_keytype (a->hash);
- break;
- case CKK_EC:
- oid = g_quark_from_string (a->curve_oid);
- alg_name = gkd_ssh_agent_proto_ecc_algo_to_keytype (oid);
- break;
- case CKK_DSA:
- alg_name = gkd_ssh_agent_proto_dsa_algo_to_keytype ();
- break;
- }
- g_assert_cmpstr (a->name, ==, alg_name);
- }
-}
-
-static void
-test_curve_from_ssh (Test *test, gconstpointer unused)
-{
- const struct alg *a;
- const gchar *alg_name;
-
- /* known */
- for (a = test->curves; a->name != NULL; a++) {
- GQuark oid = g_quark_from_string (a->curve_oid);
- alg_name = gkd_ssh_agent_proto_ecc_algo_to_keytype (oid);
- g_assert_cmpstr (a->name, ==, alg_name);
- }
-}
-
-static void
-test_ssh_from_curve (Test *test, gconstpointer unused)
-{
- const struct alg *a;
- const gchar *curve;
- GQuark oid;
-
- /* known */
- for (a = test->curves; a->name != NULL; a++) {
- /* curve is in the end of the keytype -- skip 11 chars */
- curve = a->name + 11;
- oid = gkd_ssh_agent_proto_curve_to_oid (curve);
- g_assert_cmpstr (g_quark_to_string (oid), ==, a->curve_oid);
- }
-
- oid = gkd_ssh_agent_proto_curve_to_oid ("nistpunknown");
- g_assert_cmpuint (oid, ==, 0);
-}
-
-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 ("/daemon/ssh-agent/keytypes/parse", Test, NULL, setup, test_parse, teardown);
- g_test_add ("/daemon/ssh-agent/keytypes/generate", Test, NULL, setup, test_generate, teardown);
- g_test_add ("/daemon/ssh-agent/keytypes/curve_from_ssh", Test, NULL, setup, test_curve_from_ssh, teardown);
- g_test_add ("/daemon/ssh-agent/keytypes/ssh_from_curve", Test, NULL, setup, test_ssh_from_curve, teardown);
-
- return g_test_run ();
-}