/* -*- 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, . Author: Stef Walter */ #include "config.h" #include "egg-hex.h" #include "egg-armor.h" #include "egg-secure-memory.h" #include #include #include #include /* * 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 const gchar * const ORDERED_HEADERS[] = { "Proc-Type", "DEK-Info", NULL }; 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; const gchar *at; gchar *stype; gsize len; /* Look for a prefix */ pref = g_strstr_len ((gchar*)data, n_data, ARMOR_PREF_BEGIN); if (!pref) return NULL; len = n_data - ((pref - data) + ARMOR_PREF_BEGIN_L); at = pref + ARMOR_PREF_BEGIN_L; /* Look for the end of that begin */ suff = g_strstr_len ((gchar *)at, len, 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; const gchar *at; gsize len; gsize n_type; /* Look for a prefix */ pref = g_strstr_len (data, n_data, ARMOR_PREF_END); if (!pref) return NULL; len = n_data - ((pref - data) + ARMOR_PREF_END_L); at = pref + ARMOR_PREF_END_L; /* Next comes the type string */ stype = g_quark_to_string (type); n_type = strlen (stype); if (n_type > len || strncmp ((gchar*)at, stype, n_type) != 0) return NULL; len -= n_type; at += n_type; /* Next comes the suffix */ if (ARMOR_SUFF_L > len || strncmp ((gchar *)at, 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 = g_strrstr_len (data, (pref - 1) - data, "\n"); if (line && line[1] == '=') pref = line; if (outer != NULL) { at += ARMOR_SUFF_L; if (isspace (at[0])) at++; *outer = at; } /* 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 (GBytes *data, EggArmorCallback callback, gpointer user_data) { const gchar *beg, *end, *at; const gchar *outer_beg, *outer_end; guint nfound = 0; guchar *decoded = NULL; gsize n_decoded = 0; GHashTable *headers = NULL; GBytes *dec; GBytes *outer; GQuark type; gsize n_at; g_return_val_if_fail (data != NULL, 0); at = g_bytes_get_data (data, &n_at); while (n_at > 0) { /* This returns the first character after the PEM BEGIN header */ beg = armor_find_begin (at, n_at, &type, &outer_beg); if (beg == NULL) break; g_assert (type); /* This returns the character position before the PEM END header */ end = armor_find_end (beg, n_at - (beg - at), 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); dec = g_bytes_new_with_free_func (decoded, n_decoded, egg_secure_free, decoded); if (callback != NULL) { outer = g_bytes_new_with_free_func (outer_beg, outer_end - outer_beg, (GDestroyNotify)g_bytes_unref, g_bytes_ref (data)); (callback) (type, dec, outer, headers, user_data); g_bytes_unref (outer); } g_bytes_unref (dec); ++nfound; if (headers) g_hash_table_remove_all (headers); } } /* Try for another block */ end += ARMOR_SUFF_L; n_at -= (const gchar*)end - (const gchar*)at; at = end; } if (headers) g_hash_table_destroy (headers); return nfound; } static void append_each_header (gconstpointer key, gconstpointer value, gpointer user_data) { GString *string = (GString*)user_data; if (g_strv_contains (ORDERED_HEADERS, (const gchar *) key)) return; g_string_append (string, (const gchar *)key); g_string_append (string, ": "); g_string_append (string, (const 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; gchar *value; 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. Some must come in a specific order. */ for (i = 0; ORDERED_HEADERS[i] != NULL; i++) { value = g_hash_table_lookup (headers, ORDERED_HEADERS[i]); if (value != NULL) g_string_append_printf (string, "%s: %s\n", ORDERED_HEADERS[i], value); } /* And the rest we output in any arbitrary order. */ if (headers && g_hash_table_size (headers) > 0) { g_hash_table_foreach (headers, (GHFunc) 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); }