LCOV - code coverage report
Current view: top level - egg - egg-armor.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 96.0 % 173 166
Test Date: 2024-05-07 18:02:03 Functions: 88.9 % 9 8

            Line data    Source code
       1              : /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
       2              : /* egg-openssl.c - OpenSSL compatibility functionality
       3              : 
       4              :    Copyright (C) 2007 Stefan Walter
       5              : 
       6              :    The Gnome Keyring Library is free software; you can redistribute it and/or
       7              :    modify it under the terms of the GNU Library General Public License as
       8              :    published by the Free Software Foundation; either version 2 of the
       9              :    License, or (at your option) any later version.
      10              : 
      11              :    The Gnome Keyring Library is distributed in the hope that it will be useful,
      12              :    but WITHOUT ANY WARRANTY; without even the implied warranty of
      13              :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      14              :    Library General Public License for more details.
      15              : 
      16              :    You should have received a copy of the GNU Library General Public
      17              :    License along with the Gnome Library; see the file COPYING.LIB.  If not,
      18              :    <http://www.gnu.org/licenses/>.
      19              : 
      20              :    Author: Stef Walter <stef@memberwebs.com>
      21              : */
      22              : 
      23              : #include "config.h"
      24              : 
      25              : #include "egg-hex.h"
      26              : #include "egg-armor.h"
      27              : #include "egg-secure-memory.h"
      28              : 
      29              : #include <gcrypt.h>
      30              : 
      31              : #include <glib.h>
      32              : 
      33              : #include <ctype.h>
      34              : #include <string.h>
      35              : 
      36              : /*
      37              :  * Armor looks like:
      38              :  *
      39              :  *      -----BEGIN RSA PRIVATE KEY-----
      40              :  *      Proc-Type: 4,ENCRYPTED
      41              :  *      DEK-Info: DES-EDE3-CBC,704CFFD62FBA03E9
      42              :  *
      43              :  *      4AV/g0BiTeb07hzo4/Ct47HGhHEshMhBPGJ843QzuAinpZBbg3OxwPsQsLgoPhJL
      44              :  *      Bg6Oxyz9M4UN1Xlx6Lyo2lRT908mBP6dl/OItLsVArqAzM+e29KHQVNjV1h7xN9F
      45              :  *      u84tOgZftKun+ZkQUOoRvMLLu4yV4CUraks9tgyXquugGba/tbeyj2MYsC8wwSJX
      46              :  *      ................................................................
      47              :  *      =on29
      48              :  *      -----END RSA PRIVATE KEY-----
      49              :  *
      50              :  * The last line before END is an option OpenPGP armor checksum
      51              :  */
      52              : 
      53            0 : EGG_SECURE_DECLARE (armor);
      54              : 
      55              : #define ARMOR_SUFF          "-----"
      56              : #define ARMOR_SUFF_L        5
      57              : #define ARMOR_PREF_BEGIN    "-----BEGIN "
      58              : #define ARMOR_PREF_BEGIN_L  11
      59              : #define ARMOR_PREF_END      "-----END "
      60              : #define ARMOR_PREF_END_L    9
      61              : 
      62              : static const gchar * const ORDERED_HEADERS[] = { "Proc-Type", "DEK-Info", NULL };
      63              : 
      64              : static void
      65           18 : parse_header_lines (const gchar *hbeg,
      66              :                     const gchar *hend,
      67              :                     GHashTable **result)
      68              : {
      69              :         gchar **lines, **l;
      70              :         gchar *line, *name, *value;
      71              :         gchar *copy;
      72              : 
      73           18 :         copy = g_strndup (hbeg, hend - hbeg);
      74           18 :         lines = g_strsplit (copy, "\n", 0);
      75           18 :         g_free (copy);
      76              : 
      77           90 :         for (l = lines; l && *l; ++l) {
      78           72 :                 line = *l;
      79           72 :                 g_strstrip (line);
      80              : 
      81              :                 /* Look for the break between name: value */
      82           72 :                 value = strchr (line, ':');
      83           72 :                 if (value == NULL)
      84           36 :                         continue;
      85              : 
      86           36 :                 *value = 0;
      87           36 :                 value = g_strdup (value + 1);
      88           36 :                 g_strstrip (value);
      89              : 
      90           36 :                 name = g_strdup (line);
      91           36 :                 g_strstrip (name);
      92              : 
      93           36 :                 if (!*result)
      94           18 :                         *result = egg_armor_headers_new ();
      95           36 :                 g_hash_table_replace (*result, name, value);
      96              :         }
      97              : 
      98           18 :         g_strfreev (lines);
      99           18 : }
     100              : 
     101              : static const gchar*
     102          204 : armor_find_begin (const gchar *data,
     103              :                   gsize n_data,
     104              :                   GQuark *type,
     105              :                   const gchar **outer)
     106              : {
     107              :         const gchar *pref, *suff;
     108              :         const gchar *at;
     109              :         gchar *stype;
     110              :         gsize len;
     111              : 
     112              :         /* Look for a prefix */
     113          204 :         pref = g_strstr_len ((gchar*)data, n_data, ARMOR_PREF_BEGIN);
     114          204 :         if (!pref)
     115           29 :                 return NULL;
     116              : 
     117          175 :         len = n_data - ((pref - data) + ARMOR_PREF_BEGIN_L);
     118          175 :         at = pref + ARMOR_PREF_BEGIN_L;
     119              : 
     120              :         /* Look for the end of that begin */
     121          175 :         suff = g_strstr_len ((gchar *)at, len, ARMOR_SUFF);
     122          175 :         if (!suff)
     123            1 :                 return NULL;
     124              : 
     125              :         /* Make sure on the same line */
     126          174 :         if (memchr (pref, '\n', suff - pref))
     127            0 :                 return NULL;
     128              : 
     129          174 :         if (outer)
     130          174 :                 *outer = pref;
     131              : 
     132          174 :         if (type) {
     133          174 :                 *type = 0;
     134          174 :                 pref += ARMOR_PREF_BEGIN_L;
     135          174 :                 g_assert (suff > pref);
     136          174 :                 stype = g_alloca (suff - pref + 1);
     137          174 :                 memcpy (stype, pref, suff - pref);
     138          174 :                 stype[suff - pref] = 0;
     139          174 :                 *type = g_quark_from_string (stype);
     140              :         }
     141              : 
     142              :         /* The byte after this ---BEGIN--- */
     143          174 :         return suff + ARMOR_SUFF_L;
     144              : }
     145              : 
     146              : static const gchar*
     147          174 : armor_find_end (const gchar *data,
     148              :                 gsize n_data,
     149              :                 GQuark type,
     150              :                 const gchar **outer)
     151              : {
     152              :         const gchar *stype;
     153              :         const gchar *pref;
     154              :         const gchar *line;
     155              :         const gchar *at;
     156              :         gsize len;
     157              :         gsize n_type;
     158              : 
     159              :         /* Look for a prefix */
     160          174 :         pref = g_strstr_len (data, n_data, ARMOR_PREF_END);
     161          174 :         if (!pref)
     162            1 :                 return NULL;
     163              : 
     164          173 :         len = n_data - ((pref - data) + ARMOR_PREF_END_L);
     165          173 :         at = pref + ARMOR_PREF_END_L;
     166              : 
     167              :         /* Next comes the type string */
     168          173 :         stype = g_quark_to_string (type);
     169          173 :         n_type = strlen (stype);
     170          173 :         if (n_type > len || strncmp ((gchar*)at, stype, n_type) != 0)
     171            1 :                 return NULL;
     172              : 
     173          172 :         len -= n_type;
     174          172 :         at += n_type;
     175              : 
     176              :         /* Next comes the suffix */
     177          172 :         if (ARMOR_SUFF_L > len || strncmp ((gchar *)at, ARMOR_SUFF, ARMOR_SUFF_L) != 0)
     178            2 :                 return NULL;
     179              : 
     180              :         /*
     181              :          * Check if there's a OpenPGP style armor checksum line. OpenPGP
     182              :          * does not insist that we validate this line, and is more useful
     183              :          * for PGP messages, rather than the keys we usually see.
     184              :          */
     185          170 :         line = g_strrstr_len (data, (pref - 1) - data, "\n");
     186          170 :         if (line && line[1] == '=')
     187            1 :                 pref = line;
     188              : 
     189          170 :         if (outer != NULL) {
     190          170 :                 at += ARMOR_SUFF_L;
     191          170 :                 if (isspace (at[0]))
     192          170 :                         at++;
     193          170 :                 *outer = at;
     194              :         }
     195              : 
     196              :         /* The end of the data */
     197          170 :         return pref;
     198              : }
     199              : 
     200              : static gboolean
     201          170 : armor_parse_block (const gchar *data,
     202              :                    gsize n_data,
     203              :                    guchar **decoded,
     204              :                    gsize *n_decoded,
     205              :                    GHashTable **headers)
     206              : {
     207              :         const gchar *x, *hbeg, *hend;
     208              :         const gchar *p, *end;
     209          170 :         gint state = 0;
     210          170 :         guint save = 0;
     211              : 
     212          170 :         g_assert (data);
     213          170 :         g_assert (n_data);
     214              : 
     215          170 :         g_assert (decoded);
     216          170 :         g_assert (n_decoded);
     217              : 
     218          170 :         p = data;
     219          170 :         end = p + n_data;
     220              : 
     221          170 :         hbeg = hend = NULL;
     222              : 
     223              :         /* Try and find a pair of blank lines with only white space between */
     224         3778 :         while (hend == NULL) {
     225         3760 :                 x = memchr (p, '\n', end - p);
     226         3760 :                 if (!x)
     227          152 :                         break;
     228         3608 :                 ++x;
     229         3608 :                 while (isspace (*x)) {
     230              :                         /* Found a second line, with only spaces between */
     231           18 :                         if (*x == '\n') {
     232           18 :                                 hbeg = data;
     233           18 :                                 hend = x;
     234           18 :                                 break;
     235              :                         /* Found a space between two lines */
     236              :                         } else {
     237            0 :                                 ++x;
     238              :                         }
     239              :                 }
     240              : 
     241              :                 /* Try next line */
     242         3608 :                 p = x;
     243              :         }
     244              : 
     245              :         /* Headers found? */
     246          170 :         if (hbeg && hend) {
     247           18 :                 data = hend;
     248           18 :                 n_data = end - data;
     249              :         }
     250              : 
     251          170 :         *n_decoded = (n_data * 3) / 4 + 1;
     252          170 :         if (egg_secure_check (data))
     253            0 :                 *decoded = egg_secure_alloc (*n_decoded);
     254              :         else
     255          170 :                 *decoded = g_malloc0 (*n_decoded);
     256          170 :         g_return_val_if_fail (*decoded, FALSE);
     257              : 
     258          170 :         *n_decoded = g_base64_decode_step (data, n_data, *decoded, &state, &save);
     259          170 :         if (!*n_decoded) {
     260            0 :                 egg_secure_free (*decoded);
     261            0 :                 return FALSE;
     262              :         }
     263              : 
     264          170 :         if (headers && hbeg && hend)
     265           18 :                 parse_header_lines (hbeg, hend, headers);
     266              : 
     267          170 :         return TRUE;
     268              : }
     269              : 
     270              : GHashTable*
     271           22 : egg_armor_headers_new (void)
     272              : {
     273           22 :         return g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
     274              : }
     275              : 
     276              : guint
     277           34 : egg_armor_parse (GBytes *data,
     278              :                  EggArmorCallback callback,
     279              :                  gpointer user_data)
     280              : {
     281              :         const gchar *beg, *end, *at;
     282              :         const gchar *outer_beg, *outer_end;
     283           34 :         guint nfound = 0;
     284           34 :         guchar *decoded = NULL;
     285           34 :         gsize n_decoded = 0;
     286           34 :         GHashTable *headers = NULL;
     287              :         GBytes *dec;
     288              :         GBytes *outer;
     289              :         GQuark type;
     290              :         gsize n_at;
     291              : 
     292           34 :         g_return_val_if_fail (data != NULL, 0);
     293           34 :         at = g_bytes_get_data (data, &n_at);
     294              : 
     295          204 :         while (n_at > 0) {
     296              : 
     297              :                 /* This returns the first character after the PEM BEGIN header */
     298          204 :                 beg = armor_find_begin (at, n_at, &type, &outer_beg);
     299          204 :                 if (beg == NULL)
     300           30 :                         break;
     301              : 
     302          174 :                 g_assert (type);
     303              : 
     304              :                 /* This returns the character position before the PEM END header */
     305          174 :                 end = armor_find_end (beg, n_at - (beg - at), type, &outer_end);
     306          174 :                 if (end == NULL)
     307            4 :                         break;
     308              : 
     309          170 :                 if (beg != end) {
     310          170 :                         if (armor_parse_block (beg, end - beg, &decoded, &n_decoded, &headers)) {
     311          170 :                                 g_assert (outer_end > outer_beg);
     312          170 :                                 dec = g_bytes_new_with_free_func (decoded, n_decoded,
     313              :                                                                     egg_secure_free, decoded);
     314          170 :                                 if (callback != NULL) {
     315          170 :                                         outer = g_bytes_new_with_free_func (outer_beg, outer_end - outer_beg,
     316              :                                                                             (GDestroyNotify)g_bytes_unref,
     317          170 :                                                                             g_bytes_ref (data));
     318          170 :                                         (callback) (type, dec, outer, headers, user_data);
     319          170 :                                         g_bytes_unref (outer);
     320              :                                 }
     321          170 :                                 g_bytes_unref (dec);
     322          170 :                                 ++nfound;
     323          170 :                                 if (headers)
     324           18 :                                         g_hash_table_remove_all (headers);
     325              :                         }
     326              :                 }
     327              : 
     328              :                 /* Try for another block */
     329          170 :                 end += ARMOR_SUFF_L;
     330          170 :                 n_at -= (const gchar*)end - (const gchar*)at;
     331          170 :                 at = end;
     332              :         }
     333              : 
     334           34 :         if (headers)
     335           18 :                 g_hash_table_destroy (headers);
     336              : 
     337           34 :         return nfound;
     338              : }
     339              : 
     340              : static void
     341            2 : append_each_header (gconstpointer key, gconstpointer value, gpointer user_data)
     342              : {
     343            2 :         GString *string = (GString*)user_data;
     344              : 
     345            2 :         if (g_strv_contains (ORDERED_HEADERS, (const gchar *) key))
     346            2 :                 return;
     347              : 
     348              :         g_string_append (string, (const gchar *)key);
     349            0 :         g_string_append (string, ": ");
     350              :         g_string_append (string, (const gchar *)value);
     351              :         g_string_append_c (string, '\n');
     352              : }
     353              : 
     354              : guchar*
     355            1 : egg_armor_write (const guchar *data,
     356              :                  gsize n_data,
     357              :                  GQuark type,
     358              :                  GHashTable *headers,
     359              :                  gsize *n_result)
     360              : {
     361              :         GString *string;
     362              :         gint state, save;
     363              :         gsize i, length;
     364              :         gsize n_prefix, estimate;
     365              :         gchar *value;
     366              : 
     367            1 :         g_return_val_if_fail (data || !n_data, NULL);
     368            1 :         g_return_val_if_fail (type, NULL);
     369            1 :         g_return_val_if_fail (n_result, NULL);
     370              : 
     371            1 :         string = g_string_sized_new (4096);
     372              : 
     373              :         /* The prefix */
     374              :         g_string_append_len (string, ARMOR_PREF_BEGIN, ARMOR_PREF_BEGIN_L);
     375            1 :         g_string_append (string, g_quark_to_string (type));
     376              :         g_string_append_len (string, ARMOR_SUFF, ARMOR_SUFF_L);
     377              :         g_string_append_c (string, '\n');
     378              : 
     379              :         /* The headers. Some must come in a specific order. */
     380            3 :         for (i = 0; ORDERED_HEADERS[i] != NULL; i++) {
     381            2 :                 value = g_hash_table_lookup (headers, ORDERED_HEADERS[i]);
     382            2 :                 if (value != NULL)
     383            2 :                         g_string_append_printf (string,
     384              :                                                 "%s: %s\n",
     385            2 :                                                 ORDERED_HEADERS[i],
     386              :                                                 value);
     387              :         }
     388              : 
     389              :         /* And the rest we output in any arbitrary order. */
     390            1 :         if (headers && g_hash_table_size (headers) > 0) {
     391            1 :                 g_hash_table_foreach (headers, (GHFunc) append_each_header, string);
     392              :                 g_string_append_c (string, '\n');
     393              :         }
     394              : 
     395              :         /* Resize string to fit the base64 data. Algorithm from Glib reference */
     396            1 :         estimate = n_data * 4 / 3 + n_data * 4 / (3 * 65) + 7;
     397            1 :         n_prefix = string->len;
     398            1 :         g_string_set_size (string, n_prefix + estimate);
     399              : 
     400              :         /* The actual base64 data, without line breaks */
     401            1 :         state = save = 0;
     402            1 :         length = g_base64_encode_step (data, n_data, FALSE,
     403            1 :                                        string->str + n_prefix, &state, &save);
     404            1 :         length += g_base64_encode_close (TRUE, string->str + n_prefix + length,
     405              :                                          &state, &save);
     406              : 
     407            1 :         g_assert (length <= estimate);
     408            1 :         g_string_set_size (string, n_prefix + length);
     409              : 
     410              :         /*
     411              :          * OpenSSL is absolutely certain that it wants its PEM base64
     412              :          * lines to be 64 characters in length. So go through and break
     413              :          * those lines up.
     414              :          */
     415              : 
     416           13 :         for (i = 64; i < length; i += 64) {
     417           12 :                 g_string_insert_c (string, n_prefix + i, '\n');
     418           12 :                 ++length;
     419           12 :                 ++i;
     420              :         }
     421              : 
     422              :         /* The suffix */
     423              :         g_string_append_len (string, ARMOR_PREF_END, ARMOR_PREF_END_L);
     424            1 :         g_string_append (string, g_quark_to_string (type));
     425              :         g_string_append_len (string, ARMOR_SUFF, ARMOR_SUFF_L);
     426              :         g_string_append_c (string, '\n');
     427              : 
     428            1 :         *n_result = string->len;
     429            1 :         return (guchar*)g_string_free (string, FALSE);
     430              : }
        

Generated by: LCOV version 2.0-1