[gnome-keyring] gcr: Implement OpenSSH public key parser



commit ad4fc192b5cf9c51ed73306b43e43fb36747ab22
Author: Stef Walter <stefw collabora co uk>
Date:   Tue Sep 13 08:04:41 2011 +0200

    gcr: Implement OpenSSH public key parser
    
     * And add tests for various formats.

 .gitignore                          |    1 +
 docs/reference/gcr/gcr-sections.txt |    1 +
 gcr/Makefile.am                     |    1 +
 gcr/gcr-internal.h                  |    3 +
 gcr/gcr-live-search.c               |   19 ++-
 gcr/gcr-openssh.c                   |  491 +++++++++++++++++++++++++++++++++++
 gcr/gcr-openssh.h                   |   51 ++++
 gcr/gcr-parser.c                    |  108 +++++++--
 gcr/gcr-types.h                     |    2 +
 gcr/tests/Makefile.am               |    1 +
 gcr/tests/files/openssh_keys.pub    |    2 +
 gcr/tests/test-openssh.c            |  196 ++++++++++++++
 testing/ssh-example/README          |    1 +
 testing/ssh-example/id_dsa          |   15 +
 testing/ssh-example/id_dsa.pub      |    1 +
 testing/ssh-example/id_rsa          |   30 +++
 testing/ssh-example/id_rsa.pub      |    1 +
 testing/ssh-example/identity        |  Bin 0 -> 988 bytes
 testing/ssh-example/identity.pub    |    1 +
 19 files changed, 908 insertions(+), 17 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index dc31879..d569cf7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -133,6 +133,7 @@ run-tests
 /gcr/tests/test-gnupg-key
 /gcr/tests/test-gnupg-process
 /gcr/tests/test-memory-icon
+/gcr/tests/test-openssh
 /gcr/tests/test-parser
 /gcr/tests/test-pkcs11-certificate
 /gcr/tests/test-record
diff --git a/docs/reference/gcr/gcr-sections.txt b/docs/reference/gcr/gcr-sections.txt
index 0f22399..dfb22ab 100644
--- a/docs/reference/gcr/gcr-sections.txt
+++ b/docs/reference/gcr/gcr-sections.txt
@@ -589,4 +589,5 @@ GcrMemoryIconClass
 GcrMemoryIconPrivate
 GCR_ERROR
 gcr_error_get_domain
+GcrOpensshPubCallback
 </SECTION>
diff --git a/gcr/Makefile.am b/gcr/Makefile.am
index eb8585e..f89100c 100644
--- a/gcr/Makefile.am
+++ b/gcr/Makefile.am
@@ -103,6 +103,7 @@ libgcr_base_ GCR_MAJOR@_la_SOURCES = \
 	gcr-internal.h \
 	gcr-memory.c \
 	gcr-memory-icon.c gcr-memory-icon.h \
+	gcr-openssh.c gcr-openssh.h \
 	gcr-parser.c gcr-parser.h \
 	gcr-pkcs11-certificate.c gcr-pkcs11-certificate.h \
 	gcr-record.c gcr-record.h \
diff --git a/gcr/gcr-internal.h b/gcr/gcr-internal.h
index 6923f5d..9ae3831 100644
--- a/gcr/gcr-internal.h
+++ b/gcr/gcr-internal.h
@@ -26,6 +26,9 @@
 
 #include <glib.h>
 
