[egg] Complete implementation of openssl sytle PEM writing
authorStef Walter <stefw@collabora.co.uk>
Tue, 8 Mar 2011 17:10:19 +0000 (18:10 +0100)
committerStef Walter <stefw@collabora.co.uk>
Tue, 8 Mar 2011 17:10:19 +0000 (18:10 +0100)
The openssl PEM parser is particularly fragile, so write some
stringent tests to check.

egg/egg-openssl.c
egg/tests/test-openssl.c

index bf9c487..001e61e 100644 (file)
@@ -284,8 +284,6 @@ egg_openssl_pem_parse (const guchar *data, gsize n_data,
        return nfound;
 }
 
-#ifdef UNTESTED_CODE
-
 static void
 append_each_header (gpointer key, gpointer value, gpointer user_data)
 {
@@ -303,7 +301,8 @@ egg_openssl_pem_write (const guchar *data, gsize n_data, GQuark type,
 {
        GString *string;
        gint state, save;
-       gsize length, n_prefix;
+       gsize i, length;
+       gsize n_prefix, estimate;
 
        g_return_val_if_fail (data || !n_data, NULL);
        g_return_val_if_fail (type, NULL);
@@ -324,18 +323,33 @@ egg_openssl_pem_write (const guchar *data, gsize n_data, GQuark type,
        }
 
        /* Resize string to fit the base64 data. Algorithm from Glib reference */
-       length = n_data * 4 / 3 + n_data * 4 / (3 * 72) + 7;
+       estimate = n_data * 4 / 3 + n_data * 4 / (3 * 65) + 7;
        n_prefix = string->len;
-       g_string_set_size (string, n_prefix + length);
+       g_string_set_size (string, n_prefix + estimate);
 
-       /* The actual base64 data */
+       /* The actual base64 data, without line breaks */
        state = save = 0;
-       length = g_base64_encode_step (data, n_data, TRUE,
-                                      string->str + string->len, &state, &save);
+       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_c (string, '\n');
        g_string_append_len (string, PEM_PREF_END, PEM_PREF_END_L);
        g_string_append (string, g_quark_to_string (type));
        g_string_append_len (string, PEM_SUFF, PEM_SUFF_L);
@@ -345,8 +359,6 @@ egg_openssl_pem_write (const guchar *data, gsize n_data, GQuark type,
        return (guchar*)g_string_free (string, FALSE);
 }
 
-#endif /* UNTESTED_CODE */
-
 /* ----------------------------------------------------------------------------
  * DEFINITIONS
  */
index bf8134a..18f9fd9 100644 (file)
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
+#include <unistd.h>
 
 EGG_SECURE_GLIB_DEFINITIONS ();
 
 typedef struct {
        guchar *input;
        gsize n_input;
+       GQuark reftype;
        guchar *refenc;
        guchar *refdata;
        gsize n_refenc;
@@ -76,6 +78,9 @@ parse_reference (GQuark type, const guchar *data, gsize n_data,
        gboolean res;
        const gchar *dekinfo;
 
+       g_assert (type);
+       test->reftype = type;
+
        g_assert ("no data in PEM callback" && data != NULL);
        g_assert ("no data in PEM callback" && n_data > 0);
        test->refenc = g_memdup (data, n_data);
@@ -129,6 +134,29 @@ test_write_reference (Test *test, gconstpointer unused)
        g_assert ("data doesn't match input" && memcmp (encrypted, test->refenc, n_encrypted) == 0);
 }
 
+static void
+test_write_exactly_same (Test *test, gconstpointer unused)
+{
+       guchar *result;
+       gsize n_result;
+       guint num;
+
+       num = egg_openssl_pem_parse (test->input, test->n_input, parse_reference, test);
+       g_assert ("couldn't PEM block in reference data" && num == 1);
+
+       result = egg_openssl_pem_write (test->refenc, test->n_refenc, test->reftype,
+                                       test->refheaders, &n_result);
+
+       /*
+        * Yes sirrr. Openssl's parser is so fragile, that we have to make it
+        * character for character identical. This includes line breaks, whitespace
+        * and line endings.
+        */
+
+       egg_assert_cmpmem (test->input, test->n_input, ==, result, n_result);
+       g_free (result);
+}
+
 /* 29 bytes (prime number, so block length has bad chance of matching */
 static const guchar *TEST_DATA = (guchar*)"ABCDEFGHIJKLMNOPQRSTUVWXYZ123";
 const gsize TEST_DATA_L = 29;
@@ -175,6 +203,7 @@ main (int argc, char **argv)
 
        g_test_add ("/openssl/parse_reference", Test, NULL, setup, test_parse_reference, teardown);
        g_test_add ("/openssl/write_reference", Test, NULL, setup, test_write_reference, teardown);
+       g_test_add ("/openssl/write_exactly_same", Test, NULL, setup, test_write_exactly_same, teardown);
        g_test_add ("/openssl/openssl_roundtrip", Test, NULL, setup, test_openssl_roundtrip, teardown);
 
        return g_test_run ();