diff options
author | Stef Walter <stefw@gnome.org> | 2018-02-23 16:37:01 +0100 |
---|---|---|
committer | Daiki Ueno <dueno@src.gnome.org> | 2018-02-25 07:16:09 +0100 |
commit | 90fb7fec727081d28946827e31f4330715dad283 (patch) | |
tree | 8594d803d7a0162911f940c0516f184a133afebf | |
parent | d0d059cb6e1de1a925f6853877648b1fc79807a8 (diff) | |
download | gnome-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
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, ¶ms_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 (); -} |