gnome-keyring r1419 - in trunk: . pkcs11 pkcs11/gck pkcs11/ssh-agent



Author: nnielsen
Date: Sat Jan  3 23:00:21 2009
New Revision: 1419
URL: http://svn.gnome.org/viewvc/gnome-keyring?rev=1419&view=rev

Log:
	* pkcs11/gck/gck-memory-store.c:
	* pkcs11/gck/gck-object.c:
	* pkcs11/gck/gck-session.c:
	* pkcs11/ssh-agent/gck-ssh-agent.c: (added)
	* pkcs11/ssh-agent/gck-ssh-agent.h: (added)
	* pkcs11/ssh-agent/gck-ssh-agent-ops.c: (added)
	* pkcs11/ssh-agent/gck-ssh-agent-private.h: (added)
	* pkcs11/ssh-agent/gck-ssh-agent-proto.c: (added)
	* pkcs11/ssh-agent/gck-ssh-agent-standalone.c: (added)
	* pkcs11/ssh-agent/Makefile.am: (added)
	* configure.in: Add PKCS#11 based SSH agent. 

Added:
   trunk/pkcs11/ssh-agent/   (props changed)
   trunk/pkcs11/ssh-agent/Makefile.am
   trunk/pkcs11/ssh-agent/gck-ssh-agent-ops.c
   trunk/pkcs11/ssh-agent/gck-ssh-agent-private.h
   trunk/pkcs11/ssh-agent/gck-ssh-agent-proto.c
   trunk/pkcs11/ssh-agent/gck-ssh-agent-standalone.c
   trunk/pkcs11/ssh-agent/gck-ssh-agent.c
   trunk/pkcs11/ssh-agent/gck-ssh-agent.h
Modified:
   trunk/ChangeLog
   trunk/configure.in
   trunk/pkcs11/Makefile.am
   trunk/pkcs11/gck/gck-memory-store.c
   trunk/pkcs11/gck/gck-object.c
   trunk/pkcs11/gck/gck-session.c

Modified: trunk/configure.in
==============================================================================
--- trunk/configure.in	(original)
+++ trunk/configure.in	Sat Jan  3 23:00:21 2009
@@ -517,6 +517,7 @@
 pkcs11/gck/tests/Makefile
 pkcs11/roots/Makefile
 pkcs11/roots/tests/Makefile
+pkcs11/ssh-agent/Makefile
 pkcs11/ssh-keys/Makefile
 pkcs11/ssh-keys/tests/Makefile
 pkcs11/rpc/Makefile

Modified: trunk/pkcs11/Makefile.am
==============================================================================
--- trunk/pkcs11/Makefile.am	(original)
+++ trunk/pkcs11/Makefile.am	Sat Jan  3 23:00:21 2009
@@ -47,5 +47,5 @@
 TESTS_DIR = 
 endif
 
-SUBDIRS = . rpc gck ssh-keys $(ROOTS_DIR) $(TESTS_DIR)
+SUBDIRS = . rpc gck ssh-agent ssh-keys $(ROOTS_DIR) $(TESTS_DIR)
 

Modified: trunk/pkcs11/gck/gck-memory-store.c
==============================================================================
--- trunk/pkcs11/gck/gck-memory-store.c	(original)
+++ trunk/pkcs11/gck/gck-memory-store.c	Sat Jan  3 23:00:21 2009
@@ -163,7 +163,7 @@
 	if (at != NULL && gck_attribute_equal (at, attr))
 		return;
 
-	revert = g_new0 (Revert, 1);
+	revert = g_slice_new0 (Revert);
 	revert->attributes = g_hash_table_ref (attributes);
 	revert->type = attr->type;
 	revert->attr = at;

Modified: trunk/pkcs11/gck/gck-object.c
==============================================================================
--- trunk/pkcs11/gck/gck-object.c	(original)
+++ trunk/pkcs11/gck/gck-object.c	Sat Jan  3 23:00:21 2009
@@ -164,6 +164,9 @@
 	if (self->pv->manager)
 		gck_manager_unregister_object (self->pv->manager, self);
 	g_assert (self->pv->manager == NULL);
+	
+	g_object_set (self, "store", NULL, NULL);
+	g_assert (self->pv->store == NULL);
     
 	G_OBJECT_CLASS (gck_object_parent_class)->dispose (obj);
 }

