Browse code

Add crypto_pem_{encode,decode}()

Needed for tls-crypt-v2, but isolated enough to be reviewed as a separate
patch.

The encode API allocates memory, because it fits our typical gc-oriented
code pattern and the caller does not have to do multiple calls or
calculations to determine the required destination buffer size.

The decode API does not allocate memory, because the required destination
buffer is always smaller than the input buffer (so is easy to manage by
the caller) and does not force the caller to use the heap.

Signed-off-by: Steffan Karger <steffan.karger@fox-it.com>
Acked-by: Antonio Quartulli <antonio@openvpn.net>
Message-Id: <20180722100645.5813-1-steffan@karger.me>
URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg17284.html
Signed-off-by: Gert Doering <gert@greenie.muc.de>

Steffan Karger authored on 2018/07/22 19:06:45
Showing 5 changed files
... ...
@@ -36,6 +36,7 @@
36 36
 #include "crypto_mbedtls.h"
37 37
 #endif
38 38
 #include "basic.h"
39
+#include "buffer.h"
39 40
 
40 41
 /* TLS uses a tag of 128 bytes, let's do the same for OpenVPN */
41 42
 #define OPENVPN_AEAD_TAG_LENGTH 16
... ...
@@ -105,6 +106,34 @@ void show_available_digests(void);
105 105
 
106 106
 void show_available_engines(void);
107 107
 
