From 3b76bda50a43a7315e2c8c66bbf9a7d2304cbf73 Mon Sep 17 00:00:00 2001 From: Stef Walter Date: Tue, 13 Sep 2011 08:04:41 +0200 Subject: [PATCH] gcr: Implement OpenSSH public key parser * And add tests for various formats. --- 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 + 18 files changed, 907 insertions(+), 17 deletions(-) create mode 100644 gcr/gcr-openssh.c create mode 100644 gcr/gcr-openssh.h create mode 100644 gcr/tests/files/openssh_keys.pub create mode 100644 gcr/tests/test-openssh.c create mode 100644 testing/ssh-example/README create mode 100644 testing/ssh-example/id_dsa create mode 100644 testing/ssh-example/id_dsa.pub create mode 100644 testing/ssh-example/id_rsa create mode 100644 testing/ssh-example/id_rsa.pub create mode 100644 testing/ssh-example/identity create mode 100644 testing/ssh-example/identity.pub 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 diff --git a/gcr/Makefile.am b/gcr/Makefile.am index 3f88bde..78a5448 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 +/* 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 + */ + +#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 + +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 + */ + +#if !defined (__GCR_H_INSIDE__) && !defined (GCR_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __GCR_OPENSSH_H__ +#define __GCR_OPENSSH_H__ + +#include + +#include + +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 c3d6acf..7d61483 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 +*/ + +#include "config.h" + +#include "gcr/gcr.h" +#include "gcr/gcr-openssh.h" + +#include "egg/egg-testing.h" + +#include +#include +#include + +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 0000000000000000000000000000000000000000..35bd5746ab0a1681cadcfa2feea4347de5de07c4 GIT binary patch literal 988 zcmV<210(!XQ%E3CQb|@pR7D_5MOh$5NlZl`Mo&^rK~x|yE-?xK0{{R30000800;n^ z4q^%xeffT2k?{1|T%cFkp*O)9R-`e$%7#MhM8Z7v;AHeBDLH)sS@6Rg>a)^yR~sR>tn_HiS|`O*%<8Jn*qzO)O!CVHoKd(w+@RClqPkJD0?upsdbtJ6JfQW*w#}3ELzeZ{8Ri^)`Py75DKJ0Y>5di=J z0000Nb97~9Ky!3uW-fGSWp`n0bY*fbY+-J*dzt7fND;PEeLGQ8q1d^6!!Xji(El(V zJQz7o6n}l~(8!Qz- zcB>$m@NlSsMIo)>a?-HWacV8j14tx5c&>3)8!~mR480~gXaMm4cqBcKK&M7_NO%Si zd3QLZ%Vb)ZH5_3-&q(`f#kKkla7<-@l@P?WGwFFqOB?o70)H3oAN%h6L?(D64{MhZ5(z<#zC4QMAgt-L}&~$uQC# zQ$vic-ud-IQur|40&FLFl3j-Khov?5c}*j${a)l-h0&(5B>N5JNssd!V5n{fAdAnF z$^_;5XJay75!s5r$0Rm;s2_WPw2h>?3=574F@4m8oOO6Qtk5;C~LP7 z>`szc|w8f0$=#gq$ Kv7ul4s(YCT(b&}h literal 0 HcmV?d00001 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 -- 2.7.4