Modified: trunk/pkcs11/gck/gck-session.c
==============================================================================
--- trunk/pkcs11/gck/gck-session.c	(original)
+++ trunk/pkcs11/gck/gck-session.c	Sat Jan  3 23:00:21 2009
@@ -920,10 +920,10 @@
                                CK_ULONG count)
 {
 	gboolean also_private;
+	CK_RV rv = CKR_OK;
 	CK_BBOOL token;
 	GArray *found;
 	gboolean all;
-	CK_RV rv;
 	
 	g_return_val_if_fail (GCK_IS_SESSION (self), CKR_SESSION_HANDLE_INVALID);
 	if (!(template || !count))

Added: trunk/pkcs11/ssh-agent/Makefile.am
==============================================================================
--- (empty file)
+++ trunk/pkcs11/ssh-agent/Makefile.am	Sat Jan  3 23:00:21 2009
@@ -0,0 +1,48 @@
+
+INCLUDES = \
+	-I$(top_builddir) \
+    	-I$(top_srcdir) \
+    	-I$(top_srcdir)/pkcs11 \
+    	$(GOBJECT_CFLAGS) \
+	$(GLIB_CFLAGS)
+	
+			
+# ------------------------------------------------------------------------------
+# The ssh-agent component code
+
+noinst_LTLIBRARIES = \
+	libgck-ssh-agent.la
+
+libgck_ssh_agent_la_SOURCES = \
+	gck-ssh-agent.c gck-ssh-agent.h \
+	gck-ssh-agent-private.h \
+	gck-ssh-agent-ops.c \
+	gck-ssh-agent-proto.c
+
+# ------------------------------------------------------------------------------
+# Standalone binary
+
+noinst_PROGRAMS = \
+	gck-ssh-agent-standalone
+	
+gck_ssh_agent_standalone_SOURCES = \
+	gck-ssh-agent-standalone.c
+
+gck_ssh_agent_standalone_LDADD = \
+	libgck-ssh-agent.la \
+	$(top_builddir)/gp11/libgp11.la \
+	$(top_builddir)/common/libgkr-module-common.la \
+    	$(GOBJECT_LIBS) \
+    	$(GTHREAD_LIBS) \
+    	$(LIBGCRYPT_LIBS) \
+	$(GLIB_LIBS) 
+
+# -------------------------------------------------------------------------------
+
+# if WITH_TESTS
+# TESTS_DIR = tests
+# else
+# TESTS_DIR = 
+# endif
+# 
+# SUBDIRS = . $(TESTS_DIR)

Added: trunk/pkcs11/ssh-agent/gck-ssh-agent-ops.c
==============================================================================
--- (empty file)
+++ trunk/pkcs11/ssh-agent/gck-ssh-agent-ops.c	Sat Jan  3 23:00:21 2009
@@ -0,0 +1,1152 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gkr-ssh-daemon-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 "gck-ssh-agent-private.h"
+
+#include "gp11/gp11.h"
+
+#include "common/gkr-secure-memory.h"
+
+#include <glib.h>
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdio.h>
+
+
+#define V1_LABEL "SSH1 RSA Key"
+
+/* ---------------------------------------------------------------------------- */
+
+static void
+copy_attribute (GP11Attributes *original, CK_ATTRIBUTE_TYPE type, GP11Attributes *dest)
+{
+	GP11Attribute *attr;
+	
+	g_assert (original);
+	g_assert (dest);
+	
+	attr = gp11_attributes_find (original, type);
+	if (attr)
+		gp11_attributes_add (dest, attr);
+}
+
+static GList*
+find_keys_for_attributes (GP11Session *session, GP11Attributes *attrs, 
+                          CK_OBJECT_CLASS klass)
+{
+	GP11Attributes *search;
+	GError *error = NULL;
+	gulong key_type;
+	GList *keys;
+	
+	g_assert (GP11_IS_SESSION (session));
+	g_assert (attrs);
+	
+	/* Determine the key type */
+	if (!gp11_attributes_find_ulong (attrs, CKA_KEY_TYPE, &key_type))
+		g_return_val_if_reached (NULL);
+	
+	search = gp11_attributes_new ();
+	gp11_attributes_add_ulong (search, CKA_CLASS, klass);
+	copy_attribute (attrs, CKA_KEY_TYPE, search);
+	copy_attribute (attrs, CKA_TOKEN, search);
+	
+	switch (key_type) {
+	case CKK_RSA:
+		copy_attribute (attrs, CKA_MODULUS, search);
+		copy_attribute (attrs, CKA_PUBLIC_EXPONENT, search);
+		break;
+		
+	case CKK_DSA:
+		copy_attribute (attrs, CKA_PRIME, search);
+		copy_attribute (attrs, CKA_SUBPRIME, search);
+		copy_attribute (attrs, CKA_BASE, search);
+		copy_attribute (attrs, CKA_VALUE, search);
+		break;
+		
+	default:
+		g_return_val_if_reached (NULL);
+		break;
+	}
+	
+	keys = gp11_session_find_objects_full (session, search, NULL, &error);
+	gp11_attributes_unref (search);
+	
+	if (error) {
+		g_warning ("couldn't search for matching keys: %s", error->message);
+		g_clear_error (&error);
+		return NULL;
+	}
+	
+	return keys;
+}
+
+static GP11Object*
+public_key_for_attributes (GP11Session *session, GP11Attributes *attrs)
+{
+	GList *keys;
+	GP11Object *object;
+	
+	g_assert (GP11_IS_SESSION (session));
+	g_assert (attrs);
+	
+	keys = find_keys_for_attributes (session, attrs, CKO_PUBLIC_KEY);
+	if (!keys) {
+		g_message ("couldn't find matching public key");
+		return NULL;
+	}
+	
+	object = g_object_ref (keys->data);
+	gp11_list_unref_free (keys);
+	return object;
+}
+
+static GP11Object*
+private_key_for_public (GP11Session *session, GP11Object *pub)
+{
+	GP11Object *priv = NULL;
+	GP11Attributes *attrs;
+	GError *error = NULL;
+	GList *objects;
+
+	g_assert (GP11_IS_SESSION (session));
+	g_assert (GP11_IS_OBJECT (pub));
+
+	gp11_object_set_session (pub, session);
+	attrs = gp11_object_get (pub, &error, CKA_ID, CKA_TOKEN, 
+	                         GP11_INVALID);
+	if (error) {
+		g_warning ("couldn't lookup attributes for key: %s", error->message);
+		g_clear_error (&error);
+		return NULL;
+	}
+	
+	/* Search for exactly the same attributes but with a private key class */
+	gp11_attributes_add_ulong (attrs, CKA_CLASS, CKO_PRIVATE_KEY);
+	objects = gp11_session_find_objects_full (session, attrs, NULL, &error);
+	gp11_attributes_unref (attrs);
+	
+	if (error) {
+		g_warning ("couldn't search for related key: %s", error->message);
+		g_clear_error (&error);
+		return NULL;
+	}
+	
+	if (objects)
+		priv = g_object_ref (objects->data);
+	gp11_list_unref_free (objects);
+	
+	return priv;
+}
+
+static GP11Object*
+private_key_for_attributes (GP11Session *session, GP11Attributes *attrs)
+{
+	GP11Object *pub, *prv;
+	
+	g_assert (GP11_IS_SESSION (session));
+	g_assert (attrs);
+	
+	pub = public_key_for_attributes (session, attrs);
+	if (pub == NULL)
+		return NULL;
+	
+	prv = private_key_for_public (session, pub);
+	g_object_unref (pub);
+	
+	if (prv == NULL) {
+		g_message ("couldn't find matching private key");
+		return NULL;
+	}
+	
+	return prv;
+}
+
+static void
+remove_key_pair (GP11Session *session, GP11Object *priv, GP11Object *pub)
+{
+	GError *error = NULL;
+	
+	g_assert (GP11_IS_SESSION (session));
+	
+	if (priv != NULL) {
+		gp11_object_set_session (priv, session);
+		gp11_object_destroy (priv, &error);
+	
+		if (error) {
+			if (error->code != CKR_OBJECT_HANDLE_INVALID) 
+				g_warning ("couldn't remove ssh private key: %s", error->message);
+			g_clear_error (&error);
+		}
+	}
+	
+	if (pub != NULL) {
+		gp11_object_set_session (pub, session);
+		gp11_object_destroy (pub, &error);
+	
+		if (error) {
+			if (error->code != CKR_OBJECT_HANDLE_INVALID) 
+				g_warning ("couldn't remove ssh public key: %s", error->message);
+			g_clear_error (&error);
+		}
+	}
+}
+
+static void
+lock_key_pair (GP11Session *session, GP11Object *priv, GP11Object *pub)
+{
+	/* TODO: Implement */
+}
+
+static void
+remove_by_public_key (GP11Session *session, GP11Object *pub)
+{
+	GP11Object *priv;
+	
+	g_assert (GP11_IS_SESSION (session));
+	g_assert (GP11_IS_OBJECT (pub));
+	
+	priv = private_key_for_public (session, pub);
+	remove_key_pair (session, priv, pub);
+	if (pub != NULL)
+		g_object_unref (priv);
+}
+
+static void
+remove_or_lock_by_public_key (GP11Session *session, GP11Object *pub)
+{
+	GP11Attributes *attrs;
+	GError *error = NULL;
+	GList *objects;
+	gboolean token;
+	gchar *label;
+
+	g_assert (GP11_IS_SESSION (session));
+	g_assert (GP11_IS_OBJECT (pub));
+
+	gp11_object_set_session (pub, session);
+	attrs = gp11_object_get (pub, &error, 
+	                         CKA_LABEL, CKA_ID, CKA_TOKEN, 
+	                         GP11_INVALID);
+	if (error) {
+		g_warning ("couldn't lookup attributes for key: %s", error->message);
+		g_clear_error (&error);
+		return;
+	}
+	
+	/* Skip over SSH V1 keys */
+	if (gp11_attributes_find_string (attrs, CKA_LABEL, &label)) {
+		if (label && strcmp (label, V1_LABEL) == 0) {
+			gp11_attributes_unref (attrs);
+			g_free (label);
+			return;
+		}
+	}
+
+	/* Lock token objects, remove session objects */
+	if (!gp11_attributes_find_boolean (attrs, CKA_TOKEN, &token))
+		token = FALSE;
+	
+	/* Search for exactly the same attributes but with a private key class */
+	gp11_attributes_add_ulong (attrs, CKA_CLASS, CKO_PRIVATE_KEY);
+	objects = gp11_session_find_objects_full (session, attrs, NULL, &error);
+	gp11_attributes_unref (attrs);
+	
+	if (error) {
+		g_warning ("couldn't search for related key: %s", error->message);
+		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);
+	}
+
+	gp11_list_unref_free (objects);
+}
+
+static gboolean
+create_key_pair (GP11Session *session, GP11Attributes *priv, GP11Attributes *pub)
+{
+	GP11Object *priv_key, *pub_key;
+	GError *error = NULL;
+	
+	g_assert (GP11_IS_SESSION (session));
+	g_assert (priv);
+	g_assert (pub);
+	
+	priv_key = gp11_session_create_object_full (session, priv, NULL, &error);
+	if (error) {
+		g_warning ("couldn't create session private key: %s", error->message);
+		g_clear_error (&error);
+		return FALSE;
+	}
+	
+	pub_key = gp11_session_create_object_full (session, pub, NULL, &error);
+	if (error) {
+		g_warning ("couldn't create session public key: %s", error->message);
+		g_clear_error (&error);
+		
+		/* Failed, so remove private as well */
+		gp11_object_set_session (priv_key, session);
+		gp11_object_destroy (priv_key, 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 (GP11Session *session, GList *keys)
+{
+	GError *error = NULL;
+	GList *l;
+
+	g_assert (GP11_IS_SESSION (session));
+	
+	for (l = keys; l; l = g_list_next (l)) {
+		gp11_object_set_session (l->data, session);
+		if (!gp11_object_destroy (l->data, &error)) {
+			if (error->code != CKR_OBJECT_HANDLE_INVALID)
+				g_warning ("couldn't delete a SSH key we replaced: %s", error->message);
+			g_clear_error (&error);
+		}
+	}
+}
+
+static gboolean
+replace_key_pair (GP11Session *session, GP11Attributes *priv, GP11Attributes *pub)
+{
+	GList *priv_prev, *pub_prev;
+	
+	g_assert (GP11_IS_SESSION (session));
+	g_assert (priv);
+	g_assert (pub);
+
+	gp11_attributes_add_boolean (priv, CKA_TOKEN, FALSE);
+	gp11_attributes_add_boolean (pub, CKA_TOKEN, FALSE);
+	
+	/* Find the previous keys that match the same description */
+	priv_prev = find_keys_for_attributes (session, priv, CKO_PRIVATE_KEY);
+	pub_prev = find_keys_for_attributes (session, pub, CKO_PUBLIC_KEY);
+	
+	/* Now try and create the new keys */
+	if (create_key_pair (session, priv, pub)) {
+		
+		/* Delete the old keys */
+		destroy_replaced_keys (session, priv_prev);
+		destroy_replaced_keys (session, pub_prev);
+	}
+	
+	gp11_list_unref_free (priv_prev);
+	gp11_list_unref_free (pub_prev);
+		
+	return TRUE;
+}
+
+/* -----------------------------------------------------------------------------
+ * OPERATIONS
+ */
+
+static gboolean
+op_add_identity (GckSshAgentCall *call)
+{
+	GP11Attributes *pub;
+	GP11Attributes *priv;
+	GP11Session *session;
+	gchar *stype = NULL;
+	gchar *comment = NULL;
+	gboolean ret;
+	gulong algo;
+	gsize offset;
+	
+	if (!gkr_buffer_get_string (call->req, 5, &offset, &stype, (GkrBufferAllocator)g_realloc))
+		return FALSE;
+		
+	algo = gck_ssh_agent_proto_keytype_to_algo (stype);
+	if (algo == (gulong)-1) {
+		g_warning ("unsupported algorithm from SSH: %s", stype);
+		g_free (stype);
+		return FALSE;
+	}
+
+	g_free (stype);
+	priv = gp11_attributes_new_full ((GP11Allocator)gkr_secure_realloc);
+	pub = gp11_attributes_new_full (g_realloc);
+	
+	switch (algo) {
+	case CKK_RSA:
+		ret = gck_ssh_agent_proto_read_pair_rsa (call->req, &offset, priv, pub);
+		break;
+	case CKK_DSA:
+		ret = gck_ssh_agent_proto_read_pair_dsa (call->req, &offset, priv, pub);
+		break;
+	default:
+		g_assert_not_reached ();
+		return FALSE;
+	}
+	
+	if (!ret) {
+		g_warning ("couldn't read incoming SSH private key");
+		gp11_attributes_unref (pub);
+		gp11_attributes_unref (priv);
+		return FALSE;
+	}
+		
+		
+	/* TODO: Blinding? See ssh-agent.c */
+
+	/* Get the comment */
+	if (!gkr_buffer_get_string (call->req, offset, &offset, &comment, (GkrBufferAllocator)g_realloc)) {
+		gp11_attributes_unref (pub);
+		gp11_attributes_unref (priv);
+		return FALSE;
+	}
+	
+	gp11_attributes_add_string (pub, CKA_LABEL, comment);
+	gp11_attributes_add_string (priv, CKA_LABEL, comment);
+	g_free (comment);
+		
+	/* 
+	 * This is the session that owns these objects. Only 
+	 * one thread can use it at a time. 
+	 */
+	
+	session = gck_ssh_agent_checkout_main_session ();
+	g_return_val_if_fail (session, FALSE);
+	
+	ret = replace_key_pair (session, priv, pub);
+	
+	gck_ssh_agent_checkin_main_session (session);
+	
+	gp11_attributes_unref (priv);
+	gp11_attributes_unref (pub);
+	
+	gkr_buffer_add_byte (call->resp, ret ? GCK_SSH_RES_SUCCESS : GCK_SSH_RES_FAILURE);
+	return TRUE;	
+}
+
+static gboolean
+op_v1_add_identity (GckSshAgentCall *call)
+{
+	GP11Attributes *pub, *priv;
+	GP11Session *session;
+	gboolean ret;
+	gsize offset = 5;	
+	guint32 unused;
+	
+	if (!gkr_buffer_get_uint32 (call->req, offset, &offset, &unused))
+		return FALSE;
+	
+	priv = gp11_attributes_new_full ((GP11Allocator)gkr_secure_realloc);
+	pub = gp11_attributes_new_full (g_realloc);
+
+	if (!gck_ssh_agent_proto_read_pair_v1 (call->req, &offset, priv, pub)) {
+		g_warning ("couldn't read incoming SSH private key");
+		gp11_attributes_unref (pub);
+		gp11_attributes_unref (priv);
+		return FALSE;		
+	}
+	
+	gp11_attributes_add_string (priv, CKA_LABEL, V1_LABEL);
+	gp11_attributes_add_string (pub, CKA_LABEL, V1_LABEL);
+	
+	/* 
+	 * This is the session that owns these objects. Only 
+	 * one thread can use it at a time. 
+	 */
+
+	session = gck_ssh_agent_checkout_main_session ();
+	g_return_val_if_fail (session, FALSE);
+		
+	ret = replace_key_pair (session, priv, pub);
+	
+	gck_ssh_agent_checkin_main_session (session);
+	
+	gp11_attributes_unref (priv);
+	gp11_attributes_unref (pub);
+	
+	gkr_buffer_add_byte (call->resp, ret ? GCK_SSH_RES_SUCCESS : GCK_SSH_RES_FAILURE);
+	return TRUE;	
+}
+
+static gboolean
+op_request_identities (GckSshAgentCall *call)
+{
+	GList *objects, *l;
+	GList *all_attrs;
+	GError *error = NULL;
+	GP11Attributes *attrs;
+	GP11Object *pub, *priv;
+	GP11Attribute *label;
+	gsize blobpos;
+	gchar *comment;
+
+	/* Find all the private keys*/
+	/* TODO: Check SSH purpose */
+	objects = gp11_session_find_objects (call->session, &error,
+	                                     CKA_CLASS, GP11_ULONG, CKO_PUBLIC_KEY,
+	                                     GP11_INVALID);
+	if (error) {
+		g_warning ("couldn't search for private keys: %s", error->message);
+		g_clear_error (&error);
+		gkr_buffer_add_byte (call->resp, GCK_SSH_RES_FAILURE);
+		return TRUE;
+	}
+	
+	/* Find the public key, and load attributes for each public */
+	for (l = objects; l; l = g_list_next (l)) {
+		pub = l->data;
+		
+		/* For each public key we find a private key. */
+		priv = private_key_for_public (call->session, pub);
+		if (priv == NULL) 
+			continue;
+		g_object_unref (priv);
+		
+		/*
+		 * Now get attributes for that public key. Any attributes not present on 
+		 * the key will be returned as invalid.
+		 */
+		gp11_object_set_session (pub, call->session);
+		attrs = gp11_object_get (pub, &error, CKA_LABEL, CKA_KEY_TYPE, CKA_MODULUS, 
+		                         CKA_PUBLIC_EXPONENT, CKA_PRIME, CKA_SUBPRIME, CKA_BASE, 
+		                         CKA_VALUE, CKA_CLASS, CKA_MODULUS_BITS, -1);
+		
+		if (error) {
+			g_warning ("error retrieving attributes for public key: %s", error->message);
+			g_clear_error (&error);
+			continue;
+		}
+		
+		/* Dig out the label, and see if it's not v1, skip if so */
+		label = gp11_attributes_find (attrs, CKA_LABEL);
+		if (label != NULL) {
+			if (label->length == strlen (V1_LABEL) && 
+			    strncmp ((gchar*)label->value, V1_LABEL, label->length) == 0) {
+				gp11_attributes_unref (attrs);
+				continue;
+			}
+		}
+
+		/* We have this one squared away and ready to send */
+		all_attrs = g_list_prepend (all_attrs, attrs);
+	}
+	
+	gp11_list_unref_free (objects);
+	
+	gkr_buffer_add_byte (call->resp, GCK_SSH_RES_IDENTITIES_ANSWER);
+	gkr_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 (!gp11_attributes_find_string (attrs, CKA_LABEL, &comment))
+			comment = NULL;
+		
+		/* Add a space for the key blob length */		
+		blobpos = call->resp->len;
+		gkr_buffer_add_uint32 (call->resp, 0);
+
+		/* Write out the key */
+		gck_ssh_agent_proto_write_public (call->resp, attrs);
+		
+		/* Write back the blob length */
+		gkr_buffer_set_uint32 (call->resp, blobpos, (call->resp->len - blobpos) - 4);
+		
+		/* And now a per key comment */
+		gkr_buffer_add_string (call->resp, comment ? comment : "");
+		
+		g_free (comment);
+		gp11_attributes_unref (attrs);
+	}
+	
+	g_list_free (all_attrs);
+	
+	return TRUE;
+}
+
+static gboolean
+op_v1_request_identities (GckSshAgentCall *call)
+{
+	GList *objects, *l;
+	GError *error = NULL;
+	GList *all_attrs;
+	GP11Attributes *attrs;
+	GP11Object *pub, *priv;
+
+	/* Find all the keys not on token, and are V1 */
+	/* TODO: Check SSH purpose */
+	objects = gp11_session_find_objects (call->session, &error, 
+	                                     CKA_CLASS, GP11_ULONG, CKO_PUBLIC_KEY,
+	                                     CKA_TOKEN, GP11_BOOLEAN, FALSE,
+	                                     CKA_LABEL, GP11_STRING, V1_LABEL,
+	                                     GP11_INVALID);
+	if (error) {
+		g_warning ("couldn't search for public keys: %s", error->message);
+		g_clear_error (&error);
+		gkr_buffer_add_byte (call->resp, GCK_SSH_RES_FAILURE);
+		return TRUE;
+	}
+	
+	/* Find the public key, and load attributes for each public */
+	for (l = objects; l; l = g_list_next (l)) {
+		pub = l->data;
+		
+		/* For each public key we find a private key. */
+		priv = private_key_for_public (call->session, pub);
+		if (priv == NULL) 
+			continue;
+		g_object_unref (priv);
+		
+		/*
+		 * Now get attributes for that public key. Any attributes not present on 
+		 * the key will be returned as invalid.
+		 */
+		gp11_object_set_session (pub, call->session);
+		attrs = gp11_object_get (pub, &error, CKA_KEY_TYPE, CKA_MODULUS, 
+		                         CKA_PUBLIC_EXPONENT, CKA_MODULUS_BITS, CKA_CLASS, -1);
+		
+		if (error) {
+			g_warning ("error retrieving attributes for public key: %s", error->message);
+			g_clear_error (&error);
+			continue;
+		}
+
+		/* We have this one squared away and ready to send */
+		all_attrs = g_list_prepend (all_attrs, attrs);
+	}
+	
+	gp11_list_unref_free (objects);
+	
+	gkr_buffer_add_byte (call->resp, GCK_SSH_RES_RSA_IDENTITIES_ANSWER);
+	gkr_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 */
+		gck_ssh_agent_proto_write_public_v1 (call->resp, attrs);
+	
+		/* And now a per key comment */
+		gkr_buffer_add_string (call->resp, "Public Key");
+		
+		gp11_attributes_unref (attrs);
+	}
+	
+	g_list_free (all_attrs);
+	
+	return TRUE;
+}
+
+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_MD5) {
+		asn = MD5_ASN;
+		n_asn = sizeof (MD5_ASN);
+	}
+	
+	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 gboolean 
+op_sign_request (GckSshAgentCall *call)
+{
+	GP11Attributes *attrs;
+	GError *error = NULL;
+	const guchar *data;
+	const gchar *salgo;
+	GP11Object *key;
+	guchar *result;
+	gsize n_data, n_result;
+	guint32 flags;
+	gsize offset;
+	gboolean ret;
+	guint blobpos, sz;
+	guint8 *hash;
+	gulong algo, mech;
+	GChecksumType halgo;
+	gsize n_hash;
+	
+	offset = 5;
+	
+	/* The key packet size */
+	if (!gkr_buffer_get_uint32 (call->req, offset, &offset, &sz))
+		return FALSE;
+
+	/* The key itself */
+	attrs = gp11_attributes_new ();
+	if (!gck_ssh_agent_proto_read_public (call->req, &offset, attrs, &algo))
+		return FALSE;
+	
+	/* Validate the key type / mechanism */
+	if (algo == CKK_RSA)
+		mech = CKM_RSA_PKCS;
+	else if (algo == CKK_DSA)
+		mech = CKM_DSA;
+	else
+		g_return_val_if_reached (FALSE);
+
+	if (!gkr_buffer_get_byte_array (call->req, offset, &offset, &data, &n_data) ||
+	    !gkr_buffer_get_uint32 (call->req, offset, &offset, &flags)) {
+		gp11_attributes_unref (attrs);
+	    	return FALSE;
+	}
+
+	/* Lookup the key */
+	key = private_key_for_attributes (call->session, attrs);
+	gp11_attributes_unref (attrs);
+	
+	if (!key) {
+		gkr_buffer_add_byte (call->resp, GCK_SSH_RES_FAILURE);
+		return TRUE;
+	}
+	
+	/* Usually we hash the data with SHA1 */
+	if (flags & GCK_SSH_FLAG_OLD_SIGNATURE)
+		halgo = G_CHECKSUM_MD5;
+	else
+		halgo = G_CHECKSUM_SHA1;
+	
+	/* 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);
+	
+	/* Do the magic */
+	result = gp11_session_sign (call->session, key, mech, hash, n_hash, &n_result, &error);
+	g_object_unref (key);
+	g_free (hash);
+	
+	if (error) {
+		g_warning ("signing of the data failed: %s", error->message);
+		g_clear_error (&error);
+		gkr_buffer_add_byte (call->resp, GCK_SSH_RES_FAILURE);
+		return TRUE;
+	}
+	
+	gkr_buffer_add_byte (call->resp, GCK_SSH_RES_SIGN_RESPONSE);
+	
+	/* Add a space for the sig blob length */		
+	blobpos = call->resp->len;
+	gkr_buffer_add_uint32 (call->resp, 0);
+	
+	salgo = gck_ssh_agent_proto_algo_to_keytype (algo);
+	g_assert (salgo);
+	gkr_buffer_add_string (call->resp, salgo);
+
+	switch (algo) {
+	case CKK_RSA:
+		ret = gck_ssh_agent_proto_write_signature_rsa (call->resp, result, n_result);
+		break;
+
+	case CKK_DSA:
+		ret = gck_ssh_agent_proto_write_signature_dsa (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 */
+	gkr_buffer_set_uint32 (call->resp, blobpos, (call->resp->len - blobpos) - 4);
+	
+	return TRUE; 
+}
+
+static gboolean 
+op_v1_challenge (GckSshAgentCall *call)
+{
+	gsize offset, n_data, n_result, n_hash;
+	GP11Attributes *attrs;
+	guchar session_id[16];
+	guint8 hash[16];
+	const guchar *data;
+	guchar *result = NULL;
+	GChecksum *checksum;
+	GP11Object *key = NULL;
+	guint32 resp_type;
+	GError *error = NULL;
+	gboolean ret;
+	guint i;
+	guchar b;
+	
+	ret = FALSE;
+	offset = 5;
+	
+	attrs = gp11_attributes_new ();
+	if (!gck_ssh_agent_proto_read_public_v1 (call->req, &offset, attrs)) {
+		gp11_attributes_unref (attrs);
+		return FALSE;
+	}
+	
+	/* Read the entire challenge */
+	data = gck_ssh_agent_proto_read_challenge_v1 (call->req, &offset, &n_data);
+	
+	/* Only protocol 1.1 is supported */
+	if (call->req->len <= offset) {
+		gp11_attributes_unref (attrs);
+		gkr_buffer_add_byte (call->resp, GCK_SSH_RES_FAILURE);
+		return TRUE;
+	}
+		
+	/* Read out the session id, raw, unbounded */
+	for (i = 0; i < 16; ++i) {
+		gkr_buffer_get_byte (call->req, offset, &offset, &b);
+		session_id[i] = b;
+	}
+		
+	/* And the response type */
+	gkr_buffer_get_uint32 (call->req, offset, &offset, &resp_type);
+	
+	/* Did parsing fail? */
+	if (gkr_buffer_has_error (call->req) || data == NULL) {
+		gp11_attributes_unref (attrs);
+		return FALSE;
+	}
+	
+	/* Not supported request type */
+	if (resp_type != 1) {
+		gp11_attributes_unref (attrs);
+		gkr_buffer_add_byte (call->resp, GCK_SSH_RES_FAILURE);
+		return TRUE;
+	}
+	
+	/* Lookup the key */
+	key = private_key_for_attributes (call->session, attrs);
+	gp11_attributes_unref (attrs);
+	
+	/* Didn't find a key? */
+	if (key == NULL) {
+		gkr_buffer_add_byte (call->resp, GCK_SSH_RES_FAILURE);
+		return TRUE;
+	}
+
+	result = gp11_session_decrypt (call->session, key, CKM_RSA_PKCS, data, n_data, &n_result, &error);
+	g_object_unref (key);
+	
+	if (error) {
+		g_warning ("decryption of the data failed: %s", error->message);
+		g_clear_error (&error);
+		gkr_buffer_add_byte (call->resp, GCK_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);
+	
+	gkr_buffer_add_byte (call->resp, GCK_SSH_RES_RSA_RESPONSE);
+	gkr_buffer_append (call->resp, hash, n_hash);
+	
+	g_free (result);
+	return TRUE;
+}
+
+static gboolean 
+op_remove_identity (GckSshAgentCall *call)
+{
+	GP11Attributes *attrs;
+	GP11Session *session;
+	GP11Object *key;
+	gsize offset;
+	guint sz;
+	
+	offset = 5;
+	
+	/* The key packet size */
+	if (!gkr_buffer_get_uint32 (call->req, offset, &offset, &sz))
+		return FALSE;
+
+	/* The public key itself */
+	attrs = gp11_attributes_new ();
+	if (!gck_ssh_agent_proto_read_public (call->req, &offset, attrs, NULL)) {
+		gp11_attributes_unref (attrs);
+		return FALSE;
+	}
+	
+	key = public_key_for_attributes (call->session, attrs);
+	gp11_attributes_unref (attrs);
+	
+	if (key != NULL) { 
+		
+		/* 
+		 * This is the session that owns these objects. Only 
+		 * one thread can use it at a time. 
+		 */
+		
+		session = gck_ssh_agent_checkout_main_session ();
+		g_return_val_if_fail (session, FALSE);
+		
+		remove_or_lock_by_public_key (session, key);
+		
+		gck_ssh_agent_checkin_main_session (session);
+
+		g_object_unref (key);
+	}
+
+	gkr_buffer_add_byte (call->resp, GCK_SSH_RES_SUCCESS);
+
+	return TRUE;	
+}
+
+static gboolean 
+op_v1_remove_identity (GckSshAgentCall *call)
+{
+	GP11Session *session;
+	GP11Attributes *attrs;
+	GP11Object *key;
+	gsize offset;
+	
+	offset = 5;
+	
+	attrs = gp11_attributes_new ();
+	if (!gck_ssh_agent_proto_read_public_v1 (call->req, &offset, attrs)) {
+		gp11_attributes_unref (attrs);		
+		return FALSE;
+	}
+	
+	key = public_key_for_attributes (call->session, attrs);
+	gp11_attributes_unref (attrs);
+	
+	/* 
+	 * This is the session that owns these objects. Only 
+	 * one thread can use it at a time. 
+	 */
+	
+	if (key != NULL) { 
+		
+		/* 
+		 * This is the session that owns these objects. Only 
+		 * one thread can use it at a time. 
+		 */
+		
+		session = gck_ssh_agent_checkout_main_session ();
+		g_return_val_if_fail (session, FALSE);
+		
+		remove_by_public_key (session, key);
+		
+		gck_ssh_agent_checkin_main_session (session);
+		
+		g_object_unref (key);
+	}
+	
+	gkr_buffer_add_byte (call->resp, GCK_SSH_RES_SUCCESS);
+	return TRUE;	
+}
+
+static gboolean 
+op_remove_all_identities (GckSshAgentCall *call)
+{
+	GP11Session *session;
+	GList *objects, *l;
+	GError *error = NULL;
+	
+	/* Find all session SSH public keys */
+	objects = gp11_session_find_objects (call->session, &error,
+	                                     CKA_CLASS, GP11_ULONG, CKO_PUBLIC_KEY,
+	                                     GP11_INVALID);
+
+
+	/* 
+	 * This is the session that owns these objects. Only 
+	 * one thread can use it at a time. 
+	 */
+	
+	session = gck_ssh_agent_checkout_main_session ();
+	g_return_val_if_fail (session, FALSE);
+
+	for (l = objects; l; l = g_list_next (l)) 
+		remove_or_lock_by_public_key (session, l->data);
+
+	gck_ssh_agent_checkin_main_session (session);
+	
+	gp11_list_unref_free (objects);
+	
+	gkr_buffer_add_byte (call->resp, GCK_SSH_RES_SUCCESS);
+	return TRUE;
+}
+
+static gboolean 	
+op_v1_remove_all_identities (GckSshAgentCall *call)
+{
+	GP11Session *session;
+	GList *objects, *l;
+	GError *error = NULL;
+	
+	/* Find all session SSH v1 public keys */
+	objects = gp11_session_find_objects (call->session, &error,
+	                                     CKA_TOKEN, GP11_BOOLEAN, FALSE,
+	                                     CKA_CLASS, GP11_ULONG, CKO_PUBLIC_KEY,
+	                                     CKA_LABEL, GP11_STRING, V1_LABEL,
+	                                     GP11_INVALID);
+
+	/* 
+	 * This is the session that owns these objects. Only 
+	 * one thread can use it at a time. 
+	 */
+	
+	session = gck_ssh_agent_checkout_main_session ();
+	g_return_val_if_fail (session, FALSE);
+
+	for (l = objects; l; l = g_list_next (l)) 
+		remove_by_public_key (session, l->data);
+
+	gck_ssh_agent_checkin_main_session (session);
+		
+	gp11_list_unref_free (objects);
+	
+	gkr_buffer_add_byte (call->resp, GCK_SSH_RES_SUCCESS);
+	return TRUE;
+}
+
+static gboolean 
+op_not_implemented_success (GckSshAgentCall *call)
+{
+	gkr_buffer_add_byte (call->resp, GCK_SSH_RES_SUCCESS);
+	return TRUE;
+}
+	
+static gboolean
+op_not_implemented_failure (GckSshAgentCall *call)
+{
+	gkr_buffer_add_byte (call->resp, GCK_SSH_RES_FAILURE);
+	return TRUE;
+}
+
+static gboolean
+op_invalid (GckSshAgentCall *call)
+{
+	/* Invalid request, disconnect immediately */
+	return FALSE;
+}
+
+const GckSshAgentOperation gck_ssh_agent_operations[GCK_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_not_implemented_failure,                 /* GKR_SSH_OP_ADD_ID_CONSTRAINED */
+     op_not_implemented_failure,                 /* GKR_SSH_OP_ADD_SMARTCARD_KEY_CONSTRAINED */
+};

Added: trunk/pkcs11/ssh-agent/gck-ssh-agent-private.h
==============================================================================
--- (empty file)
+++ trunk/pkcs11/ssh-agent/gck-ssh-agent-private.h	Sat Jan  3 23:00:21 2009
@@ -0,0 +1,178 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gkr-ssh-agent-private.h - Private SSH agent declarations
+
+   Copyright (C) 2007 Stefan Walter
+
+   Gnome keyring is free software; you can redistribute it and/or
+   modify it under the terms of the GNU General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+  
+   Gnome keyring is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   General Public License for more details.
+  
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+
+   Author: Stef Walter <stef memberwebs com>
+*/
+
+#ifndef GKRSSHPRIVATE_H_
+#define GKRSSHPRIVATE_H_
+
+#include "common/gkr-buffer.h"
+
+#include "pkcs11/pkcs11.h"
+
+#include <gp11/gp11.h>
+
+#include <glib.h>
+
+typedef struct _GckSshAgentCall {
+	int sock;
+	GP11Session *session;
+	GkrBuffer *req;
+	GkrBuffer *resp;
+} GckSshAgentCall;
+
+/* -----------------------------------------------------------------------------
+ * SSH OPERATIONS and CONSTANTS
+ */
+
+/* Requests from client to daemon */
+#define GCK_SSH_OP_REQUEST_RSA_IDENTITIES		1
+#define GCK_SSH_OP_RSA_CHALLENGE			3
+#define GCK_SSH_OP_ADD_RSA_IDENTITY			7
+#define GCK_SSH_OP_REMOVE_RSA_IDENTITY			8
+#define GCK_SSH_OP_REMOVE_ALL_RSA_IDENTITIES		9
+#define GCK_SSH_OP_REQUEST_IDENTITIES			11
+#define GCK_SSH_OP_SIGN_REQUEST				13
+#define GCK_SSH_OP_ADD_IDENTITY				17
+#define GCK_SSH_OP_REMOVE_IDENTITY			18
+#define GCK_SSH_OP_REMOVE_ALL_IDENTITIES		19
+#define GCK_SSH_OP_ADD_SMARTCARD_KEY			20
+#define GCK_SSH_OP_REMOVE_SMARTCARD_KEY			21
+#define GCK_SSH_OP_LOCK					22
+#define GCK_SSH_OP_UNLOCK				23
+#define GCK_SSH_OP_ADD_RSA_ID_CONSTRAINED		24
+#define GCK_SSH_OP_ADD_ID_CONSTRAINED			25
+#define GCK_SSH_OP_ADD_SMARTCARD_KEY_CONSTRAINED 	26
+
+#define GCK_SSH_OP_MAX                                  27  
+
+/* Responses from daemon to client */
+#define GCK_SSH_RES_RSA_IDENTITIES_ANSWER		2
+#define GCK_SSH_RES_RSA_RESPONSE			4
+#define GCK_SSH_RES_FAILURE				5
+#define GCK_SSH_RES_SUCCESS				6
+#define GCK_SSH_RES_IDENTITIES_ANSWER			12
+#define GCK_SSH_RES_SIGN_RESPONSE			14
+#define GCK_SSH_RES_EXTENDED_FAILURE			30
+#define GCK_SSH_RES_SSHCOM_FAILURE			102
+
+
+#define	GCK_SSH_FLAG_CONSTRAIN_LIFETIME			1
+#define	GCK_SSH_FLAG_CONSTRAIN_CONFIRM			2
+
+#define GCK_SSH_DSA_SIGNATURE_PADDING                   20
+#define	GCK_SSH_FLAG_OLD_SIGNATURE			0x01
+
+/* -----------------------------------------------------------------------------
+ * gck-ssh-agent-ops.c
+ */
+
+typedef gboolean (*GckSshAgentOperation) (GckSshAgentCall *call);
+extern const GckSshAgentOperation gck_ssh_agent_operations[GCK_SSH_OP_MAX];
+
+/* -----------------------------------------------------------------------------
+ * gck-ssh-agent.c
+ */
+
+GP11Session*          gck_ssh_agent_checkout_main_session           (void);
+
+void                  gck_ssh_agent_checkin_main_session            (GP11Session* session);
+
+/* -----------------------------------------------------------------------------
+ * gkr-ssh-proto.c
+ */
+
+gulong                gck_ssh_agent_proto_keytype_to_algo           (const gchar *salgo);
+
+const gchar*          gck_ssh_agent_proto_algo_to_keytype           (gulong algo);
+
+gboolean              gck_ssh_agent_proto_read_mpi                  (GkrBuffer *req, 
+                                                                     gsize *offset, 
+                                                                     GP11Attributes *attrs, 
+                                                                     CK_ATTRIBUTE_TYPE type);
+
+gboolean              gck_ssh_agent_proto_read_mpi_v1               (GkrBuffer *req, 
+                                                                     gsize *offset, 
+                                                                     GP11Attributes *attrs,
+                                                                     CK_ATTRIBUTE_TYPE type);
+
+const guchar*         gck_ssh_agent_proto_read_challenge_v1         (GkrBuffer *req,
+                                                                     gsize *offset,
+                                                                     gsize *n_challenge);
+                                                                     
+gboolean              gck_ssh_agent_proto_write_mpi                 (GkrBuffer *resp, 
+                                                                     GP11Attribute *attr);
+
+gboolean              gck_ssh_agent_proto_write_mpi_v1              (GkrBuffer *resp, 
+                                                                     GP11Attribute *attr);
+
+gboolean              gck_ssh_agent_proto_read_public               (GkrBuffer *req, 
+                                                                     gsize *offset, 
+                                                                     GP11Attributes *attrs, 
+                                                                     gulong *algo);
+
+gboolean              gck_ssh_agent_proto_read_public_rsa           (GkrBuffer *req, 
+                                                                     gsize *offset, 
+                                                                     GP11Attributes *attrs);
+
+gboolean              gck_ssh_agent_proto_read_public_dsa           (GkrBuffer *req, 
+                                                                     gsize *offset, 
+                                                                     GP11Attributes *attrs);
+
+gboolean              gck_ssh_agent_proto_read_public_v1            (GkrBuffer *req, 
+                                                                     gsize *offset, 
+                                                                     GP11Attributes *attrs);
+
+gboolean              gck_ssh_agent_proto_read_pair_rsa             (GkrBuffer *req, 
+                                                                     gsize *offset, 
+                                                                     GP11Attributes *priv_attrs,
+                                                                     GP11Attributes *pub_attrs);
+
+gboolean              gck_ssh_agent_proto_read_pair_dsa             (GkrBuffer *req, 
+                                                                     gsize *offset, 
+                                                                     GP11Attributes *priv_attrs,
+                                                                     GP11Attributes *pub_attrs);
+
+gboolean              gck_ssh_agent_proto_read_pair_v1              (GkrBuffer *req,
+                                                                     gsize *offset, 
+                                                                     GP11Attributes *priv_attrs,
+                                                                     GP11Attributes *pub_attrs);
+
+gboolean              gck_ssh_agent_proto_write_public              (GkrBuffer *resp, 
+                                                                     GP11Attributes *attrs);
+
+gboolean              gck_ssh_agent_proto_write_public_rsa          (GkrBuffer *resp, 
+                                                                     GP11Attributes *attrs);
+
+gboolean              gck_ssh_agent_proto_write_public_dsa          (GkrBuffer *resp, 
+                                                                     GP11Attributes *attrs);
+
+gboolean              gck_ssh_agent_proto_write_public_v1           (GkrBuffer *resp, 
+                                                                     GP11Attributes *attrs);
+
+gboolean              gck_ssh_agent_proto_write_signature_rsa       (GkrBuffer *resp, 
+                                                                     CK_BYTE_PTR signature, 
+                                                                     CK_ULONG n_signature);
+
+gboolean              gck_ssh_agent_proto_write_signature_dsa       (GkrBuffer *resp, 
+                                                                     CK_BYTE_PTR signature, 
+                                                                     CK_ULONG n_signature);
+
+#endif /*GKRSSHPRIVATE_H_*/

Added: trunk/pkcs11/ssh-agent/gck-ssh-agent-proto.c
==============================================================================
--- (empty file)
+++ trunk/pkcs11/ssh-agent/gck-ssh-agent-proto.c	Sat Jan  3 23:00:21 2009
@@ -0,0 +1,513 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gck-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 "gck-ssh-agent-private.h"
+
+#include "common/gkr-buffer.h"
+
+#include <gp11/gp11.h>
+
+#include <glib.h>
+
+#include <string.h>
+
+gulong
+gck_ssh_agent_proto_keytype_to_algo (const gchar *salgo)
+{
+	g_return_val_if_fail (salgo, (gulong)-1);
+	if (strcmp (salgo, "ssh-rsa") == 0)
+		return CKK_RSA;
+	else if (strcmp (salgo, "ssh-dss") == 0)
+		return CKK_DSA;
+	return (gulong)-1;
+}
+
+const gchar*
+gck_ssh_agent_proto_algo_to_keytype (gulong algo)
+{
+	if (algo == CKK_RSA)
+		return "ssh-rsa";
+	else if (algo == CKK_DSA)
+		return "ssh-dss";
+	return NULL;	
+}
+
+gboolean
+gck_ssh_agent_proto_read_mpi (GkrBuffer *req, gsize *offset, GP11Attributes *attrs, 
+                              CK_ATTRIBUTE_TYPE type)
+{
+	const guchar *data;
+	gsize len;
+	
+	if (!gkr_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;
+	}
+
+	gp11_attributes_add_data (attrs, type, data, len);
+	return TRUE;
+}
+
+gboolean
+gck_ssh_agent_proto_read_mpi_v1 (GkrBuffer *req, gsize *offset, GP11Attributes *attrs, 
+                                 CK_ATTRIBUTE_TYPE type)
+{
+	const guchar *data;
+	gsize bytes;
+	guint16 bits;
+	
+	/* Get the number of bits */
+	if (!gkr_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;
+	
+	gp11_attributes_add_data (attrs, type, data, bytes);
+	return TRUE;
+}
+
+gboolean
+gck_ssh_agent_proto_write_mpi (GkrBuffer *resp, GP11Attribute *attr)
+{
+	guchar *data;
+	gsize n_extra;
+	
+	g_assert (resp);
+	g_assert (attr);
+	
+	/* Convert from unsigned format */
+	n_extra = 0;
+	if (attr->length && (attr->value[0] & 0x80)) 
+		++n_extra;
+
+	data = gkr_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
+gck_ssh_agent_proto_write_mpi_v1 (GkrBuffer *resp, GP11Attribute *attr)
+{
+	guchar *data;
+	
+	g_return_val_if_fail (attr->length * 8 < G_MAXUSHORT, FALSE);
+	
+	if (!gkr_buffer_add_uint16 (resp, attr->length * 8))
+		return FALSE;
+
+	data = gkr_buffer_add_empty (resp, attr->length);
+	if (data == NULL)
+		return FALSE;
+	memcpy (data, attr->value, attr->length);
+	return TRUE;
+}
+
+const guchar*
+gck_ssh_agent_proto_read_challenge_v1 (GkrBuffer *req, gsize *offset, gsize *n_challenge)
+{
+	const guchar *data;
+	gsize bytes;
+	guint16 bits;
+	
+	/* Get the number of bits */
+	if (!gkr_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
+gck_ssh_agent_proto_read_public (GkrBuffer *req, gsize *offset, GP11Attributes* attrs, gulong *algo)
+{
+	gboolean ret;
+	gchar *stype;
+	gulong alg;
+	
+	g_assert (req);
+	g_assert (offset);
+	
+	/* The string algorithm */
+	if (!gkr_buffer_get_string (req, *offset, offset, &stype, (GkrBufferAllocator)g_realloc))
+		return FALSE;
+	
+	alg = gck_ssh_agent_proto_keytype_to_algo (stype);
+	if (alg == (gulong)-1) {
+		g_warning ("unsupported algorithm from SSH: %s", stype);
+		g_free (stype);
+		return FALSE;
+	}
+	
+	g_free (stype);
+	switch (alg) {
+	case CKK_RSA:
+		ret = gck_ssh_agent_proto_read_public_rsa (req, offset, attrs);
+		break;
+	case CKK_DSA:
+		ret = gck_ssh_agent_proto_read_public_dsa (req, offset, attrs);
+		break;
+	default:
+		g_assert_not_reached ();
+		return FALSE;
+	}
+	
+	if (!ret) {
+		g_warning ("couldn't read incoming SSH private key");
+		return FALSE;
+	}
+	
+	if (algo)
+		*algo = alg;
+	return ret;
+}
+
+gboolean
+gck_ssh_agent_proto_read_pair_rsa (GkrBuffer *req, gsize *offset, 
+                                   GP11Attributes *priv_attrs, GP11Attributes *pub_attrs)
+{
+	GP11Attribute *attr;
+	
+	g_assert (req);
+	g_assert (offset);
+	g_assert (priv_attrs);
+	g_assert (pub_attrs);
+	
+	if (!gck_ssh_agent_proto_read_mpi (req, offset, priv_attrs, CKA_MODULUS) ||
+	    !gck_ssh_agent_proto_read_mpi (req, offset, priv_attrs, CKA_PUBLIC_EXPONENT) ||
+	    !gck_ssh_agent_proto_read_mpi (req, offset, priv_attrs, CKA_PRIVATE_EXPONENT) ||
+	    !gck_ssh_agent_proto_read_mpi (req, offset, priv_attrs, CKA_COEFFICIENT) ||
+	    !gck_ssh_agent_proto_read_mpi (req, offset, priv_attrs, CKA_PRIME_1) ||
+	    !gck_ssh_agent_proto_read_mpi (req, offset, priv_attrs, CKA_PRIME_2))
+		return FALSE;
+
+	/* Copy attributes to the public key */
+	attr = gp11_attributes_find (priv_attrs, CKA_MODULUS);
+	gp11_attributes_add (pub_attrs, attr);
+	attr = gp11_attributes_find (priv_attrs, CKA_PUBLIC_EXPONENT);
+	gp11_attributes_add (pub_attrs, attr);
+	
+	/* Add in your basic other required attributes */
+	gp11_attributes_add_ulong (priv_attrs, CKA_CLASS, CKO_PRIVATE_KEY);
+	gp11_attributes_add_ulong (priv_attrs, CKA_KEY_TYPE, CKK_RSA);
+	gp11_attributes_add_ulong (pub_attrs, CKA_CLASS, CKO_PUBLIC_KEY);
+	gp11_attributes_add_ulong (pub_attrs, CKA_KEY_TYPE, CKK_RSA);
+
+	return TRUE;
+}
+
+gboolean
+gck_ssh_agent_proto_read_pair_v1 (GkrBuffer *req, gsize *offset, 
+                                  GP11Attributes *priv_attrs, GP11Attributes *pub_attrs)
+{
+	GP11Attribute *attr;
+	
+	g_assert (req);
+	g_assert (offset);
+	g_assert (priv_attrs);
+	g_assert (pub_attrs);
+	
+	if (!gck_ssh_agent_proto_read_mpi_v1 (req, offset, priv_attrs, CKA_MODULUS) ||
+	    !gck_ssh_agent_proto_read_mpi_v1 (req, offset, priv_attrs, CKA_PUBLIC_EXPONENT) ||
+	    !gck_ssh_agent_proto_read_mpi_v1 (req, offset, priv_attrs, CKA_PRIVATE_EXPONENT) ||
+	    !gck_ssh_agent_proto_read_mpi_v1 (req, offset, priv_attrs, CKA_COEFFICIENT) ||
+	    !gck_ssh_agent_proto_read_mpi_v1 (req, offset, priv_attrs, CKA_PRIME_1) ||
+	    !gck_ssh_agent_proto_read_mpi_v1 (req, offset, priv_attrs, CKA_PRIME_2)) 
+	    	return FALSE;
+	
+	/* Copy attributes to the public key */
+	attr = gp11_attributes_find (priv_attrs, CKA_MODULUS);
+	gp11_attributes_add (pub_attrs, attr);
+	attr = gp11_attributes_find (priv_attrs, CKA_PUBLIC_EXPONENT);
+	gp11_attributes_add (pub_attrs, attr);
+	
+	/* Add in your basic other required attributes */
+	gp11_attributes_add_ulong (priv_attrs, CKA_CLASS, CKO_PRIVATE_KEY);
+	gp11_attributes_add_ulong (priv_attrs, CKA_KEY_TYPE, CKK_RSA);
+	gp11_attributes_add_ulong (pub_attrs, CKA_CLASS, CKO_PUBLIC_KEY);
+	gp11_attributes_add_ulong (pub_attrs, CKA_KEY_TYPE, CKK_RSA);
+		
+	return TRUE;
+}
+
+gboolean
+gck_ssh_agent_proto_read_public_rsa (GkrBuffer *req, gsize *offset, GP11Attributes *attrs)
+{
+	g_assert (req);
+	g_assert (offset);
+	g_assert (attrs);
+	
+	if (!gck_ssh_agent_proto_read_mpi (req, offset, attrs, CKA_PUBLIC_EXPONENT) ||
+	    !gck_ssh_agent_proto_read_mpi (req, offset, attrs, CKA_MODULUS))
+		return FALSE;
+
+	/* Add in your basic other required attributes */
+	gp11_attributes_add_ulong (attrs, CKA_CLASS, CKO_PUBLIC_KEY);
+	gp11_attributes_add_ulong (attrs, CKA_KEY_TYPE, CKK_RSA);
+		
+	return TRUE;
+}
+
+gboolean
+gck_ssh_agent_proto_read_public_v1 (GkrBuffer *req, gsize *offset, GP11Attributes *attrs)
+{
+	guint32 bits;
+	
+	g_assert (req);
+	g_assert (offset);
+	g_assert (attrs);
+
+	if (!gkr_buffer_get_uint32 (req, *offset, offset, &bits))
+		return FALSE;
+	
+	if (!gck_ssh_agent_proto_read_mpi_v1 (req, offset, attrs, CKA_PUBLIC_EXPONENT) ||
+	    !gck_ssh_agent_proto_read_mpi_v1 (req, offset, attrs, CKA_MODULUS))
+		return FALSE;
+
+	/* Add in your basic other required attributes */
+	gp11_attributes_add_ulong (attrs, CKA_CLASS, CKO_PUBLIC_KEY);
+	gp11_attributes_add_ulong (attrs, CKA_KEY_TYPE, CKK_RSA);
+		
+	return TRUE;
+}
+
+gboolean
+gck_ssh_agent_proto_read_pair_dsa (GkrBuffer *req, gsize *offset, 
+                                   GP11Attributes *priv_attrs, GP11Attributes *pub_attrs)
+{
+	GP11Attribute *attr;
+	
+	g_assert (req);
+	g_assert (offset);
+	g_assert (priv_attrs);
+	g_assert (pub_attrs);
+	
+	if (!gck_ssh_agent_proto_read_mpi (req, offset, priv_attrs, CKA_PRIME) ||
+	    !gck_ssh_agent_proto_read_mpi (req, offset, priv_attrs, CKA_SUBPRIME) ||
+	    !gck_ssh_agent_proto_read_mpi (req, offset, priv_attrs, CKA_BASE) ||
+	    !gck_ssh_agent_proto_read_mpi (req, offset, pub_attrs, CKA_VALUE) ||
+	    !gck_ssh_agent_proto_read_mpi (req, offset, priv_attrs, CKA_VALUE))
+	    	return FALSE;
+	
+	/* Copy attributes to the public key */
+	attr = gp11_attributes_find (priv_attrs, CKA_PRIME);
+	gp11_attributes_add (pub_attrs, attr);
+	attr = gp11_attributes_find (priv_attrs, CKA_SUBPRIME);
+	gp11_attributes_add (pub_attrs, attr);
+	attr = gp11_attributes_find (priv_attrs, CKA_BASE);
+	gp11_attributes_add (pub_attrs, attr);
+	
+	/* Add in your basic other required attributes */
+	gp11_attributes_add_ulong (priv_attrs, CKA_CLASS, CKO_PRIVATE_KEY);
+	gp11_attributes_add_ulong (priv_attrs, CKA_KEY_TYPE, CKK_DSA);
+	gp11_attributes_add_ulong (pub_attrs, CKA_CLASS, CKO_PUBLIC_KEY);
+	gp11_attributes_add_ulong (pub_attrs, CKA_KEY_TYPE, CKK_DSA);
+
+	return TRUE;
+}
+
+gboolean
+gck_ssh_agent_proto_read_public_dsa (GkrBuffer *req, gsize *offset, GP11Attributes *attrs)
+{
+	g_assert (req);
+	g_assert (offset);
+	g_assert (attrs);
+	
+	if (!gck_ssh_agent_proto_read_mpi (req, offset, attrs, CKA_PRIME) ||
+	    !gck_ssh_agent_proto_read_mpi (req, offset, attrs, CKA_SUBPRIME) ||
+	    !gck_ssh_agent_proto_read_mpi (req, offset, attrs, CKA_BASE) ||
+	    !gck_ssh_agent_proto_read_mpi (req, offset, attrs, CKA_VALUE))
+	    	return FALSE;
+
+	/* Add in your basic other required attributes */
+	gp11_attributes_add_ulong (attrs, CKA_CLASS, CKO_PUBLIC_KEY);
+	gp11_attributes_add_ulong (attrs, CKA_KEY_TYPE, CKK_DSA);
+	
+	return TRUE;
+}
+
+gboolean
+gck_ssh_agent_proto_write_public (GkrBuffer *resp, GP11Attributes *attrs)
+{
+	gboolean ret = FALSE;
+	const gchar *salgo;
+	gulong algo;
+	
+	g_assert (resp);
+	g_assert (attrs);
+	
+	if (!gp11_attributes_find_ulong (attrs, CKA_KEY_TYPE, &algo))
+		g_return_val_if_reached (FALSE);
+	
+	salgo = gck_ssh_agent_proto_algo_to_keytype (algo);
+	g_assert (salgo);
+	gkr_buffer_add_string (resp, salgo);
+		
+	switch (algo) {
+	case CKK_RSA:
+		ret = gck_ssh_agent_proto_write_public_rsa (resp, attrs);
+		break;
+			
+	case CKK_DSA:
+		ret = gck_ssh_agent_proto_write_public_dsa (resp, attrs);
+		break;
+		
+	default:
+		g_return_val_if_reached (FALSE);
+		break;
+	}
+
+	return ret;
+}
+
+gboolean
+gck_ssh_agent_proto_write_public_rsa (GkrBuffer *resp, GP11Attributes *attrs)
+{
+	GP11Attribute *attr;
+	
+	g_assert (resp);
+	g_assert (attrs);
+	
+	attr = gp11_attributes_find (attrs, CKA_PUBLIC_EXPONENT);
+	g_return_val_if_fail (attr, FALSE);
+
+	if (!gck_ssh_agent_proto_write_mpi (resp, attr))
+		return FALSE;
+
+	attr = gp11_attributes_find (attrs, CKA_MODULUS);
+	g_return_val_if_fail (attr, FALSE);
+
+	if (!gck_ssh_agent_proto_write_mpi (resp, attr))
+		return FALSE;
+
+	return TRUE;
+}
+
+gboolean
+gck_ssh_agent_proto_write_public_dsa (GkrBuffer *resp, GP11Attributes *attrs)
+{
+	GP11Attribute *attr;
+	
+	g_assert (resp);
+	g_assert (attrs);
+	
+	attr = gp11_attributes_find (attrs, CKA_PRIME);
+	g_return_val_if_fail (attr, FALSE);
+
+	if (!gck_ssh_agent_proto_write_mpi (resp, attr))
+		return FALSE;
+
+	attr = gp11_attributes_find (attrs, CKA_SUBPRIME);
+	g_return_val_if_fail (attr, FALSE);
+
+	if (!gck_ssh_agent_proto_write_mpi (resp, attr))
+		return FALSE;
+
+	attr = gp11_attributes_find (attrs, CKA_BASE);
+	g_return_val_if_fail (attr, FALSE);
+
+	if (!gck_ssh_agent_proto_write_mpi (resp, attr))
+		return FALSE;
+
+	attr = gp11_attributes_find (attrs, CKA_VALUE);
+	g_return_val_if_fail (attr, FALSE);
+
+	if (!gck_ssh_agent_proto_write_mpi (resp, attr))
+		return FALSE;
+
+	return TRUE;
+}
+
+gboolean
+gck_ssh_agent_proto_write_public_v1 (GkrBuffer *resp, GP11Attributes *attrs)
+{
+	GP11Attribute *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 (!gp11_attributes_find_ulong (attrs, CKA_MODULUS_BITS, &bits))
+		g_return_val_if_reached (FALSE);
+	gkr_buffer_add_uint32 (resp, bits);
+
+	/* Write out the exponent */
+	attr = gp11_attributes_find (attrs, CKA_PUBLIC_EXPONENT);
+	g_return_val_if_fail (attr, FALSE);
+
+	if (!gck_ssh_agent_proto_write_mpi_v1 (resp, attr))
+		return FALSE;
+
+	/* Write out the modulus */
+	attr = gp11_attributes_find (attrs, CKA_MODULUS);
+	g_return_val_if_fail (attr, FALSE);
+
+	if (!gck_ssh_agent_proto_write_mpi_v1 (resp, attr))
+		return FALSE;
+	
+	return TRUE;
+}
+
+gboolean
+gck_ssh_agent_proto_write_signature_rsa (GkrBuffer *resp, CK_BYTE_PTR signature, CK_ULONG n_signature)
+{
+	return gkr_buffer_add_byte_array (resp, signature, n_signature);
+}
+
+gboolean
+gck_ssh_agent_proto_write_signature_dsa (GkrBuffer *resp, CK_BYTE_PTR signature, CK_ULONG n_signature)
+{
+	g_return_val_if_fail (n_signature == 40, FALSE);
+	return gkr_buffer_add_byte_array (resp, signature, n_signature);
+}
+

Added: trunk/pkcs11/ssh-agent/gck-ssh-agent-standalone.c
==============================================================================
--- (empty file)
+++ trunk/pkcs11/ssh-agent/gck-ssh-agent-standalone.c	Sat Jan  3 23:00:21 2009
@@ -0,0 +1,125 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gck-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 <glib.h>
+
+#include "gck-ssh-agent.h"
+
+#include "common/gkr-secure-memory.h"
+
+G_LOCK_DEFINE_STATIC (memory_mutex);
+
+void gkr_memory_lock (void)
+	{ G_LOCK (memory_mutex); }
+
+void gkr_memory_unlock (void)
+	{ G_UNLOCK (memory_mutex); }
+
+void* gkr_memory_fallback (void *p, unsigned long sz)
+	{ return g_realloc (p, sz); }
+
+static gboolean
+accept_client (GIOChannel *channel, GIOCondition cond, gpointer unused)
+{
+	gck_ssh_agent_accept ();
+	return TRUE;
+}
+
+static gboolean 
+authenticate_token (GP11Slot *self, 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 (GP11Slot *self, GP11Object *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[])
+{
+	GP11Module *module;
+	GList *slots;
+	GP11Slot *slot;
+	GError *error = NULL;
+	GIOChannel *channel;
+	GMainLoop *loop;
+	
+	g_type_init ();
+	
+	if (!g_thread_supported ())
+		g_thread_init (NULL);
+	
+	module = gp11_module_initialize (argv[1], argc > 2 ? argv[2] : NULL, &error);
+	if (!module) {
+		g_message ("couldn't load pkcs11 module: %s", error->message);
+		g_clear_error (&error);
+		return 1;
+	}
+	
+	/* This is currently brittle because it's just used for development */
+	slots = gp11_module_get_slots (module, TRUE);
+	if (!slots) {
+		g_message ("no slots present in pkcs11 module");
+		return 1;
+	}
+
+	slot = g_object_ref (slots->data);
+	gp11_list_unref_free (slots);
+	
+	g_signal_connect (slot, "authenticate-token", G_CALLBACK (authenticate_token), NULL);
+	g_signal_connect (slot, "authenticate-object", G_CALLBACK (authenticate_object), NULL);
+	gp11_slot_set_auto_login (slot, TRUE);
+
+	if (!gck_ssh_agent_initialize ("/tmp/test-gck-ssh-agent", slot))
+		return 1;
+	
+	channel = g_io_channel_unix_new (gck_ssh_agent_get_socket_fd ());
+	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", gck_ssh_agent_get_socket_path ());
+	
+	/* Run a main loop */
+	loop = g_main_loop_new (NULL, FALSE);
+	g_main_loop_run (loop);
+	g_main_loop_unref (loop);
+	
+	gck_ssh_agent_uninitialize ();
+	g_object_unref (slot);
+	
+	return 0;
+}

Added: trunk/pkcs11/ssh-agent/gck-ssh-agent.c
==============================================================================
--- (empty file)
+++ trunk/pkcs11/ssh-agent/gck-ssh-agent.c	Sat Jan  3 23:00:21 2009
@@ -0,0 +1,432 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/* gck-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 "gck-ssh-agent.h"
+#include "gck-ssh-agent-private.h"
+
+#include "common/gkr-buffer.h"
+#include "common/gkr-secure-memory.h"
+
+#ifndef HAVE_SOCKLEN_T
+#define socklen_t int
+#endif
+
+/* The PKCS#11 slot we call into */
+static GP11Slot *pkcs11_slot = NULL;
+
+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;
+			if (res < 0)
+				g_warning ("couldn't read %u bytes from client: %s", all, 
+				           g_strerror (errno));
+			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;
+			g_warning ("couldn't write %u bytes to client: %s", all, 
+			           res < 0 ? g_strerror (errno) : "");
+			return FALSE;
+		} else  {
+			len -= res;
+			buf += res;
+		}
+	}
+	
+	return TRUE;
+}
+
+static gboolean
+read_packet_with_size (GckSshAgentCall *call)
+{
+	int fd;
+	guint32 packet_size;
+
+	fd = call->sock;
+	
+	gkr_buffer_resize (call->req, 4);
+	if (!read_all (fd, call->req->buf, 4))
+		return FALSE;
+
+	if (!gkr_buffer_get_uint32 (call->req, 0, NULL, &packet_size) || 
+	    packet_size < 1) {
+	    	g_warning ("invalid packet size from client");
+		return FALSE;
+	}
+
+	gkr_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)
+{
+	gboolean running = TRUE;
+	GError *error = NULL;
+	gint *socket = data;
+	GckSshAgentCall call;
+	GkrBuffer req;
+	GkrBuffer resp;
+	guchar op;
+	
+	g_assert (pkcs11_slot);
+	
+	memset (&call, 0, sizeof (call));
+	call.sock = g_atomic_int_get (socket);
+	g_assert (call.sock != -1);
+	
+	gkr_buffer_init_full (&req, 128, gkr_secure_realloc);
+	gkr_buffer_init_full (&resp, 128, (GkrBufferAllocator)g_realloc);
+	call.req = &req;
+	call.resp = &resp;
+	
+	/* Try to open a session for this thread */
+	call.session = gp11_slot_open_session (pkcs11_slot, CKF_SERIAL_SESSION, &error);
+	if (!call.session) {
+		g_warning ("couldn't open pkcs#11 session for agent thread: %s", error->message);
+		g_clear_error (&error);
+		running = FALSE;
+	}
+
+	while (running) {
+		
+		gkr_buffer_reset (call.req);
+		
+		/* 1. Read in the request */
+		if (!read_packet_with_size (&call))
+			break;
+
+		/* 2. Now decode the operation */
+		if (!gkr_buffer_get_byte (call.req, 4, NULL, &op))
+			break; 
+		if (op >= GCK_SSH_OP_MAX)
+			break;
+		g_assert (gck_ssh_agent_operations[op]);
+		
+		/* 3. Execute the right operation */
+		gkr_buffer_reset (call.resp);
+		gkr_buffer_add_uint32 (call.resp, 0);
+		if (!(gck_ssh_agent_operations[op]) (&call))
+			break;
+		if (!gkr_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;
+	}
+	
+	gkr_buffer_uninit (&req);
+	gkr_buffer_uninit (&resp);
+
+	close (call.sock);
+	g_atomic_int_set (socket, -1);
+	
+	return NULL;
+}
+
+/* --------------------------------------------------------------------------------------
+ * MAIN SESSION
+ */
+
+/* The main PKCS#11 session that owns objects, and the mutex/cond for waiting on it */
+static GP11Session *pkcs11_session = NULL;
+static gboolean pkcs11_session_checked = FALSE;
+static GMutex *pkcs11_session_mutex = NULL;
+static GCond *pkcs11_session_cond = NULL;
+
+static gboolean
+init_main_session (GP11Slot *slot)
+{
+	GP11Session *session;
+	GError *error = NULL;
+	
+	g_assert (GP11_IS_SLOT (slot));
+
+	/* Load our main session */
+	session = gp11_slot_open_session (slot, CKF_SERIAL_SESSION, &error);
+	if (!session) {
+		g_warning ("couldn't create pkcs#11 session: %s", error->message);
+		g_clear_error (&error);
+		return FALSE;
+	}
+
+	pkcs11_session_mutex = g_mutex_new ();
+	pkcs11_session_cond = g_cond_new ();
+	pkcs11_session_checked = FALSE;
+	pkcs11_session = session;
+	
+	return TRUE;
+}
+
+GP11Session*
+gck_ssh_agent_checkout_main_session (void)
+{
+	GP11Session *result;
+	
+	g_mutex_lock (pkcs11_session_mutex);
+	
+		g_assert (GP11_IS_SESSION (pkcs11_session));
+		while (pkcs11_session_checked)
+			g_cond_wait (pkcs11_session_cond, pkcs11_session_mutex);
+		pkcs11_session_checked = TRUE;
+		result = g_object_ref (pkcs11_session);
+	
+	g_mutex_unlock (pkcs11_session_mutex);
+	
+	return result;
+}
+
+void
+gck_ssh_agent_checkin_main_session (GP11Session *session)
+{
+	g_assert (GP11_IS_SESSION (session));
+	
+	g_mutex_lock (pkcs11_session_mutex);
+	
+		g_assert (session == pkcs11_session);
+		g_assert (pkcs11_session_checked);
+		
+		g_object_unref (session);
+		pkcs11_session_checked = FALSE;
+		g_cond_signal (pkcs11_session_cond);
+		
+	g_mutex_unlock (pkcs11_session_mutex);
+}
+
+static void
+uninit_main_session (void)
+{
+	gboolean ret;
+	
+	g_assert (pkcs11_session_mutex);
+	ret = g_mutex_trylock (pkcs11_session_mutex);
+	g_assert (ret);
+
+		g_assert (GP11_IS_SESSION (pkcs11_session));
+		g_assert (!pkcs11_session_checked);
+		g_object_unref (pkcs11_session);
+		pkcs11_session = NULL;
+		
+	g_mutex_unlock (pkcs11_session_mutex);
+	g_mutex_free (pkcs11_session_mutex);
+	g_cond_free (pkcs11_session_cond);		
+}
+
+/* --------------------------------------------------------------------------------------
+ * 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, };
+
+int
+gck_ssh_agent_get_socket_fd (void)
+{
+	return socket_fd;
+}
+
+const gchar*
+gck_ssh_agent_get_socket_path (void)
+{
+	return socket_path;
+}
+
+void
+gck_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_create (run_client_thread, &client->sock, TRUE, &error);
+	if (!client->thread) {
+		g_warning ("couldn't create thread SSH agent connection: %s", 
+		           error && error->message ? error->message : "");
+		g_slice_free (Client, client);
+		return;
+	}
+	
+	socket_clients = g_list_append (socket_clients, client);
+}
+
+void 
+gck_ssh_agent_uninitialize (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;
+	
+	uninit_main_session ();
+	
+	g_object_unref (pkcs11_slot);
+	pkcs11_slot = NULL;
+}
+
+gboolean
+gck_ssh_agent_initialize (const gchar *prefix, GP11Slot *slot)
+{
+	struct sockaddr_un addr;
+	int sock;
+	
+	g_return_val_if_fail (GP11_IS_SLOT (slot), FALSE);
+	g_return_val_if_fail (prefix, FALSE);
+	
+	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 FALSE;
+	}
+	
+	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 FALSE;
+	}
+	
+	if (listen (sock, 128) < 0) {
+		g_warning ("couldn't listen on socket: %s", g_strerror (errno));
+		close (sock);
+		return FALSE;
+	}
+	
+	/* Load our main session */
+	if (!init_main_session (slot)) {
+		close (sock);
+		return FALSE;
+	}
+	
+	pkcs11_slot = g_object_ref (slot);
+	socket_fd = sock;
+	return TRUE;
+}

Added: trunk/pkcs11/ssh-agent/gck-ssh-agent.h
==============================================================================
--- (empty file)
+++ trunk/pkcs11/ssh-agent/gck-ssh-agent.h	Sat Jan  3 23:00:21 2009
@@ -0,0 +1,18 @@
+#ifndef GCKSSHAGENT_H_
+#define GCKSSHAGENT_H_
+
+#include <glib.h>
+
+#include "gp11/gp11.h"
+
+gboolean          gck_ssh_agent_initialize              (const gchar *prefix, GP11Slot *slot);
+
+int               gck_ssh_agent_get_socket_fd           (void);
+
+const gchar*      gck_ssh_agent_get_socket_path         (void);
+
+void              gck_ssh_agent_accept                  (void);
+
+void              gck_ssh_agent_uninitialize            (void);
+
+#endif /* GCKSSHAGENT_H_ */



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