108
+/**
109
+ * Encode binary data as PEM.
110
+ *
111
+ * @param name      The name to use in the PEM header/footer.
112
+ * @param dst       Destination buffer for PEM-encoded data.  Must be a valid
113
+ *                  pointer to an uninitialized buffer structure.  Iff this
114
+ *                  function returns true, the buffer will contain memory
115
+ *                  allocated through the supplied gc.
116
+ * @param src       Source buffer.
117
+ * @param gc        The garbage collector to use when allocating memory for dst.
118
+ *
119
+ * @return true iff PEM encode succeeded.
120
+ */
121
+bool crypto_pem_encode(const char *name, struct buffer *dst,
122
+                       const struct buffer *src, struct gc_arena *gc);
123
+
124
+/**
125
+ * Decode a PEM buffer to binary data.
126
+ *
127
+ * @param name      The name expected in the PEM header/footer.
128
+ * @param dst       Destination buffer for decoded data.
129
+ * @param src       Source buffer (PEM data).
130
+ *
131
+ * @return true iff PEM decode succeeded.
132
+ */
133
+bool crypto_pem_decode(const char *name, struct buffer *dst,
134
+                       const struct buffer *src);
135
+
108 136
 /*
109 137
  *
110 138
  * Random number functions, used in cases where we want
... ...
@@ -44,11 +44,13 @@
44 44
 #include "otime.h"
45 45
 #include "misc.h"
46 46
 
47
+#include <mbedtls/base64.h>
47 48
 #include <mbedtls/des.h>
48 49
 #include <mbedtls/error.h>
49 50
 #include <mbedtls/md5.h>
50 51
 #include <mbedtls/cipher.h>
51 52
 #include <mbedtls/havege.h>
53
+#include <mbedtls/pem.h>
52 54
 
53 55
 #include <mbedtls/entropy.h>
54 56
 
... ...
@@ -229,6 +231,79 @@ show_available_engines(void)
229 229
            "available\n");
230 230
 }
231 231
 
232
+bool
233
+crypto_pem_encode(const char *name, struct buffer *dst,
234
+                  const struct buffer *src, struct gc_arena *gc)
235
+{
236
+    /* 1000 chars is the PEM line length limit (+1 for tailing NUL) */
237
+    char header[1000+1] = { 0 };
238
+    char footer[1000+1] = { 0 };
239
+
240
+    if (!openvpn_snprintf(header, sizeof(header), "-----BEGIN %s-----\n", name))
241
+    {
242
+        return false;
243
+    }
244
+    if (!openvpn_snprintf(footer, sizeof(footer), "-----END %s-----\n", name))
245
+    {
246
+        return false;
247
+    }
248
+
249
+    size_t out_len = 0;
250
+    if (MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL !=
251
+            mbedtls_pem_write_buffer(header, footer, BPTR(src), BLEN(src),
252
+                                     NULL, 0, &out_len))
253
+    {
254
+        return false;
255
+    }
256
+
257
+    *dst = alloc_buf_gc(out_len, gc);
258
+    if (!mbed_ok(mbedtls_pem_write_buffer(header, footer, BPTR(src), BLEN(src),
259
+                                          BPTR(dst), BCAP(dst), &out_len))
260
+        || !buf_inc_len(dst, out_len))
261
+    {
262
+        CLEAR(*dst);
263
+        return false;
264
+    }
265
+
266
+    return true;
267
+}
268
+
269
+bool
270
+crypto_pem_decode(const char *name, struct buffer *dst,
271
+                  const struct buffer *src)
272
+{
273
+    /* 1000 chars is the PEM line length limit (+1 for tailing NUL) */
274
+    char header[1000+1] = { 0 };
275
+    char footer[1000+1] = { 0 };
276
+
277
+    if (*(BLAST(src)) != '\0')
278
+    {
279
+        msg(M_WARN, "PEM decode error: source buffer not null-terminated");
280
+        return false;
281
+    }
282
+    if (!openvpn_snprintf(header, sizeof(header), "-----BEGIN %s-----", name))
283
+    {
284
+        return false;
285
+    }
286
+    if (!openvpn_snprintf(footer, sizeof(footer), "-----END %s-----", name))
287
+    {
288
+        return false;
289
+    }
290
+
291
+    size_t use_len = 0;
292
+    mbedtls_pem_context ctx = { 0 };
293
+    bool ret = mbed_ok(mbedtls_pem_read_buffer(&ctx, header, footer, BPTR(src),
294
+                                               NULL, 0, &use_len));
295
+    if (ret && !buf_write(dst, ctx.buf, ctx.buflen))
296
+    {
297
+        ret = false;
298
+        msg(M_WARN, "PEM decode error: destination buffer too small");
299
+    }
300
+
301
+    mbedtls_pem_free(&ctx);
302
+    return ret;
303
+}
304
+
232 305
 /*
233 306
  *
234 307
  * Random number functions, used in cases where we want
... ...
@@ -387,6 +387,88 @@ show_available_engines(void)
387 387
 #endif
388 388
 }
389 389
 
390
+
391
+bool
392
+crypto_pem_encode(const char *name, struct buffer *dst,
393
+                  const struct buffer *src, struct gc_arena *gc)
394
+{
395
+    bool ret = false;
396
+    BIO *bio = BIO_new(BIO_s_mem());
397
+    if (!bio || !PEM_write_bio(bio, name, "", BPTR(src), BLEN(src)))
398
+    {
399
+        ret = false;
400
+        goto cleanup;
401
+    }
402
+
403
+    BUF_MEM *bptr;
404
+    BIO_get_mem_ptr(bio, &bptr);
405
+
406
+    *dst = alloc_buf_gc(bptr->length, gc);
407
+    ASSERT(buf_write(dst, bptr->data, bptr->length));
408
+
409
+    ret = true;
410
+cleanup:
411
+    if (!BIO_free(bio))
412
+    {
413
+        ret = false;;
414
+    }
415
+
416
+    return ret;
417
+}
418
+
419
+bool
420
+crypto_pem_decode(const char *name, struct buffer *dst,
421
+                  const struct buffer *src)
422
+{
423
+    bool ret = false;
424
+
425
+    BIO *bio = BIO_new_mem_buf((char *)BPTR(src), BLEN(src));
426
+    if (!bio)
427
+    {
428
+        crypto_msg(M_FATAL, "Cannot open memory BIO for PEM decode");
429
+    }
430
+
431
+    char *name_read = NULL;
432
+    char *header_read = NULL;
433
+    uint8_t *data_read = NULL;
434
+    long data_read_len = 0;
435
+    if (!PEM_read_bio(bio, &name_read, &header_read, &data_read,
436
+                      &data_read_len))
437
+    {
438
+        dmsg(D_CRYPT_ERRORS, "%s: PEM decode failed", __func__);
439
+        goto cleanup;
440
+    }
441
+
442
+    if (strcmp(name, name_read))
443
+    {
444
+        dmsg(D_CRYPT_ERRORS,
445
+             "%s: unexpected PEM name (got '%s', expected '%s')",
446
+             __func__, name_read, name);
447
+        goto cleanup;
448
+    }
449
+
450
+    uint8_t *dst_data = buf_write_alloc(dst, data_read_len);
451
+    if (!dst_data)
452
+    {
453
+        dmsg(D_CRYPT_ERRORS, "%s: dst too small (%i, needs %li)", __func__,
454
+             BCAP(dst), data_read_len);
455
+        goto cleanup;
456
+    }
457
+    memcpy(dst_data, data_read, data_read_len);
458
+
459
+    ret = true;
460
+cleanup:
461
+    OPENSSL_free(name_read);
462
+    OPENSSL_free(header_read);
463
+    OPENSSL_free(data_read);
464
+    if (!BIO_free(bio))
465
+    {
466
+        ret = false;;
467
+    }
468
+
469
+    return ret;
470
+}
471
+
390 472
 /*
391 473
  *
392 474
  * Random number functions, used in cases where we want
... ...
@@ -6,7 +6,7 @@ if HAVE_LD_WRAP_SUPPORT
6 6
 check_PROGRAMS += argv_testdriver buffer_testdriver
7 7
 endif
8 8
 
9
-check_PROGRAMS += packet_id_testdriver tls_crypt_testdriver
9
+check_PROGRAMS += crypto_testdriver packet_id_testdriver tls_crypt_testdriver
10 10
 
11 11
 TESTS = $(check_PROGRAMS)
12 12
 
... ...
@@ -31,6 +31,20 @@ buffer_testdriver_SOURCES = test_buffer.c mock_msg.c \
31 31
 	$(openvpn_srcdir)/buffer.c \
32 32
 	$(openvpn_srcdir)/platform.c
33 33
 
34
+crypto_testdriver_CFLAGS  = @TEST_CFLAGS@ \
35
+	-I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \
36
+	$(OPTIONAL_CRYPTO_CFLAGS)
37
+crypto_testdriver_LDFLAGS = @TEST_LDFLAGS@ \
38
+	$(OPTIONAL_CRYPTO_LIBS)
39
+crypto_testdriver_SOURCES = test_crypto.c mock_msg.c \
40
+	$(openvpn_srcdir)/buffer.c \
41
+	$(openvpn_srcdir)/crypto.c \
42
+	$(openvpn_srcdir)/crypto_mbedtls.c \
43
+	$(openvpn_srcdir)/crypto_openssl.c \
44
+	$(openvpn_srcdir)/otime.c \
45
+	$(openvpn_srcdir)/packet_id.c \
46
+	$(openvpn_srcdir)/platform.c
47
+
34 48
 packet_id_testdriver_CFLAGS  = @TEST_CFLAGS@ \
35 49
 	-I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \
36 50
 	$(OPTIONAL_CRYPTO_CFLAGS)
37 51
new file mode 100644
... ...
@@ -0,0 +1,88 @@
0
+/*
1
+ *  OpenVPN -- An application to securely tunnel IP networks
2
+ *             over a single UDP port, with support for SSL/TLS-based
3
+ *             session authentication and key exchange,
4
+ *             packet encryption, packet authentication, and
5
+ *             packet compression.
6
+ *
7
+ *  Copyright (C) 2016-2018 Fox Crypto B.V. <openvpn@fox-it.com>
8
+ *
9
+ *  This program is free software; you can redistribute it and/or modify
10
+ *  it under the terms of the GNU General Public License version 2
11
+ *  as published by the Free Software Foundation.
12
+ *
13
+ *  This program is distributed in the hope that it will be useful,
14
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
+ *  GNU General Public License for more details.
17
+ *
18
+ *  You should have received a copy of the GNU General Public License along
19
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
20
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
21
+ */
22
+
23
+#ifdef HAVE_CONFIG_H
24
+#include "config.h"
25
+#elif defined(_MSC_VER)
26
+#include "config-msvc.h"
27
+#endif
28
+
29
+#include "syshead.h"
30
+
31
+#include <stdio.h>
32
+#include <unistd.h>
33
+#include <stdlib.h>
34
+#include <stdarg.h>
35
+#include <string.h>
36
+#include <setjmp.h>
37
+#include <cmocka.h>
38
+
39
+#include "crypto.h"
40
+
41
+#include "mock_msg.h"
42
+
43
+static const char testtext[] = "Dummy text to test PEM encoding";
44
+
45
+static void
46
+crypto_pem_encode_decode_loopback(void **state) {
47
+    struct gc_arena gc = gc_new();
48
+    struct buffer src_buf;
49
+    buf_set_read(&src_buf, (void *)testtext, sizeof(testtext));
50
+
51
+    uint8_t dec[sizeof(testtext)];
52
+    struct buffer dec_buf;
53
+    buf_set_write(&dec_buf, dec, sizeof(dec));
54
+
55
+    struct buffer pem_buf;
56
+
57
+    assert_true(crypto_pem_encode("TESTKEYNAME", &pem_buf, &src_buf, &gc));
58
+    assert_true(BLEN(&src_buf) < BLEN(&pem_buf));
59
+
60
+    /* Wrong key name */
61
+    assert_false(crypto_pem_decode("WRONGNAME", &dec_buf, &pem_buf));
62
+
63
+    assert_true(crypto_pem_decode("TESTKEYNAME", &dec_buf, &pem_buf));
64
+    assert_int_equal(BLEN(&src_buf), BLEN(&dec_buf));
65
+    assert_memory_equal(BPTR(&src_buf), BPTR(&dec_buf), BLEN(&src_buf));
66
+
67
+    gc_free(&gc);
68
+}
69
+
70
+int
71
+main(void) {
72
+    const struct CMUnitTest tests[] = {
73
+        cmocka_unit_test(crypto_pem_encode_decode_loopback),
74
+    };
75
+
76
+#if defined(ENABLE_CRYPTO_OPENSSL)
77
+    OpenSSL_add_all_algorithms();
78
+#endif
79
+
80
+    int ret = cmocka_run_group_tests_name("crypto tests", tests, NULL, NULL);
81
+
82
+#if defined(ENABLE_CRYPTO_OPENSSL)
83
+    EVP_cleanup();
84
+#endif
85
+
86
+    return ret;
87
+}