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 : }
|