diff options
author | Stef Walter <stefw@collabora.co.uk> | 2011-09-13 17:07:28 +0200 |
---|---|---|
committer | Stef Walter <stefw@collabora.co.uk> | 2011-09-27 09:32:40 +0200 |
commit | 1e36c52404938d521ce3cb11632f393689308ac6 (patch) | |
tree | 2b70d1fe3d6c8b4906f2e134c3058fbd9f88ea53 /egg/egg-armor.c | |
parent | ad4fc192b5cf9c51ed73306b43e43fb36747ab22 (diff) |
gcr: Implement recognizing of OpenPGP packets
* Doesn't actually parse keys or anything else about OpenPGP (yet).
* Generalize the PEM code so that it works for PGP armor.
Diffstat (limited to 'egg/egg-armor.c')
-rw-r--r-- | egg/egg-armor.c | 407 |
1 files changed, 407 insertions, 0 deletions
diff --git a/egg/egg-armor.c b/egg/egg-armor.c new file mode 100644 index 00000000..5a4b0476 --- /dev/null +++ b/egg/egg-armor.c @@ -0,0 +1,407 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* egg-openssl.c - OpenSSL compatibility functionality + + Copyright (C) 2007 Stefan Walter + + 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 <stef@memberwebs.com> +*/ + +#include "config.h" + +#include "egg-hex.h" +#include "egg-armor.h" +#include "egg-secure-memory.h" + +#include <gcrypt.h> + +#include <glib.h> + +#include <ctype.h> +#include <string.h> + +/* + * Armor looks like: + * + * -----BEGIN RSA PRIVATE KEY----- + * Proc-Type: 4,ENCRYPTED + * DEK-Info: DES-EDE3-CBC,704CFFD62FBA03E9 + * + * 4AV/g0BiTeb07hzo4/Ct47HGhHEshMhBPGJ843QzuAinpZBbg3OxwPsQsLgoPhJL + * Bg6Oxyz9M4UN1Xlx6Lyo2lRT908mBP6dl/OItLsVArqAzM+e29KHQVNjV1h7xN9F + * u84tOgZftKun+ZkQUOoRvMLLu4yV4CUraks9tgyXquugGba/tbeyj2MYsC8wwSJX + * ................................................................ + * =on29 + * -----END RSA PRIVATE KEY----- + * + * The last line before END is an option OpenPGP armor checksum + */ + +EGG_SECURE_DECLARE (armor); + +#define ARMOR_SUFF "-----" +#define ARMOR_SUFF_L 5 +#define ARMOR_PREF_BEGIN "-----BEGIN " +#define ARMOR_PREF_BEGIN_L 11 +#define ARMOR_PREF_END "-----END " +#define ARMOR_PREF_END_L 9 + +static void +parse_header_lines (const gchar *hbeg, + const gchar *hend, + GHashTable **result) +{ + gchar **lines, **l; + gchar *line, *name, *value; + gchar *copy; + + copy = g_strndup (hbeg, hend - hbeg); + lines = g_strsplit (copy, "\n", 0); + g_free (copy); + + for (l = lines; l && *l; ++l) { + line = *l; + g_strstrip (line); + + /* Look for the break between name: value */ + value = strchr (line, ':'); + if (value == NULL) + continue; + + *value = 0; + value = g_strdup (value + 1); + g_strstrip (value); + + name = g_strdup (line); + g_strstrip (name); + + if (!*result) + *result = egg_armor_headers_new (); + g_hash_table_replace (*result, name, value); + } + + g_strfreev (lines); +} + +static const gchar* +armor_find_begin (const gchar *data, + gsize n_data, + GQuark *type, + const gchar **outer) +{ + const gchar *pref, *suff; + gchar *stype; + + /* Look for a prefix */ + pref = g_strstr_len ((gchar*)data, n_data, ARMOR_PREF_BEGIN); + if (!pref) + return NULL; + + n_data -= (pref - data) + ARMOR_PREF_BEGIN_L; + data = pref + ARMOR_PREF_BEGIN_L; + + /* Look for the end of that begin */ + suff = g_strstr_len ((gchar*)data, n_data, ARMOR_SUFF); + if (!suff) + return NULL; + + /* Make sure on the same line */ + if (memchr (pref, '\n', suff - pref)) + return NULL; + + if (outer) + *outer = pref; + + if (type) { + *type = 0; + pref += ARMOR_PREF_BEGIN_L; + g_assert (suff > pref); + stype = g_alloca (suff - pref + 1); + memcpy (stype, pref, suff - pref); + stype[suff - pref] = 0; + *type = g_quark_from_string (stype); + } + + /* The byte after this ---BEGIN--- */ + return suff + ARMOR_SUFF_L; +} + +static const gchar* +armor_find_end (const gchar *data, + gsize n_data, + GQuark type, + const gchar **outer) +{ + const gchar *stype; + const gchar *pref; + const gchar *line; + gsize n_type; + + /* Look for a prefix */ + pref = g_strstr_len (data, n_data, ARMOR_PREF_END); + if (!pref) + return NULL; + + n_data -= (pref - data) + ARMOR_PREF_END_L; + data = pref + ARMOR_PREF_END_L; + + /* Next comes the type string */ + stype = g_quark_to_string (type); + n_type = strlen (stype); + if (strncmp ((gchar*)data, stype, n_type) != 0) + return NULL; + + n_data -= n_type; + data += n_type; + + /* Next comes the suffix */ + if (strncmp ((gchar*)data, ARMOR_SUFF, ARMOR_SUFF_L) != 0) + return NULL; + + /* + * Check if there's a OpenPGP style armor checksum line. OpenPGP + * does not insist that we validate this line, and is more useful + * for PGP messages, rather than the keys we usually see. + */ + line = memrchr (data, '\n', (pref - 1) - data); + if (line && line[1] == '=') + pref = line; + + if (outer != NULL) { + data += ARMOR_SUFF_L; + if (isspace (data[0])) + data++; + *outer = data; + } + + /* The end of the data */ + return pref; +} + +static gboolean +armor_parse_block (const gchar *data, + gsize n_data, + guchar **decoded, + gsize *n_decoded, + GHashTable **headers) +{ + const gchar *x, *hbeg, *hend; + const gchar *p, *end; + gint state = 0; + guint save = 0; + + g_assert (data); + g_assert (n_data); + + g_assert (decoded); + g_assert (n_decoded); + + p = data; + end = p + n_data; + + hbeg = hend = NULL; + + /* Try and find a pair of blank lines with only white space between */ + while (hend == NULL) { + x = memchr (p, '\n', end - p); + if (!x) + break; + ++x; + while (isspace (*x)) { + /* Found a second line, with only spaces between */ + if (*x == '\n') { + hbeg = data; + hend = x; + break; + /* Found a space between two lines */ + } else { + ++x; + } + } + + /* Try next line */ + p = x; + } + + /* Headers found? */ + if (hbeg && hend) { + data = hend; + n_data = end - data; + } + + *n_decoded = (n_data * 3) / 4 + 1; + if (egg_secure_check (data)) + *decoded = egg_secure_alloc (*n_decoded); + else + *decoded = g_malloc0 (*n_decoded); + g_return_val_if_fail (*decoded, FALSE); + + *n_decoded = g_base64_decode_step (data, n_data, *decoded, &state, &save); + if (!*n_decoded) { + egg_secure_free (*decoded); + return FALSE; + } + + if (headers && hbeg && hend) + parse_header_lines (hbeg, hend, headers); + + return TRUE; +} + +GHashTable* +egg_armor_headers_new (void) +{ + return g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); +} + +guint +egg_armor_parse (gconstpointer data, + gsize n_data, + EggArmorCallback callback, + gpointer user_data) +{ + const gchar *beg, *end; + const gchar *outer_beg, *outer_end; + guint nfound = 0; + guchar *decoded = NULL; + gsize n_decoded = 0; + GHashTable *headers = NULL; + GQuark type; + + g_return_val_if_fail (data, 0); + g_return_val_if_fail (n_data, 0); + + while (n_data > 0) { + + /* This returns the first character after the PEM BEGIN header */ + beg = armor_find_begin ((const gchar*)data, n_data, &type, &outer_beg); + if (beg == NULL) + break; + + g_assert (type); + + /* This returns the character position before the PEM END header */ + end = armor_find_end ((const gchar*)beg, + n_data - ((const gchar*)beg - (const gchar *)data), + type, &outer_end); + if (end == NULL) + break; + + if (beg != end) { + if (armor_parse_block (beg, end - beg, &decoded, &n_decoded, &headers)) { + g_assert (outer_end > outer_beg); + if (callback != NULL) + (callback) (type, + decoded, n_decoded, + outer_beg, outer_end - outer_beg, + headers, user_data); + ++nfound; + egg_secure_free (decoded); + if (headers) + g_hash_table_remove_all (headers); + } + } + + /* Try for another block */ + end += ARMOR_SUFF_L; + n_data -= (const gchar*)end - (const gchar*)data; + data = end; + } + + if (headers) + g_hash_table_destroy (headers); + + return nfound; +} + +static void +append_each_header (gpointer key, gpointer value, gpointer user_data) +{ + GString *string = (GString*)user_data; + + g_string_append (string, (gchar*)key); + g_string_append (string, ": "); + g_string_append (string, (gchar*)value); + g_string_append_c (string, '\n'); +} + +guchar* +egg_armor_write (const guchar *data, + gsize n_data, + GQuark type, + GHashTable *headers, + gsize *n_result) +{ + GString *string; + gint state, save; + gsize i, length; + gsize n_prefix, estimate; + + g_return_val_if_fail (data || !n_data, NULL); + g_return_val_if_fail (type, NULL); + g_return_val_if_fail (n_result, NULL); + + string = g_string_sized_new (4096); + + /* The prefix */ + g_string_append_len (string, ARMOR_PREF_BEGIN, ARMOR_PREF_BEGIN_L); + g_string_append (string, g_quark_to_string (type)); + g_string_append_len (string, ARMOR_SUFF, ARMOR_SUFF_L); + g_string_append_c (string, '\n'); + + /* The headers */ + if (headers && g_hash_table_size (headers) > 0) { + g_hash_table_foreach (headers, append_each_header, string); + g_string_append_c (string, '\n'); + } + + /* Resize string to fit the base64 data. Algorithm from Glib reference */ + estimate = n_data * 4 / 3 + n_data * 4 / (3 * 65) + 7; + n_prefix = string->len; + g_string_set_size (string, n_prefix + estimate); + + /* The actual base64 data, without line breaks */ + state = save = 0; + length = g_base64_encode_step (data, n_data, FALSE, + string->str + n_prefix, &state, &save); + length += g_base64_encode_close (TRUE, string->str + n_prefix + length, + &state, &save); + + g_assert (length <= estimate); + g_string_set_size (string, n_prefix + length); + + /* + * OpenSSL is absolutely certain that it wants its PEM base64 + * lines to be 64 characters in length. So go through and break + * those lines up. + */ + + for (i = 64; i < length; i += 64) { + g_string_insert_c (string, n_prefix + i, '\n'); + ++length; + ++i; + } + + /* The suffix */ + g_string_append_len (string, ARMOR_PREF_END, ARMOR_PREF_END_L); + g_string_append (string, g_quark_to_string (type)); + g_string_append_len (string, ARMOR_SUFF, ARMOR_SUFF_L); + g_string_append_c (string, '\n'); + + *n_result = string->len; + return (guchar*)g_string_free (string, FALSE); +} |