+/* Should only be used internally */
+#define GCR_SUCCESS 0
+
 void              _gcr_initialize_library          (void);
 
 gboolean          _gcr_initialize_pkcs11           (GCancellable *cancellable,
diff --git a/gcr/gcr-live-search.c b/gcr/gcr-live-search.c
index 71c8a06..291a6d2 100644
--- a/gcr/gcr-live-search.c
+++ b/gcr/gcr-live-search.c
@@ -64,7 +64,11 @@ stripped_char (gunichar ch)
 {
 	gunichar retval = 0;
 	GUnicodeType utype;
+#if GLIB_CHECK_VERSION (2,30,0)
 	gunichar decomp[4];
+#else
+	gunichar *decomp;
+#endif
 	gsize dlen;
 
 	utype = g_unichar_type (ch);
@@ -74,7 +78,11 @@ stripped_char (gunichar ch)
 	case G_UNICODE_FORMAT:
 	case G_UNICODE_UNASSIGNED:
 	case G_UNICODE_NON_SPACING_MARK:
+#if GLIB_CHECK_VERSION (2,30,0)
 	case G_UNICODE_SPACING_MARK:
+#else
+	case G_UNICODE_COMBINING_MARK:
+#endif
 	case G_UNICODE_ENCLOSING_MARK:
 		/* Ignore those */
 		break;
@@ -104,9 +112,18 @@ stripped_char (gunichar ch)
 	case G_UNICODE_SPACE_SEPARATOR:
 	default:
 		ch = g_unichar_tolower (ch);
+#if GLIB_CHECK_VERSION (2,30,0)
 		dlen = g_unichar_fully_decompose (ch, FALSE, decomp, 4);
-		if (dlen > 0)
+		if (dlen > 0) {
+#else
+		decomp = g_unicode_canonical_decomposition (ch, &dlen);
+		if (decomp != NULL) {
+#endif
 			retval = decomp[0];
+#if !GLIB_CHECK_VERSION (2,30,0)
+			g_free (decomp);
+#endif
+		}
 	}
 
 	return retval;
diff --git a/gcr/gcr-openssh.c b/gcr/gcr-openssh.c
new file mode 100644
index 0000000..e285465
--- /dev/null
+++ b/gcr/gcr-openssh.c
@@ -0,0 +1,491 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Author: Stef Walter <stefw collabora co uk>
+ */
+
+#include "config.h"
+
+#include "gcr-openssh.h"
+#include "gcr-internal.h"
+#include "gcr-types.h"
+
+#include "egg/egg-buffer.h"
+#include "egg/egg-decimal.h"
+
+#include "pkcs11/pkcs11.h"
+
+#include <string.h>
+
+typedef struct {
+	GcrOpensshPubCallback callback;
+	gpointer user_data;
+} OpensshPubClosure;
+
+static void
+skip_spaces (const gchar ** line,
+             gsize *n_line)
+{
+	while (*n_line > 0 && (*line)[0] == ' ') {
+		(*line)++;
+		(*n_line)--;
+	}
+}
+
+static gboolean
+next_word (const gchar **line,
+           gsize *n_line,
+           const gchar **word,
+           gsize *n_word)
+{
+	const gchar *beg;
+	const gchar *end;
+	const gchar *at;
+	gboolean quotes;
+
+	skip_spaces (line, n_line);
+
+	if (!*n_line) {
+		*word = NULL;
+		*n_word = 0;
+		return FALSE;
+	}
+
+	beg = at = *line;
+	end = beg + *n_line;
+	quotes = FALSE;
+
+	do {
+		switch (*at) {
+		case '"':
+			quotes = !quotes;
+			at++;
+			break;
+		case ' ':
+			if (!quotes)
+				end = at;
+			else
+				at++;
+			break;
+		default:
+			at++;
+			break;
+		}
+	} while (at < end);
+
+	*word = beg;
+	*n_word = end - beg;
+	(*line) += *n_word;
+	(*n_line) -= *n_word;
+	return TRUE;
+}
+
+static gboolean
+match_word (const gchar *word,
+            gsize n_word,
+            const gchar *matches)
+{
+	gsize len = strlen (matches);
+	if (len != n_word)
+		return FALSE;
+	return memcmp (word, matches, n_word) == 0;
+}
+
+static gulong
+keytype_to_algo (const gchar *algo,
+                 gsize length)
+{
+	if (!algo)
+		return G_MAXULONG;
+	else if (match_word (algo, length, "ssh-rsa"))
+		return CKK_RSA;
+	else if (match_word (algo, length, "ssh-dss"))
+		return CKK_DSA;
+	return G_MAXULONG;
+}
+
+static gboolean
+read_decimal_mpi (const gchar *decimal,
+                  gsize n_decimal,
+                  GckAttributes *attrs,
+                  gulong attribute_type)
+{
+	gpointer data;
+	gsize n_data;
+
+	data = egg_decimal_decode (decimal, n_decimal, &n_data);
+	if (data == NULL)
+		return FALSE;
+
+	gck_attributes_add_data (attrs, attribute_type, data, n_data);
+	return TRUE;
+}
+
+static gint
+atoin (const char *p, gint digits)
+{
+	gint ret = 0, base = 1;
+	while(--digits >= 0) {
+		if (p[digits] < '0' || p[digits] > '9')
+			return -1;
+		ret += (p[digits] - '0') * base;
+		base *= 10;
+	}
+	return ret;
+}
+
+static GcrDataError
+parse_v1_public_line (const gchar *line,
+                      gsize length,
+                      GcrOpensshPubCallback callback,
+                      gpointer user_data)
+{
+	const gchar *word_bits, *word_exponent, *word_modulus, *word_options, *outer;
+	gsize len_bits, len_exponent, len_modulus, len_options, n_outer;
+	GckAttributes *attrs;
+	gchar *label, *options;
+	gint bits;
+
+	g_assert (line);
+
+	outer = line;
+	n_outer = length;
+	options = NULL;
+	label = NULL;
+
+	/* Eat space at the front */
+	skip_spaces (&line, &length);
+
+	/* Blank line or comment */
+	if (length == 0 || line[0] == '#')
+		return GCR_ERROR_UNRECOGNIZED;
+
+	/*
+	 * If the line starts with a digit, then no options:
+	 *
+	 * 2048 35 25213680043....93533757 Label
+	 *
+	 * If the line doesn't start with a digit, then have options:
+	 *
+	 * option,option 2048 35 25213680043....93533757 Label
+	 */
+	if (g_ascii_isdigit (line[0])) {
+		word_options = NULL;
+		len_options = 0;
+	} else {
+		if (!next_word (&line, &length, &word_options, &len_options))
+			return GCR_ERROR_UNRECOGNIZED;
+	}
+
+	if (!next_word (&line, &length, &word_bits, &len_bits) ||
+	    !next_word (&line, &length, &word_exponent, &len_exponent) ||
+	    !next_word (&line, &length, &word_modulus, &len_modulus))
+		return GCR_ERROR_UNRECOGNIZED;
+
+	bits = atoin (word_bits, len_bits);
+	if (bits <= 0)
+		return GCR_ERROR_UNRECOGNIZED;
+
+	attrs = gck_attributes_new ();
+
+	if (!read_decimal_mpi (word_exponent, len_exponent, attrs, CKA_PUBLIC_EXPONENT) ||
+	    !read_decimal_mpi (word_modulus, len_modulus, attrs, CKA_MODULUS)) {
+		gck_attributes_unref (attrs);
+		return GCR_ERROR_UNRECOGNIZED;
+	}
+
+	gck_attributes_add_ulong (attrs, CKA_KEY_TYPE, CKK_RSA);
+	gck_attributes_add_ulong (attrs, CKA_CLASS, CKO_PUBLIC_KEY);
+
+	skip_spaces (&line, &length);
+	if (length > 0) {
+		label = g_strndup (line, length);
+		g_strstrip (label);
+		gck_attributes_add_string (attrs, CKA_LABEL, label);
+	}
+
+	if (word_options)
+		options = g_strndup (word_options, len_options);
+
+	if (callback != NULL)
+		(callback) (attrs, label, options, outer, n_outer, user_data);
+
+	gck_attributes_unref (attrs);
+	g_free (options);
+	g_free (label);
+	return GCR_SUCCESS;
+}
+
+static gboolean
+read_buffer_mpi (EggBuffer *buffer,
+                 gsize *offset,
+                 GckAttributes *attrs,
+                 gulong attribute_type)
+{
+	const guchar *data;
+	gsize len;
+
+	if (!egg_buffer_get_byte_array (buffer, *offset, offset, &data, &len))
+		return FALSE;
+
+	gck_attributes_add_data (attrs, attribute_type, data, len);
+	return TRUE;
+}
+
+static GckAttributes *
+read_v2_public_dsa (EggBuffer *buffer,
+                    gsize *offset)
+{
+	GckAttributes *attrs;
+
+	attrs = gck_attributes_new ();
+
+	if (!read_buffer_mpi (buffer, offset, attrs, CKA_PRIME) ||
+	    !read_buffer_mpi (buffer, offset, attrs, CKA_SUBPRIME) ||
+	    !read_buffer_mpi (buffer, offset, attrs, CKA_BASE) ||
+	    !read_buffer_mpi (buffer, offset, attrs, CKA_VALUE)) {
+		gck_attributes_unref (attrs);
+		return NULL;
+	}
+
+	gck_attributes_add_ulong (attrs, CKA_KEY_TYPE, CKK_DSA);
+	gck_attributes_add_ulong (attrs, CKA_CLASS, CKO_PUBLIC_KEY);
+
+	return attrs;
+}
+
+static GckAttributes *
+read_v2_public_rsa (EggBuffer *buffer,
+                    gsize *offset)
+{
+	GckAttributes *attrs;
+
+	attrs = gck_attributes_new ();
+
+	if (!read_buffer_mpi (buffer, offset, attrs, CKA_PUBLIC_EXPONENT) ||
+	    !read_buffer_mpi (buffer, offset, attrs, CKA_MODULUS)) {
+		gck_attributes_unref (attrs);
+		return NULL;
+	}
+
+	gck_attributes_add_ulong (attrs, CKA_KEY_TYPE, CKK_RSA);
+	gck_attributes_add_ulong (attrs, CKA_CLASS, CKO_PUBLIC_KEY);
+
+	return attrs;
+}
+
+static GckAttributes *
+read_v2_public_key (gulong algo,
+                    gconstpointer data,
+                    gsize n_data)
+{
+	GckAttributes *attrs;
+	EggBuffer buffer;
+	gsize offset;
+	gchar *stype;
+	int alg;
+
+	egg_buffer_init_static (&buffer, data, n_data);
+	offset = 0;
+
+	/* The string algorithm */
+	if (!egg_buffer_get_string (&buffer, offset, &offset,
+	                            &stype, (EggBufferAllocator)g_realloc))
+		return NULL;
+
+	alg = keytype_to_algo (stype, stype ? strlen (stype) : 0);
+	g_free (stype);
+
+	if (alg != algo) {
+		g_message ("invalid or mis-matched algorithm in ssh public key: %s", stype);
+		egg_buffer_uninit (&buffer);
+		return NULL;
+	}
+
+	switch (algo) {
+	case CKK_RSA:
+		attrs = read_v2_public_rsa (&buffer, &offset);
+		break;
+	case CKK_DSA:
+		attrs = read_v2_public_dsa (&buffer, &offset);
+		break;
+	default:
+		g_assert_not_reached ();
+		break;
+	}
+
+	egg_buffer_uninit (&buffer);
+	return attrs;
+}
+
+static GckAttributes *
+decode_v2_public_key (gulong algo,
+                      const gchar *data,
+                      gsize n_data)
+{
+	GckAttributes *attrs;
+	gpointer decoded;
+	gsize n_decoded;
+	guint save;
+	gint state;
+
+	/* Decode the base64 key */
+	save = state = 0;
+	decoded = g_malloc (n_data * 3 / 4);
+	n_decoded = g_base64_decode_step ((gchar*)data, n_data, decoded, &state, &save);
+
+	if (!n_decoded) {
+		g_free (decoded);
+		return NULL;
+	}
+
+	/* Parse the actual key */
+	attrs = read_v2_public_key (algo, decoded, n_decoded);
+
+	g_free (decoded);
+
+	return attrs;
+}
+
+static GcrDataError
+parse_v2_public_line (const gchar *line,
+                      gsize length,
+                      GcrOpensshPubCallback callback,
+                      gpointer user_data)
+{
+	const gchar *word_options, *word_algo, *word_key;
+	gsize len_options, len_algo, len_key;
+	GckAttributes *attrs;
+	gchar *options;
+	gchar *label = NULL;
+	const gchar *outer = line;
+	gsize n_outer = length;
+	gulong algo;
+
+	g_assert (line);
+
+	/* Eat space at the front */
+	skip_spaces (&line, &length);
+
+	/* Blank line or comment */
+	if (length == 0 || line[0] == '#')
+		return GCR_ERROR_UNRECOGNIZED;
+
+	if (!next_word (&line, &length, &word_algo, &len_algo))
+		return GCR_ERROR_UNRECOGNIZED;
+
+	/*
+	 * If the first word is not the algorithm, then we have options:
+	 *
+	 * option,option ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAI...EAz8Ji= Label here
+	 *
+	 * If the first word is the algorithm, then we have no options:
+	 *
+	 * ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAI...EAz8Ji= Label here
+	 */
+	algo = keytype_to_algo (word_algo, len_algo);
+	if (algo == G_MAXULONG) {
+		word_options = word_algo;
+		len_options = len_algo;
+		if (!next_word (&line, &length, &word_algo, &len_algo))
+			return GCR_ERROR_UNRECOGNIZED;
+		algo = keytype_to_algo (word_algo, len_algo);
+		if (algo == G_MAXULONG)
+			return GCR_ERROR_UNRECOGNIZED;
+	} else {
+		word_options = NULL;
+		len_options = 0;
+	}
+
+	/* Must have at least two words */
+	if (!next_word (&line, &length, &word_key, &len_key))
+		return GCR_ERROR_FAILURE;
+
+	attrs = decode_v2_public_key (algo, word_key, len_key);
+	if (attrs == NULL)
+		return GCR_ERROR_FAILURE;
+
+	if (word_options)
+		options = g_strndup (word_options, len_options);
+	else
+		options = NULL;
+
+	/* The remainder of the line is the label */
+	skip_spaces (&line, &length);
+	if (length > 0) {
+		label = g_strndup (line, length);
+		g_strstrip (label);
+		gck_attributes_add_string (attrs, CKA_LABEL, label);
+	}
+
+	if (callback != NULL)
+		(callback) (attrs, label, options, outer, n_outer, user_data);
+
+	gck_attributes_unref (attrs);
+	g_free (options);
+	g_free (label);
+	return GCR_SUCCESS;
+}
+
+guint
+_gcr_openssh_pub_parse (gconstpointer data,
+                        gsize n_data,
+                        GcrOpensshPubCallback callback,
+                        gpointer user_data)
+{
+	const gchar *line;
+	const gchar *end;
+	gsize length;
+	gboolean last;
+	GcrDataError res;
+	guint num_parsed;
+
+	g_return_val_if_fail (data, FALSE);
+
+	line = data;
+	length = n_data;
+	last = FALSE;
+	num_parsed = 0;
+
+	for (;;) {
+		end  = memchr (line, '\n', length);
+		if (end == NULL) {
+			end = line + length;
+			last = TRUE;
+		}
+
+		if (line != end) {
+			res = parse_v2_public_line (line, end - line, callback, user_data);
+			if (res == GCR_ERROR_UNRECOGNIZED)
+				res = parse_v1_public_line (line, end - line, callback, user_data);
+			if (res == GCR_SUCCESS)
+				num_parsed++;
+		}
+
+		if (last)
+			break;
+
+		end++;
+		length -= (end - line);
+		line = end;
+	}
+
+	return num_parsed;
+}
diff --git a/gcr/gcr-openssh.h b/gcr/gcr-openssh.h
new file mode 100644
index 0000000..2fdac2a
--- /dev/null
+++ b/gcr/gcr-openssh.h
@@ -0,0 +1,51 @@
+/*
+ * gnome-keyring
+ *
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ * Author: Stef Walter <stefw collabora co uk>
+ */
+
+#if !defined (__GCR_H_INSIDE__) && !defined (GCR_COMPILATION)
+#error "Only <gcr/gcr.h> can be included directly."
+#endif
+
+#ifndef __GCR_OPENSSH_H__
+#define __GCR_OPENSSH_H__
+
+#include <glib.h>
+
+#include <gck/gck.h>
+
+G_BEGIN_DECLS
+
+typedef void             (*GcrOpensshPubCallback)          (GckAttributes *attrs,
+                                                            const gchar *label,
+                                                            const gchar *options,
+                                                            const gchar *outer,
+                                                            gsize n_outer,
+                                                            gpointer user_data);
+
+guint                    _gcr_openssh_pub_parse            (gconstpointer data,
+                                                            gsize n_data,
+                                                            GcrOpensshPubCallback callback,
+                                                            gpointer user_data);
+
+G_END_DECLS
+
+#endif /* __GCR_OPENSSH_H__ */
diff --git a/gcr/gcr-parser.c b/gcr/gcr-parser.c
index 92bb256..9e15573 100644
--- a/gcr/gcr-parser.c
+++ b/gcr/gcr-parser.c
@@ -27,6 +27,7 @@
 #include "gcr-importer.h"
 #include "gcr-marshal.h"
 #include "gcr-oids.h"
+#include "gcr-openssh.h"
 #include "gcr-parser.h"
 #include "gcr-types.h"
 
@@ -88,6 +89,7 @@
  * @GCR_FORMAT_DER_PKCS8_PLAIN: Unencrypted DER encoded PKCS\#8 file which can contain a key
  * @GCR_FORMAT_DER_PKCS8_ENCRYPTED: Encrypted DER encoded PKCS\#8 file which can contain a key
  * @GCR_FORMAT_DER_PKCS12: DER encoded PKCS\#12 file which can contain certificates and/or keys
+ * @GCR_FORMAT_OPENSSH_PUBLIC: OpenSSH v1 or v2 public key
  * @GCR_FORMAT_PEM: An OpenSSL style PEM file with unspecified contents
  * @GCR_FORMAT_PEM_PRIVATE_KEY_RSA: An OpenSSL style PEM file with a private RSA key
  * @GCR_FORMAT_PEM_PRIVATE_KEY_DSA: An OpenSSL style PEM file with a private DSA key
@@ -238,7 +240,6 @@ parsed_asn1_attribute (GcrParser *self, GNode *asn, const guchar *data, gsize n_
 
 static void
 parsing_begin (GcrParser *self,
-               CK_OBJECT_CLASS klass,
                gconstpointer block,
                gsize n_block)
 {
@@ -248,12 +249,16 @@ parsing_begin (GcrParser *self,
 
 	if (self->pv->parsed_attrs)
 		gck_attributes_unref (self->pv->parsed_attrs);
-	if (klass == CKO_PRIVATE_KEY)
-		self->pv->parsed_attrs = gck_attributes_new_full ((GckAllocator)egg_secure_realloc);
-	else 
-		self->pv->parsed_attrs = gck_attributes_new ();
-	gck_attributes_add_ulong (self->pv->parsed_attrs, CKA_CLASS, klass);
+	self->pv->parsed_attrs = NULL;
+
+	self->pv->parsing_block = block;
+	self->pv->parsing_n_block = n_block;
+}
 
+static void
+parsed_description (GcrParser *self,
+                    CK_OBJECT_CLASS klass)
+{
 	switch (klass) {
 	case CKO_PRIVATE_KEY:
 		self->pv->parsed_desc = _("Private Key");
@@ -268,9 +273,34 @@ parsing_begin (GcrParser *self,
 		self->pv->parsed_desc = NULL;
 		break;
 	}
+}
 
-	self->pv->parsing_block = block;
-	self->pv->parsing_n_block = n_block;
+static void
+parsing_object (GcrParser *self,
+                CK_OBJECT_CLASS klass)
+{
+	g_return_if_fail (self->pv->parsed_attrs == NULL);
+
+	if (klass == CKO_PRIVATE_KEY)
+		self->pv->parsed_attrs = gck_attributes_new_full ((GckAllocator)egg_secure_realloc);
+	else
+		self->pv->parsed_attrs = gck_attributes_new ();
+	gck_attributes_add_ulong (self->pv->parsed_attrs, CKA_CLASS, klass);
+	parsed_description (self, klass);
+}
+
+static void
+parsed_attributes (GcrParser *self,
+                   GckAttributes *attrs)
+{
+	gulong klass;
+
+	g_return_if_fail (self->pv->parsed_attrs == NULL);
+	g_return_if_fail (attrs != NULL);
+
+	self->pv->parsed_attrs = gck_attributes_ref (attrs);
+	if (gck_attributes_find_ulong (attrs, CKA_CLASS, &klass))
+		parsed_description (self, klass);
 }
 
 static void
@@ -389,7 +419,8 @@ parse_der_private_key_rsa (GcrParser *self, const guchar *data, gsize n_data)
 	if (!asn)
 		goto done;
 
-	parsing_begin (self, CKO_PRIVATE_KEY, data, n_data);
+	parsing_begin (self, data, n_data);
+	parsing_object (self, CKO_PRIVATE_KEY);
 	parsed_ulong (self, CKA_KEY_TYPE, CKK_RSA);
 	parsed_boolean (self, CKA_PRIVATE, CK_TRUE);
 	res = GCR_ERROR_FAILURE;
@@ -438,7 +469,8 @@ parse_der_private_key_dsa (GcrParser *self, const guchar *data, gsize n_data)
 	if (!asn)
 		goto done;
 
-	parsing_begin (self, CKO_PRIVATE_KEY, data, n_data);
+	parsing_begin (self, data, n_data);
+	parsing_object (self, CKO_PRIVATE_KEY);
 	parsed_ulong (self, CKA_KEY_TYPE, CKK_DSA);
 	parsed_boolean (self, CKA_PRIVATE, CK_TRUE);
 	ret = GCR_ERROR_FAILURE;
@@ -568,11 +600,14 @@ done:
 			/* Try the normal sane format */
 			ret = parse_der_private_key_dsa (self, keydata, n_keydata);
 
-			parsing_begin (self, CKO_PRIVATE_KEY, data, n_data);
+			parsing_begin (self, data, n_data);
+			parsing_object (self, CKO_PRIVATE_KEY);
+
 			/* Otherwise try the two part format that everyone seems to like */
 			if (ret == GCR_ERROR_UNRECOGNIZED && params && n_params)
 				ret = parse_der_private_key_dsa_parts (self, keydata, n_keydata, 
 				                                       params, n_params);
+
 			parsing_end (self);
 			break;
 		default:
@@ -619,7 +654,8 @@ parse_der_pkcs8_encrypted (GcrParser *self, const guchar *data, gsize n_data)
 
 	params = egg_asn1x_get_raw_element (egg_asn1x_node (asn, "encryptionAlgorithm", "parameters", NULL), &n_params);
 
-	parsing_begin (self, CKO_PRIVATE_KEY, data, n_data);
+	parsing_begin (self, data, n_data);
+	parsing_object (self, CKO_PRIVATE_KEY);
 
 	/* Loop to try different passwords */                       
 	for (;;) {
@@ -704,7 +740,8 @@ parse_der_certificate (GcrParser *self, const guchar *data, gsize n_data)
 	if (asn == NULL)
 		return GCR_ERROR_UNRECOGNIZED;
 
-	parsing_begin (self, CKO_CERTIFICATE, data, n_data);
+	parsing_begin (self, data, n_data);
+	parsing_object (self, CKO_CERTIFICATE);
 
 	parsed_ulong (self, CKA_CERTIFICATE_TYPE, CKC_X_509);
 
@@ -1285,7 +1322,7 @@ parse_der_pkcs12 (GcrParser *self, const guchar *data, gsize n_data)
 	if (!asn)
 		goto done;
 
-	parsing_begin (self, 0, data, n_data);
+	parsing_begin (self, data, n_data);
 
 	oid = egg_asn1x_get_oid_as_quark (egg_asn1x_node (asn, "authSafe", "contentType", NULL));
 	if (!oid)
@@ -1476,7 +1513,8 @@ handle_pem_data (GQuark type,
 		return;
 
 	/* Fill in information necessary for prompting */
-	parsing_begin (args->parser, pem_type_to_class (type), outer, n_outer);
+	parsing_begin (args->parser, outer, n_outer);
+	parsing_object (args->parser, pem_type_to_class (type));
 
 	/* See if it's encrypted PEM all openssl like*/
 	if (headers) {
@@ -1569,6 +1607,42 @@ parse_pem_pkcs12 (GcrParser *self, const guchar *data, gsize n_data)
 }
 
 /* -----------------------------------------------------------------------------
+ * OPENSSH
+ */
+
+static void
+on_openssh_public_key_parsed (GckAttributes *attrs,
+                              const gchar *label,
+                              const gchar *options,
+                              const gchar *outer,
+                              gsize n_outer,
+                              gpointer user_data)
+{
+	GcrParser *self = GCR_PARSER (user_data);
+
+	parsing_begin (self, outer, n_outer);
+	parsed_attributes (self, attrs);
+	parsed_label (self, label);
+	parsed_fire (self);
+	parsing_end (self);
+}
+
+static gint
+parse_der_openssh_public (GcrParser *self,
+                          const guchar *data,
+                          gsize n_data)
+{
+	guint num_parsed;
+
+	num_parsed = _gcr_openssh_pub_parse (data, n_data,
+	                                     on_openssh_public_key_parsed, self);
+
+	if (num_parsed == 0)
+		return GCR_ERROR_UNRECOGNIZED;
+	return SUCCESS;
+}
+
+/* -----------------------------------------------------------------------------
  * FORMATS
  */
 
@@ -1581,7 +1655,8 @@ static const ParserFormat parser_normal[] = {
 	{ GCR_FORMAT_DER_PKCS7, parse_der_pkcs7 },
 	{ GCR_FORMAT_DER_PKCS8_PLAIN, parse_der_pkcs8_plain },
 	{ GCR_FORMAT_DER_PKCS8_ENCRYPTED, parse_der_pkcs8_encrypted },
-	{ GCR_FORMAT_DER_PKCS12, parse_der_pkcs12 }
+	{ GCR_FORMAT_DER_PKCS12, parse_der_pkcs12 },
+	{ GCR_FORMAT_OPENSSH_PUBLIC, parse_der_openssh_public },
 };
 
 /* Must be in format_id numeric order */
@@ -1595,6 +1670,7 @@ static const ParserFormat parser_formats[] = {
 	{ GCR_FORMAT_DER_PKCS8_PLAIN, parse_der_pkcs8_plain },
 	{ GCR_FORMAT_DER_PKCS8_ENCRYPTED, parse_der_pkcs8_encrypted },
 	{ GCR_FORMAT_DER_PKCS12, parse_der_pkcs12 },
+	{ GCR_FORMAT_OPENSSH_PUBLIC, parse_der_openssh_public },
 	{ GCR_FORMAT_PEM, parse_pem },
 	{ GCR_FORMAT_PEM_PRIVATE_KEY_RSA, parse_pem_private_key_rsa },
 	{ GCR_FORMAT_PEM_PRIVATE_KEY_DSA, parse_pem_private_key_dsa },
diff --git a/gcr/gcr-types.h b/gcr/gcr-types.h
index 23635ca..184eedd 100644
--- a/gcr/gcr-types.h
+++ b/gcr/gcr-types.h
@@ -71,6 +71,8 @@ typedef enum {
 
 	GCR_FORMAT_DER_PKCS12 = 500,
 
+	GCR_FORMAT_OPENSSH_PUBLIC = 600,
+
 	GCR_FORMAT_PEM = 1000,
 	GCR_FORMAT_PEM_PRIVATE_KEY_RSA,
 	GCR_FORMAT_PEM_PRIVATE_KEY_DSA,
diff --git a/gcr/tests/Makefile.am b/gcr/tests/Makefile.am
index f2f3b7a..a63b090 100644
--- a/gcr/tests/Makefile.am
+++ b/gcr/tests/Makefile.am
@@ -26,6 +26,7 @@ TEST_PROGS = \
 	test-certificate-chain \
 	test-fingerprint \
 	test-pkcs11-certificate \
+	test-openssh \
 	test-trust \
 	test-parser \
 	test-record \
diff --git a/gcr/tests/files/openssh_keys.pub b/gcr/tests/files/openssh_keys.pub
new file mode 100644
index 0000000..00d294b
--- /dev/null
+++ b/gcr/tests/files/openssh_keys.pub
@@ -0,0 +1,2 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAL4z+ad0ZJYzMOQuGp00UJ+AijKhrPVUEYLcxBmFQonb/KIlLSWJua4Rl9DB4tDj30Y9c/oApqC4n+FIYlUZMSnxmpvcLF6aeXOiHHPvm0EDYjjyVubyYQWI7CROrrzSc+x++ha3TuJEvF3PlKlZmTKKVYEkZNjwFqYysGyPxPalAAAAFQDtDSEF9Gvnv5fQtSbbsp7j78uVBwAAAIAtNpAg/Mbd/E2241enedB9AxAbJWZ5QYnoPe6/zx5dOmU7+qz8mG6tgvF8F7IgXPabuAKslzTDGS3zgaEhWicDS3CIYik2UR8hXdxfovIEqZKZe7u02FCEoXYCEiFUAdzDGzjI7PswgtEJWWNqKeNis3HmDDha9lMkqz/3fLZGXwAAAIEAiaRPYKZDMoJG+aVZ5A3R/m2gl+mYE2MsjPKXuBKcrZ6ItA9BMe4G/An0/+E3A+DuoGxdeNNMF8U9Dy2N8Sch/Ngtg2E/FBo5geljWobJXd1jxmPtF2WAliYJXDdIt6RBVPGL9H/KSjDmBMsVd42wxVJywawzypklVZjSUuWuBMI= dsa-key example com
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDCs8z2y0cCPYRAPkq8tAt6FC/kdfnR/p8B2ZoY0oiLNt7kQEwJfexgwLqTxWYd2fSDUSSDPrsqAxZAwLLS/eF04kXiJO2VfqAWFpTLNToERHpFF1yZQe26ELTlNNfna7LqfCRvpNDwu6AqndsT3eFt7DWvBDXbbEiTLW21Z2OFAAH/J2iCFn4c0a8Myf7IaMYcy5GG3mpk39kEO4aNV/67U7kfooek24ObwD0vlXzlsi5VZIUFOIUi0UdkNEMCtUWpfkZ1STUlmwp9HVM7xb7/9PESQKDnZdxpB09S9cIjdpDecpDlMDDEbEUECM1PIas3ndhB7gAN1i2JsPHTcXZ1 rsa-key example com
diff --git a/gcr/tests/test-openssh.c b/gcr/tests/test-openssh.c
new file mode 100644
index 0000000..81634da
--- /dev/null
+++ b/gcr/tests/test-openssh.c
@@ -0,0 +1,196 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+   Copyright (C) 2011 Collabora Ltd
+
+   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,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.
+
+   Author: Stef Walter <stefw collabora co uk>
+*/
+
+#include "config.h"
+
+#include "gcr/gcr.h"
+#include "gcr/gcr-openssh.h"
+
+#include "egg/egg-testing.h"
+
+#include <gcrypt.h>
+#include <glib.h>
+#include <string.h>
+
+typedef struct {
+	const gchar *expected_label;
+	const gchar *expected_options;
+} Test;
+
+#define OPENSSH_PUBLIC_RSA1 \
+	"2048 65537 19574029774826276058535216798260123376543523095248321838931" \
+	"8476099051534660565418100376122247153936738716140984293302866595208305" \
+	"7124376564328644357957081508003798389808113087527047927841196160520784" \
+	"3971799891833860159372766201922902824211581515042106928142039998651198" \
+	"7806024885997262427984841536983221992403267030558391252672804492615887" \
+	"9294713324466630490990131504557923061505441555447586185019409756877006" \
+	"5871190731807718592844942425524851665039303855329966512492845780563670" \
+	"0617451083369174928502647995734856960603065454655489558179113130210712" \
+	"74638931037011169213563881172297734240201883475566393175838117784693 r" \
+	"sa-key example com\n"
+
+#define OPENSSH_PUBLIC_RSA2 \
+	"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDCs8z2y0cCPYRAPkq8tAt6FC/kdfnR/p" \
+	"8B2ZoY0oiLNt7kQEwJfexgwLqTxWYd2fSDUSSDPrsqAxZAwLLS/eF04kXiJO2VfqAWFpTL" \
+	"NToERHpFF1yZQe26ELTlNNfna7LqfCRvpNDwu6AqndsT3eFt7DWvBDXbbEiTLW21Z2OFAA" \
+	"H/J2iCFn4c0a8Myf7IaMYcy5GG3mpk39kEO4aNV/67U7kfooek24ObwD0vlXzlsi5VZIUF" \
+	"OIUi0UdkNEMCtUWpfkZ1STUlmwp9HVM7xb7/9PESQKDnZdxpB09S9cIjdpDecpDlMDDEbE" \
+	"UECM1PIas3ndhB7gAN1i2JsPHTcXZ1 rsa-key example com\r\n" \
+	"# Comment\n"
+
+#define OPENSSH_PUBLIC_DSA2 \
+	"ssh-dss AAAAB3NzaC1kc3MAAACBAL4z+ad0ZJYzMOQuGp00UJ+AijKhrPVUEYLcxBmFQo" \
+	"nb/KIlLSWJua4Rl9DB4tDj30Y9c/oApqC4n+FIYlUZMSnxmpvcLF6aeXOiHHPvm0EDYjjy" \
+	"VubyYQWI7CROrrzSc+x++ha3TuJEvF3PlKlZmTKKVYEkZNjwFqYysGyPxPalAAAAFQDtDS" \
+	"EF9Gvnv5fQtSbbsp7j78uVBwAAAIAtNpAg/Mbd/E2241enedB9AxAbJWZ5QYnoPe6/zx5d" \
+	"OmU7+qz8mG6tgvF8F7IgXPabuAKslzTDGS3zgaEhWicDS3CIYik2UR8hXdxfovIEqZKZe7" \
+	"u02FCEoXYCEiFUAdzDGzjI7PswgtEJWWNqKeNis3HmDDha9lMkqz/3fLZGXwAAAIEAiaRP" \
+	"YKZDMoJG+aVZ5A3R/m2gl+mYE2MsjPKXuBKcrZ6ItA9BMe4G/An0/+E3A+DuoGxdeNNMF8" \
+	"U9Dy2N8Sch/Ngtg2E/FBo5geljWobJXd1jxmPtF2WAliYJXDdIt6RBVPGL9H/KSjDmBMsV" \
+	"d42wxVJywawzypklVZjSUuWuBMI= dsa-key example com \n"
+
+#define EXTRA_LINES_WITHOUT_KEY \
+	"\n# Comment\n\n" \
+	"20aa3\n" \
+	"not a key\n"
+
+static void
+setup (Test *test,
+       gconstpointer unused)
+{
+
+}
+
+static void
+teardown (Test *test,
+          gconstpointer unused)
+{
+
+}
+
+static void
+on_openssh_pub_parse (GckAttributes *attrs,
+                      const gchar *label,
+                      const gchar *options,
+                      const gchar *outer,
+                      gsize n_outer,
+                      gpointer user_data)
+{
+	Test *test = user_data;
+	guint keys;
+
+	if (test->expected_label)
+		g_assert_cmpstr (label, ==, test->expected_label);
+	if (test->expected_options)
+		g_assert_cmpstr (options, ==, test->expected_options);
+
+	/* The block should parse properly */
+	keys = _gcr_openssh_pub_parse (outer, n_outer, NULL, NULL);
+	g_assert_cmpuint (keys, ==, 1);
+}
+
+static void
+test_parse_v1_rsa (Test *test,
+                   gconstpointer unused)
+{
+	const gchar *data = OPENSSH_PUBLIC_RSA1 EXTRA_LINES_WITHOUT_KEY;
+	gint keys;
+
+	test->expected_label = "rsa-key example com";
+
+	keys = _gcr_openssh_pub_parse (data, strlen (data),
+	                               on_openssh_pub_parse, test);
+	g_assert_cmpint (keys, ==, 1);
+
+}
+
+static void
+test_parse_v2_rsa (Test *test,
+                   gconstpointer unused)
+{
+	const gchar *data = OPENSSH_PUBLIC_RSA2 EXTRA_LINES_WITHOUT_KEY;
+	gint keys;
+
+	test->expected_label = "rsa-key example com";
+
+	keys = _gcr_openssh_pub_parse (data, strlen (data),
+	                               on_openssh_pub_parse, test);
+	g_assert_cmpint (keys, ==, 1);
+}
+
+static void
+test_parse_v2_dsa (Test *test,
+               gconstpointer unused)
+{
+	const gchar *data = OPENSSH_PUBLIC_DSA2 EXTRA_LINES_WITHOUT_KEY;
+	gint keys;
+
+	test->expected_label = "dsa-key example com";
+
+	keys = _gcr_openssh_pub_parse (data, strlen (data),
+	                               on_openssh_pub_parse, test);
+	g_assert_cmpint (keys, ==, 1);
+}
+
+static void
+test_parse_v1_options (Test *test,
+                       gconstpointer unused)
+{
+	const gchar *data = "option1,option2=\"value 2\",option3 " OPENSSH_PUBLIC_RSA1;
+	gint keys;
+
+	test->expected_options = "option1,option2=\"value 2\",option3";
+
+	keys = _gcr_openssh_pub_parse (data, strlen (data),
+	                               on_openssh_pub_parse, test);
+	g_assert_cmpint (keys, ==, 1);
+}
+
+static void
+test_parse_v2_options (Test *test,
+                       gconstpointer unused)
+{
+	const gchar *data = "option1,option2=\"value 2\",option3 " OPENSSH_PUBLIC_RSA2;
+	gint keys;
+
+	test->expected_options = "option1,option2=\"value 2\",option3";
+
+	keys = _gcr_openssh_pub_parse (data, strlen (data),
+	                               on_openssh_pub_parse, test);
+	g_assert_cmpint (keys, ==, 1);
+}
+
+int
+main (int argc, char **argv)
+{
+	g_type_init ();
+	g_test_init (&argc, &argv, NULL);
+	g_set_prgname ("test-gnupg-process");
+
+	g_test_add ("/gcr/openssh/parse_v1_rsa", Test, NULL, setup, test_parse_v1_rsa, teardown);
+	g_test_add ("/gcr/openssh/parse_v2_rsa", Test, NULL, setup, test_parse_v2_rsa, teardown);
+	g_test_add ("/gcr/openssh/parse_v2_dsa", Test, NULL, setup, test_parse_v2_dsa, teardown);
+	g_test_add ("/gcr/openssh/parse_v1_options", Test, NULL, setup, test_parse_v1_options, teardown);
+	g_test_add ("/gcr/openssh/parse_v2_options", Test, NULL, setup, test_parse_v2_options, teardown);
+
+	return egg_tests_run_in_thread_with_loop ();
+}
diff --git a/testing/ssh-example/README b/testing/ssh-example/README
new file mode 100644
index 0000000..f3b240c
--- /dev/null
+++ b/testing/ssh-example/README
@@ -0,0 +1 @@
+The passwords for the private keys are: boooo
diff --git a/testing/ssh-example/id_dsa b/testing/ssh-example/id_dsa
new file mode 100644
index 0000000..ef23625
--- /dev/null
+++ b/testing/ssh-example/id_dsa
@@ -0,0 +1,15 @@
+-----BEGIN DSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,ECBE9F6DAE948508DB1A5E51AF32B5A5
+
+2LW/70yuiadQM/tVWZ4QqxV0JZQQ3Sjqz28Soj5sUKDuKCszTBFyiRKLvI091Z6G
+NsDZ5agRr6t4e6ysne0aiJg1ex7ymx6GyVDI3PmNEstBqrhrxgrHgtfhwR0vVdSf
+go0nrftWdU/tlAd2h2JGQSwngwaW8aDlqG9rA86CYYcXLTyF9xW9ECtJMXohl3x2
+fS7t4vLZCnCP0lj36andZdRHAMwwHHvO1tgCotQdWeNksN4cIE4m3huM1kIDBcIW
+oL4z9qWM3CC4r++Sutn1I9xXpKu86QosKaOJJmObkpwcMagxz+fMvbMDIl9L0GGw
+PMM7d5Rl2Fs7H7z2tK9PnKwIpNCuHIMe29RUr65LJxqeqoEc9zgWKgZuPjarBlTa
+hob+mWJHi1WntSwoG9QE7Hj0FVuaUwf8QSPBvnNZYFZMzFR8SH4FK57yKXvtVoGI
+JFt+4aea0EClJ0X9+zBfS0aAFkwqi+Lbn99OVHhw/DL2XgZa9mjd5xDcMxseh0wQ
+Kqc8VQdOVeyfYOwLQgoiimtlgwdimzrG7UWCBQomEGf50d7HlBVAEqv9MeJCm2bb
+RJ4ERL/hpmxZulshatfwbA==
+-----END DSA PRIVATE KEY-----
diff --git a/testing/ssh-example/id_dsa.pub b/testing/ssh-example/id_dsa.pub
new file mode 100644
index 0000000..1e111d7
--- /dev/null
+++ b/testing/ssh-example/id_dsa.pub
@@ -0,0 +1 @@
+ssh-dss AAAAB3NzaC1kc3MAAACBAL4z+ad0ZJYzMOQuGp00UJ+AijKhrPVUEYLcxBmFQonb/KIlLSWJua4Rl9DB4tDj30Y9c/oApqC4n+FIYlUZMSnxmpvcLF6aeXOiHHPvm0EDYjjyVubyYQWI7CROrrzSc+x++ha3TuJEvF3PlKlZmTKKVYEkZNjwFqYysGyPxPalAAAAFQDtDSEF9Gvnv5fQtSbbsp7j78uVBwAAAIAtNpAg/Mbd/E2241enedB9AxAbJWZ5QYnoPe6/zx5dOmU7+qz8mG6tgvF8F7IgXPabuAKslzTDGS3zgaEhWicDS3CIYik2UR8hXdxfovIEqZKZe7u02FCEoXYCEiFUAdzDGzjI7PswgtEJWWNqKeNis3HmDDha9lMkqz/3fLZGXwAAAIEAiaRPYKZDMoJG+aVZ5A3R/m2gl+mYE2MsjPKXuBKcrZ6ItA9BMe4G/An0/+E3A+DuoGxdeNNMF8U9Dy2N8Sch/Ngtg2E/FBo5geljWobJXd1jxmPtF2WAliYJXDdIt6RBVPGL9H/KSjDmBMsVd42wxVJywawzypklVZjSUuWuBMI= dsa-key example com
diff --git a/testing/ssh-example/id_rsa b/testing/ssh-example/id_rsa
new file mode 100644
index 0000000..b3b32ab
--- /dev/null
+++ b/testing/ssh-example/id_rsa
@@ -0,0 +1,30 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: AES-128-CBC,2166F6D1CF1E7548690D572639503DCF
+
+vWdHuKtNO8dMYBxgQgxjVUiiek6KTZpb0nnm9tRo6KJp08Ce3TCC184LSA+AQ+zJ
+tJ54GTyHaTB4jf+BTn0LjmHT5VSFevmyWCWvQ2UbEJTmOhmEHwVpzJHcB9jVKhYG
+axHMtckUelUXlOmx7YFVU5YTbnIOyXkG8XSARk3j1NSmSGMK7S4vB0I7KpJi76gN
+LpWGFsiYjr6vUdcvjsZUB/CbyJ456N/ND41AERVM0Ecl6vQC8touN/U9+BHHWOAb
++e6I0v2Oje0TEm/qDpq834JJb6auCWmCDQLEe63WeWgceG48DmdyDwJ5sOH0kSxq
+HNGYFRB2PEOaNI4b0keSeXTjxKCxhaFs+krJZaXAfKIzGg1AJ1w9Hb7whFpvwRMv
+SUhfdFkzQW1/fj6qCgRlq78h7rAahoLroPpY9Uhd1qKH4PTpx4oD+nvAIqPN4Ch8
+vVGh+NtiGWNT7HnTG6cWkGZnWUQ4PZ9dkPVSPWsea8dVEHjpbUKGikxIx4D7abbC
+ZA2ifcAMt2rklYWoXVST53qBrKTo2hlpI9c5sn3/A7JBZuE3ljgCZL/TozuKk+ZH
+UzqbxWsGl1u3pMNc8/T8l6PuuKl+D3vczBFkQWlSjRGY3UCnQ+nQEI8zxKes61B9
+y/b1/zNYJiGqNzJG427qd3EGAlt0FS5N/5iFCGXyo80rS34i/UnJkcJcAuIQdqIX
+GKgRrekxepff/mirn58WKIxWobp9y0vOMCZm8lD53wXLCIsbbWCqxbmBttvdr6hM
+UWZ2QTbKn+3KeBm2O8PZGDooP2bhocnBP2tdm/6rC9cYbESikFUjC6IJFxFsRr7z
+Hmwnwxdkb0vJCKXLf4ww48nDODrSu3Fhc6YhRR7uIpt3VswKG1Kfm5xVbjUhu+4S
+rf0Y69sivYFi+mlpr0GD6EpQsZ9nHhd1e6pPmVprBt5s5LbOa+SO8k3iTa+/BDGZ
+Ft8PXG3alEEUYllavubkP5kLnsVZ8C+UNFEeDRzhzWjmcv3er6U1WwTEzgIxos76
+2+leOvtQ5l0wzk0U3fL0c694dOoG0rm1E2jAd6LkZwn7e/15raP9igEB72uM5bea
+ikrd2gd5AEwaDKESErRp+CCcHXJ8C1zaAhgws21qbNegV44AwtDPkYN8ID2DSwUv
+KQZX3hwNc0g3sRGlQ35lpwe7H8qTGwr5l5TPM0eKuNi/V+qmFFjCMEpDkp+/hSSu
+ThpazlWoWUg7qBlopOmTLF7wN1byfwiu5DeF56HE1+W1cPyAzhEkbuuEuzZcaJit
+6aS5pCeq2dGwyNgb65+n/jZiucCXJQToIUZvdlPXfsstABDs//iAARwqjtxmIdoZ
+uyI7tjK9MkRIvHIyCSQRKdhpelDuMiKxORhOiLLJsZ3Wd+Acr14vNk7nHJZcvaX/
+UuF6/iKFsg+smWIsSPE/OtsWJ+4g0jOg5oHphOH57m89cqOQe2njcTWpOSMGdF9P
+u9QrCVOaRnswPpFuNJQrRjte+9HIxn5Qgzw3vr+Me21NW0fvDaaKUrBm4PPPEUFg
+V4/XW2WtotyszoIAMdzUOmLNUGNh1I+GwJY7Mb16jHDQe0RjtGYlDjH3ykj1isAM
+-----END RSA PRIVATE KEY-----
diff --git a/testing/ssh-example/id_rsa.pub b/testing/ssh-example/id_rsa.pub
new file mode 100644
index 0000000..83067d4
--- /dev/null
+++ b/testing/ssh-example/id_rsa.pub
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDCs8z2y0cCPYRAPkq8tAt6FC/kdfnR/p8B2ZoY0oiLNt7kQEwJfexgwLqTxWYd2fSDUSSDPrsqAxZAwLLS/eF04kXiJO2VfqAWFpTLNToERHpFF1yZQe26ELTlNNfna7LqfCRvpNDwu6AqndsT3eFt7DWvBDXbbEiTLW21Z2OFAAH/J2iCFn4c0a8Myf7IaMYcy5GG3mpk39kEO4aNV/67U7kfooek24ObwD0vlXzlsi5VZIUFOIUi0UdkNEMCtUWpfkZ1STUlmwp9HVM7xb7/9PESQKDnZdxpB09S9cIjdpDecpDlMDDEbEUECM1PIas3ndhB7gAN1i2JsPHTcXZ1 rsa-key example com
diff --git a/testing/ssh-example/identity b/testing/ssh-example/identity
new file mode 100644
index 0000000..35bd574
Binary files /dev/null and b/testing/ssh-example/identity differ
diff --git a/testing/ssh-example/identity.pub b/testing/ssh-example/identity.pub
new file mode 100644
index 0000000..503486b
--- /dev/null
+++ b/testing/ssh-example/identity.pub
@@ -0,0 +1 @@
+2048 65537 19574029774826276058535216798260123376543523095248321838931847609905153466056541810037612224715393673871614098429330286659520830571243765643286443579570815080037983898081130875270479278411961605207843971799891833860159372766201922902824211581515042106928142039998651198780602488599726242798484153698322199240326703055839125267280449261588792947133244666304909901315045579230615054415554475861850194097568770065871190731807718592844942425524851665039303855329966512492845780563670061745108336917492850264799573485696060306545465548955817911313021071274638931037011169213563881172297734240201883475566393175838117784693 rsa-key example com



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