Skip to contentSkip to conversation

Client-specific tls-crypt keys (--tls-crypt-v2)

PATCH In review Steffan Karger 5 participants 94 messages Dec 8, 2017 – Oct 25, 2018 Page 1 of 2

lore
Share & tools
lore
#0 lore raw

cover Client-specific tls-crypt keys (--tls-crypt-v2)

Hi,

The following patch set adds support for client-specific tls-crypt keys.

For a rationale, description and specification see patch 2/10, which adds all that to doc/tls-crypt-v2.txt.

This set is also available as a branch in my github fork:
https://github.com/syzzer/openvpn/tree/tls-crypt-v2-preview4

-Steffan

#1 lore raw

PATCH 1/10 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.

---
 src/openvpn/crypto_backend.h           | 30 +++++++++++
 src/openvpn/crypto_mbedtls.c           | 74 +++++++++++++++++++++++++++
 src/openvpn/crypto_openssl.c           | 82 ++++++++++++++++++++++++++++++
 tests/unit_tests/openvpn/Makefile.am   | 16 +++++-
 tests/unit_tests/openvpn/test_crypto.c | 92 ++++++++++++++++++++++++++++++++++
 5 files changed, 293 insertions(+), 1 deletion(-)
 create mode 100644 tests/unit_tests/openvpn/test_crypto.c
src/openvpn/crypto_backend.h +30 -0
index 567fd9b..83e14c8 100644
@@ -36,6 +36,7 @@
 #include "crypto_mbedtls.h"
 #endif
 #include "basic.h"
+#include "buffer.h"
 
 /* TLS uses a tag of 128 bytes, let's do the same for OpenVPN */
 #define OPENVPN_AEAD_TAG_LENGTH 16
@@ -105,6 +106,35 @@ void show_available_digests(void);
 
 void show_available_engines(void);
 
+/**
+ * Encode binary data as PEM
+ *
+ * @param name      The name to use in the PEM header/footer
+ * @param dst       Destination buffer for PEM-encoded data.  Must be a valid
+ *                  pointer to an uninitialized buffer structure.  Iff this
+ *                  function returns true, the buffer will contain memory
+ *                  allocated through the supplied gc.
+ * @param src       Source buffer
+ * @param gc        The garbage collector to use when allocating memory for
+ *                  dst.
+ *
+ * @return true iff PEM encode succeeded.
+ */
+bool crypto_pem_encode(const char *name, struct buffer *dst,
+                       const struct buffer *src, struct gc_arena *gc);
+
+/**
+ * Decode a PEM buffer to binary data
+ *
+ * @param name      The name expected in the PEM header/footer
+ * @param dst       Destination buffer for decoded data
+ * @param src       Source buffer (PEM data)
+ *
+ * @return true iff PEM decode succeeded.
+ */
+bool crypto_pem_decode(const char *name, struct buffer *dst,
+                       const struct buffer *src);
+
 /*
  *
  * Random number functions, used in cases where we want
src/openvpn/crypto_mbedtls.c +74 -0

91 lines not on this page · raw message

src/openvpn/crypto_openssl.c +82 -0

88 lines not on this page · raw message

tests/unit_tests/openvpn/Makefile.am +15 -1
index 23d758b..d100c21 100644
@@ -6,7 +6,7 @@ if HAVE_LD_WRAP_SUPPORT
 check_PROGRAMS += argv_testdriver buffer_testdriver
 endif
 
-check_PROGRAMS += packet_id_testdriver tls_crypt_testdriver
+check_PROGRAMS += crypto_testdriver packet_id_testdriver tls_crypt_testdriver
 
 TESTS = $(check_PROGRAMS)
 
@@ -29,6 +29,20 @@ buffer_testdriver_SOURCES = test_buffer.c mock_msg.c \
 	$(openvpn_srcdir)/buffer.c \
 	$(openvpn_srcdir)/platform.c
 
+crypto_testdriver_CFLAGS  = @TEST_CFLAGS@ \
+	-I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \
+	$(OPTIONAL_CRYPTO_CFLAGS)
+crypto_testdriver_LDFLAGS = @TEST_LDFLAGS@ \
+	$(OPTIONAL_CRYPTO_LIBS)
+crypto_testdriver_SOURCES = test_crypto.c mock_msg.c \
+	$(openvpn_srcdir)/buffer.c \
+	$(openvpn_srcdir)/crypto.c \
+	$(openvpn_srcdir)/crypto_mbedtls.c \
+	$(openvpn_srcdir)/crypto_openssl.c \
+	$(openvpn_srcdir)/otime.c \
+	$(openvpn_srcdir)/packet_id.c \
+	$(openvpn_srcdir)/platform.c
+
 packet_id_testdriver_CFLAGS  = @TEST_CFLAGS@ \
 	-I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \
 	$(OPTIONAL_CRYPTO_CFLAGS)
tests/unit_tests/openvpn/test_crypto.c new file +92 -0

92 lines not on this page · raw message

Signature · 1 line
2.7.4
#2 lore raw

Re: [Openvpn-devel] [PATCH 01/10] Add crypto_pem_{encode,decode}()

Hi Steffan,

Quoted 35 lines · Steffan Karger

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.

---
 src/openvpn/crypto_backend.h           | 30 +++++++++++
 src/openvpn/crypto_mbedtls.c           | 74 +++++++++++++++++++++++++++
 src/openvpn/crypto_openssl.c           | 82 ++++++++++++++++++++++++++++++
 tests/unit_tests/openvpn/Makefile.am   | 16 +++++-
 tests/unit_tests/openvpn/test_crypto.c | 92 ++++++++++++++++++++++++++++++++++
 5 files changed, 293 insertions(+), 1 deletion(-)
 create mode 100644 tests/unit_tests/openvpn/test_crypto.c
src/openvpn/crypto_backend.h +1 -0
index 567fd9b..83e14c8 100644
@@ -36,6 +36,7 @@
 #include "crypto_mbedtls.h"
 #endif
 #include "basic.h"
+#include "buffer.h"
 
 /* TLS uses a tag of 128 bytes, let's do the same for OpenVPN */
 #define OPENVPN_AEAD_TAG_LENGTH 16
@@ -105,6 +106,35 @@ void show_available_digests(void);
 

src/openvpn/crypto_backend.h void show_available_digests(void); · line 116

Quoted 10 lines
src/openvpn/crypto_backend.h +8 -0
 void show_available_engines(void);
 
+/**
+ * Encode binary data as PEM
+ *
+ * @param name      The name to use in the PEM header/footer
+ * @param dst       Destination buffer for PEM-encoded data.  Must be a valid
+ *                  pointer to an uninitialized buffer structure.  Iff this
+ *                  function returns true, the buffer will contain memory
+ *                  allocated through the supplied gc.

minor: I see the current style is inconsistent wrt having a '.' at the end of each doxygen line. Maybe we should decide what to do and stick to that ;P But does not need to be changed in this patch of course.

Quoted 65 lines
src/openvpn/crypto_backend.h +21 -0
+ * @param src       Source buffer
+ * @param gc        The garbage collector to use when allocating memory for
+ *                  dst.
+ *
+ * @return true iff PEM encode succeeded.
+ */
+bool crypto_pem_encode(const char *name, struct buffer *dst,
+                       const struct buffer *src, struct gc_arena *gc);
+
+/**
+ * Decode a PEM buffer to binary data
+ *
+ * @param name      The name expected in the PEM header/footer
+ * @param dst       Destination buffer for decoded data
+ * @param src       Source buffer (PEM data)
+ *
+ * @return true iff PEM decode succeeded.
+ */
+bool crypto_pem_decode(const char *name, struct buffer *dst,
+                       const struct buffer *src);
+
 /*
  *
  * Random number functions, used in cases where we want
src/openvpn/crypto_mbedtls.c +21 -0
index 8fa03da..1e86854 100644
@@ -44,11 +44,13 @@
 #include "otime.h"
 #include "misc.h"
 
+#include <mbedtls/base64.h>
 #include <mbedtls/des.h>
 #include <mbedtls/error.h>
 #include <mbedtls/md5.h>
 #include <mbedtls/cipher.h>
 #include <mbedtls/havege.h>
+#include <mbedtls/pem.h>
 
 #include <mbedtls/entropy.h>
 
@@ -229,6 +231,78 @@ show_available_engines(void)
            "available\n");
 }
 
+bool
+crypto_pem_encode(const char *name, struct buffer *dst,
+                  const struct buffer *src, struct gc_arena *gc)
+{
+    /* 1000 chars is the PEM line length limit (+1 for tailing NUL) */
+    char header[1000+1] = { 0 };
+    char footer[1000+1] = { 0 };
+
+    if (!openvpn_snprintf(header, sizeof(header), "-----BEGIN %s-----\n", name))
+    {
+        return false;
+    }
+    if (!openvpn_snprintf(footer, sizeof(footer), "-----END %s-----\n", name))
+    {
+        return false;
+    }
+
+    size_t out_len = 0;
+    if (MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL !=

src/openvpn/crypto_mbedtls.c show_available_engines(void) · line 262

Quoted 10 lines
src/openvpn/crypto_mbedtls.c +10 -0
+            mbedtls_pem_write_buffer(header, footer, BPTR(src), BLEN(src),
+                                     NULL, 0, &out_len))
+    {
+        return false;
+    }
+
+    *dst = alloc_buf_gc(out_len, gc);
+    if (!mbed_ok(mbedtls_pem_write_buffer(header, footer, BPTR(src), BLEN(src),
+                                          BPTR(dst), BCAP(dst), &out_len))
+        || !buf_inc_len(dst, out_len))

Isn't in the spec of this function to keep the buffer uninitialized when returning false?
Not a big deal because the buffer area was allocated via gc so it can't be leaked.

Don't you think that if buf_inc_len() fails (can this really happen?) the buffer should better be reset?

Quoted 51 lines
src/openvpn/crypto_mbedtls.c +43 -0
+    {
+        return false;
+    }
+
+    return true;
+}
+
+bool
+crypto_pem_decode(const char *name, struct buffer *dst,
+                  const struct buffer *src)
+{
+    /* 1000 chars is the PEM line length limit (+1 for tailing NUL) */
+    char header[1000+1] = { 0 };
+    char footer[1000+1] = { 0 };
+
+    if (*(BLAST(src)) != '\0')
+    {
+        msg(M_WARN, "PEM decode error: source buffer not null-terminated");
+        return false;
+    }
+    if (!openvpn_snprintf(header, sizeof(header), "-----BEGIN %s-----", name))
+    {
+        return false;
+    }
+    if (!openvpn_snprintf(footer, sizeof(footer), "-----END %s-----", name))
+    {
+        return false;
+    }
+
+    size_t use_len = 0;
+    mbedtls_pem_context ctx = { 0 };
+    bool ret = mbed_ok(mbedtls_pem_read_buffer(&ctx, header, footer, BPTR(src),
+                                               NULL, 0, &use_len));
+    if (ret && !buf_write(dst, ctx.buf, ctx.buflen))
+    {
+        ret = false;
+        msg(M_WARN, "PEM decode error: destination buffer too small");
+    }
+
+    mbedtls_pem_free(&ctx);
+    return ret;
+}
+
 /*
  *
  * Random number functions, used in cases where we want
src/openvpn/crypto_openssl.c +0 -0
index 20a519e..49d3aeb 100644

src/openvpn/crypto_openssl.c show_available_engines(void) · line 396

Quoted 10 lines
src/openvpn/crypto_openssl.c +7 -0
@@ -387,6 +387,88 @@ show_available_engines(void)
 #endif
 }
 
+
+bool
+crypto_pem_encode(const char *name, struct buffer *dst,
+                  const struct buffer *src, struct gc_arena *gc)
+{
+    bool ret = false;
+    BIO *bio = BIO_new(BIO_s_mem());

can we assume bio is always initialized with non-NULL here?

src/openvpn/crypto_openssl.c show_available_engines(void) · line 407

Quoted 11 lines
src/openvpn/crypto_openssl.c +11 -0
+    if (!PEM_write_bio(bio, name, "", BPTR(src), BLEN(src)))
+    {
+        ret = false;
+        goto cleanup;
+    }
+
+    BUF_MEM *bptr;
+    BIO_get_mem_ptr(bio, &bptr);
+
+    *dst = alloc_buf_gc(bptr->length, gc);
+    ASSERT(buf_write(dst, bptr->data, bptr->length));

why using an ASSERT() here and none in the mbedtls counterpart?

If there is no special reason I (personally) think that these helper functions should not use ASSERT() (unless something really bad is happening).
Or do you think that a failure here indicates a general memory problem?

Quoted 108 lines
src/openvpn/crypto_openssl.c +64 -0
+
+    ret = true;
+cleanup:
+    if (!BIO_free(bio))
+    {
+        ret = false;;
+    }
+
+    return ret;
+}
+
+bool
+crypto_pem_decode(const char *name, struct buffer *dst,
+                  const struct buffer *src)
+{
+    bool ret = false;
+    BIO *bio;
+
+    if (!(bio = BIO_new_mem_buf((char *)BPTR(src), BLEN(src))))
+    {
+        crypto_msg(M_FATAL, "Cannot open memory BIO for PEM decode");
 
+    }
+
+    char *name_read = NULL;
+    char *header_read = NULL;
+    uint8_t *data_read = NULL;
+    long data_read_len = 0;
+    if (!PEM_read_bio(bio, &name_read, &header_read, &data_read,
+                      &data_read_len))
+    {
+        dmsg(D_CRYPT_ERRORS, "%s: PEM decode failed", __func__);
+        goto cleanup;
+    }
+
+    if (strcmp(name, name_read))
+    {
+        dmsg(D_CRYPT_ERRORS,
+             "%s: unexpected PEM name (got '%s', expected '%s')",
+             __func__, name_read, name);
+        goto cleanup;
+    }
+
+    uint8_t *dst_data = buf_write_alloc(dst, data_read_len);
+    if (!dst_data)
+    {
+        dmsg(D_CRYPT_ERRORS, "%s: dst too small (%i, needs %li)", __func__,
+             BCAP(dst), data_read_len);
+        goto cleanup;
+    }
+    memcpy(dst_data, data_read, data_read_len);
+
+    ret = true;
+cleanup:
+    OPENSSL_free(name_read);
+    OPENSSL_free(header_read);
+    OPENSSL_free(data_read);
+    if (!BIO_free(bio))
+    {
+        ret = false;;
+    }
+
+    return ret;
+}
+
 /*
  *

* Random number functions, used in cases where we want

tests/unit_tests/openvpn/Makefile.am +15 -1
index 23d758b..d100c21 100644
@@ -6,7 +6,7 @@ if HAVE_LD_WRAP_SUPPORT
 check_PROGRAMS += argv_testdriver buffer_testdriver
 endif
 
-check_PROGRAMS += packet_id_testdriver tls_crypt_testdriver
+check_PROGRAMS += crypto_testdriver packet_id_testdriver tls_crypt_testdriver
 
 TESTS = $(check_PROGRAMS)
 
@@ -29,6 +29,20 @@ buffer_testdriver_SOURCES = test_buffer.c mock_msg.c \
 	$(openvpn_srcdir)/buffer.c \
 	$(openvpn_srcdir)/platform.c
 
+crypto_testdriver_CFLAGS  = @TEST_CFLAGS@ \
+	-I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \
+	$(OPTIONAL_CRYPTO_CFLAGS)
+crypto_testdriver_LDFLAGS = @TEST_LDFLAGS@ \
+	$(OPTIONAL_CRYPTO_LIBS)
+crypto_testdriver_SOURCES = test_crypto.c mock_msg.c \
+	$(openvpn_srcdir)/buffer.c \
+	$(openvpn_srcdir)/crypto.c \
+	$(openvpn_srcdir)/crypto_mbedtls.c \
+	$(openvpn_srcdir)/crypto_openssl.c \
+	$(openvpn_srcdir)/otime.c \
+	$(openvpn_srcdir)/packet_id.c \
+	$(openvpn_srcdir)/platform.c
+
 packet_id_testdriver_CFLAGS  = @TEST_CFLAGS@ \
 	-I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \
 	$(OPTIONAL_CRYPTO_CFLAGS)
tests/unit_tests/openvpn/test_crypto.c new file +0 -0
new file mode 100644
index 0000000..62d5b3f

tests/unit_tests/openvpn/test_crypto.c line 9

Quoted 9 lines
tests/unit_tests/openvpn/test_crypto.c new file +9 -0
@@ -0,0 +1,92 @@
+/*
+ *  OpenVPN -- An application to securely tunnel IP networks
+ *             over a single UDP port, with support for SSL/TLS-based
+ *             session authentication and key exchange,
+ *             packet encryption, packet authentication, and
+ *             packet compression.
+ *
+ *  Copyright (C) 2016-2017 Fox Crypto B.V. <[email protected]...>
+ *

this should be updated, time flies :)

Quoted 25 lines
tests/unit_tests/openvpn/test_crypto.c new file +25 -0
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#elif defined(_MSC_VER)
+#include "config-msvc.h"
+#endif
+
+#include "syshead.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>

tests/unit_tests/openvpn/test_crypto.c line 44

Quoted 10 lines
tests/unit_tests/openvpn/test_crypto.c new file +10 -0
+#include <stdarg.h>
+#include <string.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include "crypto.h"
+
+#include "mock_msg.h"
+
+int script_security = 0; /* Avoid including misc.c */

I tried commenting it and I don't see any warning. Which include chain should lead to its declaration?

tests/unit_tests/openvpn/test_crypto.c line 52

Quoted 8 lines
tests/unit_tests/openvpn/test_crypto.c new file +8 -0
+
+static const char testtext[] = "Dummy text to test PEM encoding";
+
+/**
+ * Check that packet replays are accepted when CO_IGNORE_PACKET_ID is set. This
+ * is used for the first control channel packet that arrives, because we don't
+ * know the packet ID yet.
+ */

Is the comment above a copy/paste incident? or there is something hidden in this unit test?

Quoted 9 lines
tests/unit_tests/openvpn/test_crypto.c new file +9 -0
+static void
+crypto_pem_encode_decode_loopback(void **state) {
+    struct gc_arena gc = gc_new();
+    struct buffer src_buf;
+    buf_set_read(&src_buf, (void *)testtext, sizeof(testtext));
+
+    uint8_t dec[sizeof(testtext)];
+    struct buffer dec_buf;
+    buf_set_write(&dec_buf, dec, sizeof(dec));

tests/unit_tests/openvpn/test_crypto.c line 71

Quoted 10 lines
tests/unit_tests/openvpn/test_crypto.c new file +10 -0
+
+    struct buffer pem_buf;
+
+    assert_true(crypto_pem_encode("TESTKEYNAME", &pem_buf, &src_buf, &gc));
+
+    /* Wrong key name */
+    assert_false(crypto_pem_decode("WRONGNAME", &dec_buf, &pem_buf));
+
+    assert_true(crypto_pem_decode("TESTKEYNAME", &dec_buf, &pem_buf));
+

As a final check, wouldn't it be meaningful to compare the content of dec_buf with src_buf to ensure that we properly obtained the original char array?

Another question: do we have any way to verify that pem_buf contains well-formatted PEM data after pem_encode()?
I am asking because if both encode and decode become no-op (because of some bug) we won't be able to realize that.

However, I am not sure we have an easy way to check that..maybe you could use some SSL function that would normally load PEM data?

Quoted 12 lines
tests/unit_tests/openvpn/test_crypto.c new file +11 -0
+    gc_free(&gc);
+}
+
+int
+main(void) {
+    const struct CMUnitTest tests[] = {
+        cmocka_unit_test(crypto_pem_encode_decode_loopback),
+    };
+
+#if defined(ENABLE_CRYPTO_OPENSSL)
+    OpenSSL_add_all_algorithms();

tests/unit_tests/openvpn/test_crypto.c line 92

Quoted 10 lines
tests/unit_tests/openvpn/test_crypto.c new file +10 -0
+#endif
+
+    int ret = cmocka_run_group_tests_name("crypto tests", tests, NULL, NULL);
+
+#if defined(ENABLE_CRYPTO_OPENSSL)
+    EVP_cleanup();
+#endif
+
+    return ret;
+}

Cheers,

Signature · 1 line
Antonio Quartulli
#3 lore raw

Re: [Openvpn-devel] [PATCH 01/10] Add crypto_pem_{encode,decode}()

Hi Antonio,

Thanks for the review!

Quoted 38 lines · Antonio Quartulli
Quoted 37 lines · Steffan Karger

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.

---
 src/openvpn/crypto_backend.h           | 30 +++++++++++
 src/openvpn/crypto_mbedtls.c           | 74 +++++++++++++++++++++++++++
 src/openvpn/crypto_openssl.c           | 82 ++++++++++++++++++++++++++++++
 tests/unit_tests/openvpn/Makefile.am   | 16 +++++-
 tests/unit_tests/openvpn/test_crypto.c | 92 ++++++++++++++++++++++++++++++++++
 5 files changed, 293 insertions(+), 1 deletion(-)
 create mode 100644 tests/unit_tests/openvpn/test_crypto.c
src/openvpn/crypto_backend.h +1 -0
index 567fd9b..83e14c8 100644
@@ -36,6 +36,7 @@
 #include "crypto_mbedtls.h"
 #endif
 #include "basic.h"
+#include "buffer.h"
 
 /* TLS uses a tag of 128 bytes, let's do the same for OpenVPN */
 #define OPENVPN_AEAD_TAG_LENGTH 16
@@ -105,6 +106,35 @@ void show_available_digests(void);
 
 void show_available_engines(void);
 
Quoted 12 lines

src/openvpn/crypto_backend.h void show_available_digests(void); · line 116

Quoted 8 lines
src/openvpn/crypto_backend.h +8 -0
+/**
+ * Encode binary data as PEM
+ *
+ * @param name      The name to use in the PEM header/footer
+ * @param dst       Destination buffer for PEM-encoded data.  Must be a valid
+ *                  pointer to an uninitialized buffer structure.  Iff this
+ *                  function returns true, the buffer will contain memory
+ *                  allocated through the supplied gc.

minor: I see the current style is inconsistent wrt having a '.' at the end of each doxygen line. Maybe we should decide what to do and stick to that ;P But does not need to be changed in this patch of course.

You sound just like my colleague who's pointing out the missing dots in my doxygen :') Any preference from your side? I think ending each line with a dot is best, because that looks best in the generated docs.

Quoted 81 lines
Quoted 65 lines
src/openvpn/crypto_backend.h +21 -0
+ * @param src       Source buffer
+ * @param gc        The garbage collector to use when allocating memory for
+ *                  dst.
+ *
+ * @return true iff PEM encode succeeded.
+ */
+bool crypto_pem_encode(const char *name, struct buffer *dst,
+                       const struct buffer *src, struct gc_arena *gc);
+
+/**
+ * Decode a PEM buffer to binary data
+ *
+ * @param name      The name expected in the PEM header/footer
+ * @param dst       Destination buffer for decoded data
+ * @param src       Source buffer (PEM data)
+ *
+ * @return true iff PEM decode succeeded.
+ */
+bool crypto_pem_decode(const char *name, struct buffer *dst,
+                       const struct buffer *src);
+
 /*
  *
  * Random number functions, used in cases where we want
src/openvpn/crypto_mbedtls.c +21 -0
index 8fa03da..1e86854 100644
@@ -44,11 +44,13 @@
 #include "otime.h"
 #include "misc.h"
 
+#include <mbedtls/base64.h>
 #include <mbedtls/des.h>
 #include <mbedtls/error.h>
 #include <mbedtls/md5.h>
 #include <mbedtls/cipher.h>
 #include <mbedtls/havege.h>
+#include <mbedtls/pem.h>
 
 #include <mbedtls/entropy.h>
 
@@ -229,6 +231,78 @@ show_available_engines(void)
            "available\n");
 }
 
+bool
+crypto_pem_encode(const char *name, struct buffer *dst,
+                  const struct buffer *src, struct gc_arena *gc)
+{
+    /* 1000 chars is the PEM line length limit (+1 for tailing NUL) */
+    char header[1000+1] = { 0 };
+    char footer[1000+1] = { 0 };
+
+    if (!openvpn_snprintf(header, sizeof(header), "-----BEGIN %s-----\n", name))
+    {
+        return false;
+    }
+    if (!openvpn_snprintf(footer, sizeof(footer), "-----END %s-----\n", name))
+    {
+        return false;
+    }
+
+    size_t out_len = 0;
+    if (MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL !=

src/openvpn/crypto_mbedtls.c show_available_engines(void) · line 262

Quoted 10 lines
src/openvpn/crypto_mbedtls.c +10 -0
+            mbedtls_pem_write_buffer(header, footer, BPTR(src), BLEN(src),
+                                     NULL, 0, &out_len))
+    {
+        return false;
+    }
+
+    *dst = alloc_buf_gc(out_len, gc);
+    if (!mbed_ok(mbedtls_pem_write_buffer(header, footer, BPTR(src), BLEN(src),
+                                          BPTR(dst), BCAP(dst), &out_len))
+        || !buf_inc_len(dst, out_len))

Isn't in the spec of this function to keep the buffer uninitialized when returning false?
Not a big deal because the buffer area was allocated via gc so it can't be leaked.

Quoted 2 lines

Don't you think that if buf_inc_len() fails (can this really happen?) the buffer should better be reset?

This should really never happen according to the function specification by mbed TLS, but I'd rather check anyway. We might catch a bug from their side, or better detect an API change.

But yeah, I'll add a CLEAR(*dst) here.

Quoted 54 lines
Quoted 54 lines
src/openvpn/crypto_mbedtls.c +43 -0
+    {
+        return false;
+    }
+
+    return true;
+}
+
+bool
+crypto_pem_decode(const char *name, struct buffer *dst,
+                  const struct buffer *src)
+{
+    /* 1000 chars is the PEM line length limit (+1 for tailing NUL) */
+    char header[1000+1] = { 0 };
+    char footer[1000+1] = { 0 };
+
+    if (*(BLAST(src)) != '\0')
+    {
+        msg(M_WARN, "PEM decode error: source buffer not null-terminated");
+        return false;
+    }
+    if (!openvpn_snprintf(header, sizeof(header), "-----BEGIN %s-----", name))
+    {
+        return false;
+    }
+    if (!openvpn_snprintf(footer, sizeof(footer), "-----END %s-----", name))
+    {
+        return false;
+    }
+
+    size_t use_len = 0;
+    mbedtls_pem_context ctx = { 0 };
+    bool ret = mbed_ok(mbedtls_pem_read_buffer(&ctx, header, footer, BPTR(src),
+                                               NULL, 0, &use_len));
+    if (ret && !buf_write(dst, ctx.buf, ctx.buflen))
+    {
+        ret = false;
+        msg(M_WARN, "PEM decode error: destination buffer too small");
+    }
+
+    mbedtls_pem_free(&ctx);
+    return ret;
+}
+
 /*
  *
  * Random number functions, used in cases where we want
src/openvpn/crypto_openssl.c +0 -0
index 20a519e..49d3aeb 100644
@@ -387,6 +387,88 @@ show_available_engines(void)
 #endif
 }
 
Quoted 9 lines

src/openvpn/crypto_openssl.c show_available_engines(void) · line 396

Quoted 7 lines
src/openvpn/crypto_openssl.c +7 -0
+
+bool
+crypto_pem_encode(const char *name, struct buffer *dst,
+                  const struct buffer *src, struct gc_arena *gc)
+{
+    bool ret = false;
+    BIO *bio = BIO_new(BIO_s_mem());

can we assume bio is always initialized with non-NULL here?

Don't think so. Will fix.

Quoted 14 lines

src/openvpn/crypto_openssl.c show_available_engines(void) · line 407

Quoted 11 lines
src/openvpn/crypto_openssl.c +11 -0
+    if (!PEM_write_bio(bio, name, "", BPTR(src), BLEN(src)))
+    {
+        ret = false;
+        goto cleanup;
+    }
+
+    BUF_MEM *bptr;
+    BIO_get_mem_ptr(bio, &bptr);
+
+    *dst = alloc_buf_gc(bptr->length, gc);
+    ASSERT(buf_write(dst, bptr->data, bptr->length));

why using an ASSERT() here and none in the mbedtls counterpart?

Quoted 4 lines

If there is no special reason I (personally) think that these helper functions should not use ASSERT() (unless something really bad is happening).
Or do you think that a failure here indicates a general memory problem?

Because in the mbedtls part, mbed my change the "outlen" value. While here, we create a buffer of bptr->length bytes and then write bptr->length bytes to it. If that fails, our entire logic (or executable integrity) is broken. It's our code only, and it can not fail.

Quoted 111 lines
Quoted 111 lines
src/openvpn/crypto_openssl.c +64 -0
+
+    ret = true;
+cleanup:
+    if (!BIO_free(bio))
+    {
+        ret = false;;
+    }
+
+    return ret;
+}
+
+bool
+crypto_pem_decode(const char *name, struct buffer *dst,
+                  const struct buffer *src)
+{
+    bool ret = false;
+    BIO *bio;
+
+    if (!(bio = BIO_new_mem_buf((char *)BPTR(src), BLEN(src))))
+    {
+        crypto_msg(M_FATAL, "Cannot open memory BIO for PEM decode");
 
+    }
+
+    char *name_read = NULL;
+    char *header_read = NULL;
+    uint8_t *data_read = NULL;
+    long data_read_len = 0;
+    if (!PEM_read_bio(bio, &name_read, &header_read, &data_read,
+                      &data_read_len))
+    {
+        dmsg(D_CRYPT_ERRORS, "%s: PEM decode failed", __func__);
+        goto cleanup;
+    }
+
+    if (strcmp(name, name_read))
+    {
+        dmsg(D_CRYPT_ERRORS,
+             "%s: unexpected PEM name (got '%s', expected '%s')",
+             __func__, name_read, name);
+        goto cleanup;
+    }
+
+    uint8_t *dst_data = buf_write_alloc(dst, data_read_len);
+    if (!dst_data)
+    {
+        dmsg(D_CRYPT_ERRORS, "%s: dst too small (%i, needs %li)", __func__,
+             BCAP(dst), data_read_len);
+        goto cleanup;
+    }
+    memcpy(dst_data, data_read, data_read_len);
+
+    ret = true;
+cleanup:
+    OPENSSL_free(name_read);
+    OPENSSL_free(header_read);
+    OPENSSL_free(data_read);
+    if (!BIO_free(bio))
+    {
+        ret = false;;
+    }
+
+    return ret;
+}
+
 /*
  *

* Random number functions, used in cases where we want

tests/unit_tests/openvpn/Makefile.am +15 -1
index 23d758b..d100c21 100644
@@ -6,7 +6,7 @@ if HAVE_LD_WRAP_SUPPORT
 check_PROGRAMS += argv_testdriver buffer_testdriver
 endif
 
-check_PROGRAMS += packet_id_testdriver tls_crypt_testdriver
+check_PROGRAMS += crypto_testdriver packet_id_testdriver tls_crypt_testdriver
 
 TESTS = $(check_PROGRAMS)
 
@@ -29,6 +29,20 @@ buffer_testdriver_SOURCES = test_buffer.c mock_msg.c \
 	$(openvpn_srcdir)/buffer.c \
 	$(openvpn_srcdir)/platform.c
 
+crypto_testdriver_CFLAGS  = @TEST_CFLAGS@ \
+	-I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \
+	$(OPTIONAL_CRYPTO_CFLAGS)
+crypto_testdriver_LDFLAGS = @TEST_LDFLAGS@ \
+	$(OPTIONAL_CRYPTO_LIBS)
+crypto_testdriver_SOURCES = test_crypto.c mock_msg.c \
+	$(openvpn_srcdir)/buffer.c \
+	$(openvpn_srcdir)/crypto.c \
+	$(openvpn_srcdir)/crypto_mbedtls.c \
+	$(openvpn_srcdir)/crypto_openssl.c \
+	$(openvpn_srcdir)/otime.c \
+	$(openvpn_srcdir)/packet_id.c \
+	$(openvpn_srcdir)/platform.c
+
 packet_id_testdriver_CFLAGS  = @TEST_CFLAGS@ \
 	-I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \
 	$(OPTIONAL_CRYPTO_CFLAGS)
tests/unit_tests/openvpn/test_crypto.c new file +3 -0
new file mode 100644
index 0000000..62d5b3f
@@ -0,0 +1,92 @@
+/*
+ *  OpenVPN -- An application to securely tunnel IP networks
+ *             over a single UDP port, with support for SSL/TLS-based
Quoted 8 lines

tests/unit_tests/openvpn/test_crypto.c line 9

Quoted 6 lines
tests/unit_tests/openvpn/test_crypto.c new file +6 -0
+ *             session authentication and key exchange,
+ *             packet encryption, packet authentication, and
+ *             packet compression.
+ *
+ *  Copyright (C) 2016-2017 Fox Crypto B.V. <[email protected]...>
+ *

this should be updated, time flies :)

Will do.

Quoted 30 lines
Quoted 30 lines
tests/unit_tests/openvpn/test_crypto.c new file +30 -0
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#elif defined(_MSC_VER)
+#include "config-msvc.h"
+#endif
+
+#include "syshead.h"
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
Quoted 8 lines

tests/unit_tests/openvpn/test_crypto.c line 44

Quoted 5 lines
tests/unit_tests/openvpn/test_crypto.c new file +5 -0
+#include "crypto.h"
+
+#include "mock_msg.h"
+
+int script_security = 0; /* Avoid including misc.c */

I tried commenting it and I don't see any warning. Which include chain should lead to its declaration?

Ah, this is no longer the case, probably because either I messed up or 7/10 is already applied. Either way, I'll remove it.

Quoted 11 lines

tests/unit_tests/openvpn/test_crypto.c line 52

Quoted 8 lines
tests/unit_tests/openvpn/test_crypto.c new file +8 -0
+
+static const char testtext[] = "Dummy text to test PEM encoding";
+
+/**
+ * Check that packet replays are accepted when CO_IGNORE_PACKET_ID is set. This
+ * is used for the first control channel packet that arrives, because we don't
+ * know the packet ID yet.
+ */

Is the comment above a copy/paste incident? or there is something hidden in this unit test?

Woops, definitely a pasto.

Quoted 15 lines
Quoted 15 lines
tests/unit_tests/openvpn/test_crypto.c new file +15 -0
+static void
+crypto_pem_encode_decode_loopback(void **state) {
+    struct gc_arena gc = gc_new();
+    struct buffer src_buf;
+    buf_set_read(&src_buf, (void *)testtext, sizeof(testtext));
+
+    uint8_t dec[sizeof(testtext)];
+    struct buffer dec_buf;
+    buf_set_write(&dec_buf, dec, sizeof(dec));
+
+    struct buffer pem_buf;
+
+    assert_true(crypto_pem_encode("TESTKEYNAME", &pem_buf, &src_buf, &gc));
+
+    /* Wrong key name */
Quoted 8 lines

tests/unit_tests/openvpn/test_crypto.c line 71

Quoted 4 lines
tests/unit_tests/openvpn/test_crypto.c new file +4 -0
+    assert_false(crypto_pem_decode("WRONGNAME", &dec_buf, &pem_buf));
+
+    assert_true(crypto_pem_decode("TESTKEYNAME", &dec_buf, &pem_buf));
+

As a final check, wouldn't it be meaningful to compare the content of dec_buf with src_buf to ensure that we properly obtained the original char array?

Yes, will do.

Quoted 7 lines

Another question: do we have any way to verify that pem_buf contains well-formatted PEM data after pem_encode()?
I am asking because if both encode and decode become no-op (because of some bug) we won't be able to realize that.

However, I am not sure we have an easy way to check that..maybe you could use some SSL function that would normally load PEM data?

That is a bit annoying to test indeed. How about I just verify that this is encoding is not a no-op by adding a check that pem_buf is larger that src_buf?

As for correct PEM en/decoding, I expect that the crypto lib has tested that.

Quoted 16 lines
Quoted 16 lines
tests/unit_tests/openvpn/test_crypto.c new file +16 -0
+    gc_free(&gc);
+}
+
+int
+main(void) {
+    const struct CMUnitTest tests[] = {
+        cmocka_unit_test(crypto_pem_encode_decode_loopback),
+    };
+
+#if defined(ENABLE_CRYPTO_OPENSSL)
+    OpenSSL_add_all_algorithms();
+#endif
+
+    int ret = cmocka_run_group_tests_name("crypto tests", tests, NULL, NULL);
+
+#if defined(ENABLE_CRYPTO_OPENSSL)
Quoted 8 lines

tests/unit_tests/openvpn/test_crypto.c line 92

Quoted 6 lines
tests/unit_tests/openvpn/test_crypto.c new file +5 -0
+    EVP_cleanup();
+#endif
+
+    return ret;
+}

Cheers,

Thanks!

-Steffan

#4 lore raw

Re: [Openvpn-devel] [PATCH 01/10] Add crypto_pem_{encode,decode}()

Hi,

On 22/06/18 14:14, Steffan Karger wrote:
[cut]

Quoted 9 lines
Quoted 9 lines
Quoted 8 lines

+/**
+ * Encode binary data as PEM
+ *

+ * @param name      The name to use in the PEM header/footer
+ * @param dst       Destination buffer for PEM-encoded data.  Must be a valid
+ *                  pointer to an uninitialized buffer structure.  Iff this
+ *                  function returns true, the buffer will contain memory
+ *                  allocated through the supplied gc.
Quoted 8 lines
Quoted 3 lines

minor: I see the current style is inconsistent wrt having a '.' at the end of each doxygen line. Maybe we should decide what to do and stick to that ;P But does not need to be changed in this patch of course.

You sound just like my colleague who's pointing out the missing dots in my doxygen :') Any preference from your side? I think ending each line with a dot is best, because that looks best in the generated docs.

hehe I don't have a strong preference. Let's go for 'always ending with a dot' then.

[cut]

Quoted 19 lines
Quoted 14 lines

src/openvpn/crypto_openssl.c show_available_engines(void) · line 407

Quoted 11 lines
src/openvpn/crypto_openssl.c +11 -0
+    if (!PEM_write_bio(bio, name, "", BPTR(src), BLEN(src)))
+    {
+        ret = false;
+        goto cleanup;
+    }
+
+    BUF_MEM *bptr;
+    BIO_get_mem_ptr(bio, &bptr);
+
+    *dst = alloc_buf_gc(bptr->length, gc);
+    ASSERT(buf_write(dst, bptr->data, bptr->length));

why using an ASSERT() here and none in the mbedtls counterpart?

Quoted 4 lines

If there is no special reason I (personally) think that these helper functions should not use ASSERT() (unless something really bad is happening).
Or do you think that a failure here indicates a general memory problem?

Quoted 4 lines

Because in the mbedtls part, mbed my change the "outlen" value. While here, we create a buffer of bptr->length bytes and then write bptr->length bytes to it. If that fails, our entire logic (or executable integrity) is broken. It's our code only, and it can not fail.

Yeah, makes sense. Thanks for the explanation. My only worries about ASSERTs if when the code can be tricked to trigger them without a real general failure. But this can't be the case.

[cut]

Quoted 5 lines
Quoted 5 lines

Another question: do we have any way to verify that pem_buf contains well-formatted PEM data after pem_encode()?
I am asking because if both encode and decode become no-op (because of some bug) we won't be able to realize that.

Quoted 7 lines
Quoted 2 lines

However, I am not sure we have an easy way to check that..maybe you could use some SSL function that would normally load PEM data?

That is a bit annoying to test indeed. How about I just verify that this is encoding is not a no-op by adding a check that pem_buf is larger that src_buf?

Yeah probably enough.

Quoted 2 lines

As for correct PEM en/decoding, I expect that the crypto lib has tested that.

I agree.

Quoted 1 line

[cut]

Quoted 4 lines
Quoted 2 lines

Cheers,

Thanks!

Thank you!

Signature · 1 line
Antonio Quartulli
#5 lore raw

PATCH 2/10 tls-crypt-v2: add specification to doc/

In reply to Steffan Karger

This is a preliminary description of tls-crypt-v2. It should give a good impression about the reasoning and design behind tls-crypt-v2, but might need some polishing and updating.

---
 doc/tls-crypt-v2.txt | 164 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 164 insertions(+)
 create mode 100644 doc/tls-crypt-v2.txt
doc/tls-crypt-v2.txt new file +164 -0
new file mode 100644
index 0000000..578b2f9
@@ -0,0 +1,164 @@
+Client-specific tls-crypt keys (--tls-crypt-v2)
+===============================================
+
+This document describes the ``--tls-crypt-v2`` option, which enables OpenVPN
+to use client-specific ``--tls-crypt`` keys.
+
+Rationale
+---------
+
+``--tls-auth`` and ``tls-crypt`` use a pre-shared group key, which is shared
+among all clients and servers in an OpenVPN deployment.  If any client or
+server is compromised, the attacker will have access to this shared key, and it
+will no longer provide any security.  To reduce the risk of loosing pre-shared
+keys, ``tls-crypt-v2`` adds the ability to supply each client with a unique
+tls-crypt key.  This allows large organisations and VPN providers to profit
+from the same DoS and TLS stack protection that small deployments can already
+achieve using ``tls-auth`` or ``tls-crypt``.
+
+Also, for ``tls-crypt``, even if all these peers succeed in keeping the key
+secret, the key lifetime is limited to roughly 8000 years, divided by the
+number of clients (see the ``--tls-crypt`` section of the man page).  Using
+client-specific keys, we lift this lifetime requirement to roughly 8000 years
+for each client key (which "Should Be Enough For Everybody (tm)").
+
+
+Introduction
+------------
+
+``tls-crypt-v2`` uses an encrypted cookie mechanism to introduce
+client-specific tls-crypt keys without introducing a lot of server-side state.
+The client-specific key is encrypted using a server key.  The server key is the
+same for all servers in a group.  When a client connects, it first sends the
+encrypted key to the server, such that the server can decrypt the key and all
+messages can thereafter be encrypted using the client-specific key.
+
+A wrapped (encrypted and authenticated) client-specific key can also contain
+metadata.  The metadata is wrapped together with the key, and can be used to
+allow servers to identify clients and/or key validity.  This allows the server
+to abort the connection immediately after receiving the first packet, rather
+than performing an entire TLS handshake.  Aborting the connection this early
+greatly improves the DoS resilience and reduces attack service against
+malicious clients that have the ``tls-crypt`` or ``tls-auth`` key.  This is
+particularly relevant for large deployments (think lost key or disgruntled
+employee) and VPN providers (clients are not trusted).
+
+To allow for a smooth transition, ``tls-crypt-v2`` is designed such that a
+server can enable both ``tls-crypt-v2`` and either ``tls-crypt`` or
+``tls-auth``.  This is achieved by introducing a P_CONTROL_HARD_RESET_CLIENT_V3
+opcode, that indicates that the client wants to use ``tls-crypt-v2`` for the
+current connection.
+
+For an exact specification and more details, read the Implementation section.
+
+
+Implementation
+--------------
+
+When setting up a tls-crypt-v2 group (similar to generating a tls-crypt or
+tls-auth key previously):
+
+1. Generate a tls-crypt-v2 server key using OpenVPN's ``--genkey``.  This key
+   contains 4 512-bit keys, of which we use:
+
+   * the first 256 bits of key 1 as AES-256-CTR encryption key ``Ke``
+   * the first 256 bits of key 2 as HMAC-SHA-256 authentication key ``Ka``
+
+2. Add the tls-crypt-v2 server key to all server configs
+   (``tls-crypt-v2 /path/to/server.key``)
+
+
+When provisioning a client, create a client-specific tls-crypt key:
+
+1. Generate 2048 bits client-specific key ``Kc``
+2. Optionally generate metadata
+3. Create a wrapped client key ``WKc``, using the same nonce-misuse-resistant
+   SIV conruction we use for tls-crypt:
+
+   ``T = HMAC-SHA256(Ka, Kc || metadata)``
+
+   ``IV = 128 most significant bits of T``
+
+   ``WKc = T || AES-256-CTR(Ke, IV, Kc || metadata)``
+
+4. Create a tls-crypt-v2 client key: PEM-encode ``Kc || WKc`` and store in a
+   file, using the header ``-----BEGIN OpenVPN tls-crypt-v2 client key-----``
+   and the footer ``-----END OpenVPN tls-crypt-v2 client key-----``.  (The PEM
+   format is simple, and following PEM allows us to use the crypto lib function
+   for en/decoding.)
+5. Add the tls-crypt-v2 client key to the client config
+   (``tls-crypt-v2 /path/to/client-specific.key``)
+
+
+When setting up the openvpn connection:
+
+1. The client reads the tls-crypt-v2 key from its config, and:
+
+   1. loads ``Kc`` as its tls-crypt key,
+   2. stores ``WKc`` in memory for sending to the server.
+
+2. To start the connection, the client creates a P_CONTROL_HARD_RESET_CLIENT_V3
+   message without payload, wraps it with tls-crypt using ``Kc`` as the key,
+   and appends ``WKc``.  (``WKc`` must not be encrypted, to prevent a
+   chicken-and-egg problem.)
+
+3. The server receives the P_CONTROL_HARD_RESET_CLIENT_V3 message, and
+
+   a. unwraps ``WKc`` and strips ``WKc`` from the message.
+   b. uses unwrapped ``Kc`` to verify the remaining
+      P_CONTROL_HARD_RESET_CLIENT_V3 message's authentication.
+
+   The message is dropped and no error response is sent when either a or b
+   fails (DoS protection).
+
+4. Server optionally checks metadata using a --tls-crypt-v2-verify script
+
+   Metadata could for example contain the users certificate serial, such that
+   the incoming connection can be verified against a CRL, or a notAfter
+   timestamp that limits the key's validity period.
+
+   This allows early abort of connection, *before* we expose any of the
+   notoriously dangerous TLS, X.509 and ASN.1 parsers and thereby reduces the
+   attack surface of the server.
+
+   The metadata is checked *after* the OpenVPN three-way handshake has
+   completed, to prevent DoS attacks.  (That is, once the client has proved to
+   the server that it possesses Kc, by authenticating a packet that contains the
+   session ID picked by the server.)
+
+   RFC: should the server send a 'key rejected' message if the key is e.g.
+   revoked or expired?  That allows for better client-side error reporting, but
+   also reduces the DoS resilience.
+
+6. Client and server use ``Kc`` for (un)wrapping any following control channel
+   messages.
+
+
+Considerations
+--------------
+
+To allow for a smooth transition, the server implementation allows
+``tls-crypt`` or ``tls-auth`` to be used simultaneously with ``tls-crypt-v2``.
+This specification does not allow simultaneously using ``tls-crypt-v2`` and
+connections without any control channel wrapping, because that would break DoS
+resilience.  RFC: should we add an option along the lines of
+--tls-crypt-v2-allow-insecure-fallback to allow admins to enable this anyway?
+It might help with transitioning.
+
+``tls-crypt-v2`` uses fixed crypto algorithms, because:
+
+ * The crypto is used before we can do any negotiation, so the algorithms have
+   to be predefined.
+ * The crypto primitives are chosen conservatively, making problems with these
+   primitives unlikely.
+ * Making anything configurable adds complexity, both in implementation and
+   usage.  We should not add anymore complexity than is absolutely necessary.
+
+Potential ``tls-crypt-v2`` risks:
+
+ * Slightly more work on first connection (``WKc`` unwrap + hard reset unwrap)
+   than with ``tls-crypt`` (hard reset unwrap) or ``tls-auth`` (hard reset auth).
+ * Flexible metadata allow mistakes
+   (So we should make it easy to do it right.  Provide tooling to create client
+   keys based on cert serial + CA fingerprint, provide script that uses CRL (if
+   available) to drop revoked keys.)
Signature · 1 line
2.7.4
#6 lore raw

Re: [Openvpn-devel] [PATCH 02/10] tls-crypt-v2: add specification to doc/

Hi,

Quoted 19 lines · Steffan Karger

This is a preliminary description of tls-crypt-v2. It should give a good impression about the reasoning and design behind tls-crypt-v2, but might need some polishing and updating.

---
 doc/tls-crypt-v2.txt | 164 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 164 insertions(+)
 create mode 100644 doc/tls-crypt-v2.txt
doc/tls-crypt-v2.txt new file +3 -0
new file mode 100644
index 0000000..578b2f9
@@ -0,0 +1,164 @@
+Client-specific tls-crypt keys (--tls-crypt-v2)
+===============================================
+

doc/tls-crypt-v2.txt line 13

Quoted 10 lines
doc/tls-crypt-v2.txt new file +10 -0
+This document describes the ``--tls-crypt-v2`` option, which enables OpenVPN
+to use client-specific ``--tls-crypt`` keys.
+
+Rationale
+---------
+
+``--tls-auth`` and ``tls-crypt`` use a pre-shared group key, which is shared
+among all clients and servers in an OpenVPN deployment.  If any client or
+server is compromised, the attacker will have access to this shared key, and it
+will no longer provide any security.  To reduce the risk of loosing pre-shared

did you really mean "loosing"? or is it a misspelled "losing"?

Quoted 67 lines
doc/tls-crypt-v2.txt new file +67 -0
+keys, ``tls-crypt-v2`` adds the ability to supply each client with a unique
+tls-crypt key.  This allows large organisations and VPN providers to profit
+from the same DoS and TLS stack protection that small deployments can already
+achieve using ``tls-auth`` or ``tls-crypt``.
+
+Also, for ``tls-crypt``, even if all these peers succeed in keeping the key
+secret, the key lifetime is limited to roughly 8000 years, divided by the
+number of clients (see the ``--tls-crypt`` section of the man page).  Using
+client-specific keys, we lift this lifetime requirement to roughly 8000 years
+for each client key (which "Should Be Enough For Everybody (tm)").
+
+
+Introduction
+------------
+
+``tls-crypt-v2`` uses an encrypted cookie mechanism to introduce
+client-specific tls-crypt keys without introducing a lot of server-side state.
+The client-specific key is encrypted using a server key.  The server key is the
+same for all servers in a group.  When a client connects, it first sends the
+encrypted key to the server, such that the server can decrypt the key and all
+messages can thereafter be encrypted using the client-specific key.
+
+A wrapped (encrypted and authenticated) client-specific key can also contain
+metadata.  The metadata is wrapped together with the key, and can be used to
+allow servers to identify clients and/or key validity.  This allows the server
+to abort the connection immediately after receiving the first packet, rather
+than performing an entire TLS handshake.  Aborting the connection this early
+greatly improves the DoS resilience and reduces attack service against
+malicious clients that have the ``tls-crypt`` or ``tls-auth`` key.  This is
+particularly relevant for large deployments (think lost key or disgruntled
+employee) and VPN providers (clients are not trusted).
+
+To allow for a smooth transition, ``tls-crypt-v2`` is designed such that a
+server can enable both ``tls-crypt-v2`` and either ``tls-crypt`` or
+``tls-auth``.  This is achieved by introducing a P_CONTROL_HARD_RESET_CLIENT_V3
+opcode, that indicates that the client wants to use ``tls-crypt-v2`` for the
+current connection.
+
+For an exact specification and more details, read the Implementation section.
+
+
+Implementation
+--------------
+
+When setting up a tls-crypt-v2 group (similar to generating a tls-crypt or
+tls-auth key previously):
+
+1. Generate a tls-crypt-v2 server key using OpenVPN's ``--genkey``.  This key
+   contains 4 512-bit keys, of which we use:
+
+   * the first 256 bits of key 1 as AES-256-CTR encryption key ``Ke``
+   * the first 256 bits of key 2 as HMAC-SHA-256 authentication key ``Ka``
+
+2. Add the tls-crypt-v2 server key to all server configs
+   (``tls-crypt-v2 /path/to/server.key``)
+
+
+When provisioning a client, create a client-specific tls-crypt key:
+
+1. Generate 2048 bits client-specific key ``Kc``
+2. Optionally generate metadata
+3. Create a wrapped client key ``WKc``, using the same nonce-misuse-resistant
+   SIV conruction we use for tls-crypt:
+
+   ``T = HMAC-SHA256(Ka, Kc || metadata)``
+
+   ``IV = 128 most significant bits of T``

doc/tls-crypt-v2.txt line 90

Quoted 10 lines
doc/tls-crypt-v2.txt new file +10 -0
+
+   ``WKc = T || AES-256-CTR(Ke, IV, Kc || metadata)``
+
+4. Create a tls-crypt-v2 client key: PEM-encode ``Kc || WKc`` and store in a
+   file, using the header ``-----BEGIN OpenVPN tls-crypt-v2 client key-----``
+   and the footer ``-----END OpenVPN tls-crypt-v2 client key-----``.  (The PEM
+   format is simple, and following PEM allows us to use the crypto lib function
+   for en/decoding.)
+5. Add the tls-crypt-v2 client key to the client config
+   (``tls-crypt-v2 /path/to/client-specific.key``)

For the server key you've said "using OpenVPN's ``--genkey``", while for the client key you haven't mentioned the ``--tls-crypt-v2-genkey`` introduced in patch 3/10. Any reason for that?

Quoted 27 lines
doc/tls-crypt-v2.txt new file +27 -0
+
+
+When setting up the openvpn connection:
+
+1. The client reads the tls-crypt-v2 key from its config, and:
+
+   1. loads ``Kc`` as its tls-crypt key,
+   2. stores ``WKc`` in memory for sending to the server.
+
+2. To start the connection, the client creates a P_CONTROL_HARD_RESET_CLIENT_V3
+   message without payload, wraps it with tls-crypt using ``Kc`` as the key,
+   and appends ``WKc``.  (``WKc`` must not be encrypted, to prevent a
+   chicken-and-egg problem.)
+
+3. The server receives the P_CONTROL_HARD_RESET_CLIENT_V3 message, and
+
+   a. unwraps ``WKc`` and strips ``WKc`` from the message.
+   b. uses unwrapped ``Kc`` to verify the remaining
+      P_CONTROL_HARD_RESET_CLIENT_V3 message's authentication.
+
+   The message is dropped and no error response is sent when either a or b
+   fails (DoS protection).
+
+4. Server optionally checks metadata using a --tls-crypt-v2-verify script
+
+   Metadata could for example contain the users certificate serial, such that
+   the incoming connection can be verified against a CRL, or a notAfter

doc/tls-crypt-v2.txt line 127

Quoted 10 lines
doc/tls-crypt-v2.txt new file +10 -0
+   timestamp that limits the key's validity period.
+
+   This allows early abort of connection, *before* we expose any of the
+   notoriously dangerous TLS, X.509 and ASN.1 parsers and thereby reduces the
+   attack surface of the server.
+
+   The metadata is checked *after* the OpenVPN three-way handshake has
+   completed, to prevent DoS attacks.  (That is, once the client has proved to
+   the server that it possesses Kc, by authenticating a packet that contains the
+   session ID picked by the server.)

just a thought here: the metadata is actually created by the server/provider and it is authenticated/encrypted with the server key.

Isn't this enough to ensure that its content is not malicious and thus allow the server to parse it right after having received the HARD_RESET_V3 (instead of performing a "three-way handshake" first)?

Quoted 9 lines
doc/tls-crypt-v2.txt new file +9 -0
+
+   RFC: should the server send a 'key rejected' message if the key is e.g.
+   revoked or expired?  That allows for better client-side error reporting, but
+   also reduces the DoS resilience.
+
+6. Client and server use ``Kc`` for (un)wrapping any following control channel
+   messages.
+
+

doc/tls-crypt-v2.txt line 146

Quoted 10 lines
doc/tls-crypt-v2.txt new file +10 -0
+Considerations
+--------------
+
+To allow for a smooth transition, the server implementation allows
+``tls-crypt`` or ``tls-auth`` to be used simultaneously with ``tls-crypt-v2``.
+This specification does not allow simultaneously using ``tls-crypt-v2`` and
+connections without any control channel wrapping, because that would break DoS
+resilience.  RFC: should we add an option along the lines of
+--tls-crypt-v2-allow-insecure-fallback to allow admins to enable this anyway?
+It might help with transitioning.

good question, but I think people are already fighting with this now while transitioning to tls-crypt, thus I would vote for not introducing more code complexity.

Quoted 9 lines
doc/tls-crypt-v2.txt new file +8 -0
+
+``tls-crypt-v2`` uses fixed crypto algorithms, because:
+
+ * The crypto is used before we can do any negotiation, so the algorithms have
+   to be predefined.
+ * The crypto primitives are chosen conservatively, making problems with these
+   primitives unlikely.
+ * Making anything configurable adds complexity, both in implementation and

doc/tls-crypt-v2.txt line 164

Quoted 10 lines
doc/tls-crypt-v2.txt new file +10 -0
+   usage.  We should not add anymore complexity than is absolutely necessary.
+
+Potential ``tls-crypt-v2`` risks:
+
+ * Slightly more work on first connection (``WKc`` unwrap + hard reset unwrap)
+   than with ``tls-crypt`` (hard reset unwrap) or ``tls-auth`` (hard reset auth).
+ * Flexible metadata allow mistakes
+   (So we should make it easy to do it right.  Provide tooling to create client
+   keys based on cert serial + CA fingerprint, provide script that uses CRL (if
+   available) to drop revoked keys.)

The rest looks good! Thanks for this exhaustive description!

Cheers,

Signature · 1 line
Antonio Quartulli
#7 lore raw

PATCH v2 1/1 tls-crypt-v2: add specification to doc/

From: Steffan Karger <[email protected]...>

This is a preliminary description of tls-crypt-v2. It should give a good impression about the reasoning and design behind tls-crypt-v2, but might need some polishing and updating.

---
v2: update text for --tls-crypt-v2-genkey, fix typo

 doc/tls-crypt-v2.txt | 165 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 165 insertions(+)
 create mode 100644 doc/tls-crypt-v2.txt
doc/tls-crypt-v2.txt new file +165 -0
new file mode 100644
index 00000000..cb061aee
@@ -0,0 +1,165 @@
+Client-specific tls-crypt keys (--tls-crypt-v2)
+===============================================
+
+This document describes the ``--tls-crypt-v2`` option, which enables OpenVPN
+to use client-specific ``--tls-crypt`` keys.
+
+Rationale
+---------
+
+``--tls-auth`` and ``tls-crypt`` use a pre-shared group key, which is shared
+among all client and servers in an OpenVPN deployment.  If any client or
+server is compromised, the attacker will have access to this shared key, and it
+will no longer provide any security.  To reduce the risk of losing pre-shared
+keys, ``tls-crypt-v2`` adds the ability to supply each client with a unique
+tls-crypt key.  This allows large organisations and VPN providers to profit
+from the same DoS and TLS stack protection that small deployments can already
+achieve using ``tls-auth`` or ``tls-crypt``.
+
+Also, for ``tls-crypt``, even if all these peers succeed in keeping the key
+secret, the key lifetime is limited to roughly 8000 years, divided by the
+number of clients (see the ``--tls-crypt`` section of the man page).  Using
+client-specific keys, we lift this lifetime requirement to roughly 8000 years
+for each client key (which "Should Be Enough For Everybody (tm)").
+
+
+Introduction
+------------
+
+``tls-crypt-v2`` uses an encrypted cookie mechanism to introduce
+client-specific tls-crypt keys without introducing a lot of server-side state.
+The client-specific key is encrypted using a server key.  The server key is the
+same for all servers in a group.  When a client connects, it first sends the
+encrypted key to the server, such that the server can decrypt the key and all
+messages can thereafter be encrypted using the client-specific key.
+
+A wrapped (encrypted and authenticated) client-specific key can also contain
+metadata.  The metadata is wrapped together with the key, and can be used to
+allow servers to identify clients and/or key validity.  This allows the server
+to abort the connection immediately after receiving the first packet, rather
+than performing an entire TLS handshake.  Aborting the connection this early
+greatly improves the DoS resilience and reduces attack service against
+malicious clients that have the ``tls-crypt`` or ``tls-auth`` key.  This is
+particularly relevant for large deployments (think lost key or disgruntled
+employee) and VPN providers (client are not trusted).
+
+To allow for a smooth transition, ``tls-crypt-v2`` is designed such that a
+server can enable both ``tls-crypt-v2`` and either ``tls-crypt`` or
+``tls-auth``.  This is achieved by introducing a P_CONTROL_HARD_RESET_CLIENT_V3
+opcode, that indicates that the client wants to use ``tls-crypt-v2`` for the
+current connection.
+
+For an exact specification and more details, read the Implementation section.
+
+
+Implementation
+--------------
+
+When setting up a tls-crypt-v2 group (similar to generating a tls-crypt or
+tls-auth key previously):
+
+1. Generate a tls-crypt-v2 server key using ``--tls-crypt-v2-genkey``.  This
+   key contains 2 512-bit keys, of which we use:
+
+   * the first 256 bits of key 1 as AES-256-CTR encryption key ``Ke``
+   * the first 256 bits of key 2 as HMAC-SHA-256 authentication key ``Ka``
+
+2. Add the tls-crypt-v2 server key to all server configs
+   (``tls-crypt-v2 /path/to/server.key``)
+
+
+When provisioning a client, create a client-specific tls-crypt key:
+
+1. Generate 2048 bits client-specific key ``Kc``
+2. Optionally generate metadata
+3. Create a wrapped client key ``WKc``, using the same nonce-misuse-resistant
+   SIV conruction we use for tls-crypt:
+
+   ``T = HMAC-SHA256(Ka, Kc || metadata)``
+
+   ``IV = 128 most significant bits of T``
+
+   ``WKc = T || AES-256-CTR(Ke, IV, Kc || metadata)``
+
+4. Create a tls-crypt-v2 client key using ``--tls-crypto-v2-genkey``.  This
+   PEM-encodes ``Kc || WKc`` and stores it in a file, using the header
+   ``-----BEGIN OpenVPN tls-crypt-v2 client key-----`` and the footer
+   ``-----END OpenVPN tls-crypt-v2 client key-----``.  (The PEM format is
+   simple, and following PEM allows us to use the crypto lib function for
+   en/decoding.)
+5. Add the tls-crypt-v2 client key to the client config
+   (``tls-crypt-v2 /path/to/client-specific.key``)
+
+
+When setting up the openvpn connection:
+
+1. The client reads the tls-crypt-v2 key from its config, and:
+
+   1. loads ``Kc`` as its tls-crypt key,
+   2. stores ``WKc`` in memory for sending to the server.
+
+2. To start the connection, the client creates a P_CONTROL_HARD_RESET_CLIENT_V3
+   message without payload, wraps it with tls-crypt using ``Kc`` as the key,
+   and appends ``WKc``.  (``WKc`` must not be encrypted, to prevent a
+   chicken-and-egg problem.)
+
+3. The server receives the P_CONTROL_HARD_RESET_CLIENT_V3 message, and
+
+   a. unwraps ``WKc`` and strips ``WKc`` from the message.
+   b. uses unwrapped ``Kc`` to verify the remaining
+      P_CONTROL_HARD_RESET_CLIENT_V3 message's authentication.
+
+   The message is dropped and no error response is sent when either a or b
+   fails (DoS protection).
+
+4. Server optionally checks metadata using a --tls-crypt-v2-verify script
+
+   Metadata could for example contain the users certificate serial, such that
+   the incoming connection can be verified against a CRL, or a notAfter
+   timestamp that limits the key's validity period.
+
+   This allows early abort of connection, *before* we expose any of the
+   notoriously dangerous TLS, X.509 and ASN.1 parsers and thereby reduces the
+   attack surface of the server.
+
+   The metadata is checked *after* the OpenVPN three-way handshake has
+   completed, to prevent DoS attacks.  (That is, once the client has proved to
+   the server that it possesses Kc, by authenticating a packet that contains the
+   session ID picked by the server.)
+
+   RFC: should the server send a 'key rejected' message if the key is e.g.
+   revoked or expired?  That allows for better client-side error reporting, but
+   also reduces the DoS resilience.
+
+6. Client and server use ``Kc`` for (un)wrapping any following control channel
+   messages.
+
+
+Considerations
+--------------
+
+To allow for a smooth transition, the server implementation allows
+``tls-crypt`` or ``tls-auth`` to be used simultaneously with ``tls-crypt-v2``.
+This specification does not allow simultaneously using ``tls-crypt-v2`` and
+connections without any control channel wrapping, because that would break DoS
+resilience.  RFC: should we add an option along the lines of
+--tls-crypt-v2-allow-insecure-fallback to allow admins to enable this anyway?
+It might help with transitioning.
+
+``tls-crypt-v2`` uses fixed crypto algorithms, because:
+
+ * The crypto is used before we can do any negotiation, so the algorithms have
+   to be predefined.
+ * The crypto primitives are chosen conservatively, making problems with these
+   primitives unlikely.
+ * Making anything configurable adds complexity, both in implementation and
+   usage.  We should not add anymore complexity than is absolutely necessary.
+
+Potential ``tls-crypt-v2`` risks:
+
+ * Slightly more work on first connection (``WKc`` unwrap + hard reset unwrap)
+   than with ``tls-crypt`` (hard reset unwrap) or ``tls-auth`` (hard reset auth).
+ * Flexible metadata allow mistakes
+   (So we should make it easy to do it right.  Provide tooling to create client
+   keys based on cert serial + CA fingerprint, provide script that uses CRL (if
+   available) to drop revoked keys.)
Signature · 1 line
2.15.1
#8 lore raw

Re: [Openvpn-devel] [PATCH v2] tls-crypt-v2: add specification to doc/

Hi,

there was a comment in my previous review that remained unanswer. Please, see it below

On 09/03/18 04:23, Steffan Karger wrote:
[CUT]

doc/tls-crypt-v2.txt line 128

Quoted 4 lines
doc/tls-crypt-v2.txt new file +4 -0
+   The metadata is checked *after* the OpenVPN three-way handshake has
+   completed, to prevent DoS attacks.  (That is, once the client has proved to
+   the server that it possesses Kc, by authenticating a packet that contains the
+   session ID picked by the server.)

just a thought here: the metadata is actually created by the server/provider and it is authenticated/encrypted with the server key.

Isn't this enough to ensure that its content is not malicious and thus allow the server to parse it right after having received the HARD_RESET_V3 (instead of performing a "three-way handshake" first)?

Cheers,

Signature · 1 line
Antonio Quartulli
#9 lore raw

Re: [Openvpn-devel] [PATCH v2] tls-crypt-v2: add specification to doc/

Hi Antonio,

Sorry for taking so long to respond - life was getting in the way.

Quoted 11 lines · Antonio Quartulli

there was a comment in my previous review that remained unanswer. Please, see it below

On 09/03/18 04:23, Steffan Karger wrote:
[CUT]

doc/tls-crypt-v2.txt line 128

Quoted 4 lines
doc/tls-crypt-v2.txt new file +4 -0
+   The metadata is checked *after* the OpenVPN three-way handshake has
+   completed, to prevent DoS attacks.  (That is, once the client has proved to
+   the server that it possesses Kc, by authenticating a packet that contains the
+   session ID picked by the server.)
Quoted 6 lines

just a thought here: the metadata is actually created by the server/provider and it is authenticated/encrypted with the server key.

Isn't this enough to ensure that its content is not malicious and thus allow the server to parse it right after having received the HARD_RESET_V3 (instead of performing a "three-way handshake" first)?

Not really, because a mitm attacker can simply record the cookie, and replay it. By postponing our parsing, we reduce the impact of a DoS by an attacker that is setting up many new connections with the cookie attached. Instead of performing a potential expensive check (running a script) for each HARD_RESET_V3, all we do is unwrap and reply. In more formal terms, we require a proof-of-possession of Kc before we will do any (potential) expensive operations.

-Steffan

#10 lore raw

PATCH 3/10 tls-crypt-v2: generate client keys

In reply to Steffan Karger

As a first step towards a full tls-crypt-v2 implementation, add functionality to generate tls-crypt-v2 client keys.

---
 doc/openvpn.8                        |  51 +++++++
 src/openvpn/buffer.c                 |  63 ++++++++
 src/openvpn/buffer.h                 |   6 +
 src/openvpn/init.c                   |  35 ++++-
 src/openvpn/integer.h                |  10 ++
 src/openvpn/options.c                |  37 +++++
 src/openvpn/options.h                |   9 ++
 src/openvpn/tls_crypt.c              | 284 +++++++++++++++++++++++++++++++++++
 src/openvpn/tls_crypt.h              |  81 +++++++++-
 tests/t_lpback.sh                    |  40 ++++-
 tests/unit_tests/openvpn/Makefile.am |   1 +
 11 files changed, 601 insertions(+), 16 deletions(-)
doc/openvpn.8 +51 -0
index 2b5cab1..0719087 100644
@@ -5199,6 +5199,57 @@ degrading to the same security as using
 That is, the control channel still benefits from the extra protection against
 active man\-in\-the\-middle\-attacks and DoS attacks, but may no longer offer
 extra privacy and post\-quantum security on top of what TLS itself offers.
+
+For large setups or setups where clients are not trusted, consider using
+.B \-\-tls\-crypt\-v2
+instead.  That uses per\-client unique keys, and thereby improves the bounds to
+\fR'rotate a client key at least once per 8000 years'.
+.\"*********************************************************
+.TP
+.B \-\-tls\-crypt\-v2 keyfile
+
+Use client\-specific tls\-crypt keys.
+
+For clients,
+.B keyfile
+is a client\-specific tls\-crypt key.  Such a key can be generated using the
+.B \-\-tls\-crypt\-v2\-genkey
+option.
+
+For servers,
+.B keyfile
+is used to unwrap client\-specific keys supplied by the client during connection
+setup.  This key must be the same as the key used to generate the
+client\-specific key (see
+.B \-\-tls\-crypt\-v2\-genkey\fR).
+
+On servers, this option can be used together with the
+.B \-\-tls\-auth
+or
+.B \-\-tls\-crypt
+option.  In that case, the server will detect whether the client is using
+client\-specific keys, and automatically select the right mode.
+.\"*********************************************************
+.TP
+.B \-\-tls\-crypt\-v2\-genkey client|server keyfile [metadata]
+
+If the first parameter equals "server", generate a \-\-tls\-crypt\-v2 server
+key and store the key in
+.B keyfile\fR.
+
+
+If the first parameter equals "client", generate a \-\-tls\-crypt\-v2 client
+key, and store the key in
+.B keyfile\fR.
+
+If supplied, include the supplied
+.B metadata
+in the wrapped client key.  This metadata must be supplied in base64\-encoded
+form.  The metadata must be at most 735 bytes long (980 bytes in base64).
+
+.B TODO
+Metadata handling is not yet implemented.  This text will be updated by the
+commit that introduces metadata handling.
 .\"*********************************************************
 .TP
 .B \-\-askpass [file]
src/openvpn/buffer.c +63 -0

69 lines not on this page · raw message

src/openvpn/buffer.h +6 -0
index 84ad3ef..d15af07 100644
@@ -469,6 +469,12 @@ const char *skip_leading_whitespace(const char *str);
 
 void string_null_terminate(char *str, int len, int capacity);
 
+/** Write buffer contents to file */
+bool buffer_write_file(const char *filename, const struct buffer *buf);
+
+/** Read file contents. Returns invalid buffer on error. */
+struct buffer buffer_read_from_file(const char *filename, struct gc_arena *gc);
+
 /*
  * Write string in buf to file descriptor fd.
  * NOTE: requires that string be null terminated.
src/openvpn/init.c +30 -5
index 30beadb..3e694b9 100644
@@ -1027,6 +1027,11 @@ print_openssl_info(const struct options *options)
 bool
 do_genkey(const struct options *options)
 {
+    /* should we disable paging? */
+    if (options->mlock && (options->genkey || options->tls_crypt_v2_genkey_file))
+    {
+        platform_mlockall(true);
+    }
     if (options->genkey)
     {
         int nbits_written;
@@ -1034,11 +1039,6 @@ do_genkey(const struct options *options)
         notnull(options->shared_secret_file,
                 "shared secret output file (--secret)");
 
-        if (options->mlock)     /* should we disable paging? */
-        {
-            platform_mlockall(true);
-        }
-
         nbits_written = write_key_file(2, options->shared_secret_file);
 
         msg(D_GENKEY | M_NOPREFIX,
@@ -1046,6 +1046,31 @@ do_genkey(const struct options *options)
             options->shared_secret_file);
         return true;
     }
+    if (options->tls_crypt_v2_genkey_type)
+    {
+        if(!strcmp(options->tls_crypt_v2_genkey_type, "server"))
+        {
+            tls_crypt_v2_write_server_key_file(options->tls_crypt_v2_genkey_file);
+            return true;
+        }
+        else if (options->tls_crypt_v2_genkey_type
+                 && !strcmp(options->tls_crypt_v2_genkey_type, "client"))
+        {
+            if (!options->tls_crypt_v2_file)
+            {
+                msg(M_USAGE, "--tls-crypt-v2-gen-client-key requires a server key to be set via --tls-crypt-v2");
+            }
+
+            tls_crypt_v2_write_client_key_file(options->tls_crypt_v2_genkey_file,
+                    options->tls_crypt_v2_metadata, options->tls_crypt_v2_file,
+                    options->tls_crypt_v2_inline);
+            return true;
+        }
+        else
+        {
+            msg(M_USAGE, "--tls-crypt-v2-genkey type should be \"client\" or \"server\"");
+        }
+    }
     return false;
 }
 
src/openvpn/integer.h +10 -0
index 9bb00a3..02edc63 100644
@@ -26,6 +26,16 @@
 
 #include "error.h"
 
+#ifndef htonll
+#define htonll(x) ((1==htonl(1)) ? (x) : \
+                  ((uint64_t)htonl((x) & 0xFFFFFFFF) << 32) | htonl((x) >> 32))
+#endif
+
+#ifndef ntohll
+#define ntohll(x) ((1==ntohl(1)) ? (x) : \
+                  ((uint64_t)ntohl((x) & 0xFFFFFFFF) << 32) | ntohl((x) >> 32))
+#endif
+
 /*
  * min/max functions
  */
src/openvpn/options.c +37 -0
index 7be5f38..aa73fd9 100644
@@ -621,6 +621,13 @@ static const char usage_message[] =
     "                  attacks on the TLS stack and DoS attacks.\n"
     "                  key (required) provides the pre-shared key file.\n"
     "                  see --secret option for more info.\n"
+    "--tls-crypt-v2 key : For clients: use key as a client-specific tls-crypt key.\n"
+    "                  For servers: use key to decrypt client-specific keys.  For\n"
+    "                  key generation (--tls-crypt-v2-genkey): use key to\n"
+    "                  encrypt generated client-specific key.  (See --tls-crypt.)\n"
+    "--tls-crypt-v2-genkey client|server keyfile [base64 metadata]: Generate a\n"
+    "                  fresh tls-crypt-v2 client or server key, and store to\n"
+    "                  keyfile.  If supplied, include metadata in wrapped key.\n"
     "--askpass [file]: Get PEM password from controlling tty before we daemonize.\n"
     "--auth-nocache  : Don't cache --askpass or --auth-user-pass passwords.\n"
     "--crl-verify crl ['dir']: Check peer certificate against a CRL.\n"
@@ -1775,6 +1782,10 @@ show_settings(const struct options *o)
 
     SHOW_STR(tls_auth_file);
     SHOW_STR(tls_crypt_file);
+    SHOW_STR(tls_crypt_v2_file);
+    SHOW_STR(tls_crypt_v2_genkey_type);
+    SHOW_STR(tls_crypt_v2_genkey_file);
+    SHOW_STR(tls_crypt_v2_metadata);
 
 #ifdef ENABLE_PKCS11
     {
@@ -2727,6 +2738,7 @@ options_postprocess_verify_ce(const struct options *options, const struct connec
         MUST_BE_UNDEF(transition_window);
         MUST_BE_UNDEF(tls_auth_file);
         MUST_BE_UNDEF(tls_crypt_file);
+        MUST_BE_UNDEF(tls_crypt_v2_file);
         MUST_BE_UNDEF(single_session);
         MUST_BE_UNDEF(push_peer_info);
         MUST_BE_UNDEF(tls_exit);
@@ -3274,6 +3286,12 @@ options_postprocess_filechecks(struct options *options)
     errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE|CHKACC_PRIVATE,
                               options->tls_crypt_file, R_OK, "--tls-crypt");
     errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE|CHKACC_PRIVATE,
+                              options->tls_crypt_v2_file, R_OK,
+                              "--tls-crypt-v2");
+    errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE|CHKACC_PRIVATE,
+                              options->tls_crypt_v2_genkey_file, R_OK,
+                              "--tls-crypt-v2-genkey");
+    errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE|CHKACC_PRIVATE,
                               options->shared_secret_file, R_OK, "--secret");
     errs |= check_file_access(CHKACC_DIRPATH|CHKACC_FILEXSTWR,
                               options->packet_id_file, R_OK|W_OK, "--replay-persist");
@@ -8010,6 +8028,25 @@ add_option(struct options *options,
         }
         options->tls_crypt_file = p[1];
     }
+    else if (streq(p[0], "tls-crypt-v2") && p[1] && !p[3])
+    {
+        VERIFY_PERMISSION(OPT_P_GENERAL);
+        if (streq(p[1], INLINE_FILE_TAG) && p[2])
+        {
+            options->tls_crypt_v2_inline = p[2];
+        }
+        options->tls_crypt_v2_file = p[1];
+    }
+    else if (streq(p[0], "tls-crypt-v2-genkey") && p[2] && !p[4])
+    {
+        VERIFY_PERMISSION(OPT_P_GENERAL);
+        options->tls_crypt_v2_genkey_type = p[1];
+        options->tls_crypt_v2_genkey_file = p[2];
+        if (p[3])
+        {
+            options->tls_crypt_v2_metadata = p[3];
+        }
+    }
     else if (streq(p[0], "key-method") && p[1] && !p[2])
     {
         int key_method;
src/openvpn/options.h +9 -0
index f70760c..7d995c3 100644
@@ -566,6 +566,15 @@ struct options
     const char *tls_crypt_file;
     const char *tls_crypt_inline;
 
+    /* Client-specific secret or server key used for TLS control channel
+     * authenticated encryption v2 */
+    const char *tls_crypt_v2_file;
+    const char *tls_crypt_v2_inline;
+
+    const char *tls_crypt_v2_genkey_type;
+    const char *tls_crypt_v2_genkey_file;
+    const char *tls_crypt_v2_metadata;
+
     /* Allow only one session */
     bool single_session;
 
src/openvpn/tls_crypt.c +284 -0

298 lines not on this page · raw message

src/openvpn/tls_crypt.h +73 -8

129 lines not on this page · raw message

tests/t_lpback.sh +37 -3
index 2052c62..107e7c1 100755
@@ -21,8 +21,8 @@
 
 set -eu
 top_builddir="${top_builddir:-..}"
-trap "rm -f key.$$ log.$$ ; trap 0 ; exit 77" 1 2 15
-trap "rm -f key.$$ log.$$ ; exit 1" 0 3
+trap "rm -f key.$$ tc-server-key.$$ tc-client-key.$$ log.$$ ; trap 0 ; exit 77" 1 2 15
+trap "rm -f key.$$ tc-server-key.$$ tc-client-key.$$ log.$$ ; exit 1" 0 3
 
 # Get list of supported ciphers from openvpn --show-ciphers output
 CIPHERS=$(${top_builddir}/src/openvpn/openvpn --show-ciphers | \
@@ -55,6 +55,40 @@ do
     fi
 done
 
-rm key.$$ log.$$
+echo -n "Testing tls-crypt-v2 server key generation..."
+"${top_builddir}/src/openvpn/openvpn" \
+    --tls-crypt-v2-genkey server tc-server-key.$$ >log.$$ 2>&1
+if [ $? != 0 ] ; then
+    echo "FAILED"
+    cat log.$$
+    e=1
+else
+    echo "OK"
+fi
+
+echo -n "Testing tls-crypt-v2 key generation (no metadata)..."
+"${top_builddir}/src/openvpn/openvpn" --tls-crypt-v2 tc-server-key.$$ \
+    --tls-crypt-v2-genkey client tc-client-key.$$ >log.$$ 2>&1
+if [ $? != 0 ] ; then
+    echo "FAILED"
+    cat log.$$
+    e=1
+else
+    echo "OK"
+fi
+
+echo -n "Testing tls-crypt-v2 key generation (max length metadata)..."
+"${top_builddir}/src/openvpn/openvpn" --tls-crypt-v2 tc-server-key.$$ \
+    --tls-crypt-v2-genkey client tc-client-key.$$ \
+    $(head -c735 /dev/zero | base64 -w0) >log.$$ 2>&1
+if [ $? != 0 ] ; then
+    echo "FAILED"
+    cat log.$$
+    e=1
+else
+    echo "OK"
+fi
+
+rm key.$$ tc-server-key.$$ tc-client-key.$$ log.$$
 trap 0
 exit $e
tests/unit_tests/openvpn/Makefile.am +1 -0
index d100c21..937393d 100644
@@ -60,6 +60,7 @@ tls_crypt_testdriver_CFLAGS  = @TEST_CFLAGS@ \
 tls_crypt_testdriver_LDFLAGS = @TEST_LDFLAGS@ \
 	$(OPTIONAL_CRYPTO_LIBS)
 tls_crypt_testdriver_SOURCES = test_tls_crypt.c mock_msg.c \
+	$(openvpn_srcdir)/base64.c \
 	$(openvpn_srcdir)/buffer.c \
 	$(openvpn_srcdir)/crypto.c \
 	$(openvpn_srcdir)/crypto_mbedtls.c \
Signature · 1 line
2.7.4
#11 lore raw

PATCH 4/10 tls-crypt-v2: add unwrap_client_key

In reply to Steffan Karger

Add helper functions to unwrap tls-crypt-v2 client keys.

---
 src/openvpn/buffer.h                      |   7 +
 src/openvpn/tls_crypt.c                   | 102 ++++++++++++
 tests/unit_tests/openvpn/test_tls_crypt.c | 253 +++++++++++++++++++++++++++---
 3 files changed, 342 insertions(+), 20 deletions(-)
src/openvpn/buffer.h +7 -0
index d15af07..c900c32 100644
@@ -839,6 +839,13 @@ buf_read_u32(struct buffer *buf, bool *good)
     }
 }
 
+/** Return true if buffer contents are equal */
+static inline bool
+buf_equal(const struct buffer *a, const struct buffer *b)
+{
+    return BLEN(a) == BLEN(b) && 0 == memcmp(BPTR(a), BPTR(b), BLEN(a));
+}
+
 /**
  * Compare src buffer contents with match.
  * *NOT* constant time. Do not use when comparing HMACs.
src/openvpn/tls_crypt.c +102 -0

114 lines not on this page · raw message

tests/unit_tests/openvpn/test_tls_crypt.c +233 -20

334 lines not on this page · raw message

Signature · 1 line
2.7.4
#12 lore raw

PATCH 5/10 tls-crypt-v2: add P_CONTROL_HARD_RESET_CLIENT_V3 opcode

In reply to Steffan Karger

Not used yet, but prepare for sending and receiving tls-crypt-v2 handshake messages.

---
 src/openvpn/ps.c         |  3 ++-
 src/openvpn/ssl.c        | 23 ++++++++++++++++++-----
 src/openvpn/ssl.h        |  5 ++++-
 src/openvpn/ssl_common.h |  2 ++
 4 files changed, 26 insertions(+), 7 deletions(-)
src/openvpn/ps.c +2 -1
index 5136a20..6c37753 100644
@@ -985,7 +985,8 @@ is_openvpn_protocol(const struct buffer *buf)
     {
         return p[0] == 0
                && p[1] >= 14
-               && p[2] == (P_CONTROL_HARD_RESET_CLIENT_V2<<P_OPCODE_SHIFT);
+               && (p[2] == (P_CONTROL_HARD_RESET_CLIENT_V2<<P_OPCODE_SHIFT)
+                   || p[2] == (P_CONTROL_HARD_RESET_CLIENT_V3<<P_OPCODE_SHIFT));
     }
     else if (len >= 2)
     {
src/openvpn/ssl.c +18 -5
index 7b42845..d45cfe4 100644
@@ -785,6 +785,9 @@ packet_opcode_name(int op)
         case P_CONTROL_HARD_RESET_SERVER_V2:
             return "P_CONTROL_HARD_RESET_SERVER_V2";
 
+        case P_CONTROL_HARD_RESET_CLIENT_V3:
+            return "P_CONTROL_HARD_RESET_CLIENT_V3";
+
         case P_CONTROL_SOFT_RESET_V1:
             return "P_CONTROL_SOFT_RESET_V1";
 
@@ -857,7 +860,8 @@ is_hard_reset(int op, int key_method)
 
     if (!key_method || key_method >= 2)
     {
-        if (op == P_CONTROL_HARD_RESET_CLIENT_V2 || op == P_CONTROL_HARD_RESET_SERVER_V2)
+        if (op == P_CONTROL_HARD_RESET_CLIENT_V2 || op == P_CONTROL_HARD_RESET_SERVER_V2
+            || op == P_CONTROL_HARD_RESET_CLIENT_V3)
         {
             return true;
         }
@@ -1088,8 +1092,15 @@ tls_session_init(struct tls_multi *multi, struct tls_session *session)
     }
     else /* session->opt->key_method >= 2 */
     {
-        session->initial_opcode = session->opt->server ?
-                                  P_CONTROL_HARD_RESET_SERVER_V2 : P_CONTROL_HARD_RESET_CLIENT_V2;
+        if (session->opt->server)
+        {
+            session->initial_opcode = P_CONTROL_HARD_RESET_SERVER_V2;
+        }
+        else
+        {
+            session->initial_opcode = session->opt->tls_crypt_v2 ?
+                    P_CONTROL_HARD_RESET_CLIENT_V3 : P_CONTROL_HARD_RESET_CLIENT_V2;
+        }
     }
 
     /* Initialize control channel authentication parameters */
@@ -3417,7 +3428,8 @@ tls_pre_decrypt(struct tls_multi *multi,
             {
                 /* verify client -> server or server -> client connection */
                 if (((op == P_CONTROL_HARD_RESET_CLIENT_V1
-                      || op == P_CONTROL_HARD_RESET_CLIENT_V2) && !multi->opt.server)
+                      || op == P_CONTROL_HARD_RESET_CLIENT_V2
+                      || op == P_CONTROL_HARD_RESET_CLIENT_V3) && !multi->opt.server)
                     || ((op == P_CONTROL_HARD_RESET_SERVER_V1
                          || op == P_CONTROL_HARD_RESET_SERVER_V2) && multi->opt.server))
                 {
@@ -3802,7 +3814,8 @@ tls_pre_decrypt_lite(const struct tls_auth_standalone *tas,
         /* this packet is from an as-yet untrusted source, so
          * scrutinize carefully */
 
-        if (op != P_CONTROL_HARD_RESET_CLIENT_V2)
+        if (op != P_CONTROL_HARD_RESET_CLIENT_V2
+            && op != P_CONTROL_HARD_RESET_CLIENT_V3)
         {
             /*
              * This can occur due to bogus data or DoS packets.
src/openvpn/ssl.h +4 -1
index dd1ab0f..1a138ab 100644
@@ -63,9 +63,12 @@
 #define P_CONTROL_HARD_RESET_CLIENT_V2 7     /* initial key from client, forget previous state */
 #define P_CONTROL_HARD_RESET_SERVER_V2 8     /* initial key from server, forget previous state */
 
+/* indicates key_method >= 2 and client-specific tls-crypt key */
+#define P_CONTROL_HARD_RESET_CLIENT_V3 10    /* initial key from client, forget previous state */
+
 /* define the range of legal opcodes */
 #define P_FIRST_OPCODE                 1
-#define P_LAST_OPCODE                  9
+#define P_LAST_OPCODE                  10
 
 /*
  * Set the max number of acknowledgments that can "hitch a ride" on an outgoing
src/openvpn/ssl_common.h +2 -0
index 60ed5f8..7e8f7d9 100644
@@ -286,6 +286,8 @@ struct tls_options
     const char *config_authname;
     bool ncp_enabled;
 
+    bool tls_crypt_v2;
+
     /** TLS handshake wrapping state */
     struct tls_wrap_ctx tls_wrap;
 
Signature · 1 line
2.7.4
#13 lore raw

PATCH 6/10 tls-crypt-v2: implement tls-crypt-v2 handshake

In reply to Steffan Karger

This makes clients send-and-use, and servers receive-unwrap-and-use tls-crypt-v2 client keys, which completes the on-the-wire work.

---
 src/openvpn/init.c                   | 39 +++++++++++++++++-
 src/openvpn/openvpn.h                |  2 +
 src/openvpn/options.c                |  5 +++
 src/openvpn/ssl.c                    | 79 ++++++++++++++++++++++++++++--------
 src/openvpn/ssl_common.h             |  6 +++
 src/openvpn/tls_crypt.c              | 50 +++++++++++++++++++++++
 src/openvpn/tls_crypt.h              | 14 +++++++
 tests/unit_tests/openvpn/Makefile.am |  2 +-
 8 files changed, 179 insertions(+), 18 deletions(-)
src/openvpn/init.c +38 -1
index 3e694b9..a4603b3 100644
@@ -2432,6 +2432,9 @@ key_schedule_free(struct key_schedule *ks, bool free_ssl_ctx)
     {
         tls_ctx_free(&ks->ssl_ctx);
         free_key_ctx_bi(&ks->tls_wrap_key);
+        free_key_ctx(&ks->tls_crypt_v2_server_key);
+        buf_clear(&ks->tls_crypt_v2_wkc);
+        free_buf(&ks->tls_crypt_v2_wkc);
     }
     CLEAR(*ks);
 }
@@ -2600,6 +2603,24 @@ do_init_crypto_tls_c1(struct context *c)
                                options->tls_crypt_inline, options->tls_server);
         }
 
+        /* tls-crypt with client-specific keys (--tls-crypt-v2) */
+        if (options->tls_crypt_v2_file)
+        {
+            if (options->tls_server)
+            {
+                tls_crypt_v2_init_server_key(&c->c1.ks.tls_crypt_v2_server_key,
+                                             true, options->tls_crypt_v2_file,
+                                             options->tls_crypt_v2_inline);
+            }
+            else
+            {
+                tls_crypt_v2_init_client_key(&c->c1.ks.tls_wrap_key,
+                                             &c->c1.ks.tls_crypt_v2_wkc,
+                                             options->tls_crypt_v2_file,
+                                             options->tls_crypt_v2_inline);
+            }
+        }
+
         c->c1.ciphername = options->ciphername;
         c->c1.authname = options->authname;
         c->c1.keysize = options->keysize;
@@ -2819,13 +2840,28 @@ do_init_crypto_tls(struct context *c, const unsigned int flags)
     }
 
     /* TLS handshake encryption (--tls-crypt) */
-    if (options->tls_crypt_file)
+    if (options->tls_crypt_file
+        || (options->tls_crypt_v2_file && options->tls_client))
     {
         to.tls_wrap.mode = TLS_WRAP_CRYPT;
         to.tls_wrap.opt.key_ctx_bi = c->c1.ks.tls_wrap_key;
         to.tls_wrap.opt.pid_persist = &c->c1.pid_persist;
         to.tls_wrap.opt.flags |= CO_PACKET_ID_LONG_FORM;
         tls_crypt_adjust_frame_parameters(&to.frame);
+
+        if (options->tls_crypt_v2_file)
+        {
+            to.tls_wrap.tls_crypt_v2_wkc = &c->c1.ks.tls_crypt_v2_wkc;
+        }
+    }
+
+    if (options->tls_crypt_v2_file)
+    {
+        to.tls_crypt_v2 = true;
+        if (options->tls_server)
+        {
+            to.tls_wrap.tls_crypt_v2_server_key = c->c1.ks.tls_crypt_v2_server_key;
+        }
     }
 
     /* If we are running over TCP, allow for
@@ -4349,6 +4385,7 @@ inherit_context_child(struct context *dest,
     dest->c1.ks.ssl_ctx = src->c1.ks.ssl_ctx;
     dest->c1.ks.tls_wrap_key = src->c1.ks.tls_wrap_key;
     dest->c1.ks.tls_auth_key_type = src->c1.ks.tls_auth_key_type;
+    dest->c1.ks.tls_crypt_v2_server_key = src->c1.ks.tls_crypt_v2_server_key;
     /* inherit pre-NCP ciphers */
     dest->c1.ciphername = src->c1.ciphername;
     dest->c1.authname = src->c1.authname;
src/openvpn/openvpn.h +2 -0
index db16842..38b4822 100644
@@ -66,6 +66,8 @@ struct key_schedule
     /* optional TLS control channel wrapping */
     struct key_type tls_auth_key_type;
     struct key_ctx_bi tls_wrap_key;
+    struct key_ctx tls_crypt_v2_server_key;
+    struct buffer tls_crypt_v2_wkc;             /**< Wrapped client key */
 };
 
 /*
src/openvpn/options.c +5 -0
index aa73fd9..89bff1e 100644
@@ -2704,6 +2704,11 @@ options_postprocess_verify_ce(const struct options *options, const struct connec
         {
             msg(M_USAGE, "--tls-auth and --tls-crypt are mutually exclusive");
         }
+        if (options->client && options->tls_crypt_v2_file
+            && (options->tls_auth_file || options->tls_crypt_file))
+        {
+            msg(M_USAGE, "--tls-crypt-v2, --tls-auth and --tls-crypt are mutually exclusive in client mode");
+        }
     }
     else
     {
src/openvpn/ssl.c +63 -16

126 lines not on this page · raw message

src/openvpn/ssl_common.h +6 -0
index 7e8f7d9..d6e0d1b 100644
@@ -213,6 +213,12 @@ struct tls_wrap_ctx
     } mode;                     /**< Control channel wrapping mode */
     struct crypto_options opt;  /**< Crypto state */
     struct buffer work;         /**< Work buffer (only for --tls-crypt) */
+    struct key_ctx tls_crypt_v2_server_key;  /**< Decrypts client keys */
+    const struct buffer *tls_crypt_v2_wkc;   /**< Wrapped client key,
+                                                  sent to server */
+    struct buffer tls_crypt_v2_metadata;     /**< Received from client */
+    bool cleanup_key_ctx;                    /**< opt.key_ctx_bi is owned by
+                                                  this context */
 };
 
 /*
src/openvpn/tls_crypt.c +50 -0

62 lines not on this page · raw message

src/openvpn/tls_crypt.h +14 -0
index f4d3ca7..e720729 100644
@@ -83,6 +83,7 @@
 #include "buffer.h"
 #include "crypto.h"
 #include "session_id.h"
+#include "ssl_common.h"
 
 #define TLS_CRYPT_TAG_SIZE (256/8)
 #define TLS_CRYPT_PID_SIZE (sizeof(packet_id_type) + sizeof(net_time_t))
@@ -182,6 +183,19 @@ void tls_crypt_v2_init_client_key(struct key_ctx_bi *key,
                                   const char *key_inline);
 
 /**
+ * Extract a tls-crypt-v2 client key from a P_CONTROL_HARD_RESET_CLIENT_V3
+ * message, and load the key into the supplied tls wrap context.
+ *
+ * @param buf   Buffer containing a received P_CONTROL_HARD_RESET_CLIENT_V3
+ *              message.
+ * @param ctx   tls-wrap context to be initialized with the client key.
+ *
+ * @returns true if a key was successfully extracted.
+ */
+bool tls_crypt_v2_extract_client_key(struct buffer *buf,
+                                     struct tls_wrap_ctx *ctx);
+
+/**
  * Generate a tls-crypt-v2 server key, and write to file.
  *
  * @param filename          Filename of the server key file to create.
tests/unit_tests/openvpn/Makefile.am +1 -1
index 937393d..c457ef9 100644
@@ -56,7 +56,7 @@ packet_id_testdriver_SOURCES = test_packet_id.c mock_msg.c \
 
 tls_crypt_testdriver_CFLAGS  = @TEST_CFLAGS@ \
 	-I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \
-	$(OPTIONAL_CRYPTO_CFLAGS)
+	$(OPTIONAL_CRYPTO_CFLAGS) $(OPTIONAL_PKCS11_HELPER_CFLAGS)
 tls_crypt_testdriver_LDFLAGS = @TEST_LDFLAGS@ \
 	$(OPTIONAL_CRYPTO_LIBS)
 tls_crypt_testdriver_SOURCES = test_tls_crypt.c mock_msg.c \
Signature · 1 line
2.7.4
#14 lore raw

PATCH 7/10 Move env helper functions into their own module/file

In reply to Steffan Karger

To avoid a dependency on misc.c - which is a dependency mess - in the tls-crypt unit tests, split the env_set functionality out into it's own file.

---
 src/openvpn/Makefile.am              |   1 +
 src/openvpn/argv.c                   |   1 +
 src/openvpn/env_set.c                | 459 +++++++++++++++++++++++++++++++++++
 src/openvpn/env_set.h                | 121 +++++++++
 src/openvpn/misc.c                   | 441 ---------------------------------
 src/openvpn/misc.h                   |  73 +-----
 tests/unit_tests/openvpn/Makefile.am |   1 +
 7 files changed, 584 insertions(+), 513 deletions(-)
 create mode 100644 src/openvpn/env_set.c
 create mode 100644 src/openvpn/env_set.h
src/openvpn/Makefile.am +1 -0
index fcc22d6..441700a 100644
@@ -51,6 +51,7 @@ openvpn_SOURCES = \
 	crypto_openssl.c crypto_openssl.h \
 	crypto_mbedtls.c crypto_mbedtls.h \
 	dhcp.c dhcp.h \
+	env_set.c env_set.h \
 	errlevel.h \
 	error.c error.h \
 	event.c event.h \
src/openvpn/argv.c +1 -0
index 95bdfea..7a41169 100644
@@ -37,6 +37,7 @@
 
 #include "argv.h"
 #include "integer.h"
+#include "env_set.h"
 #include "options.h"
 
 static void
src/openvpn/env_set.c new file +459 -0

459 lines not on this page · raw message

src/openvpn/env_set.h new file +121 -0

121 lines not on this page · raw message

src/openvpn/misc.c +0 -441

453 lines not on this page · raw message

src/openvpn/misc.h +1 -72

99 lines not on this page · raw message

tests/unit_tests/openvpn/Makefile.am +1 -0
index c457ef9..7b41363 100644
@@ -39,6 +39,7 @@ crypto_testdriver_SOURCES = test_crypto.c mock_msg.c \
 	$(openvpn_srcdir)/crypto.c \
 	$(openvpn_srcdir)/crypto_mbedtls.c \
 	$(openvpn_srcdir)/crypto_openssl.c \
+	$(openvpn_srcdir)/env_set.c \
 	$(openvpn_srcdir)/otime.c \
 	$(openvpn_srcdir)/packet_id.c \
 	$(openvpn_srcdir)/platform.c
Signature · 1 line
2.7.4
#15 lore raw

Re: [Openvpn-devel] [PATCH 07/10] Move env helper functions into their own module/file

Hi,

Quoted 6 lines · Steffan Karger

To avoid a dependency on misc.c - which is a dependency mess - in the tls-crypt unit tests, split the env_set functionality out into it's own file.

*sigh*

Not exactly sure what to do with this one - I was about to ACK and merge this, but it fails cmocka building

tls_crypt_testdriver-env_set.o: In function `env_allowed':
/tmp/openvpn-236/tests/unit_tests/openvpn/../../../src/openvpn/env_set.c:417: undefined reference to script_security' /tmp/openvpn-236/tests/unit_tests/openvpn/../../../src/openvpn/env_set.c:417: undefined reference to script_security'

... because *that* change is in 9/10...

So while the change itself is fine (besides the "%lld" to PRIi64 change in setenv_long_long() that came in with 06ad53e067d9a8b) it will cause lots of build fails for the foreseeable future, so I'm not sure it's a good plan.

gert

Signature · 3 lines
now what should I write here...

Gert Doering - Munich, Germany                             [email protected]...
Acked-by on v1 7/10
#16 lore raw

[Openvpn-devel] [PATCH applied] Re: Move env helper functions into their own module/file

In reply to Steffan Karger

Does what it says on the lid: code is moved 1:1 with no code changes, and only #include lines are adjusted elsewhere (which is actually painful to review in this case, as the "moved out" stuff was scattered along misc.c and parts ended in env_set.h, so "plain diff" between old-misc.c and new-env_set.c didn't help much).

One small catch: this collided with 06ad53e067d9a8b and setenv_long_long() being changed from "%lld" to PRIi64 - so I changed the patch accordingly so that it still only moves lines from misc.c to env_set.c

Your patch has been applied to the master branch.

commit 0afe0ca5620d5ab7cec3380d40048dce2286f048

Author: Steffan Karger
Date:   Fri Dec 8 13:07:47 2017 +0100
     Move env helper functions into their own module/file

     Signed-off-by: Antonio Quartulli <[email protected]...>
     Signed-off-by: Steffan Karger <[email protected]...>
     Acked-by: Gert Doering <[email protected]...>
     Message-Id: <[email protected]...>
     URL: https://www.mail-archive.com/[email protected]/msg16066.html
     Signed-off-by: Gert Doering <[email protected]...>

--
kind regards,

Gert Doering

#17 lore raw

Re: [Openvpn-devel] [PATCH applied] Re: Move env helper functions into their own module/file

Hi,

*this* mail should have never gone out, it was the first draft yesterday after which I noticed that it failed cmocka testing... so, please ignore it.

git head is

commit 68b97b25e4c479156d697bf3df90a4b68fbbbcea (HEAD -> master, stable/master, gitlab/master, github/master, delta2/master)

Author: Steffan Karger <[email protected]...>
Date:   Fri Dec 8 13:07:47 2017 +0100

Move env helper functions into their own module/file

Quoted 42 lines · Gert Doering

Does what it says on the lid: code is moved 1:1 with no code changes, and only #include lines are adjusted elsewhere (which is actually painful to review in this case, as the "moved out" stuff was scattered along misc.c and parts ended in env_set.h, so "plain diff" between old-misc.c and new-env_set.c didn't help much).

One small catch: this collided with 06ad53e067d9a8b and setenv_long_long() being changed from "%lld" to PRIi64 - so I changed the patch accordingly so that it still only moves lines from misc.c to env_set.c

Your patch has been applied to the master branch.

commit 0afe0ca5620d5ab7cec3380d40048dce2286f048

Author: Steffan Karger
Date:   Fri Dec 8 13:07:47 2017 +0100
     Move env helper functions into their own module/file

     Signed-off-by: Antonio Quartulli <[email protected]...>
     Signed-off-by: Steffan Karger <[email protected]...>
     Acked-by: Gert Doering <[email protected]...>
     Message-Id: <[email protected]...>
     URL: https://www.mail-archive.com/[email protected]/msg16066.html
     Signed-off-by: Gert Doering <[email protected]...>

--
kind regards,

Gert Doering

------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most engaging tech sites, Slashdot.org! http://sdm.link/slashdot
_______________________________________________
Openvpn-devel mailing list [email protected]
https://lists.sourceforge.net/lists/listinfo/openvpn-devel

Signature · 3 lines
now what should I write here...

Gert Doering - Munich, Germany                             [email protected]...
Acked-by on v1 7/10
#18 lore raw

[Openvpn-devel] [PATCH applied] Re: Move env helper functions into their own module/file

In reply to Steffan Karger

Does what it says on the lid: code is moved 1:1 with no code changes, and only #include lines are adjusted elsewhere (which is actually painful to review in this case, as the "moved out" stuff was scattered along misc.c and parts ended in env_set.h, so "plain diff" between old-misc.c and new-env_set.c didn't help much. "git show --color-moved" helps, tho).

Two small catches:

- this collided with 06ad53e067d9a8b and setenv_long_long() being changed from "%lld" to PRIi64 - so I changed the patch accordingly so that it still only moves lines from misc.c to env_set.c

- the hunk that adds env_set.c to "some of the test modules" caused build fails here (because it was added to the wrong module, assuming test code that wasn't there yet) - so I've removed it. I'm aware that this will cause build failures later on when the test driver in question shows up, but then I'll re-add it. "Building test code" follows slightly easier rules on "changing patches" :-)

Your patch has been applied to the master branch.

commit 68b97b25e4c479156d697bf3df90a4b68fbbbcea

Author: Steffan Karger
Date:   Fri Dec 8 13:07:47 2017 +0100
     Move env helper functions into their own module/file

     Signed-off-by: Antonio Quartulli <[email protected]...>
     Signed-off-by: Steffan Karger <[email protected]...>
     Acked-by: Gert Doering <[email protected]...>
     Message-Id: <[email protected]...>
     URL: https://www.mail-archive.com/[email protected]/msg16066.html
     Signed-off-by: Gert Doering <[email protected]...>

--
kind regards,

Gert Doering

#19 lore raw

PATCH 8/10 Move file-related functions from misc.c to platform.c

In reply to Steffan Karger

To avoid having to include misc.c - which is a dependency mess - in the tls-crypt unit tests, move file-handing related functions to platform.c (which is where other file-related functions already reside).

---
 src/openvpn/init.c                         |   2 +-
 src/openvpn/misc.c                         | 148 -----------------------------
 src/openvpn/misc.h                         |  12 ---
 src/openvpn/multi.c                        |  25 ++---
 src/openvpn/pf.c                           |   4 +-
 src/openvpn/platform.c                     | 148 +++++++++++++++++++++++++++++
 src/openvpn/platform.h                     |  18 ++++
 src/openvpn/plugin.c                       |   6 +-
 src/openvpn/ssl_verify.c                   |  12 ++-
 tests/unit_tests/openvpn/Makefile.am       |   3 +
 tests/unit_tests/openvpn/mock_get_random.c |  36 +++++++
 11 files changed, 232 insertions(+), 182 deletions(-)
 create mode 100644 tests/unit_tests/openvpn/mock_get_random.c
src/openvpn/init.c +1 -1
index a4603b3..4055f28 100644
@@ -807,7 +807,7 @@ init_static(void)
 #ifdef STATUS_PRINTF_TEST
     {
         struct gc_arena gc = gc_new();
-        const char *tmp_file = create_temp_file("/tmp", "foo", &gc);
+        const char *tmp_file = platform_create_temp_file("/tmp", "foo", &gc);
         struct status_output *so = status_open(tmp_file, 0, -1, NULL, STATUS_OUTPUT_WRITE);
         status_printf(so, "%s", "foo");
         status_printf(so, "%s", "bar");
src/openvpn/misc.c +0 -148

160 lines not on this page · raw message

src/openvpn/misc.h +0 -12
index 3725fcf..82bff9a 100644
@@ -79,18 +79,6 @@ const char **make_extended_arg_array(char **p, struct gc_arena *gc);
 /* an analogue to the random() function, but use OpenSSL functions if available */
 long int get_random(void);
 
-/* return true if filename can be opened for read */
-bool test_file(const char *filename);
-
-/* create a temporary file in directory, returns the filename of the created file */
-const char *create_temp_file(const char *directory, const char *prefix, struct gc_arena *gc);
-
-/* put a directory and filename together */
-const char *gen_path(const char *directory, const char *filename, struct gc_arena *gc);
-
-/* return true if pathname is absolute */
-bool absolute_pathname(const char *pathname);
-
 /* prepend a random prefix to hostname */
 const char *hostname_randomize(const char *hostname, struct gc_arena *gc);
 
src/openvpn/multi.c +14 -11
index 25b2d09..acc6912 100644
@@ -1639,7 +1639,7 @@ multi_client_connect_post(struct multi_context *m,
                           unsigned int *option_types_found)
 {
     /* Did script generate a dynamic config file? */
-    if (test_file(dc_file))
+    if (platform_test_file(dc_file))
     {
         options_server_import(&mi->context.options,
                               dc_file,
@@ -1826,12 +1826,13 @@ multi_connection_established(struct multi_context *m, struct multi_instance *mi)
         {
             const char *ccd_file;
 
-            ccd_file = gen_path(mi->context.options.client_config_dir,
-                                tls_common_name(mi->context.c2.tls_multi, false),
-                                &gc);
+            ccd_file = platform_gen_path(mi->context.options.client_config_dir,
+                                         tls_common_name(mi->context.c2.tls_multi,
+                                                         false),
+                                         &gc);
 
             /* try common-name file */
-            if (test_file(ccd_file))
+            if (platform_test_file(ccd_file))
             {
                 options_server_import(&mi->context.options,
                                       ccd_file,
@@ -1842,11 +1843,11 @@ multi_connection_established(struct multi_context *m, struct multi_instance *mi)
             }
             else /* try default file */
             {
-                ccd_file = gen_path(mi->context.options.client_config_dir,
-                                    CCD_DEFAULT,
-                                    &gc);
+                ccd_file = platform_gen_path(mi->context.options.client_config_dir,
+                                             CCD_DEFAULT,
+                                             &gc);
 
-                if (test_file(ccd_file))
+                if (platform_test_file(ccd_file))
                 {
                     options_server_import(&mi->context.options,
                                           ccd_file,
@@ -1876,7 +1877,8 @@ multi_connection_established(struct multi_context *m, struct multi_instance *mi)
         if (plugin_defined(mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT))
         {
             struct argv argv = argv_new();
-            const char *dc_file = create_temp_file(mi->context.options.tmp_dir, "cc", &gc);
+            const char *dc_file = platform_create_temp_file(mi->context.options.tmp_dir,
+                                                            "cc", &gc);
 
             if (!dc_file)
             {
@@ -1938,7 +1940,8 @@ script_depr_failed:
 
             setenv_str(mi->context.c2.es, "script_type", "client-connect");
 
-            dc_file = create_temp_file(mi->context.options.tmp_dir, "cc", &gc);
+            dc_file = platform_create_temp_file(mi->context.options.tmp_dir,
+                                                "cc", &gc);
             if (!dc_file)
             {
                 cc_succeeded = false;
src/openvpn/pf.c +2 -2
index 6e4107c..fd9f215 100644
@@ -621,8 +621,8 @@ pf_init_context(struct context *c)
 #ifdef PLUGIN_PF
     if (plugin_defined(c->plugins, OPENVPN_PLUGIN_ENABLE_PF))
     {
-        c->c2.pf.filename = create_temp_file(c->options.tmp_dir, "pf",
-                                             &c->c2.gc);
+        c->c2.pf.filename = platform_create_temp_file(c->options.tmp_dir, "pf",
+                                                      &c->c2.gc);
         if (c->c2.pf.filename)
         {
             setenv_str(c->c2.es, "pf_file", c->c2.pf.filename);
src/openvpn/platform.c +148 -0

157 lines not on this page · raw message

src/openvpn/platform.h +18 -0
index cd2bbc9..e116fd5 100644
@@ -49,6 +49,7 @@
 #endif
 
 #include "basic.h"
+#include "buffer.h"
 
 /* Get/Set UID of process */
 
@@ -143,4 +144,21 @@ typedef struct stat platform_stat_t;
 #endif
 int platform_stat(const char *path, platform_stat_t *buf);
 
+/**
+ * Create a temporary file in directory, returns the filename of the created
+ * file.
+ */
+const char *platform_create_temp_file(const char *directory, const char *prefix,
+                                      struct gc_arena *gc);
+
+/** Put a directory and filename together. */
+const char *platform_gen_path(const char *directory, const char *filename,
+                              struct gc_arena *gc);
+
+/** Return true if pathname is absolute. */
+bool platform_absolute_pathname(const char *pathname);
+
+/** Return true if filename can be opened for read. */
+bool platform_test_file(const char *filename);
+
 #endif /* ifndef PLATFORM_H */
src/openvpn/plugin.c +3 -3
index 7387f8b..9dfa744 100644
@@ -249,7 +249,7 @@ plugin_init_item(struct plugin *p, const struct plugin_option *o)
      * was parsed.
      *
      */
-    if (!absolute_pathname(p->so_pathname)
+    if (!platform_absolute_pathname(p->so_pathname)
         && p->so_pathname[0] != '.')
     {
         char full[PATH_MAX];
@@ -259,7 +259,7 @@ plugin_init_item(struct plugin *p, const struct plugin_option *o)
     }
     else
     {
-        rel = !absolute_pathname(p->so_pathname);
+        rel = !platform_absolute_pathname(p->so_pathname);
         p->handle = dlopen(p->so_pathname, RTLD_NOW);
     }
     if (!p->handle)
@@ -271,7 +271,7 @@ plugin_init_item(struct plugin *p, const struct plugin_option *o)
 
 #else  /* ifndef _WIN32 */
 
-    rel = !absolute_pathname(p->so_pathname);
+    rel = !platform_absolute_pathname(p->so_pathname);
     p->module = LoadLibraryW(wide_string(p->so_pathname, &gc));
     if (!p->module)
     {
src/openvpn/ssl_verify.c +7 -5
index ebb1da2..3568bad 100644
@@ -547,7 +547,7 @@ verify_cert_export_cert(openvpn_x509_cert_t *peercert, const char *tmp_dir, stru
 
     /* create tmp file to store peer cert */
     if (!tmp_dir
-        || !(peercert_filename = create_temp_file(tmp_dir, "pcf", gc)))
+        || !(peercert_filename = platform_create_temp_file(tmp_dir, "pcf", gc)))
     {
         msg (M_WARN, "Failed to create peer cert file");
         return NULL;
@@ -887,7 +887,7 @@ key_state_gen_auth_control_file(struct key_state *ks, const struct tls_options *
     struct gc_arena gc = gc_new();
 
     key_state_rm_auth_control_file(ks);
-    const char *acf = create_temp_file(opt->tmp_dir, "acf", &gc);
+    const char *acf = platform_create_temp_file(opt->tmp_dir, "acf", &gc);
     if (acf)
     {
         ks->auth_control_file = string_alloc(acf, NULL);
@@ -1100,7 +1100,8 @@ verify_user_pass_script(struct tls_session *session, const struct user_pass *up)
         {
             struct status_output *so;
 
-            tmp_file = create_temp_file(session->opt->tmp_dir, "up", &gc);
+            tmp_file = platform_create_temp_file(session->opt->tmp_dir, "up",
+                                                 &gc);
             if (tmp_file)
             {
                 so = status_open(tmp_file, 0, -1, NULL, STATUS_OUTPUT_WRITE);
@@ -1510,8 +1511,9 @@ verify_final_auth_checks(struct tls_multi *multi, struct tls_session *session)
         struct gc_arena gc = gc_new();
 
         const char *cn = session->common_name;
-        const char *path = gen_path(session->opt->client_config_dir_exclusive, cn, &gc);
-        if (!cn || !strcmp(cn, CCD_DEFAULT) || !test_file(path))
+        const char *path = platform_gen_path(session->opt->client_config_dir_exclusive,
+                                             cn, &gc);
+        if (!cn || !strcmp(cn, CCD_DEFAULT) || !platform_test_file(path))
         {
             ks->authenticated = false;
             wipe_auth_token(multi);
tests/unit_tests/openvpn/Makefile.am +3 -0
index 7b41363..b69da84 100644
@@ -19,6 +19,7 @@ argv_testdriver_CFLAGS  = @TEST_CFLAGS@ -I$(openvpn_srcdir) -I$(compat_srcdir) \
 argv_testdriver_LDFLAGS = @TEST_LDFLAGS@ -L$(openvpn_srcdir) -Wl,--wrap=parse_line \
 	$(OPTIONAL_CRYPTO_LIBS)
 argv_testdriver_SOURCES = test_argv.c mock_msg.c \
+	mock_get_random.c \
 	$(openvpn_srcdir)/platform.c \
 	$(openvpn_srcdir)/buffer.c \
 	$(openvpn_srcdir)/argv.c
@@ -26,6 +27,7 @@ argv_testdriver_SOURCES = test_argv.c mock_msg.c \
 buffer_testdriver_CFLAGS  = @TEST_CFLAGS@ -I$(openvpn_srcdir) -I$(compat_srcdir)
 buffer_testdriver_LDFLAGS = @TEST_LDFLAGS@ -L$(openvpn_srcdir) -Wl,--wrap=parse_line
 buffer_testdriver_SOURCES = test_buffer.c mock_msg.c \
+	mock_get_random.c \
 	$(openvpn_srcdir)/buffer.c \
 	$(openvpn_srcdir)/platform.c
 
@@ -50,6 +52,7 @@ packet_id_testdriver_CFLAGS  = @TEST_CFLAGS@ \
 packet_id_testdriver_LDFLAGS = @TEST_LDFLAGS@ \
 	$(OPTIONAL_CRYPTO_LIBS)
 packet_id_testdriver_SOURCES = test_packet_id.c mock_msg.c \
+	mock_get_random.c \
 	$(openvpn_srcdir)/buffer.c \
 	$(openvpn_srcdir)/otime.c \
 	$(openvpn_srcdir)/packet_id.c \
tests/unit_tests/openvpn/mock_get_random.c new file +36 -0
new file mode 100644
index 0000000..da92a9b
@@ -0,0 +1,36 @@
+/*
+ *  OpenVPN -- An application to securely tunnel IP networks
+ *             over a single UDP port, with support for SSL/TLS-based
+ *             session authentication and key exchange,
+ *             packet encryption, packet authentication, and
+ *             packet compression.
+ *
+ *  Copyright (C) 2017 Fox Crypto B.V. <[email protected]...>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+unsigned long
+get_random(void)
+{
+    /* rand() is not very random, but it's C99 and this is just for testing */
+    return rand();
+}
Signature · 1 line
2.7.4
#20 lore raw

Re: [Openvpn-devel] [PATCH 08/10] Move file-related functions from misc.c to platform.c

Quoted 5 lines · Steffan Karger

To avoid having to include misc.c - which is a dependency mess - in the tls-crypt unit tests, move file-handing related functions to platform.c (which is where other file-related functions already reside).

I have applied this patch right after the one about PEM and it triggered the following:

Falling back to patching base and 3-way merge...

How about rebasing it before the actual tls-crypt-v2 patches so that it could be applied earlier and shrink down this patchset?

On top of that, would you please extend the commit message a little bit to include the fact that you are adding a new mock'd component (get_random) needed by the tls-crypt unit-test?

Actually....If I understand correctly, this new mock'd component will only be used when the other patches will be merged too. Therefore, I guess this patch could be divided in 2: one part being the code move and the second part being the introduction of the mock'd component. The latter may directly be included in the patch adding the tls-crypt unit-test.

What do you think?

Other than this, the patch looks good.
Checked with "git show --color-moved=zebra" to ensure that the code being moved wasn't modified.

And +1 for this patch for cleaning up the misc.c mess some more.

Cheers,

Quoted 642 lines
---
 src/openvpn/init.c                         |   2 +-
 src/openvpn/misc.c                         | 148 -----------------------------
 src/openvpn/misc.h                         |  12 ---
 src/openvpn/multi.c                        |  25 ++---
 src/openvpn/pf.c                           |   4 +-
 src/openvpn/platform.c                     | 148 +++++++++++++++++++++++++++++
 src/openvpn/platform.h                     |  18 ++++
 src/openvpn/plugin.c                       |   6 +-
 src/openvpn/ssl_verify.c                   |  12 ++-
 tests/unit_tests/openvpn/Makefile.am       |   3 +
 tests/unit_tests/openvpn/mock_get_random.c |  36 +++++++
 11 files changed, 232 insertions(+), 182 deletions(-)
 create mode 100644 tests/unit_tests/openvpn/mock_get_random.c
src/openvpn/init.c +1 -1
index a4603b3..4055f28 100644
@@ -807,7 +807,7 @@ init_static(void)
 #ifdef STATUS_PRINTF_TEST
     {
         struct gc_arena gc = gc_new();
-        const char *tmp_file = create_temp_file("/tmp", "foo", &gc);
+        const char *tmp_file = platform_create_temp_file("/tmp", "foo", &gc);
         struct status_output *so = status_open(tmp_file, 0, -1, NULL, STATUS_OUTPUT_WRITE);
         status_printf(so, "%s", "foo");
         status_printf(so, "%s", "bar");
src/openvpn/misc.c +0 -148
index ad26f3d..b04a9d7 100644
@@ -313,87 +313,6 @@ openvpn_popen(const struct argv *a,  const struct env_set *es)
     return ret;
 }
 
-
-/* return true if filename can be opened for read */
-bool
-test_file(const char *filename)
-{
-    bool ret = false;
-    if (filename)
-    {
-        FILE *fp = platform_fopen(filename, "r");
-        if (fp)
-        {
-            fclose(fp);
-            ret = true;
-        }
-        else
-        {
-            if (openvpn_errno() == EACCES)
-            {
-                msg( M_WARN | M_ERRNO, "Could not access file '%s'", filename);
-            }
-        }
-    }
-
-    dmsg(D_TEST_FILE, "TEST FILE '%s' [%d]",
-         filename ? filename : "UNDEF",
-         ret);
-
-    return ret;
-}
-
-/* create a temporary filename in directory */
-const char *
-create_temp_file(const char *directory, const char *prefix, struct gc_arena *gc)
-{
-    int fd;
-    const char *retfname = NULL;
-    unsigned int attempts = 0;
-    char fname[256] = { 0 };
-    const char *fname_fmt = PACKAGE "_%.*s_%08lx%08lx.tmp";
-    const int max_prefix_len = sizeof(fname) - (sizeof(PACKAGE) + 7 + (2 * 8));
-
-    while (attempts < 6)
-    {
-        ++attempts;
-
-        if (!openvpn_snprintf(fname, sizeof(fname), fname_fmt, max_prefix_len,
-                              prefix, (unsigned long) get_random(),
-                              (unsigned long) get_random()))
-        {
-            msg(M_WARN, "ERROR: temporary filename too long");
-            return NULL;
-        }
-
-        retfname = gen_path(directory, fname, gc);
-        if (!retfname)
-        {
-            msg(M_WARN, "Failed to create temporary filename and path");
-            return NULL;
-        }
-
-        /* Atomically create the file.  Errors out if the file already
-         * exists.  */
-        fd = platform_open(retfname, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR);
-        if (fd != -1)
-        {
-            close(fd);
-            return retfname;
-        }
-        else if (fd == -1 && errno != EEXIST)
-        {
-            /* Something else went wrong, no need to retry.  */
-            msg(M_WARN | M_ERRNO, "Could not create temporary file '%s'",
-                retfname);
-            return NULL;
-        }
-    }
-
-    msg(M_WARN, "Failed to create temporary file after %i attempts", attempts);
-    return NULL;
-}
-
 /*
  * Prepend a random string to hostname to prevent DNS caching.
  * For example, foo.bar.gov would be modified to <random-chars>.foo.bar.gov.
@@ -416,73 +335,6 @@ hostname_randomize(const char *hostname, struct gc_arena *gc)
 }
 
 /*
- * Put a directory and filename together.
- */
-const char *
-gen_path(const char *directory, const char *filename, struct gc_arena *gc)
-{
-#ifdef _WIN32
-    const int CC_PATH_RESERVED = CC_LESS_THAN|CC_GREATER_THAN|CC_COLON
-                                 |CC_DOUBLE_QUOTE|CC_SLASH|CC_BACKSLASH|CC_PIPE|CC_QUESTION_MARK|CC_ASTERISK;
-#else
-    const int CC_PATH_RESERVED = CC_SLASH;
-#endif
-
-    if (!gc)
-    {
-        return NULL; /* Would leak memory otherwise */
-    }
-
-    const char *safe_filename = string_mod_const(filename, CC_PRINT, CC_PATH_RESERVED, '_', gc);
-
-    if (safe_filename
-        && strcmp(safe_filename, ".")
-        && strcmp(safe_filename, "..")
-#ifdef _WIN32
-        && win_safe_filename(safe_filename)
-#endif
-        )
-    {
-        const size_t outsize = strlen(safe_filename) + (directory ? strlen(directory) : 0) + 16;
-        struct buffer out = alloc_buf_gc(outsize, gc);
-        char dirsep[2];
-
-        dirsep[0] = OS_SPECIFIC_DIRSEP;
-        dirsep[1] = '\0';
-
-        if (directory)
-        {
-            buf_printf(&out, "%s%s", directory, dirsep);
-        }
-        buf_printf(&out, "%s", safe_filename);
-
-        return BSTR(&out);
-    }
-    else
-    {
-        return NULL;
-    }
-}
-
-bool
-absolute_pathname(const char *pathname)
-{
-    if (pathname)
-    {
-        const int c = pathname[0];
-#ifdef _WIN32
-        return c == '\\' || (isalpha(c) && pathname[1] == ':' && pathname[2] == '\\');
-#else
-        return c == '/';
-#endif
-    }
-    else
-    {
-        return false;
-    }
-}
-
-/*
  * Get and store a username/password
  */
 
src/openvpn/misc.h +0 -12
index 3725fcf..82bff9a 100644
@@ -79,18 +79,6 @@ const char **make_extended_arg_array(char **p, struct gc_arena *gc);
 /* an analogue to the random() function, but use OpenSSL functions if available */
 long int get_random(void);
 
-/* return true if filename can be opened for read */
-bool test_file(const char *filename);
-
-/* create a temporary file in directory, returns the filename of the created file */
-const char *create_temp_file(const char *directory, const char *prefix, struct gc_arena *gc);
-
-/* put a directory and filename together */
-const char *gen_path(const char *directory, const char *filename, struct gc_arena *gc);
-
-/* return true if pathname is absolute */
-bool absolute_pathname(const char *pathname);
-
 /* prepend a random prefix to hostname */
 const char *hostname_randomize(const char *hostname, struct gc_arena *gc);
 
src/openvpn/multi.c +14 -11
index 25b2d09..acc6912 100644
@@ -1639,7 +1639,7 @@ multi_client_connect_post(struct multi_context *m,
                           unsigned int *option_types_found)
 {
     /* Did script generate a dynamic config file? */
-    if (test_file(dc_file))
+    if (platform_test_file(dc_file))
     {
         options_server_import(&mi->context.options,
                               dc_file,
@@ -1826,12 +1826,13 @@ multi_connection_established(struct multi_context *m, struct multi_instance *mi)
         {
             const char *ccd_file;
 
-            ccd_file = gen_path(mi->context.options.client_config_dir,
-                                tls_common_name(mi->context.c2.tls_multi, false),
-                                &gc);
+            ccd_file = platform_gen_path(mi->context.options.client_config_dir,
+                                         tls_common_name(mi->context.c2.tls_multi,
+                                                         false),
+                                         &gc);
 
             /* try common-name file */
-            if (test_file(ccd_file))
+            if (platform_test_file(ccd_file))
             {
                 options_server_import(&mi->context.options,
                                       ccd_file,
@@ -1842,11 +1843,11 @@ multi_connection_established(struct multi_context *m, struct multi_instance *mi)
             }
             else /* try default file */
             {
-                ccd_file = gen_path(mi->context.options.client_config_dir,
-                                    CCD_DEFAULT,
-                                    &gc);
+                ccd_file = platform_gen_path(mi->context.options.client_config_dir,
+                                             CCD_DEFAULT,
+                                             &gc);
 
-                if (test_file(ccd_file))
+                if (platform_test_file(ccd_file))
                 {
                     options_server_import(&mi->context.options,
                                           ccd_file,
@@ -1876,7 +1877,8 @@ multi_connection_established(struct multi_context *m, struct multi_instance *mi)
         if (plugin_defined(mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT))
         {
             struct argv argv = argv_new();
-            const char *dc_file = create_temp_file(mi->context.options.tmp_dir, "cc", &gc);
+            const char *dc_file = platform_create_temp_file(mi->context.options.tmp_dir,
+                                                            "cc", &gc);
 
             if (!dc_file)
             {
@@ -1938,7 +1940,8 @@ script_depr_failed:
 
             setenv_str(mi->context.c2.es, "script_type", "client-connect");
 
-            dc_file = create_temp_file(mi->context.options.tmp_dir, "cc", &gc);
+            dc_file = platform_create_temp_file(mi->context.options.tmp_dir,
+                                                "cc", &gc);
             if (!dc_file)
             {
                 cc_succeeded = false;
src/openvpn/pf.c +2 -2
index 6e4107c..fd9f215 100644
@@ -621,8 +621,8 @@ pf_init_context(struct context *c)
 #ifdef PLUGIN_PF
     if (plugin_defined(c->plugins, OPENVPN_PLUGIN_ENABLE_PF))
     {
-        c->c2.pf.filename = create_temp_file(c->options.tmp_dir, "pf",
-                                             &c->c2.gc);
+        c->c2.pf.filename = platform_create_temp_file(c->options.tmp_dir, "pf",
+                                                      &c->c2.gc);
         if (c->c2.pf.filename)
         {
             setenv_str(c->c2.es, "pf_file", c->c2.pf.filename);
src/openvpn/platform.c +148 -0
index e942ba9..5281267 100644
@@ -31,6 +31,7 @@
 
 #include "buffer.h"
 #include "error.h"
+#include "misc.h"
 #include "win32.h"
 
 #include "memdbg.h"
@@ -335,3 +336,150 @@ platform_stat(const char *path, platform_stat_t *buf)
 #endif
 }
 
+/* create a temporary filename in directory */
+const char *
+platform_create_temp_file(const char *directory, const char *prefix, struct gc_arena *gc)
+{
+    int fd;
+    const char *retfname = NULL;
+    unsigned int attempts = 0;
+    char fname[256] = { 0 };
+    const char *fname_fmt = PACKAGE "_%.*s_%08lx%08lx.tmp";
+    const int max_prefix_len = sizeof(fname) - (sizeof(PACKAGE) + 7 + (2 * 8));
+
+    while (attempts < 6)
+    {
+        ++attempts;
+
+        if (!openvpn_snprintf(fname, sizeof(fname), fname_fmt, max_prefix_len,
+                              prefix, (unsigned long) get_random(),
+                              (unsigned long) get_random()))
+        {
+            msg(M_WARN, "ERROR: temporary filename too long");
+            return NULL;
+        }
+
+        retfname = platform_gen_path(directory, fname, gc);
+        if (!retfname)
+        {
+            msg(M_WARN, "Failed to create temporary filename and path");
+            return NULL;
+        }
+
+        /* Atomically create the file.  Errors out if the file already
+         * exists.  */
+        fd = platform_open(retfname, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR);
+        if (fd != -1)
+        {
+            close(fd);
+            return retfname;
+        }
+        else if (fd == -1 && errno != EEXIST)
+        {
+            /* Something else went wrong, no need to retry.  */
+            msg(M_WARN | M_ERRNO, "Could not create temporary file '%s'",
+                retfname);
+            return NULL;
+        }
+    }
+
+    msg(M_WARN, "Failed to create temporary file after %i attempts", attempts);
+    return NULL;
+}
+
+/*
+ * Put a directory and filename together.
+ */
+const char *
+platform_gen_path(const char *directory, const char *filename,
+                  struct gc_arena *gc)
+{
+#ifdef _WIN32
+    const int CC_PATH_RESERVED = CC_LESS_THAN|CC_GREATER_THAN|CC_COLON
+                                 |CC_DOUBLE_QUOTE|CC_SLASH|CC_BACKSLASH|CC_PIPE|CC_QUESTION_MARK|CC_ASTERISK;
+#else
+    const int CC_PATH_RESERVED = CC_SLASH;
+#endif
+
+    if (!gc)
+    {
+        return NULL; /* Would leak memory otherwise */
+    }
+
+    const char *safe_filename = string_mod_const(filename, CC_PRINT, CC_PATH_RESERVED, '_', gc);
+
+    if (safe_filename
+        && strcmp(safe_filename, ".")
+        && strcmp(safe_filename, "..")
+#ifdef _WIN32
+        && win_safe_filename(safe_filename)
+#endif
+        )
+    {
+        const size_t outsize = strlen(safe_filename) + (directory ? strlen(directory) : 0) + 16;
+        struct buffer out = alloc_buf_gc(outsize, gc);
+        char dirsep[2];
+
+        dirsep[0] = OS_SPECIFIC_DIRSEP;
+        dirsep[1] = '\0';
+
+        if (directory)
+        {
+            buf_printf(&out, "%s%s", directory, dirsep);
+        }
+        buf_printf(&out, "%s", safe_filename);
+
+        return BSTR(&out);
+    }
+    else
+    {
+        return NULL;
+    }
+}
+
+bool
+platform_absolute_pathname(const char *pathname)
+{
+    if (pathname)
+    {
+        const int c = pathname[0];
+#ifdef _WIN32
+        return c == '\\' || (isalpha(c) && pathname[1] == ':' && pathname[2] == '\\');
+#else
+        return c == '/';
+#endif
+    }
+    else
+    {
+        return false;
+    }
+}
+
+/* return true if filename can be opened for read */
+bool
+platform_test_file(const char *filename)
+{
+    bool ret = false;
+    if (filename)
+    {
+        FILE *fp = platform_fopen(filename, "r");
+        if (fp)
+        {
+            fclose(fp);
+            ret = true;
+        }
+        else
+        {
+            if (openvpn_errno() == EACCES)
+            {
+                msg( M_WARN | M_ERRNO, "Could not access file '%s'", filename);
+            }
+        }
+    }
+
+    dmsg(D_TEST_FILE, "TEST FILE '%s' [%d]",
+         filename ? filename : "UNDEF",
+         ret);
+
+    return ret;
+}
src/openvpn/platform.h +18 -0
index cd2bbc9..e116fd5 100644
@@ -49,6 +49,7 @@
 #endif
 
 #include "basic.h"
+#include "buffer.h"
 
 /* Get/Set UID of process */
 
@@ -143,4 +144,21 @@ typedef struct stat platform_stat_t;
 #endif
 int platform_stat(const char *path, platform_stat_t *buf);
 
+/**
+ * Create a temporary file in directory, returns the filename of the created
+ * file.
+ */
+const char *platform_create_temp_file(const char *directory, const char *prefix,
+                                      struct gc_arena *gc);
+
+/** Put a directory and filename together. */
+const char *platform_gen_path(const char *directory, const char *filename,
+                              struct gc_arena *gc);
+
+/** Return true if pathname is absolute. */
+bool platform_absolute_pathname(const char *pathname);
+
+/** Return true if filename can be opened for read. */
+bool platform_test_file(const char *filename);
+
 #endif /* ifndef PLATFORM_H */
src/openvpn/plugin.c +3 -3
index 7387f8b..9dfa744 100644
@@ -249,7 +249,7 @@ plugin_init_item(struct plugin *p, const struct plugin_option *o)
      * was parsed.
      *
      */
-    if (!absolute_pathname(p->so_pathname)
+    if (!platform_absolute_pathname(p->so_pathname)
         && p->so_pathname[0] != '.')
     {
         char full[PATH_MAX];
@@ -259,7 +259,7 @@ plugin_init_item(struct plugin *p, const struct plugin_option *o)
     }
     else
     {
-        rel = !absolute_pathname(p->so_pathname);
+        rel = !platform_absolute_pathname(p->so_pathname);
         p->handle = dlopen(p->so_pathname, RTLD_NOW);
     }
     if (!p->handle)
@@ -271,7 +271,7 @@ plugin_init_item(struct plugin *p, const struct plugin_option *o)
 
 #else  /* ifndef _WIN32 */
 
-    rel = !absolute_pathname(p->so_pathname);
+    rel = !platform_absolute_pathname(p->so_pathname);
     p->module = LoadLibraryW(wide_string(p->so_pathname, &gc));
     if (!p->module)
     {
src/openvpn/ssl_verify.c +7 -5
index ebb1da2..3568bad 100644
@@ -547,7 +547,7 @@ verify_cert_export_cert(openvpn_x509_cert_t *peercert, const char *tmp_dir, stru
 
     /* create tmp file to store peer cert */
     if (!tmp_dir
-        || !(peercert_filename = create_temp_file(tmp_dir, "pcf", gc)))
+        || !(peercert_filename = platform_create_temp_file(tmp_dir, "pcf", gc)))
     {
         msg (M_WARN, "Failed to create peer cert file");
         return NULL;
@@ -887,7 +887,7 @@ key_state_gen_auth_control_file(struct key_state *ks, const struct tls_options *
     struct gc_arena gc = gc_new();
 
     key_state_rm_auth_control_file(ks);
-    const char *acf = create_temp_file(opt->tmp_dir, "acf", &gc);
+    const char *acf = platform_create_temp_file(opt->tmp_dir, "acf", &gc);
     if (acf)
     {
         ks->auth_control_file = string_alloc(acf, NULL);
@@ -1100,7 +1100,8 @@ verify_user_pass_script(struct tls_session *session, const struct user_pass *up)
         {
             struct status_output *so;
 
-            tmp_file = create_temp_file(session->opt->tmp_dir, "up", &gc);
+            tmp_file = platform_create_temp_file(session->opt->tmp_dir, "up",
+                                                 &gc);
             if (tmp_file)
             {
                 so = status_open(tmp_file, 0, -1, NULL, STATUS_OUTPUT_WRITE);
@@ -1510,8 +1511,9 @@ verify_final_auth_checks(struct tls_multi *multi, struct tls_session *session)
         struct gc_arena gc = gc_new();
 
         const char *cn = session->common_name;
-        const char *path = gen_path(session->opt->client_config_dir_exclusive, cn, &gc);
-        if (!cn || !strcmp(cn, CCD_DEFAULT) || !test_file(path))
+        const char *path = platform_gen_path(session->opt->client_config_dir_exclusive,
+                                             cn, &gc);
+        if (!cn || !strcmp(cn, CCD_DEFAULT) || !platform_test_file(path))
         {
             ks->authenticated = false;
             wipe_auth_token(multi);
tests/unit_tests/openvpn/Makefile.am +3 -0
index 7b41363..b69da84 100644
@@ -19,6 +19,7 @@ argv_testdriver_CFLAGS  = @TEST_CFLAGS@ -I$(openvpn_srcdir) -I$(compat_srcdir) \
 argv_testdriver_LDFLAGS = @TEST_LDFLAGS@ -L$(openvpn_srcdir) -Wl,--wrap=parse_line \
 	$(OPTIONAL_CRYPTO_LIBS)
 argv_testdriver_SOURCES = test_argv.c mock_msg.c \
+	mock_get_random.c \
 	$(openvpn_srcdir)/platform.c \
 	$(openvpn_srcdir)/buffer.c \
 	$(openvpn_srcdir)/argv.c
@@ -26,6 +27,7 @@ argv_testdriver_SOURCES = test_argv.c mock_msg.c \
 buffer_testdriver_CFLAGS  = @TEST_CFLAGS@ -I$(openvpn_srcdir) -I$(compat_srcdir)
 buffer_testdriver_LDFLAGS = @TEST_LDFLAGS@ -L$(openvpn_srcdir) -Wl,--wrap=parse_line
 buffer_testdriver_SOURCES = test_buffer.c mock_msg.c \
+	mock_get_random.c \
 	$(openvpn_srcdir)/buffer.c \
 	$(openvpn_srcdir)/platform.c
 
@@ -50,6 +52,7 @@ packet_id_testdriver_CFLAGS  = @TEST_CFLAGS@ \
 packet_id_testdriver_LDFLAGS = @TEST_LDFLAGS@ \
 	$(OPTIONAL_CRYPTO_LIBS)
 packet_id_testdriver_SOURCES = test_packet_id.c mock_msg.c \
+	mock_get_random.c \
 	$(openvpn_srcdir)/buffer.c \
 	$(openvpn_srcdir)/otime.c \
 	$(openvpn_srcdir)/packet_id.c \
tests/unit_tests/openvpn/mock_get_random.c new file +36 -0
new file mode 100644
index 0000000..da92a9b
@@ -0,0 +1,36 @@
+/*
+ *  OpenVPN -- An application to securely tunnel IP networks
+ *             over a single UDP port, with support for SSL/TLS-based
+ *             session authentication and key exchange,
+ *             packet encryption, packet authentication, and
+ *             packet compression.
+ *
+ *  Copyright (C) 2017 Fox Crypto B.V. <[email protected]...>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+unsigned long
+get_random(void)
+{
+    /* rand() is not very random, but it's C99 and this is just for testing */
+    return rand();
+}
Signature · 1 line
Antonio Quartulli
#21 lore raw

Re: [Openvpn-devel] [PATCH 08/10] Move file-related functions from misc.c to platform.c

Hi,

Quoted 22 lines · Antonio Quartulli
Quoted 5 lines · Steffan Karger

To avoid having to include misc.c - which is a dependency mess - in the tls-crypt unit tests, move file-handing related functions to platform.c (which is where other file-related functions already reside).

I have applied this patch right after the one about PEM and it triggered the following:

Falling back to patching base and 3-way merge...

How about rebasing it before the actual tls-crypt-v2 patches so that it could be applied earlier and shrink down this patchset?

On top of that, would you please extend the commit message a little bit to include the fact that you are adding a new mock'd component (get_random) needed by the tls-crypt unit-test?

Quoted 8 lines

Actually....If I understand correctly, this new mock'd component will only be used when the other patches will be merged too. Therefore, I guess this patch could be divided in 2: one part being the code move and the second part being the introduction of the mock'd component. The latter may directly be included in the patch adding the tls-crypt unit-test.

What do you think?

I think you have a good point. I'll reorder, split and merge.

Quoted 5 lines

Other than this, the patch looks good.
Checked with "git show --color-moved=zebra" to ensure that the code being moved wasn't modified.

And +1 for this patch for cleaning up the misc.c mess some more.

Thanks!

-Steffan

#22 lore raw

Re: [Openvpn-devel] [PATCH 08/10] Move file-related functions from misc.c to platform.c

Hi,

Quoted 11 lines · Steffan Karger
Quoted 8 lines · Antonio Quartulli

Actually....If I understand correctly, this new mock'd component will only be used when the other patches will be merged too. Therefore, I guess this patch could be divided in 2: one part being the code move and the second part being the introduction of the mock'd component. The latter may directly be included in the patch adding the tls-crypt unit-test.

What do you think?

I think you have a good point. I'll reorder, split and merge.

Hm, not that easy actually. The file functions themselves use get_random, to produce a random filename for the temp files. So just reorder, not split/merge. I'll add a note about this in the commit message.

-Steffan

#23 lore raw

PATCH 9/10 Move execve/run_script helper functions to run_command.c

In reply to Steffan Karger

From: Steffan Karger <[email protected]...>

To avoid having to include misc.c - which is a dependency mess - in the tls-crypt unit tests, move the command execution helper functions to a new run_command.c module.

While at it, abstract away the script_security global variable.

---
 src/openvpn/Makefile.am                |   1 +
 src/openvpn/env_set.c                  |   6 +-
 src/openvpn/init.c                     |   5 +-
 src/openvpn/lladdr.c                   |   1 +
 src/openvpn/misc.c                     | 218 ---------------------------
 src/openvpn/misc.h                     |  32 ----
 src/openvpn/multi.c                    |   2 +-
 src/openvpn/options.c                  |   3 +-
 src/openvpn/route.c                    |   2 +-
 src/openvpn/run_command.c              | 267 +++++++++++++++++++++++++++++++++
 src/openvpn/run_command.h              |  63 ++++++++
 src/openvpn/socket.c                   |   1 +
 src/openvpn/ssl_verify.c               |   4 +-
 src/openvpn/tls_crypt.c                |   1 +
 src/openvpn/tun.c                      |   2 +-
 src/openvpn/win32.c                    |   4 +-
 tests/unit_tests/openvpn/Makefile.am   |   3 +-
 tests/unit_tests/openvpn/test_crypto.c |   2 -
 18 files changed, 351 insertions(+), 266 deletions(-)
 create mode 100644 src/openvpn/run_command.c
 create mode 100644 src/openvpn/run_command.h
src/openvpn/Makefile.am +1 -0
index 441700a..86bb40a 100644
@@ -102,6 +102,7 @@ openvpn_SOURCES = \
 	pushlist.h \
 	reliable.c reliable.h \
 	route.c route.h \
+	run_command.c run_command.h \
 	schedule.c schedule.h \
 	session_id.c session_id.h \
 	shaper.c shaper.h \
src/openvpn/env_set.c +3 -3
index 2a22e0b..4a9de1a 100644
@@ -32,10 +32,10 @@
 
 #include "syshead.h"
 
-#include "misc.h"
-
 #include "env_set.h"
 
+#include "run_command.h"
+
 /*
  * Set environmental variable (int or string).
  *
@@ -414,7 +414,7 @@ setenv_str_i(struct env_set *es, const char *name, const char *value, const int
 bool
 env_allowed(const char *str)
 {
-    return (script_security >= SSEC_PW_ENV || !is_password_env_var(str));
+    return (script_security() >= SSEC_PW_ENV || !is_password_env_var(str));
 }
 
 /* Make arrays of strings */
src/openvpn/init.c +3 -2
index 4055f28..93143b1 100644
@@ -35,6 +35,7 @@
 
 #include "win32.h"
 #include "init.h"
+#include "run_command.h"
 #include "sig.h"
 #include "occ.h"
 #include "list.h"
@@ -3153,11 +3154,11 @@ do_option_warnings(struct context *c)
     /* If a script is used, print appropiate warnings */
     if (o->user_script_used)
     {
-        if (script_security >= SSEC_SCRIPTS)
+        if (script_security() >= SSEC_SCRIPTS)
         {
             msg(M_WARN, "NOTE: the current --script-security setting may allow this configuration to call user-defined scripts");
         }
-        else if (script_security >= SSEC_PW_ENV)
+        else if (script_security() >= SSEC_PW_ENV)
         {
             msg(M_WARN, "WARNING: the current --script-security setting may allow passwords to be passed to scripts via environmental variables");
         }
src/openvpn/lladdr.c +1 -0
index ff71e48..f24596b 100644
@@ -11,6 +11,7 @@
 #include "syshead.h"
 #include "error.h"
 #include "misc.h"
+#include "run_command.h"
 
 int
 set_lladdr(const char *ifname, const char *lladdr,
src/openvpn/misc.c +0 -218

230 lines not on this page · raw message

src/openvpn/misc.h +0 -32
index 82bff9a..491906b 100644
@@ -38,30 +38,6 @@
 /* forward declarations */
 struct plugin_list;
 
-/* system flags */
-#define S_SCRIPT (1<<0)
-#define S_FATAL  (1<<1)
-
-const char *system_error_message(int, struct gc_arena *gc);
-
-/* wrapper around the execve() call */
-int openvpn_popen(const struct argv *a,  const struct env_set *es);
-
-int openvpn_execve(const struct argv *a, const struct env_set *es, const unsigned int flags);
-
-bool openvpn_execve_check(const struct argv *a, const struct env_set *es, const unsigned int flags, const char *error_message);
-
-bool openvpn_execve_allowed(const unsigned int flags);
-
-static inline bool
-openvpn_run_script(const struct argv *a, const struct env_set *es, const unsigned int flags, const char *hook)
-{
-    char msg[256];
-
-    openvpn_snprintf(msg, sizeof(msg), "WARNING: Failed running command (%s)", hook);
-    return openvpn_execve_check(a, es, flags | S_SCRIPT, msg);
-}
-
 
 /* Set standard file descriptors to /dev/null */
 void set_std_files_to_null(bool stdin_only);
@@ -198,14 +174,6 @@ void get_user_pass_auto_userid(struct user_pass *up, const char *tag);
 extern const char *iproute_path;
 #endif
 
-/* Script security */
-#define SSEC_NONE      0 /* strictly no calling of external programs */
-#define SSEC_BUILT_IN  1 /* only call built-in programs such as ifconfig, route, netsh, etc.*/
-#define SSEC_SCRIPTS   2 /* allow calling of built-in programs and user-defined scripts */
-#define SSEC_PW_ENV    3 /* allow calling of built-in programs and user-defined scripts that may receive a password as an environmental variable */
-extern int script_security; /* GLOBAL */
-
-
 #define COMPAT_FLAG_QUERY         0       /** compat_flags operator: Query for a flag */
 #define COMPAT_FLAG_SET           (1<<0)  /** compat_flags operator: Set a compat flag */
 #define COMPAT_NAMES              (1<<1)  /** compat flag: --compat-names set */
src/openvpn/multi.c +1 -1
index acc6912..2818b9a 100644
@@ -38,7 +38,7 @@
 
 #include "multi.h"
 #include "push.h"
-#include "misc.h"
+#include "run_command.h"
 #include "otime.h"
 #include "gremlin.h"
 #include "mstats.h"
src/openvpn/options.c +2 -1
index 89bff1e..2d577f8 100644
@@ -41,6 +41,7 @@
 #include "buffer.h"
 #include "error.h"
 #include "common.h"
+#include "run_command.h"
 #include "shaper.h"
 #include "crypto.h"
 #include "ssl.h"
@@ -6386,7 +6387,7 @@ add_option(struct options *options,
     else if (streq(p[0], "script-security") && p[1] && !p[2])
     {
         VERIFY_PERMISSION(OPT_P_GENERAL);
-        script_security = atoi(p[1]);
+        script_security_set(atoi(p[1]));
     }
     else if (streq(p[0], "mssfix") && !p[2])
     {
src/openvpn/route.c +1 -1
index 8c71e6e..955e15f 100644
@@ -36,7 +36,7 @@
 #include "common.h"
 #include "error.h"
 #include "route.h"
-#include "misc.h"
+#include "run_command.h"
 #include "socket.h"
 #include "manage.h"
 #include "win32.h"
src/openvpn/run_command.c new file +267 -0

267 lines not on this page · raw message

src/openvpn/run_command.h new file +63 -0

63 lines not on this page · raw message

src/openvpn/socket.c +1 -0
index 0fc91f2..e7126da 100644
@@ -35,6 +35,7 @@
 #include "gremlin.h"
 #include "plugin.h"
 #include "ps.h"
+#include "run_command.h"
 #include "manage.h"
 #include "misc.h"
 #include "manage.h"
src/openvpn/ssl_verify.c +2 -2
index 3568bad..6e7b0b0 100644
@@ -34,10 +34,10 @@
 
 #include "syshead.h"
 
-#include "misc.h"
+#include "base64.h"
 #include "manage.h"
 #include "otime.h"
-#include "base64.h"
+#include "run_command.h"
 #include "ssl_verify.h"
 #include "ssl_verify_backend.h"
 
src/openvpn/tls_crypt.c +1 -0
index 0c94d73..6fbe3e5 100644
@@ -32,6 +32,7 @@
 #include "base64.h"
 #include "crypto.h"
 #include "platform.h"
+#include "run_command.h"
 #include "session_id.h"
 #include "ssl.h"
 
src/openvpn/tun.c +1 -1
index 25831ce..6103163 100644
@@ -40,7 +40,7 @@
 #include "tun.h"
 #include "fdmisc.h"
 #include "common.h"
-#include "misc.h"
+#include "run_command.h"
 #include "socket.h"
 #include "manage.h"
 #include "route.h"
src/openvpn/win32.c +2 -2
index 95fea5d..1e5d871 100644
@@ -39,9 +39,9 @@
 #include "buffer.h"
 #include "error.h"
 #include "mtu.h"
+#include "run_command.h"
 #include "sig.h"
 #include "win32.h"
-#include "misc.h"
 #include "openvpn-msg.h"
 
 #include "memdbg.h"
@@ -1137,7 +1137,7 @@ openvpn_execve(const struct argv *a, const struct env_set *es, const unsigned in
             free(env);
             gc_free(&gc);
         }
-        else if (!exec_warn && (script_security < SSEC_SCRIPTS))
+        else if (!exec_warn && (script_security() < SSEC_SCRIPTS))
         {
             msg(M_WARN, SCRIPT_SECURITY_WARNING);
             exec_warn = true;
tests/unit_tests/openvpn/Makefile.am +2 -1
index b69da84..a5d7f01 100644
@@ -44,7 +44,8 @@ crypto_testdriver_SOURCES = test_crypto.c mock_msg.c \
 	$(openvpn_srcdir)/env_set.c \
 	$(openvpn_srcdir)/otime.c \
 	$(openvpn_srcdir)/packet_id.c \
-	$(openvpn_srcdir)/platform.c
+	$(openvpn_srcdir)/platform.c \
+	$(openvpn_srcdir)/run_command.c
 
 packet_id_testdriver_CFLAGS  = @TEST_CFLAGS@ \
 	-I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \
tests/unit_tests/openvpn/test_crypto.c +0 -2
index 62d5b3f..b4ef668 100644
@@ -41,8 +41,6 @@
 
 #include "mock_msg.h"
 
-int script_security = 0; /* Avoid including misc.c */
-
 static const char testtext[] = "Dummy text to test PEM encoding";
 
 /**
Signature · 1 line
2.7.4
#24 lore raw

Re: [Openvpn-devel] [PATCH 09/10] Move execve/run_script helper functions to run_command.c

Hi,

Quoted 9 lines · Steffan Karger

From: Steffan Karger <[email protected]...>

To avoid having to include misc.c - which is a dependency mess - in the tls-crypt unit tests, move the command execution helper functions to a new run_command.c module.

While at it, abstract away the script_security global variable.

I can't apply this patch without having applied the tls-crypt-v2 patches first, however I strongly believe that this patch should come before those. Like the those about PEM_encode/decode and and platform_file_*.

This would ensure that the tls-crypt unit-test, once introduced, will have all the required pieces to work right away (bisect will benefit from this too).

I also see that here you are removing the script_security variable that you had declared in your first patch (in the crypt unit-test).

How about re-arranging this patch (maybe by making it the first patch in the set) so that you don't need to do and undo the same code?

Does it make sense?

Cheers,

Signature · 1 line
Antonio Quartulli
#25 lore raw

PATCH 10/10 tls-crypt-v2: Add script hook to verify metadata

In reply to Steffan Karger

To allow rejecting incoming connections very early in the handshake, add a --tls-crypt-v2-verify option that allows administators to run an external command to verify the metadata from the client key. See doc/tls-crypt-v2.txt for more details.

Because of the extra dependencies, this requires adding a mock parse_line() to the tls-crypt unit tests.

---
 Changes.rst                               | 12 ++++++
 doc/openvpn.8                             | 35 ++++++++++++++--
 src/openvpn/init.c                        |  1 +
 src/openvpn/options.c                     |  7 ++++
 src/openvpn/options.h                     |  2 +
 src/openvpn/ssl.c                         | 16 ++++---
 src/openvpn/ssl_common.h                  |  1 +
 src/openvpn/tls_crypt.c                   | 69 ++++++++++++++++++++++++++++++-
 src/openvpn/tls_crypt.h                   |  3 +-
 tests/unit_tests/openvpn/Makefile.am      | 12 ++++--
 tests/unit_tests/openvpn/test_tls_crypt.c | 16 +++++++
 11 files changed, 160 insertions(+), 14 deletions(-)
Changes.rst +12 -0
index a6090cf..e77b3d7 100644
@@ -1,3 +1,15 @@
+Overview of changes in 2.5
+==========================
+
+New features
+------------
+Client-specific tls-crypt keys (``--tls-crypt-v2``)
+    ``tls-crypt-v2`` adds the ability to supply each client with a unique
+    tls-crypt key.  This allows large organisations and VPN providers to profit
+    from the same DoS and TLS stack protection that small deployments can
+    already achieve using ``tls-auth`` or ``tls-crypt``.
+
+
 Overview of changes in 2.4
 ==========================
 
doc/openvpn.8 +32 -3
index 0719087..2cbdfcd 100644
@@ -5247,9 +5247,38 @@ If supplied, include the supplied
 in the wrapped client key.  This metadata must be supplied in base64\-encoded
 form.  The metadata must be at most 735 bytes long (980 bytes in base64).
 
-.B TODO
-Metadata handling is not yet implemented.  This text will be updated by the
-commit that introduces metadata handling.
+If no metadata is supplied, OpenVPN will use a 64-bit unix timestamp
+representing the current time in UTC, encoded in network order, as metadata for
+the generated key.
+
+Servers can use
+.B \-\-tls\-crypt\-v2\-verify
+to specify a metadata verification command.
+.\"*********************************************************
+.TP
+.B \-\-tls\-crypt\-v2\-verify cmd
+
+Run command
+.B cmd
+to verify the metadata of the client-specific tls-crypt-v2 key of a connecting
+client.  This allows server administrators to reject client connections, before
+exposing the TLS stack (including the notoriously dangerous X.509 and ASN.1
+stacks) to the connecting client.
+
+OpenVPN supplies the following env vars to the command:
+.RS
+.IP \[bu] 2
+script_type is set to "tls-crypt-v2-verify"
+.IP \[bu]
+metadata_type is set to "0" is the metadata was user supplied, or "1" if it's a
+64-bit unix timestamp representing the key creation time.
+.IP \[bu]
+metadata_file contains the filename of a temporary file that contains the client
+metadata.
+.RE
+
+.IP
+The command can reject the connection by exitingwith a non-zero exit code.
 .\"*********************************************************
 .TP
 .B \-\-askpass [file]
src/openvpn/init.c +1 -0
index 93143b1..c80df81 100644
@@ -2862,6 +2862,7 @@ do_init_crypto_tls(struct context *c, const unsigned int flags)
         if (options->tls_server)
         {
             to.tls_wrap.tls_crypt_v2_server_key = c->c1.ks.tls_crypt_v2_server_key;
+            to.tls_crypt_v2_verify_script = c->options.tls_crypt_v2_verify_script;
         }
     }
 
src/openvpn/options.c +7 -0
index 2d577f8..3d0e03b 100644
@@ -629,6 +629,8 @@ static const char usage_message[] =
     "--tls-crypt-v2-genkey client|server keyfile [base64 metadata]: Generate a\n"
     "                  fresh tls-crypt-v2 client or server key, and store to\n"
     "                  keyfile.  If supplied, include metadata in wrapped key.\n"
+    "--tls-crypt-v2-verify cmd : Run command cmd to verify the metadata of the\n"
+    "                  client-supplied tls-crypt-v2 client key\n"
     "--askpass [file]: Get PEM password from controlling tty before we daemonize.\n"
     "--auth-nocache  : Don't cache --askpass or --auth-user-pass passwords.\n"
     "--crl-verify crl ['dir']: Check peer certificate against a CRL.\n"
@@ -8053,6 +8055,11 @@ add_option(struct options *options,
             options->tls_crypt_v2_metadata = p[3];
         }
     }
+    else if (streq(p[0], "tls-crypt-v2-verify") && p[1] && !p[2])
+    {
+        VERIFY_PERMISSION(OPT_P_GENERAL);
+        options->tls_crypt_v2_verify_script = p[1];
+    }
     else if (streq(p[0], "key-method") && p[1] && !p[2])
     {
         int key_method;
src/openvpn/options.h +2 -0
index 7d995c3..068e051 100644
@@ -575,6 +575,8 @@ struct options
     const char *tls_crypt_v2_genkey_file;
     const char *tls_crypt_v2_metadata;
 
+    const char *tls_crypt_v2_verify_script;
+
     /* Allow only one session */
     bool single_session;
 
src/openvpn/ssl.c +10 -6
index 6090826..854b923 100644
@@ -1534,14 +1534,15 @@ write_control_auth(struct tls_session *session,
 static bool
 read_control_auth(struct buffer *buf,
                   struct tls_wrap_ctx *ctx,
-                  const struct link_socket_actual *from)
+                  const struct link_socket_actual *from,
+                  const struct tls_options *opt)
 {
     struct gc_arena gc = gc_new();
     bool ret = false;
 
     const uint8_t opcode = *(BPTR(buf)) >> P_OPCODE_SHIFT;
     if (opcode == P_CONTROL_HARD_RESET_CLIENT_V3
-        && !tls_crypt_v2_extract_client_key(buf, ctx))
+        && !tls_crypt_v2_extract_client_key(buf, ctx, opt))
     {
         msg (D_TLS_ERRORS,
              "TLS Error: can not extract tls-crypt-v2 client key from %s",
@@ -3619,7 +3620,8 @@ tls_pre_decrypt(struct tls_multi *multi,
                     goto error;
                 }
 
-                if (!read_control_auth(buf, &session->tls_wrap, from))
+                if (!read_control_auth(buf, &session->tls_wrap, from,
+                                       session->opt))
                 {
                     goto error;
                 }
@@ -3672,7 +3674,8 @@ tls_pre_decrypt(struct tls_multi *multi,
                 if (op == P_CONTROL_SOFT_RESET_V1
                     && DECRYPT_KEY_ENABLED(multi, ks))
                 {
-                    if (!read_control_auth(buf, &session->tls_wrap, from))
+                    if (!read_control_auth(buf, &session->tls_wrap, from,
+                                           session->opt))
                     {
                         goto error;
                     }
@@ -3693,7 +3696,8 @@ tls_pre_decrypt(struct tls_multi *multi,
                         do_burst = true;
                     }
 
-                    if (!read_control_auth(buf, &session->tls_wrap, from))
+                    if (!read_control_auth(buf, &session->tls_wrap, from,
+                                           session->opt))
                     {
                         goto error;
                     }
@@ -3895,7 +3899,7 @@ tls_pre_decrypt_lite(const struct tls_auth_standalone *tas,
             bool status;
 
             /* HMAC test, if --tls-auth was specified */
-            status = read_control_auth(&newbuf, &tls_wrap_tmp, from);
+            status = read_control_auth(&newbuf, &tls_wrap_tmp, from, NULL);
             free_buf(&newbuf);
             if (tls_wrap_tmp.cleanup_key_ctx)
             {
src/openvpn/ssl_common.h +1 -0
index d6e0d1b..d52f337 100644
@@ -293,6 +293,7 @@ struct tls_options
     bool ncp_enabled;
 
     bool tls_crypt_v2;
+    const char *tls_crypt_v2_verify_script;
 
     /** TLS handshake wrapping state */
     struct tls_wrap_ctx tls_wrap;
src/openvpn/tls_crypt.c +68 -1

91 lines not on this page · raw message

src/openvpn/tls_crypt.h +2 -1
index e720729..d9174da 100644
@@ -193,7 +193,8 @@ void tls_crypt_v2_init_client_key(struct key_ctx_bi *key,
  * @returns true if a key was successfully extracted.
  */
 bool tls_crypt_v2_extract_client_key(struct buffer *buf,
-                                     struct tls_wrap_ctx *ctx);
+                                     struct tls_wrap_ctx *ctx,
+                                     const struct tls_options *opt);
 
 /**
  * Generate a tls-crypt-v2 server key, and write to file.
tests/unit_tests/openvpn/Makefile.am +9 -3
index a5d7f01..7346230 100644
@@ -6,7 +6,10 @@ if HAVE_LD_WRAP_SUPPORT
 check_PROGRAMS += argv_testdriver buffer_testdriver
 endif
 
-check_PROGRAMS += crypto_testdriver packet_id_testdriver tls_crypt_testdriver
+check_PROGRAMS += crypto_testdriver packet_id_testdriver
+if HAVE_LD_WRAP_SUPPORT
+check_PROGRAMS += tls_crypt_testdriver
+endif
 
 TESTS = $(check_PROGRAMS)
 
@@ -62,14 +65,17 @@ packet_id_testdriver_SOURCES = test_packet_id.c mock_msg.c \
 tls_crypt_testdriver_CFLAGS  = @TEST_CFLAGS@ \
 	-I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \
 	$(OPTIONAL_CRYPTO_CFLAGS) $(OPTIONAL_PKCS11_HELPER_CFLAGS)
-tls_crypt_testdriver_LDFLAGS = @TEST_LDFLAGS@ \
+tls_crypt_testdriver_LDFLAGS = @TEST_LDFLAGS@ -Wl,--wrap=parse_line \
 	$(OPTIONAL_CRYPTO_LIBS)
 tls_crypt_testdriver_SOURCES = test_tls_crypt.c mock_msg.c \
+	$(openvpn_srcdir)/argv.c \
 	$(openvpn_srcdir)/base64.c \
 	$(openvpn_srcdir)/buffer.c \
 	$(openvpn_srcdir)/crypto.c \
 	$(openvpn_srcdir)/crypto_mbedtls.c \
 	$(openvpn_srcdir)/crypto_openssl.c \
+	$(openvpn_srcdir)/env_set.c \
 	$(openvpn_srcdir)/otime.c \
 	$(openvpn_srcdir)/packet_id.c \
-	$(openvpn_srcdir)/platform.c
+	$(openvpn_srcdir)/platform.c \
+	$(openvpn_srcdir)/run_command.c
tests/unit_tests/openvpn/test_tls_crypt.c +16 -0
index 724abda..ca23ecb 100644
@@ -43,8 +43,24 @@
 
 #define TESTBUF_SIZE            128
 
+/* Defines for use in the tests and the mock parse_line() */
+#define PATH1       "/s p a c e"
+#define PATH2       "/foo bar/baz"
+#define PARAM1      "param1"
+#define PARAM2      "param two"
+
 const char plaintext_short[1];
 
+int
+__wrap_parse_line(const char *line, char **p, const int n, const char *file,
+                  const int line_num, int msglevel, struct gc_arena *gc)
+{
+    p[0] = PATH1 PATH2;
+    p[1] = PARAM1;
+    p[2] = PARAM2;
+    return 3;
+}
+
 struct test_tls_crypt_context {
     struct crypto_options co;
     struct key_type kt;
Signature · 1 line
2.7.4
#26 lore raw

PATCH v2 1/9 Move file-related functions from misc.c to platform.c

In reply to Steffan Karger

From: Steffan Karger <[email protected]...>

To avoid having to include misc.c - which is a dependency mess - in the tls-crypt unit tests, move file-handing related functions to platform.c (which is where other file-related functions already reside).

Note that platform_create_temp_file() needs random. To avoid including misc.c in other tests that use platform.c, add a mock get_random().

(Almost every test includes platform.c, because buffer.c depends on it. That smells like it needs cleanup too, but not in this patch set.)

---
 src/openvpn/init.c                         |   2 +-
 src/openvpn/misc.c                         | 148 ---------------------
 src/openvpn/misc.h                         |  12 --
 src/openvpn/multi.c                        |  25 ++--
 src/openvpn/pf.c                           |   4 +-
 src/openvpn/platform.c                     | 148 +++++++++++++++++++++
 src/openvpn/platform.h                     |  18 +++
 src/openvpn/plugin.c                       |   6 +-
 src/openvpn/ssl_verify.c                   |  12 +-
 tests/unit_tests/openvpn/Makefile.am       |   4 +
 tests/unit_tests/openvpn/mock_get_random.c |  36 +++++
 11 files changed, 233 insertions(+), 182 deletions(-)
 create mode 100644 tests/unit_tests/openvpn/mock_get_random.c
src/openvpn/init.c +1 -1
index b748357d..f6c8f08e 100644
@@ -807,7 +807,7 @@ init_static(void)
 #ifdef STATUS_PRINTF_TEST
     {
         struct gc_arena gc = gc_new();
-        const char *tmp_file = create_temp_file("/tmp", "foo", &gc);
+        const char *tmp_file = platform_create_temp_file("/tmp", "foo", &gc);
         struct status_output *so = status_open(tmp_file, 0, -1, NULL, STATUS_OUTPUT_WRITE);
         status_printf(so, "%s", "foo");
         status_printf(so, "%s", "bar");
src/openvpn/misc.c +0 -148

160 lines not on this page · raw message

src/openvpn/misc.h +0 -12
index 5680c274..c23d4cd1 100644
@@ -79,18 +79,6 @@ const char **make_extended_arg_array(char **p, struct gc_arena *gc);
 /* an analogue to the random() function, but use OpenSSL functions if available */
 long int get_random(void);
 
-/* return true if filename can be opened for read */
-bool test_file(const char *filename);
-
-/* create a temporary file in directory, returns the filename of the created file */
-const char *create_temp_file(const char *directory, const char *prefix, struct gc_arena *gc);
-
-/* put a directory and filename together */
-const char *gen_path(const char *directory, const char *filename, struct gc_arena *gc);
-
-/* return true if pathname is absolute */
-bool absolute_pathname(const char *pathname);
-
 /* prepend a random prefix to hostname */
 const char *hostname_randomize(const char *hostname, struct gc_arena *gc);
 
src/openvpn/multi.c +14 -11
index 3da8c110..2944eef2 100644
@@ -1642,7 +1642,7 @@ multi_client_connect_post(struct multi_context *m,
                           unsigned int *option_types_found)
 {
     /* Did script generate a dynamic config file? */
-    if (test_file(dc_file))
+    if (platform_test_file(dc_file))
     {
         options_server_import(&mi->context.options,
                               dc_file,
@@ -1829,12 +1829,13 @@ multi_connection_established(struct multi_context *m, struct multi_instance *mi)
         {
             const char *ccd_file;
 
-            ccd_file = gen_path(mi->context.options.client_config_dir,
-                                tls_common_name(mi->context.c2.tls_multi, false),
-                                &gc);
+            ccd_file = platform_gen_path(mi->context.options.client_config_dir,
+                                         tls_common_name(mi->context.c2.tls_multi,
+                                                         false),
+                                         &gc);
 
             /* try common-name file */
-            if (test_file(ccd_file))
+            if (platform_test_file(ccd_file))
             {
                 options_server_import(&mi->context.options,
                                       ccd_file,
@@ -1845,11 +1846,11 @@ multi_connection_established(struct multi_context *m, struct multi_instance *mi)
             }
             else /* try default file */
             {
-                ccd_file = gen_path(mi->context.options.client_config_dir,
-                                    CCD_DEFAULT,
-                                    &gc);
+                ccd_file = platform_gen_path(mi->context.options.client_config_dir,
+                                             CCD_DEFAULT,
+                                             &gc);
 
-                if (test_file(ccd_file))
+                if (platform_test_file(ccd_file))
                 {
                     options_server_import(&mi->context.options,
                                           ccd_file,
@@ -1879,7 +1880,8 @@ multi_connection_established(struct multi_context *m, struct multi_instance *mi)
         if (plugin_defined(mi->context.plugins, OPENVPN_PLUGIN_CLIENT_CONNECT))
         {
             struct argv argv = argv_new();
-            const char *dc_file = create_temp_file(mi->context.options.tmp_dir, "cc", &gc);
+            const char *dc_file = platform_create_temp_file(mi->context.options.tmp_dir,
+                                                            "cc", &gc);
 
             if (!dc_file)
             {
@@ -1941,7 +1943,8 @@ script_depr_failed:
 
             setenv_str(mi->context.c2.es, "script_type", "client-connect");
 
-            dc_file = create_temp_file(mi->context.options.tmp_dir, "cc", &gc);
+            dc_file = platform_create_temp_file(mi->context.options.tmp_dir,
+                                                "cc", &gc);
             if (!dc_file)
             {
                 cc_succeeded = false;
src/openvpn/pf.c +2 -2
index cb22a1cb..d4e405a8 100644
@@ -621,8 +621,8 @@ pf_init_context(struct context *c)
 #ifdef PLUGIN_PF
     if (plugin_defined(c->plugins, OPENVPN_PLUGIN_ENABLE_PF))
     {
-        c->c2.pf.filename = create_temp_file(c->options.tmp_dir, "pf",
-                                             &c->c2.gc);
+        c->c2.pf.filename = platform_create_temp_file(c->options.tmp_dir, "pf",
+                                                      &c->c2.gc);
         if (c->c2.pf.filename)
         {
             setenv_str(c->c2.es, "pf_file", c->c2.pf.filename);
src/openvpn/platform.c +148 -0

157 lines not on this page · raw message

src/openvpn/platform.h +18 -0
index 288937df..091fc9c4 100644
@@ -49,6 +49,7 @@
 #endif
 
 #include "basic.h"
+#include "buffer.h"
 
 /* Get/Set UID of process */
 
@@ -143,4 +144,21 @@ typedef struct stat platform_stat_t;
 #endif
 int platform_stat(const char *path, platform_stat_t *buf);
 
+/**
+ * Create a temporary file in directory, returns the filename of the created
+ * file.
+ */
+const char *platform_create_temp_file(const char *directory, const char *prefix,
+                                      struct gc_arena *gc);
+
+/** Put a directory and filename together. */
+const char *platform_gen_path(const char *directory, const char *filename,
+                              struct gc_arena *gc);
+
+/** Return true if pathname is absolute. */
+bool platform_absolute_pathname(const char *pathname);
+
+/** Return true if filename can be opened for read. */
+bool platform_test_file(const char *filename);
+
 #endif /* ifndef PLATFORM_H */
src/openvpn/plugin.c +3 -3
index 0f091ef5..3e7d2b2a 100644
@@ -249,7 +249,7 @@ plugin_init_item(struct plugin *p, const struct plugin_option *o)
      * was parsed.
      *
      */
-    if (!absolute_pathname(p->so_pathname)
+    if (!platform_absolute_pathname(p->so_pathname)
         && p->so_pathname[0] != '.')
     {
         char full[PATH_MAX];
@@ -259,7 +259,7 @@ plugin_init_item(struct plugin *p, const struct plugin_option *o)
     }
     else
     {
-        rel = !absolute_pathname(p->so_pathname);
+        rel = !platform_absolute_pathname(p->so_pathname);
         p->handle = dlopen(p->so_pathname, RTLD_NOW);
     }
     if (!p->handle)
@@ -271,7 +271,7 @@ plugin_init_item(struct plugin *p, const struct plugin_option *o)
 
 #else  /* ifndef _WIN32 */
 
-    rel = !absolute_pathname(p->so_pathname);
+    rel = !platform_absolute_pathname(p->so_pathname);
     p->module = LoadLibraryW(wide_string(p->so_pathname, &gc));
     if (!p->module)
     {
src/openvpn/ssl_verify.c +7 -5
index 25395b27..a3699252 100644
@@ -547,7 +547,7 @@ verify_cert_export_cert(openvpn_x509_cert_t *peercert, const char *tmp_dir, stru
 
     /* create tmp file to store peer cert */
     if (!tmp_dir
-        || !(peercert_filename = create_temp_file(tmp_dir, "pcf", gc)))
+        || !(peercert_filename = platform_create_temp_file(tmp_dir, "pcf", gc)))
     {
         msg(M_NONFATAL, "Failed to create peer cert file");
         return NULL;
@@ -890,7 +890,7 @@ key_state_gen_auth_control_file(struct key_state *ks, const struct tls_options *
     struct gc_arena gc = gc_new();
 
     key_state_rm_auth_control_file(ks);
-    const char *acf = create_temp_file(opt->tmp_dir, "acf", &gc);
+    const char *acf = platform_create_temp_file(opt->tmp_dir, "acf", &gc);
     if (acf)
     {
         ks->auth_control_file = string_alloc(acf, NULL);
@@ -1103,7 +1103,8 @@ verify_user_pass_script(struct tls_session *session, const struct user_pass *up)
         {
             struct status_output *so;
 
-            tmp_file = create_temp_file(session->opt->tmp_dir, "up", &gc);
+            tmp_file = platform_create_temp_file(session->opt->tmp_dir, "up",
+                                                 &gc);
             if (tmp_file)
             {
                 so = status_open(tmp_file, 0, -1, NULL, STATUS_OUTPUT_WRITE);
@@ -1513,8 +1514,9 @@ verify_final_auth_checks(struct tls_multi *multi, struct tls_session *session)
         struct gc_arena gc = gc_new();
 
         const char *cn = session->common_name;
-        const char *path = gen_path(session->opt->client_config_dir_exclusive, cn, &gc);
-        if (!cn || !strcmp(cn, CCD_DEFAULT) || !test_file(path))
+        const char *path = platform_gen_path(session->opt->client_config_dir_exclusive,
+                                             cn, &gc);
+        if (!cn || !strcmp(cn, CCD_DEFAULT) || !platform_test_file(path))
         {
             ks->authenticated = false;
             wipe_auth_token(multi);
tests/unit_tests/openvpn/Makefile.am +4 -0
index 23d758b7..db4d46e1 100644
@@ -19,6 +19,7 @@ argv_testdriver_CFLAGS  = @TEST_CFLAGS@ -I$(openvpn_srcdir) -I$(compat_srcdir) \
 argv_testdriver_LDFLAGS = @TEST_LDFLAGS@ -L$(openvpn_srcdir) -Wl,--wrap=parse_line \
 	$(OPTIONAL_CRYPTO_LIBS)
 argv_testdriver_SOURCES = test_argv.c mock_msg.c \
+	mock_get_random.c \
 	$(openvpn_srcdir)/platform.c \
 	$(openvpn_srcdir)/buffer.c \
 	$(openvpn_srcdir)/argv.c
@@ -26,6 +27,7 @@ argv_testdriver_SOURCES = test_argv.c mock_msg.c \
 buffer_testdriver_CFLAGS  = @TEST_CFLAGS@ -I$(openvpn_srcdir) -I$(compat_srcdir)
 buffer_testdriver_LDFLAGS = @TEST_LDFLAGS@ -L$(openvpn_srcdir) -Wl,--wrap=parse_line
 buffer_testdriver_SOURCES = test_buffer.c mock_msg.c \
+	mock_get_random.c \
 	$(openvpn_srcdir)/buffer.c \
 	$(openvpn_srcdir)/platform.c
 
@@ -35,6 +37,7 @@ packet_id_testdriver_CFLAGS  = @TEST_CFLAGS@ \
 packet_id_testdriver_LDFLAGS = @TEST_LDFLAGS@ \
 	$(OPTIONAL_CRYPTO_LIBS)
 packet_id_testdriver_SOURCES = test_packet_id.c mock_msg.c \
+	mock_get_random.c \
 	$(openvpn_srcdir)/buffer.c \
 	$(openvpn_srcdir)/otime.c \
 	$(openvpn_srcdir)/packet_id.c \
@@ -46,6 +49,7 @@ tls_crypt_testdriver_CFLAGS  = @TEST_CFLAGS@ \
 tls_crypt_testdriver_LDFLAGS = @TEST_LDFLAGS@ \
 	$(OPTIONAL_CRYPTO_LIBS)
 tls_crypt_testdriver_SOURCES = test_tls_crypt.c mock_msg.c \
+	$(openvpn_srcdir)/base64.c \
 	$(openvpn_srcdir)/buffer.c \
 	$(openvpn_srcdir)/crypto.c \
 	$(openvpn_srcdir)/crypto_mbedtls.c \
tests/unit_tests/openvpn/mock_get_random.c new file +36 -0
new file mode 100644
index 00000000..da92a9bb
@@ -0,0 +1,36 @@
+/*
+ *  OpenVPN -- An application to securely tunnel IP networks
+ *             over a single UDP port, with support for SSL/TLS-based
+ *             session authentication and key exchange,
+ *             packet encryption, packet authentication, and
+ *             packet compression.
+ *
+ *  Copyright (C) 2017 Fox Crypto B.V. <[email protected]...>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2
+ *  as published by the Free Software Foundation.
+ *
+ *  This program 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 General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+unsigned long
+get_random(void)
+{
+    /* rand() is not very random, but it's C99 and this is just for testing */
+    return rand();
+}
Signature · 1 line
2.17.1
#27 lore raw

PATCH v2 2/9 Move execve/run_script helper functions to run_command.c

To avoid having to include misc.c - which is a dependency mess - in the tls-crypt unit tests, move the command execution helper functions to a new run_command.c module.

While at it, abstract away the script_security global variable.

---
 src/openvpn/Makefile.am   |   1 +
 src/openvpn/env_set.c     |   6 +-
 src/openvpn/init.c        |   5 +-
 src/openvpn/lladdr.c      |   1 +
 src/openvpn/misc.c        | 218 -------------------------------
 src/openvpn/misc.h        |  32 -----
 src/openvpn/multi.c       |   2 +-
 src/openvpn/options.c     |   3 +-
 src/openvpn/route.c       |   2 +-
 src/openvpn/run_command.c | 267 ++++++++++++++++++++++++++++++++++++++
 src/openvpn/run_command.h |  63 +++++++++
 src/openvpn/socket.c      |   1 +
 src/openvpn/ssl_verify.c  |   4 +-
 src/openvpn/tun.c         |   2 +-
 src/openvpn/win32.c       |   4 +-
 15 files changed, 348 insertions(+), 263 deletions(-)
 create mode 100644 src/openvpn/run_command.c
 create mode 100644 src/openvpn/run_command.h
src/openvpn/Makefile.am +1 -0
index eda08351..66410611 100644
@@ -102,6 +102,7 @@ openvpn_SOURCES = \
 	pushlist.h \
 	reliable.c reliable.h \
 	route.c route.h \
+	run_command.c run_command.h \
 	schedule.c schedule.h \
 	session_id.c session_id.h \
 	shaper.c shaper.h \
src/openvpn/env_set.c +3 -3
index 17675625..e7fb2d83 100644
@@ -32,10 +32,10 @@
 
 #include "syshead.h"
 
-#include "misc.h"
-
 #include "env_set.h"
 
+#include "run_command.h"
+
 /*
  * Set environmental variable (int or string).
  *
@@ -414,7 +414,7 @@ setenv_str_i(struct env_set *es, const char *name, const char *value, const int
 bool
 env_allowed(const char *str)
 {
-    return (script_security >= SSEC_PW_ENV || !is_password_env_var(str));
+    return (script_security() >= SSEC_PW_ENV || !is_password_env_var(str));
 }
 
 /* Make arrays of strings */
src/openvpn/init.c +3 -2
index f6c8f08e..d28d1fd2 100644
@@ -35,6 +35,7 @@
 
 #include "win32.h"
 #include "init.h"
+#include "run_command.h"
 #include "sig.h"
 #include "occ.h"
 #include "list.h"
@@ -3095,11 +3096,11 @@ do_option_warnings(struct context *c)
     /* If a script is used, print appropiate warnings */
     if (o->user_script_used)
     {
-        if (script_security >= SSEC_SCRIPTS)
+        if (script_security() >= SSEC_SCRIPTS)
         {
             msg(M_WARN, "NOTE: the current --script-security setting may allow this configuration to call user-defined scripts");
         }
-        else if (script_security >= SSEC_PW_ENV)
+        else if (script_security() >= SSEC_PW_ENV)
         {
             msg(M_WARN, "WARNING: the current --script-security setting may allow passwords to be passed to scripts via environmental variables");
         }
src/openvpn/lladdr.c +1 -0
index ff71e48c..f24596b5 100644
@@ -11,6 +11,7 @@
 #include "syshead.h"
 #include "error.h"
 #include "misc.h"
+#include "run_command.h"
 
 int
 set_lladdr(const char *ifname, const char *lladdr,
src/openvpn/misc.c +0 -218

230 lines not on this page · raw message

src/openvpn/misc.h +0 -32
index c23d4cd1..14abb0f3 100644
@@ -38,30 +38,6 @@
 /* forward declarations */
 struct plugin_list;
 
-/* system flags */
-#define S_SCRIPT (1<<0)
-#define S_FATAL  (1<<1)
-
-const char *system_error_message(int, struct gc_arena *gc);
-
-/* wrapper around the execve() call */
-int openvpn_popen(const struct argv *a,  const struct env_set *es);
-
-int openvpn_execve(const struct argv *a, const struct env_set *es, const unsigned int flags);
-
-bool openvpn_execve_check(const struct argv *a, const struct env_set *es, const unsigned int flags, const char *error_message);
-
-bool openvpn_execve_allowed(const unsigned int flags);
-
-static inline bool
-openvpn_run_script(const struct argv *a, const struct env_set *es, const unsigned int flags, const char *hook)
-{
-    char msg[256];
-
-    openvpn_snprintf(msg, sizeof(msg), "WARNING: Failed running command (%s)", hook);
-    return openvpn_execve_check(a, es, flags | S_SCRIPT, msg);
-}
-
 
 /* Set standard file descriptors to /dev/null */
 void set_std_files_to_null(bool stdin_only);
@@ -198,14 +174,6 @@ void get_user_pass_auto_userid(struct user_pass *up, const char *tag);
 extern const char *iproute_path;
 #endif
 
-/* Script security */
-#define SSEC_NONE      0 /* strictly no calling of external programs */
-#define SSEC_BUILT_IN  1 /* only call built-in programs such as ifconfig, route, netsh, etc.*/
-#define SSEC_SCRIPTS   2 /* allow calling of built-in programs and user-defined scripts */
-#define SSEC_PW_ENV    3 /* allow calling of built-in programs and user-defined scripts that may receive a password as an environmental variable */
-extern int script_security; /* GLOBAL */
-
-
 #define COMPAT_FLAG_QUERY         0       /** compat_flags operator: Query for a flag */
 #define COMPAT_FLAG_SET           (1<<0)  /** compat_flags operator: Set a compat flag */
 #define COMPAT_NAMES              (1<<1)  /** compat flag: --compat-names set */
src/openvpn/multi.c +1 -1
index 2944eef2..db32500d 100644
@@ -38,7 +38,7 @@
 
 #include "multi.h"
 #include "push.h"
-#include "misc.h"
+#include "run_command.h"
 #include "otime.h"
 #include "gremlin.h"
 #include "mstats.h"
src/openvpn/options.c +2 -1
index 426057ab..b89f4ba2 100644
@@ -41,6 +41,7 @@
 #include "buffer.h"
 #include "error.h"
 #include "common.h"
+#include "run_command.h"
 #include "shaper.h"
 #include "crypto.h"
 #include "ssl.h"
@@ -6379,7 +6380,7 @@ add_option(struct options *options,
     else if (streq(p[0], "script-security") && p[1] && !p[2])
     {
         VERIFY_PERMISSION(OPT_P_GENERAL);
-        script_security = atoi(p[1]);
+        script_security_set(atoi(p[1]));
     }
     else if (streq(p[0], "mssfix") && !p[2])
     {
src/openvpn/route.c +1 -1
index f6962848..ff392308 100644
@@ -36,7 +36,7 @@
 #include "common.h"
 #include "error.h"
 #include "route.h"
-#include "misc.h"
+#include "run_command.h"
 #include "socket.h"
 #include "manage.h"
 #include "win32.h"
src/openvpn/run_command.c new file +267 -0

267 lines not on this page · raw message

src/openvpn/run_command.h new file +63 -0

63 lines not on this page · raw message

src/openvpn/socket.c +1 -0
index 211e7441..911b2335 100644
@@ -35,6 +35,7 @@
 #include "gremlin.h"
 #include "plugin.h"
 #include "ps.h"
+#include "run_command.h"
 #include "manage.h"
 #include "misc.h"
 #include "manage.h"
src/openvpn/ssl_verify.c +2 -2
index a3699252..61872251 100644
@@ -34,10 +34,10 @@
 
 #include "syshead.h"
 
-#include "misc.h"
+#include "base64.h"
 #include "manage.h"
 #include "otime.h"
-#include "base64.h"
+#include "run_command.h"
 #include "ssl_verify.h"
 #include "ssl_verify_backend.h"
 
src/openvpn/tun.c +1 -1
index 26baa206..1c7e51d9 100644
@@ -40,7 +40,7 @@
 #include "tun.h"
 #include "fdmisc.h"
 #include "common.h"
-#include "misc.h"
+#include "run_command.h"
 #include "socket.h"
 #include "manage.h"
 #include "route.h"
src/openvpn/win32.c +2 -2
index 29bbb841..3905524a 100644
@@ -39,9 +39,9 @@
 #include "buffer.h"
 #include "error.h"
 #include "mtu.h"
+#include "run_command.h"
 #include "sig.h"
 #include "win32.h"
-#include "misc.h"
 #include "openvpn-msg.h"
 
 #include "memdbg.h"
@@ -1137,7 +1137,7 @@ openvpn_execve(const struct argv *a, const struct env_set *es, const unsigned in
             free(env);
             gc_free(&gc);
         }
-        else if (!exec_warn && (script_security < SSEC_SCRIPTS))
+        else if (!exec_warn && (script_security() < SSEC_SCRIPTS))
         {
             msg(M_WARN, SCRIPT_SECURITY_WARNING);
             exec_warn = true;
Signature · 1 line
2.17.1
Acked-by on v2 2/9
#28 lore raw

Re: [Openvpn-devel] [PATCH v2 2/9] Move execve/run_script helper functions to run_command.c

Hi,

Quoted 4 lines · Steffan Karger

To avoid having to include misc.c - which is a dependency mess - in the tls-crypt unit tests, move the command execution helper functions to a new run_command.c module.

one thing less into misc.c :-)

After having witnessed the dependency mess in the unit-test, I am definitely "pro"-this-patch as it simplifies quite a bit the dep-hell.

Quoted 1 line

While at it, abstract away the script_security global variable.

I like the idea of getting rid of a process-wide global variable, however I think that script_security should simply be a member of the options object or of one of the contexts, rather than just hanging on its own.

However, I'd postpone that to another patch. Your change here is already a step forward.

Quoted 3 lines

---

[CUT]

src/openvpn/run_command.c line 42

Quoted 2 lines
src/openvpn/run_command.c new file +2 -0
+/* contains an SSEC_x value defined in platform.h */
+static int script_security_level = SSEC_BUILT_IN; /* GLOBAL */

Maybe this "GLOBAL" thing could be removed as this variable now exists only within this file?

src/openvpn/run_command.c line 53

Quoted 11 lines
src/openvpn/run_command.c new file +11 -0
+
+int script_security(void)
+{
+    return script_security_level;
+}
+
+void script_security_set(int level)
+{
+    script_security_level = level;
+}
+

[CUT]

Other than that little nitpick, the patch looks good.

Checked with "git show --color-moved" and I could verify that the code has only been moved (slightly adjusted to avoid long lines) and that the access to the security_script variable has been substituted with getter/setter functions.

So, with or without the nitpick:

Cheers,

Signature · 1 line
Antonio Quartulli
#29 lore raw

Re: [Openvpn-devel] [PATCH v2 2/9] Move execve/run_script helper functions to run_command.c

Quoted 10 lines · Antonio Quartulli

Other than that little nitpick, the patch looks good.

Checked with "git show --color-moved" and I could verify that the code has only been moved (slightly adjusted to avoid long lines) and that the access to the security_script variable has been substituted with getter/setter functions.

So, with or without the nitpick:

Additionally, basic tests with --up and --down worked as expected. No change in behaviour was noted.

Cheers,

Signature · 1 line
Antonio Quartulli
#30 lore raw

[Openvpn-devel] [PATCH applied] Re: Move execve/run_script helper functions to run_command.c

In reply to Steffan Karger

Your patch has been applied to the master branch.

(I've done my own review, but Antonio beat me with the ACK :-) - but I can confirm that this is "just moved code" plus script_security now hidden behind getter/setter functions, and it passes my tests)

commit bf97c00f7dba441b504881f38e40afcbb610a39f

Author: Steffan Karger
Date:   Wed Jul 4 19:53:57 2018 +0200
     Move execve/run_script helper functions to run_command.c

     Signed-off-by: Steffan Karger <[email protected]...>
     Acked-by: Antonio Quartulli <[email protected]...>
     Message-Id: <[email protected]...>
     URL: https://www.mail-archive.com/[email protected]/msg17212.html
     Signed-off-by: Gert Doering <[email protected]...>

--
kind regards,

Gert Doering

#31 lore raw

PATCH v2 3/9 Add crypto_pem_{encode,decode}()

In reply to Steffan Karger

From: Steffan Karger <[email protected]...>

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.

---
 src/openvpn/crypto_backend.h           | 29 +++++++++
 src/openvpn/crypto_mbedtls.c           | 75 ++++++++++++++++++++++
 src/openvpn/crypto_openssl.c           | 82 ++++++++++++++++++++++++
 tests/unit_tests/openvpn/Makefile.am   | 16 ++++-
 tests/unit_tests/openvpn/test_crypto.c | 88 ++++++++++++++++++++++++++
 5 files changed, 289 insertions(+), 1 deletion(-)
 create mode 100644 tests/unit_tests/openvpn/test_crypto.c
src/openvpn/crypto_backend.h +29 -0
index bb53b8c9..f917c8d7 100644
@@ -36,6 +36,7 @@
 #include "crypto_mbedtls.h"
 #endif
 #include "basic.h"
+#include "buffer.h"
 
 /* TLS uses a tag of 128 bytes, let's do the same for OpenVPN */
 #define OPENVPN_AEAD_TAG_LENGTH 16
@@ -105,6 +106,34 @@ void show_available_digests(void);
 
 void show_available_engines(void);
 
+/**
+ * Encode binary data as PEM.
+ *
+ * @param name      The name to use in the PEM header/footer.
+ * @param dst       Destination buffer for PEM-encoded data.  Must be a valid
+ *                  pointer to an uninitialized buffer structure.  Iff this
+ *                  function returns true, the buffer will contain memory
+ *                  allocated through the supplied gc.
+ * @param src       Source buffer.
+ * @param gc        The garbage collector to use when allocating memory for dst.
+ *
+ * @return true iff PEM encode succeeded.
+ */
+bool crypto_pem_encode(const char *name, struct buffer *dst,
+                       const struct buffer *src, struct gc_arena *gc);
+
+/**
+ * Decode a PEM buffer to binary data.
+ *
+ * @param name      The name expected in the PEM header/footer.
+ * @param dst       Destination buffer for decoded data.
+ * @param src       Source buffer (PEM data).
+ *
+ * @return true iff PEM decode succeeded.
+ */
+bool crypto_pem_decode(const char *name, struct buffer *dst,
+                       const struct buffer *src);
+
 /*
  *
  * Random number functions, used in cases where we want
src/openvpn/crypto_mbedtls.c +75 -0

92 lines not on this page · raw message

src/openvpn/crypto_openssl.c +82 -0

88 lines not on this page · raw message

tests/unit_tests/openvpn/Makefile.am +15 -1
index db4d46e1..1ff62615 100644
@@ -6,7 +6,7 @@ if HAVE_LD_WRAP_SUPPORT
 check_PROGRAMS += argv_testdriver buffer_testdriver
 endif
 
-check_PROGRAMS += packet_id_testdriver tls_crypt_testdriver
+check_PROGRAMS += crypto_testdriver packet_id_testdriver tls_crypt_testdriver
 
 TESTS = $(check_PROGRAMS)
 
@@ -31,6 +31,20 @@ buffer_testdriver_SOURCES = test_buffer.c mock_msg.c \
 	$(openvpn_srcdir)/buffer.c \
 	$(openvpn_srcdir)/platform.c
 
+crypto_testdriver_CFLAGS  = @TEST_CFLAGS@ \
+	-I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \
+	$(OPTIONAL_CRYPTO_CFLAGS)
+crypto_testdriver_LDFLAGS = @TEST_LDFLAGS@ \
+	$(OPTIONAL_CRYPTO_LIBS)
+crypto_testdriver_SOURCES = test_crypto.c mock_msg.c \
+	$(openvpn_srcdir)/buffer.c \
+	$(openvpn_srcdir)/crypto.c \
+	$(openvpn_srcdir)/crypto_mbedtls.c \
+	$(openvpn_srcdir)/crypto_openssl.c \
+	$(openvpn_srcdir)/otime.c \
+	$(openvpn_srcdir)/packet_id.c \
+	$(openvpn_srcdir)/platform.c
+
 packet_id_testdriver_CFLAGS  = @TEST_CFLAGS@ \
 	-I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \
 	$(OPTIONAL_CRYPTO_CFLAGS)
tests/unit_tests/openvpn/test_crypto.c new file +88 -0

88 lines not on this page · raw message

Signature · 1 line
2.17.1
Acked-by on v2 3/9
#32 lore raw

Re: [Openvpn-devel] [PATCH v2 3/9] Add crypto_pem_{encode, decode}()

Hi,

On 05/07/18 01:53, Steffan Karger wrote:
[CUT]

src/openvpn/crypto_openssl.c show_available_engines(void) · line 426

Quoted 8 lines
src/openvpn/crypto_openssl.c +8 -0
+bool
+crypto_pem_decode(const char *name, struct buffer *dst,
+                  const struct buffer *src)
+{
+    bool ret = false;
+    BIO *bio;
+
+    if (!(bio = BIO_new_mem_buf((char *)BPTR(src), BLEN(src))))

minor style niptick: I think it's common habit to have the assignment on one line and the if-condition on the next one, especially when it's about functions allocating some kind of object. Check your own crypto_pem_encode() in this patch :-)

For the sake of consistency, I think this should be split in two lines:

BIO *bio = BIO_new_mem_buf((char *)BPTR(src), BLEN(src)); if (!bio)

Quoted 19 lines
src/openvpn/crypto_openssl.c +19 -0
+    {
+        crypto_msg(M_FATAL, "Cannot open memory BIO for PEM decode");
+    }
+
+    char *name_read = NULL;
+    char *header_read = NULL;
+    uint8_t *data_read = NULL;
+    long data_read_len = 0;
+    if (!PEM_read_bio(bio, &name_read, &header_read, &data_read,
+                      &data_read_len))
+    {
+        dmsg(D_CRYPT_ERRORS, "%s: PEM decode failed", __func__);
+        goto cleanup;
+    }
+
+    if (strcmp(name, name_read))
+    {
+        dmsg(D_CRYPT_ERRORS,
+             "%s: unexpected PEM name (got '%s', expected '%s')",

src/openvpn/crypto_openssl.c show_available_engines(void) · line 455

Quoted 10 lines
src/openvpn/crypto_openssl.c +10 -0
+             __func__, name_read, name);
+        goto cleanup;
+    }
+
+    uint8_t *dst_data = buf_write_alloc(dst, data_read_len);
+    if (!dst_data)
+    {
+        dmsg(D_CRYPT_ERRORS, "%s: dst too small (%i, needs %li)", __func__,
+             BCAP(dst), data_read_len);
+        goto cleanup;

Am I wrong or we are leaking dst_data in case of failure? or are we assuming that this memory is now owned by dst and therefore its owner (the caller) will take care of this?

Quoted 5 lines
src/openvpn/crypto_openssl.c +5 -0
+    }
+    memcpy(dst_data, data_read, data_read_len);
+
+    ret = true;
+cleanup:> +    OPENSSL_free(name_read);

src/openvpn/crypto_openssl.c show_available_engines(void) · line 471

Quoted 10 lines
src/openvpn/crypto_openssl.c +10 -0
+    OPENSSL_free(header_read);
+    OPENSSL_free(data_read);
+    if (!BIO_free(bio))
+    {
+        ret = false;;
+    }
+
+    return ret;
+}
+

[CUT]

Other than those small remarks the patch looks good. Therefore:

This code does not do anything yet, as these functions are not used at the moment (will be in one of the next patches), but they are tested thanks to the new unit_test and everything seems to be working as expected.

Cheers,

Signature · 1 line
Antonio Quartulli
#33 lore raw

Re: [Openvpn-devel] [PATCH v2 3/9] Add crypto_pem_{encode, decode}()

Hi,

Quoted 19 lines · Antonio Quartulli

Hi,

On 05/07/18 01:53, Steffan Karger wrote:
[CUT]

src/openvpn/crypto_openssl.c show_available_engines(void) · line 426

Quoted 8 lines
src/openvpn/crypto_openssl.c +8 -0
+bool
+crypto_pem_decode(const char *name, struct buffer *dst,
+                  const struct buffer *src)
+{
+    bool ret = false;
+    BIO *bio;
+
+    if (!(bio = BIO_new_mem_buf((char *)BPTR(src), BLEN(src))))

minor style niptick: I think it's common habit to have the assignment on one line and the if-condition on the next one, especially when it's about functions allocating some kind of object. Check your own crypto_pem_encode() in this patch :-)

Quoted 4 lines

For the sake of consistency, I think this should be split in two lines:

BIO *bio = BIO_new_mem_buf((char *)BPTR(src), BLEN(src)); if (!bio)

Agreed, will do.

Quoted 25 lines
Quoted 25 lines
src/openvpn/crypto_openssl.c +25 -0
+    {
+        crypto_msg(M_FATAL, "Cannot open memory BIO for PEM decode");
+    }
+
+    char *name_read = NULL;
+    char *header_read = NULL;
+    uint8_t *data_read = NULL;
+    long data_read_len = 0;
+    if (!PEM_read_bio(bio, &name_read, &header_read, &data_read,
+                      &data_read_len))
+    {
+        dmsg(D_CRYPT_ERRORS, "%s: PEM decode failed", __func__);
+        goto cleanup;
+    }
+
+    if (strcmp(name, name_read))
+    {
+        dmsg(D_CRYPT_ERRORS,
+             "%s: unexpected PEM name (got '%s', expected '%s')",
+             __func__, name_read, name);
+        goto cleanup;
+    }
+
+    uint8_t *dst_data = buf_write_alloc(dst, data_read_len);
+    if (!dst_data)
Quoted 8 lines

src/openvpn/crypto_openssl.c show_available_engines(void) · line 455

Quoted 4 lines
src/openvpn/crypto_openssl.c +4 -0
+    {
+        dmsg(D_CRYPT_ERRORS, "%s: dst too small (%i, needs %li)", __func__,
+             BCAP(dst), data_read_len);
+        goto cleanup;

Am I wrong or we are leaking dst_data in case of failure? or are we assuming that this memory is now owned by dst and therefore its owner (the caller) will take care of this?

I don't think this leaks data; buf_write_alloc returns NULL if there is not enough space available in dst, and won't change dst in that case. So nothing to clean up in that case?

Quoted 18 lines
Quoted 15 lines
src/openvpn/crypto_openssl.c +15 -0
+    }
+    memcpy(dst_data, data_read, data_read_len);
+
+    ret = true;
+cleanup:> +    OPENSSL_free(name_read);
+    OPENSSL_free(header_read);
+    OPENSSL_free(data_read);
+    if (!BIO_free(bio))
+    {
+        ret = false;;
+    }
+
+    return ret;
+}
+

[CUT]

Quoted 8 lines

Other than those small remarks the patch looks good. Therefore:

This code does not do anything yet, as these functions are not used at the moment (will be in one of the next patches), but they are tested thanks to the new unit_test and everything seems to be working as expected.

Thanks!

-Steffan

#34 lore raw

Re: [Openvpn-devel] [PATCH v2 3/9] Add crypto_pem_{encode, decode}()

Hi,

Quoted 7 lines · Steffan Karger
Quoted 7 lines
Quoted 6 lines
src/openvpn/crypto_openssl.c +6 -0
+    uint8_t *dst_data = buf_write_alloc(dst, data_read_len);
+    if (!dst_data)
+    {
+        dmsg(D_CRYPT_ERRORS, "%s: dst too small (%i, needs %li)", __func__,
+             BCAP(dst), data_read_len);
+        goto cleanup;
Quoted 8 lines
Quoted 3 lines

Am I wrong or we are leaking dst_data in case of failure? or are we assuming that this memory is now owned by dst and therefore its owner (the caller) will take care of this?

I don't think this leaks data; buf_write_alloc returns NULL if there is not enough space available in dst, and won't change dst in that case. So nothing to clean up in that case?

Right. The name *_alloc() fooled me, but actually there is nothing being allocated here so nothing to clean up.

Cheers,

Signature · 1 line
Antonio Quartulli
#35 lore raw

PATCH v3 2/9 Add crypto_pem_{encode,decode}()

In reply to Antonio Quartulli

From: Steffan Karger <[email protected]...>

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.

---
v2:
- Add doxygen lines with "."
- mbedtls: clear dst if encoding failed
- openssl: check BIO_new() return value
- update copyright date
- rebase op cleanup patches, removes script_security hack
- extend unit test to check that pem_encode is not a no-op
v3:
- move bio assigned in openssl pem decode to own line

 src/openvpn/crypto_backend.h           | 29 +++++++++
 src/openvpn/crypto_mbedtls.c           | 75 ++++++++++++++++++++++
 src/openvpn/crypto_openssl.c           | 82 ++++++++++++++++++++++++
 tests/unit_tests/openvpn/Makefile.am   | 16 ++++-
 tests/unit_tests/openvpn/test_crypto.c | 88 ++++++++++++++++++++++++++
 5 files changed, 289 insertions(+), 1 deletion(-)
 create mode 100644 tests/unit_tests/openvpn/test_crypto.c
src/openvpn/crypto_backend.h +29 -0
index bb53b8c9..f917c8d7 100644
@@ -36,6 +36,7 @@
 #include "crypto_mbedtls.h"
 #endif
 #include "basic.h"
+#include "buffer.h"
 
 /* TLS uses a tag of 128 bytes, let's do the same for OpenVPN */
 #define OPENVPN_AEAD_TAG_LENGTH 16
@@ -105,6 +106,34 @@ void show_available_digests(void);
 
 void show_available_engines(void);
 
+/**
+ * Encode binary data as PEM.
+ *
+ * @param name      The name to use in the PEM header/footer.
+ * @param dst       Destination buffer for PEM-encoded data.  Must be a valid
+ *                  pointer to an uninitialized buffer structure.  Iff this
+ *                  function returns true, the buffer will contain memory
+ *                  allocated through the supplied gc.
+ * @param src       Source buffer.
+ * @param gc        The garbage collector to use when allocating memory for dst.
+ *
+ * @return true iff PEM encode succeeded.
+ */
+bool crypto_pem_encode(const char *name, struct buffer *dst,
+                       const struct buffer *src, struct gc_arena *gc);
+
+/**
+ * Decode a PEM buffer to binary data.
+ *
+ * @param name      The name expected in the PEM header/footer.
+ * @param dst       Destination buffer for decoded data.
+ * @param src       Source buffer (PEM data).
+ *
+ * @return true iff PEM decode succeeded.
+ */
+bool crypto_pem_decode(const char *name, struct buffer *dst,
+                       const struct buffer *src);
+
 /*
  *
  * Random number functions, used in cases where we want
src/openvpn/crypto_mbedtls.c +75 -0

92 lines not on this page · raw message

src/openvpn/crypto_openssl.c +82 -0

88 lines not on this page · raw message

tests/unit_tests/openvpn/Makefile.am +15 -1
index db4d46e1..1ff62615 100644
@@ -6,7 +6,7 @@ if HAVE_LD_WRAP_SUPPORT
 check_PROGRAMS += argv_testdriver buffer_testdriver
 endif
 
-check_PROGRAMS += packet_id_testdriver tls_crypt_testdriver
+check_PROGRAMS += crypto_testdriver packet_id_testdriver tls_crypt_testdriver
 
 TESTS = $(check_PROGRAMS)
 
@@ -31,6 +31,20 @@ buffer_testdriver_SOURCES = test_buffer.c mock_msg.c \
 	$(openvpn_srcdir)/buffer.c \
 	$(openvpn_srcdir)/platform.c
 
+crypto_testdriver_CFLAGS  = @TEST_CFLAGS@ \
+	-I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \
+	$(OPTIONAL_CRYPTO_CFLAGS)
+crypto_testdriver_LDFLAGS = @TEST_LDFLAGS@ \
+	$(OPTIONAL_CRYPTO_LIBS)
+crypto_testdriver_SOURCES = test_crypto.c mock_msg.c \
+	$(openvpn_srcdir)/buffer.c \
+	$(openvpn_srcdir)/crypto.c \
+	$(openvpn_srcdir)/crypto_mbedtls.c \
+	$(openvpn_srcdir)/crypto_openssl.c \
+	$(openvpn_srcdir)/otime.c \
+	$(openvpn_srcdir)/packet_id.c \
+	$(openvpn_srcdir)/platform.c
+
 packet_id_testdriver_CFLAGS  = @TEST_CFLAGS@ \
 	-I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \
 	$(OPTIONAL_CRYPTO_CFLAGS)
tests/unit_tests/openvpn/test_crypto.c new file +88 -0

88 lines not on this page · raw message

Signature · 1 line
2.17.1
Acked-by on v3 2/9
#36 lore raw

Re: [Openvpn-devel] [PATCH v3 2/9] Add crypto_pem_{encode, decode}()

Hi,

Quoted 9 lines · Steffan Karger

From: Steffan Karger <[email protected]...>

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.

Quoted 5 lines

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.

Signature · 1 line
Antonio Quartulli
#37 lore raw

[Openvpn-devel] [PATCH applied] Re: Add crypto_pem_{encode, decode}()

In reply to Steffan Karger

Your patch has been applied to the master branch.

commit a5d35a01dcf73e6a93f59d687adb6e5be38c7750

Author: Steffan Karger
Date:   Sun Jul 22 12:06:45 2018 +0200
     Add crypto_pem_{encode,decode}()

     Signed-off-by: Steffan Karger <[email protected]...>
     Acked-by: Antonio Quartulli <[email protected]...>
     Message-Id: <[email protected]...>
     URL: https://www.mail-archive.com/[email protected]/msg17284.html
     Signed-off-by: Gert Doering <[email protected]...>

--
kind regards,

Gert Doering

#38 lore raw

PATCH v2 4/9 tls-crypt-v2: add specification to doc/

In reply to Steffan Karger

From: Steffan Karger <[email protected]...>

This is a preliminary description of tls-crypt-v2. It should give a good impression about the reasoning and design behind tls-crypt-v2, but might need some polishing and updating.

---
 doc/tls-crypt-v2.txt | 164 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 164 insertions(+)
 create mode 100644 doc/tls-crypt-v2.txt
doc/tls-crypt-v2.txt new file +164 -0
new file mode 100644
index 00000000..578b2f9b
@@ -0,0 +1,164 @@
+Client-specific tls-crypt keys (--tls-crypt-v2)
+===============================================
+
+This document describes the ``--tls-crypt-v2`` option, which enables OpenVPN
+to use client-specific ``--tls-crypt`` keys.
+
+Rationale
+---------
+
+``--tls-auth`` and ``tls-crypt`` use a pre-shared group key, which is shared
+among all clients and servers in an OpenVPN deployment.  If any client or
+server is compromised, the attacker will have access to this shared key, and it
+will no longer provide any security.  To reduce the risk of loosing pre-shared
+keys, ``tls-crypt-v2`` adds the ability to supply each client with a unique
+tls-crypt key.  This allows large organisations and VPN providers to profit
+from the same DoS and TLS stack protection that small deployments can already
+achieve using ``tls-auth`` or ``tls-crypt``.
+
+Also, for ``tls-crypt``, even if all these peers succeed in keeping the key
+secret, the key lifetime is limited to roughly 8000 years, divided by the
+number of clients (see the ``--tls-crypt`` section of the man page).  Using
+client-specific keys, we lift this lifetime requirement to roughly 8000 years
+for each client key (which "Should Be Enough For Everybody (tm)").
+
+
+Introduction
+------------
+
+``tls-crypt-v2`` uses an encrypted cookie mechanism to introduce
+client-specific tls-crypt keys without introducing a lot of server-side state.
+The client-specific key is encrypted using a server key.  The server key is the
+same for all servers in a group.  When a client connects, it first sends the
+encrypted key to the server, such that the server can decrypt the key and all
+messages can thereafter be encrypted using the client-specific key.
+
+A wrapped (encrypted and authenticated) client-specific key can also contain
+metadata.  The metadata is wrapped together with the key, and can be used to
+allow servers to identify clients and/or key validity.  This allows the server
+to abort the connection immediately after receiving the first packet, rather
+than performing an entire TLS handshake.  Aborting the connection this early
+greatly improves the DoS resilience and reduces attack service against
+malicious clients that have the ``tls-crypt`` or ``tls-auth`` key.  This is
+particularly relevant for large deployments (think lost key or disgruntled
+employee) and VPN providers (clients are not trusted).
+
+To allow for a smooth transition, ``tls-crypt-v2`` is designed such that a
+server can enable both ``tls-crypt-v2`` and either ``tls-crypt`` or
+``tls-auth``.  This is achieved by introducing a P_CONTROL_HARD_RESET_CLIENT_V3
+opcode, that indicates that the client wants to use ``tls-crypt-v2`` for the
+current connection.
+
+For an exact specification and more details, read the Implementation section.
+
+
+Implementation
+--------------
+
+When setting up a tls-crypt-v2 group (similar to generating a tls-crypt or
+tls-auth key previously):
+
+1. Generate a tls-crypt-v2 server key using OpenVPN's ``--genkey``.  This key
+   contains 4 512-bit keys, of which we use:
+
+   * the first 256 bits of key 1 as AES-256-CTR encryption key ``Ke``
+   * the first 256 bits of key 2 as HMAC-SHA-256 authentication key ``Ka``
+
+2. Add the tls-crypt-v2 server key to all server configs
+   (``tls-crypt-v2 /path/to/server.key``)
+
+
+When provisioning a client, create a client-specific tls-crypt key:
+
+1. Generate 2048 bits client-specific key ``Kc``
+2. Optionally generate metadata
+3. Create a wrapped client key ``WKc``, using the same nonce-misuse-resistant
+   SIV conruction we use for tls-crypt:
+
+   ``T = HMAC-SHA256(Ka, Kc || metadata)``
+
+   ``IV = 128 most significant bits of T``
+
+   ``WKc = T || AES-256-CTR(Ke, IV, Kc || metadata)``
+
+4. Create a tls-crypt-v2 client key: PEM-encode ``Kc || WKc`` and store in a
+   file, using the header ``-----BEGIN OpenVPN tls-crypt-v2 client key-----``
+   and the footer ``-----END OpenVPN tls-crypt-v2 client key-----``.  (The PEM
+   format is simple, and following PEM allows us to use the crypto lib function
+   for en/decoding.)
+5. Add the tls-crypt-v2 client key to the client config
+   (``tls-crypt-v2 /path/to/client-specific.key``)
+
+
+When setting up the openvpn connection:
+
+1. The client reads the tls-crypt-v2 key from its config, and:
+
+   1. loads ``Kc`` as its tls-crypt key,
+   2. stores ``WKc`` in memory for sending to the server.
+
+2. To start the connection, the client creates a P_CONTROL_HARD_RESET_CLIENT_V3
+   message without payload, wraps it with tls-crypt using ``Kc`` as the key,
+   and appends ``WKc``.  (``WKc`` must not be encrypted, to prevent a
+   chicken-and-egg problem.)
+
+3. The server receives the P_CONTROL_HARD_RESET_CLIENT_V3 message, and
+
+   a. unwraps ``WKc`` and strips ``WKc`` from the message.
+   b. uses unwrapped ``Kc`` to verify the remaining
+      P_CONTROL_HARD_RESET_CLIENT_V3 message's authentication.
+
+   The message is dropped and no error response is sent when either a or b
+   fails (DoS protection).
+
+4. Server optionally checks metadata using a --tls-crypt-v2-verify script
+
+   Metadata could for example contain the users certificate serial, such that
+   the incoming connection can be verified against a CRL, or a notAfter
+   timestamp that limits the key's validity period.
+
+   This allows early abort of connection, *before* we expose any of the
+   notoriously dangerous TLS, X.509 and ASN.1 parsers and thereby reduces the
+   attack surface of the server.
+
+   The metadata is checked *after* the OpenVPN three-way handshake has
+   completed, to prevent DoS attacks.  (That is, once the client has proved to
+   the server that it possesses Kc, by authenticating a packet that contains the
+   session ID picked by the server.)
+
+   RFC: should the server send a 'key rejected' message if the key is e.g.
+   revoked or expired?  That allows for better client-side error reporting, but
+   also reduces the DoS resilience.
+
+6. Client and server use ``Kc`` for (un)wrapping any following control channel
+   messages.
+
+
+Considerations
+--------------
+
+To allow for a smooth transition, the server implementation allows
+``tls-crypt`` or ``tls-auth`` to be used simultaneously with ``tls-crypt-v2``.
+This specification does not allow simultaneously using ``tls-crypt-v2`` and
+connections without any control channel wrapping, because that would break DoS
+resilience.  RFC: should we add an option along the lines of
+--tls-crypt-v2-allow-insecure-fallback to allow admins to enable this anyway?
+It might help with transitioning.
+
+``tls-crypt-v2`` uses fixed crypto algorithms, because:
+
+ * The crypto is used before we can do any negotiation, so the algorithms have
+   to be predefined.
+ * The crypto primitives are chosen conservatively, making problems with these
+   primitives unlikely.
+ * Making anything configurable adds complexity, both in implementation and
+   usage.  We should not add anymore complexity than is absolutely necessary.
+
+Potential ``tls-crypt-v2`` risks:
+
+ * Slightly more work on first connection (``WKc`` unwrap + hard reset unwrap)
+   than with ``tls-crypt`` (hard reset unwrap) or ``tls-auth`` (hard reset auth).
+ * Flexible metadata allow mistakes
+   (So we should make it easy to do it right.  Provide tooling to create client
+   keys based on cert serial + CA fingerprint, provide script that uses CRL (if
+   available) to drop revoked keys.)
Signature · 1 line
2.17.1
#39 lore raw

PATCH v2 5/9 tls-crypt-v2: generate client keys

In reply to Steffan Karger

From: Steffan Karger <[email protected]...>

As a first step towards a full tls-crypt-v2 implementation, add functionality to generate tls-crypt-v2 client keys.

---
 doc/openvpn.8           |  51 ++++++++
 src/openvpn/buffer.c    |  63 +++++++++
 src/openvpn/buffer.h    |   6 +
 src/openvpn/init.c      |  35 ++++-
 src/openvpn/integer.h   |  10 ++
 src/openvpn/options.c   |  37 ++++++
 src/openvpn/options.h   |   9 ++
 src/openvpn/tls_crypt.c | 284 ++++++++++++++++++++++++++++++++++++++++
 src/openvpn/tls_crypt.h |  81 ++++++++++--
 tests/t_lpback.sh       |  40 +++++-
 10 files changed, 600 insertions(+), 16 deletions(-)
doc/openvpn.8 +51 -0
index 46ea58b6..abefa86a 100644
@@ -5245,6 +5245,57 @@ degrading to the same security as using
 That is, the control channel still benefits from the extra protection against
 active man\-in\-the\-middle\-attacks and DoS attacks, but may no longer offer
 extra privacy and post\-quantum security on top of what TLS itself offers.
+
+For large setups or setups where clients are not trusted, consider using
+.B \-\-tls\-crypt\-v2
+instead.  That uses per\-client unique keys, and thereby improves the bounds to
+\fR'rotate a client key at least once per 8000 years'.
+.\"*********************************************************
+.TP
+.B \-\-tls\-crypt\-v2 keyfile
+
+Use client\-specific tls\-crypt keys.
+
+For clients,
+.B keyfile
+is a client\-specific tls\-crypt key.  Such a key can be generated using the
+.B \-\-tls\-crypt\-v2\-genkey
+option.
+
+For servers,
+.B keyfile
+is used to unwrap client\-specific keys supplied by the client during connection
+setup.  This key must be the same as the key used to generate the
+client\-specific key (see
+.B \-\-tls\-crypt\-v2\-genkey\fR).
+
+On servers, this option can be used together with the
+.B \-\-tls\-auth
+or
+.B \-\-tls\-crypt
+option.  In that case, the server will detect whether the client is using
+client\-specific keys, and automatically select the right mode.
+.\"*********************************************************
+.TP
+.B \-\-tls\-crypt\-v2\-genkey client|server keyfile [metadata]
+
+If the first parameter equals "server", generate a \-\-tls\-crypt\-v2 server
+key and store the key in
+.B keyfile\fR.
+
+
+If the first parameter equals "client", generate a \-\-tls\-crypt\-v2 client
+key, and store the key in
+.B keyfile\fR.
+
+If supplied, include the supplied
+.B metadata
+in the wrapped client key.  This metadata must be supplied in base64\-encoded
+form.  The metadata must be at most 735 bytes long (980 bytes in base64).
+
+.B TODO
+Metadata handling is not yet implemented.  This text will be updated by the
+commit that introduces metadata handling.
 .\"*********************************************************
 .TP
 .B \-\-askpass [file]
src/openvpn/buffer.c +63 -0

69 lines not on this page · raw message

src/openvpn/buffer.h +6 -0
index d848490a..9ab60420 100644
@@ -469,6 +469,12 @@ const char *skip_leading_whitespace(const char *str);
 
 void string_null_terminate(char *str, int len, int capacity);
 
+/** Write buffer contents to file */
+bool buffer_write_file(const char *filename, const struct buffer *buf);
+
+/** Read file contents. Returns invalid buffer on error. */
+struct buffer buffer_read_from_file(const char *filename, struct gc_arena *gc);
+
 /*
  * Write string in buf to file descriptor fd.
  * NOTE: requires that string be null terminated.
src/openvpn/init.c +30 -5
index d28d1fd2..c1454f36 100644
@@ -1028,6 +1028,11 @@ print_openssl_info(const struct options *options)
 bool
 do_genkey(const struct options *options)
 {
+    /* should we disable paging? */
+    if (options->mlock && (options->genkey || options->tls_crypt_v2_genkey_file))
+    {
+        platform_mlockall(true);
+    }
     if (options->genkey)
     {
         int nbits_written;
@@ -1035,11 +1040,6 @@ do_genkey(const struct options *options)
         notnull(options->shared_secret_file,
                 "shared secret output file (--secret)");
 
-        if (options->mlock)     /* should we disable paging? */
-        {
-            platform_mlockall(true);
-        }
-
         nbits_written = write_key_file(2, options->shared_secret_file);
 
         msg(D_GENKEY | M_NOPREFIX,
@@ -1047,6 +1047,31 @@ do_genkey(const struct options *options)
             options->shared_secret_file);
         return true;
     }
+    if (options->tls_crypt_v2_genkey_type)
+    {
+        if(!strcmp(options->tls_crypt_v2_genkey_type, "server"))
+        {
+            tls_crypt_v2_write_server_key_file(options->tls_crypt_v2_genkey_file);
+            return true;
+        }
+        else if (options->tls_crypt_v2_genkey_type
+                 && !strcmp(options->tls_crypt_v2_genkey_type, "client"))
+        {
+            if (!options->tls_crypt_v2_file)
+            {
+                msg(M_USAGE, "--tls-crypt-v2-gen-client-key requires a server key to be set via --tls-crypt-v2");
+            }
+
+            tls_crypt_v2_write_client_key_file(options->tls_crypt_v2_genkey_file,
+                    options->tls_crypt_v2_metadata, options->tls_crypt_v2_file,
+                    options->tls_crypt_v2_inline);
+            return true;
+        }
+        else
+        {
+            msg(M_USAGE, "--tls-crypt-v2-genkey type should be \"client\" or \"server\"");
+        }
+    }
     return false;
 }
 
src/openvpn/integer.h +10 -0
index a7e19d35..b1ae0eda 100644
@@ -26,6 +26,16 @@
 
 #include "error.h"
 
+#ifndef htonll
+#define htonll(x) ((1==htonl(1)) ? (x) : \
+                  ((uint64_t)htonl((x) & 0xFFFFFFFF) << 32) | htonl((x) >> 32))
+#endif
+
+#ifndef ntohll
+#define ntohll(x) ((1==ntohl(1)) ? (x) : \
+                  ((uint64_t)ntohl((x) & 0xFFFFFFFF) << 32) | ntohl((x) >> 32))
+#endif
+
 /*
  * min/max functions
  */
src/openvpn/options.c +37 -0
index b89f4ba2..c1d53a96 100644
@@ -622,6 +622,13 @@ static const char usage_message[] =
     "                  attacks on the TLS stack and DoS attacks.\n"
     "                  key (required) provides the pre-shared key file.\n"
     "                  see --secret option for more info.\n"
+    "--tls-crypt-v2 key : For clients: use key as a client-specific tls-crypt key.\n"
+    "                  For servers: use key to decrypt client-specific keys.  For\n"
+    "                  key generation (--tls-crypt-v2-genkey): use key to\n"
+    "                  encrypt generated client-specific key.  (See --tls-crypt.)\n"
+    "--tls-crypt-v2-genkey client|server keyfile [base64 metadata]: Generate a\n"
+    "                  fresh tls-crypt-v2 client or server key, and store to\n"
+    "                  keyfile.  If supplied, include metadata in wrapped key.\n"
     "--askpass [file]: Get PEM password from controlling tty before we daemonize.\n"
     "--auth-nocache  : Don't cache --askpass or --auth-user-pass passwords.\n"
     "--crl-verify crl ['dir']: Check peer certificate against a CRL.\n"
@@ -1789,6 +1796,10 @@ show_settings(const struct options *o)
 
     SHOW_STR(tls_auth_file);
     SHOW_STR(tls_crypt_file);
+    SHOW_STR(tls_crypt_v2_file);
+    SHOW_STR(tls_crypt_v2_genkey_type);
+    SHOW_STR(tls_crypt_v2_genkey_file);
+    SHOW_STR(tls_crypt_v2_metadata);
 
 #ifdef ENABLE_PKCS11
     {
@@ -2762,6 +2773,7 @@ options_postprocess_verify_ce(const struct options *options, const struct connec
         MUST_BE_UNDEF(transition_window);
         MUST_BE_UNDEF(tls_auth_file);
         MUST_BE_UNDEF(tls_crypt_file);
+        MUST_BE_UNDEF(tls_crypt_v2_file);
         MUST_BE_UNDEF(single_session);
         MUST_BE_UNDEF(push_peer_info);
         MUST_BE_UNDEF(tls_exit);
@@ -3290,6 +3302,12 @@ options_postprocess_filechecks(struct options *options)
                               options->tls_auth_file, R_OK, "--tls-auth");
     errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE|CHKACC_PRIVATE,
                               options->tls_crypt_file, R_OK, "--tls-crypt");
+    errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE|CHKACC_PRIVATE,
+                              options->tls_crypt_v2_file, R_OK,
+                              "--tls-crypt-v2");
+    errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE|CHKACC_PRIVATE,
+                              options->tls_crypt_v2_genkey_file, R_OK,
+                              "--tls-crypt-v2-genkey");
     errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE|CHKACC_PRIVATE,
                               options->shared_secret_file, R_OK, "--secret");
     errs |= check_file_access(CHKACC_DIRPATH|CHKACC_FILEXSTWR,
@@ -8023,6 +8041,25 @@ add_option(struct options *options,
         }
         options->tls_crypt_file = p[1];
     }
+    else if (streq(p[0], "tls-crypt-v2") && p[1] && !p[3])
+    {
+        VERIFY_PERMISSION(OPT_P_GENERAL);
+        if (streq(p[1], INLINE_FILE_TAG) && p[2])
+        {
+            options->tls_crypt_v2_inline = p[2];
+        }
+        options->tls_crypt_v2_file = p[1];
+    }
+    else if (streq(p[0], "tls-crypt-v2-genkey") && p[2] && !p[4])
+    {
+        VERIFY_PERMISSION(OPT_P_GENERAL);
+        options->tls_crypt_v2_genkey_type = p[1];
+        options->tls_crypt_v2_genkey_file = p[2];
+        if (p[3])
+        {
+            options->tls_crypt_v2_metadata = p[3];
+        }
+    }
     else if (streq(p[0], "key-method") && p[1] && !p[2])
     {
         int key_method;
src/openvpn/options.h +9 -0
index 3a6c33f8..d5407b5b 100644
@@ -567,6 +567,15 @@ struct options
     const char *tls_crypt_file;
     const char *tls_crypt_inline;
 
+    /* Client-specific secret or server key used for TLS control channel
+     * authenticated encryption v2 */
+    const char *tls_crypt_v2_file;
+    const char *tls_crypt_v2_inline;
+
+    const char *tls_crypt_v2_genkey_type;
+    const char *tls_crypt_v2_genkey_file;
+    const char *tls_crypt_v2_metadata;
+
     /* Allow only one session */
     bool single_session;
 
src/openvpn/tls_crypt.c +284 -0

298 lines not on this page · raw message

src/openvpn/tls_crypt.h +73 -8

129 lines not on this page · raw message

tests/t_lpback.sh +37 -3
index 2052c626..107e7c1e 100755
@@ -21,8 +21,8 @@
 
 set -eu
 top_builddir="${top_builddir:-..}"
-trap "rm -f key.$$ log.$$ ; trap 0 ; exit 77" 1 2 15
-trap "rm -f key.$$ log.$$ ; exit 1" 0 3
+trap "rm -f key.$$ tc-server-key.$$ tc-client-key.$$ log.$$ ; trap 0 ; exit 77" 1 2 15
+trap "rm -f key.$$ tc-server-key.$$ tc-client-key.$$ log.$$ ; exit 1" 0 3
 
 # Get list of supported ciphers from openvpn --show-ciphers output
 CIPHERS=$(${top_builddir}/src/openvpn/openvpn --show-ciphers | \
@@ -55,6 +55,40 @@ do
     fi
 done
 
-rm key.$$ log.$$
+echo -n "Testing tls-crypt-v2 server key generation..."
+"${top_builddir}/src/openvpn/openvpn" \
+    --tls-crypt-v2-genkey server tc-server-key.$$ >log.$$ 2>&1
+if [ $? != 0 ] ; then
+    echo "FAILED"
+    cat log.$$
+    e=1
+else
+    echo "OK"
+fi
+
+echo -n "Testing tls-crypt-v2 key generation (no metadata)..."
+"${top_builddir}/src/openvpn/openvpn" --tls-crypt-v2 tc-server-key.$$ \
+    --tls-crypt-v2-genkey client tc-client-key.$$ >log.$$ 2>&1
+if [ $? != 0 ] ; then
+    echo "FAILED"
+    cat log.$$
+    e=1
+else
+    echo "OK"
+fi
+
+echo -n "Testing tls-crypt-v2 key generation (max length metadata)..."
+"${top_builddir}/src/openvpn/openvpn" --tls-crypt-v2 tc-server-key.$$ \
+    --tls-crypt-v2-genkey client tc-client-key.$$ \
+    $(head -c735 /dev/zero | base64 -w0) >log.$$ 2>&1
+if [ $? != 0 ] ; then
+    echo "FAILED"
+    cat log.$$
+    e=1
+else
+    echo "OK"
+fi
+
+rm key.$$ tc-server-key.$$ tc-client-key.$$ log.$$
 trap 0
 exit $e
Signature · 1 line
2.17.1
#40 lore raw

PATCH v2 6/9 tls-crypt-v2: add unwrap_client_key

In reply to Steffan Karger

From: Steffan Karger <[email protected]...>

Add helper functions to unwrap tls-crypt-v2 client keys.

---
 src/openvpn/buffer.h                      |   7 +
 src/openvpn/tls_crypt.c                   | 102 +++++++++
 tests/unit_tests/openvpn/test_tls_crypt.c | 253 ++++++++++++++++++++--
 3 files changed, 342 insertions(+), 20 deletions(-)
src/openvpn/buffer.h +7 -0
index 9ab60420..ce36279a 100644
@@ -839,6 +839,13 @@ buf_read_u32(struct buffer *buf, bool *good)
     }
 }
 
+/** Return true if buffer contents are equal */
+static inline bool
+buf_equal(const struct buffer *a, const struct buffer *b)
+{
+    return BLEN(a) == BLEN(b) && 0 == memcmp(BPTR(a), BPTR(b), BLEN(a));
+}
+
 /**
  * Compare src buffer contents with match.
  * *NOT* constant time. Do not use when comparing HMACs.
src/openvpn/tls_crypt.c +102 -0

114 lines not on this page · raw message

tests/unit_tests/openvpn/test_tls_crypt.c +233 -20

334 lines not on this page · raw message

Signature · 1 line
2.17.1
#41 lore raw

PATCH v2 7/9 tls-crypt-v2: add P_CONTROL_HARD_RESET_CLIENT_V3 opcode

In reply to Steffan Karger

From: Steffan Karger <[email protected]...>

Not used yet, but prepare for sending and receiving tls-crypt-v2 handshake messages.

---
 src/openvpn/ps.c         |  3 ++-
 src/openvpn/ssl.c        | 23 ++++++++++++++++++-----
 src/openvpn/ssl.h        |  5 ++++-
 src/openvpn/ssl_common.h |  2 ++
 4 files changed, 26 insertions(+), 7 deletions(-)
src/openvpn/ps.c +2 -1
index 25ab3749..adec0724 100644
@@ -985,7 +985,8 @@ is_openvpn_protocol(const struct buffer *buf)
     {
         return p[0] == 0
                && p[1] >= 14
-               && p[2] == (P_CONTROL_HARD_RESET_CLIENT_V2<<P_OPCODE_SHIFT);
+               && (p[2] == (P_CONTROL_HARD_RESET_CLIENT_V2<<P_OPCODE_SHIFT)
+                   || p[2] == (P_CONTROL_HARD_RESET_CLIENT_V3<<P_OPCODE_SHIFT));
     }
     else if (len >= 2)
     {
src/openvpn/ssl.c +18 -5
index 669f941b..bfe37fcd 100644
@@ -792,6 +792,9 @@ packet_opcode_name(int op)
         case P_CONTROL_HARD_RESET_SERVER_V2:
             return "P_CONTROL_HARD_RESET_SERVER_V2";
 
+        case P_CONTROL_HARD_RESET_CLIENT_V3:
+            return "P_CONTROL_HARD_RESET_CLIENT_V3";
+
         case P_CONTROL_SOFT_RESET_V1:
             return "P_CONTROL_SOFT_RESET_V1";
 
@@ -864,7 +867,8 @@ is_hard_reset(int op, int key_method)
 
     if (!key_method || key_method >= 2)
     {
-        if (op == P_CONTROL_HARD_RESET_CLIENT_V2 || op == P_CONTROL_HARD_RESET_SERVER_V2)
+        if (op == P_CONTROL_HARD_RESET_CLIENT_V2 || op == P_CONTROL_HARD_RESET_SERVER_V2
+            || op == P_CONTROL_HARD_RESET_CLIENT_V3)
         {
             return true;
         }
@@ -1095,8 +1099,15 @@ tls_session_init(struct tls_multi *multi, struct tls_session *session)
     }
     else /* session->opt->key_method >= 2 */
     {
-        session->initial_opcode = session->opt->server ?
-                                  P_CONTROL_HARD_RESET_SERVER_V2 : P_CONTROL_HARD_RESET_CLIENT_V2;
+        if (session->opt->server)
+        {
+            session->initial_opcode = P_CONTROL_HARD_RESET_SERVER_V2;
+        }
+        else
+        {
+            session->initial_opcode = session->opt->tls_crypt_v2 ?
+                    P_CONTROL_HARD_RESET_CLIENT_V3 : P_CONTROL_HARD_RESET_CLIENT_V2;
+        }
     }
 
     /* Initialize control channel authentication parameters */
@@ -3427,7 +3438,8 @@ tls_pre_decrypt(struct tls_multi *multi,
             {
                 /* verify client -> server or server -> client connection */
                 if (((op == P_CONTROL_HARD_RESET_CLIENT_V1
-                      || op == P_CONTROL_HARD_RESET_CLIENT_V2) && !multi->opt.server)
+                      || op == P_CONTROL_HARD_RESET_CLIENT_V2
+                      || op == P_CONTROL_HARD_RESET_CLIENT_V3) && !multi->opt.server)
                     || ((op == P_CONTROL_HARD_RESET_SERVER_V1
                          || op == P_CONTROL_HARD_RESET_SERVER_V2) && multi->opt.server))
                 {
@@ -3812,7 +3824,8 @@ tls_pre_decrypt_lite(const struct tls_auth_standalone *tas,
         /* this packet is from an as-yet untrusted source, so
          * scrutinize carefully */
 
-        if (op != P_CONTROL_HARD_RESET_CLIENT_V2)
+        if (op != P_CONTROL_HARD_RESET_CLIENT_V2
+            && op != P_CONTROL_HARD_RESET_CLIENT_V3)
         {
             /*
              * This can occur due to bogus data or DoS packets.
src/openvpn/ssl.h +4 -1
index a2501c9b..bbc84192 100644
@@ -63,9 +63,12 @@
 #define P_CONTROL_HARD_RESET_CLIENT_V2 7     /* initial key from client, forget previous state */
 #define P_CONTROL_HARD_RESET_SERVER_V2 8     /* initial key from server, forget previous state */
 
+/* indicates key_method >= 2 and client-specific tls-crypt key */
+#define P_CONTROL_HARD_RESET_CLIENT_V3 10    /* initial key from client, forget previous state */
+
 /* define the range of legal opcodes */
 #define P_FIRST_OPCODE                 1
-#define P_LAST_OPCODE                  9
+#define P_LAST_OPCODE                  10
 
 /*
  * Set the max number of acknowledgments that can "hitch a ride" on an outgoing
src/openvpn/ssl_common.h +2 -0
index 08ef6ffa..e3c852af 100644
@@ -286,6 +286,8 @@ struct tls_options
     const char *config_authname;
     bool ncp_enabled;
 
+    bool tls_crypt_v2;
+
     /** TLS handshake wrapping state */
     struct tls_wrap_ctx tls_wrap;
 
Signature · 1 line
2.17.1
#42 lore raw

PATCH v2 8/9 tls-crypt-v2: implement tls-crypt-v2 handshake

In reply to Steffan Karger

From: Steffan Karger <[email protected]...>

This makes clients send-and-use, and servers receive-unwrap-and-use tls-crypt-v2 client keys, which completes the on-the-wire work.

---
 src/openvpn/init.c                   | 39 +++++++++++++-
 src/openvpn/openvpn.h                |  2 +
 src/openvpn/options.c                |  5 ++
 src/openvpn/ssl.c                    | 79 ++++++++++++++++++++++------
 src/openvpn/ssl_common.h             |  6 +++
 src/openvpn/tls_crypt.c              | 50 ++++++++++++++++++
 src/openvpn/tls_crypt.h              | 14 +++++
 tests/unit_tests/openvpn/Makefile.am |  2 +-
 8 files changed, 179 insertions(+), 18 deletions(-)
src/openvpn/init.c +38 -1
index c1454f36..0874edff 100644
@@ -2436,6 +2436,9 @@ key_schedule_free(struct key_schedule *ks, bool free_ssl_ctx)
     {
         tls_ctx_free(&ks->ssl_ctx);
         free_key_ctx_bi(&ks->tls_wrap_key);
+        free_key_ctx(&ks->tls_crypt_v2_server_key);
+        buf_clear(&ks->tls_crypt_v2_wkc);
+        free_buf(&ks->tls_crypt_v2_wkc);
     }
     CLEAR(*ks);
 }
@@ -2604,6 +2607,24 @@ do_init_crypto_tls_c1(struct context *c)
                                options->tls_crypt_inline, options->tls_server);
         }
 
+        /* tls-crypt with client-specific keys (--tls-crypt-v2) */
+        if (options->tls_crypt_v2_file)
+        {
+            if (options->tls_server)
+            {
+                tls_crypt_v2_init_server_key(&c->c1.ks.tls_crypt_v2_server_key,
+                                             true, options->tls_crypt_v2_file,
+                                             options->tls_crypt_v2_inline);
+            }
+            else
+            {
+                tls_crypt_v2_init_client_key(&c->c1.ks.tls_wrap_key,
+                                             &c->c1.ks.tls_crypt_v2_wkc,
+                                             options->tls_crypt_v2_file,
+                                             options->tls_crypt_v2_inline);
+            }
+        }
+
         c->c1.ciphername = options->ciphername;
         c->c1.authname = options->authname;
         c->c1.keysize = options->keysize;
@@ -2823,13 +2844,28 @@ do_init_crypto_tls(struct context *c, const unsigned int flags)
     }
 
     /* TLS handshake encryption (--tls-crypt) */
-    if (options->tls_crypt_file)
+    if (options->tls_crypt_file
+        || (options->tls_crypt_v2_file && options->tls_client))
     {
         to.tls_wrap.mode = TLS_WRAP_CRYPT;
         to.tls_wrap.opt.key_ctx_bi = c->c1.ks.tls_wrap_key;
         to.tls_wrap.opt.pid_persist = &c->c1.pid_persist;
         to.tls_wrap.opt.flags |= CO_PACKET_ID_LONG_FORM;
         tls_crypt_adjust_frame_parameters(&to.frame);
+
+        if (options->tls_crypt_v2_file)
+        {
+            to.tls_wrap.tls_crypt_v2_wkc = &c->c1.ks.tls_crypt_v2_wkc;
+        }
+    }
+
+    if (options->tls_crypt_v2_file)
+    {
+        to.tls_crypt_v2 = true;
+        if (options->tls_server)
+        {
+            to.tls_wrap.tls_crypt_v2_server_key = c->c1.ks.tls_crypt_v2_server_key;
+        }
     }
 
     /* If we are running over TCP, allow for
@@ -4359,6 +4395,7 @@ inherit_context_child(struct context *dest,
     dest->c1.ks.ssl_ctx = src->c1.ks.ssl_ctx;
     dest->c1.ks.tls_wrap_key = src->c1.ks.tls_wrap_key;
     dest->c1.ks.tls_auth_key_type = src->c1.ks.tls_auth_key_type;
+    dest->c1.ks.tls_crypt_v2_server_key = src->c1.ks.tls_crypt_v2_server_key;
     /* inherit pre-NCP ciphers */
     dest->c1.ciphername = src->c1.ciphername;
     dest->c1.authname = src->c1.authname;
src/openvpn/openvpn.h +2 -0
index c5c767c0..96cff870 100644
@@ -66,6 +66,8 @@ struct key_schedule
     /* optional TLS control channel wrapping */
     struct key_type tls_auth_key_type;
     struct key_ctx_bi tls_wrap_key;
+    struct key_ctx tls_crypt_v2_server_key;
+    struct buffer tls_crypt_v2_wkc;             /**< Wrapped client key */
 };
 
 /*
src/openvpn/options.c +5 -0
index c1d53a96..60be7d15 100644
@@ -2739,6 +2739,11 @@ options_postprocess_verify_ce(const struct options *options, const struct connec
         {
             msg(M_USAGE, "--tls-auth and --tls-crypt are mutually exclusive");
         }
+        if (options->client && options->tls_crypt_v2_file
+            && (options->tls_auth_file || options->tls_crypt_file))
+        {
+            msg(M_USAGE, "--tls-crypt-v2, --tls-auth and --tls-crypt are mutually exclusive in client mode");
+        }
     }
     else
     {
src/openvpn/ssl.c +63 -16

126 lines not on this page · raw message

src/openvpn/ssl_common.h +6 -0
index e3c852af..d744881c 100644
@@ -213,6 +213,12 @@ struct tls_wrap_ctx
     } mode;                     /**< Control channel wrapping mode */
     struct crypto_options opt;  /**< Crypto state */
     struct buffer work;         /**< Work buffer (only for --tls-crypt) */
+    struct key_ctx tls_crypt_v2_server_key;  /**< Decrypts client keys */
+    const struct buffer *tls_crypt_v2_wkc;   /**< Wrapped client key,
+                                                  sent to server */
+    struct buffer tls_crypt_v2_metadata;     /**< Received from client */
+    bool cleanup_key_ctx;                    /**< opt.key_ctx_bi is owned by
+                                                  this context */
 };
 
 /*
src/openvpn/tls_crypt.c +50 -0

62 lines not on this page · raw message

src/openvpn/tls_crypt.h +14 -0
index 746f3be8..6521144e 100644
@@ -83,6 +83,7 @@
 #include "buffer.h"
 #include "crypto.h"
 #include "session_id.h"
+#include "ssl_common.h"
 
 #define TLS_CRYPT_TAG_SIZE (256/8)
 #define TLS_CRYPT_PID_SIZE (sizeof(packet_id_type) + sizeof(net_time_t))
@@ -181,6 +182,19 @@ void tls_crypt_v2_init_client_key(struct key_ctx_bi *key,
                                   const char *key_file,
                                   const char *key_inline);
 
+/**
+ * Extract a tls-crypt-v2 client key from a P_CONTROL_HARD_RESET_CLIENT_V3
+ * message, and load the key into the supplied tls wrap context.
+ *
+ * @param buf   Buffer containing a received P_CONTROL_HARD_RESET_CLIENT_V3
+ *              message.
+ * @param ctx   tls-wrap context to be initialized with the client key.
+ *
+ * @returns true if a key was successfully extracted.
+ */
+bool tls_crypt_v2_extract_client_key(struct buffer *buf,
+                                     struct tls_wrap_ctx *ctx);
+
 /**
  * Generate a tls-crypt-v2 server key, and write to file.
  *
tests/unit_tests/openvpn/Makefile.am +1 -1
index 1ff62615..b51973fa 100644
@@ -59,7 +59,7 @@ packet_id_testdriver_SOURCES = test_packet_id.c mock_msg.c \
 
 tls_crypt_testdriver_CFLAGS  = @TEST_CFLAGS@ \
 	-I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \
-	$(OPTIONAL_CRYPTO_CFLAGS)
+	$(OPTIONAL_CRYPTO_CFLAGS) $(OPTIONAL_PKCS11_HELPER_CFLAGS)
 tls_crypt_testdriver_LDFLAGS = @TEST_LDFLAGS@ \
 	$(OPTIONAL_CRYPTO_LIBS)
 tls_crypt_testdriver_SOURCES = test_tls_crypt.c mock_msg.c \
Signature · 1 line
2.17.1
#43 lore raw

PATCH v2 9/9 tls-crypt-v2: add script hook to verify metadata

In reply to Steffan Karger

From: Steffan Karger <[email protected]...>

To allow rejecting incoming connections very early in the handshake, add a --tls-crypt-v2-verify option that allows administators to run an external command to verify the metadata from the client key. See doc/tls-crypt-v2.txt for more details.

Because of the extra dependencies, this requires adding a mock parse_line() to the tls-crypt unit tests.

---
 Changes.rst                               | 12 ++++
 doc/openvpn.8                             | 35 +++++++++++-
 src/openvpn/init.c                        |  1 +
 src/openvpn/options.c                     |  7 +++
 src/openvpn/options.h                     |  2 +
 src/openvpn/ssl.c                         | 16 ++++--
 src/openvpn/ssl_common.h                  |  1 +
 src/openvpn/tls_crypt.c                   | 69 ++++++++++++++++++++++-
 src/openvpn/tls_crypt.h                   |  3 +-
 tests/unit_tests/openvpn/Makefile.am      | 12 +++-
 tests/unit_tests/openvpn/test_tls_crypt.c | 16 ++++++
 11 files changed, 160 insertions(+), 14 deletions(-)
Changes.rst +12 -0
index a6090cf5..e77b3d79 100644
@@ -1,3 +1,15 @@
+Overview of changes in 2.5
+==========================
+
+New features
+------------
+Client-specific tls-crypt keys (``--tls-crypt-v2``)
+    ``tls-crypt-v2`` adds the ability to supply each client with a unique
+    tls-crypt key.  This allows large organisations and VPN providers to profit
+    from the same DoS and TLS stack protection that small deployments can
+    already achieve using ``tls-auth`` or ``tls-crypt``.
+
+
 Overview of changes in 2.4
 ==========================
 
doc/openvpn.8 +32 -3
index abefa86a..497f6ca3 100644
@@ -5293,9 +5293,38 @@ If supplied, include the supplied
 in the wrapped client key.  This metadata must be supplied in base64\-encoded
 form.  The metadata must be at most 735 bytes long (980 bytes in base64).
 
-.B TODO
-Metadata handling is not yet implemented.  This text will be updated by the
-commit that introduces metadata handling.
+If no metadata is supplied, OpenVPN will use a 64-bit unix timestamp
+representing the current time in UTC, encoded in network order, as metadata for
+the generated key.
+
+Servers can use
+.B \-\-tls\-crypt\-v2\-verify
+to specify a metadata verification command.
+.\"*********************************************************
+.TP
+.B \-\-tls\-crypt\-v2\-verify cmd
+
+Run command
+.B cmd
+to verify the metadata of the client-specific tls-crypt-v2 key of a connecting
+client.  This allows server administrators to reject client connections, before
+exposing the TLS stack (including the notoriously dangerous X.509 and ASN.1
+stacks) to the connecting client.
+
+OpenVPN supplies the following env vars to the command:
+.RS
+.IP \[bu] 2
+script_type is set to "tls-crypt-v2-verify"
+.IP \[bu]
+metadata_type is set to "0" is the metadata was user supplied, or "1" if it's a
+64-bit unix timestamp representing the key creation time.
+.IP \[bu]
+metadata_file contains the filename of a temporary file that contains the client
+metadata.
+.RE
+
+.IP
+The command can reject the connection by exitingwith a non-zero exit code.
 .\"*********************************************************
 .TP
 .B \-\-askpass [file]
src/openvpn/init.c +1 -0
index 0874edff..9da1f218 100644
@@ -2865,6 +2865,7 @@ do_init_crypto_tls(struct context *c, const unsigned int flags)
         if (options->tls_server)
         {
             to.tls_wrap.tls_crypt_v2_server_key = c->c1.ks.tls_crypt_v2_server_key;
+            to.tls_crypt_v2_verify_script = c->options.tls_crypt_v2_verify_script;
         }
     }
 
src/openvpn/options.c +7 -0
index 60be7d15..19ac470f 100644
@@ -629,6 +629,8 @@ static const char usage_message[] =
     "--tls-crypt-v2-genkey client|server keyfile [base64 metadata]: Generate a\n"
     "                  fresh tls-crypt-v2 client or server key, and store to\n"
     "                  keyfile.  If supplied, include metadata in wrapped key.\n"
+    "--tls-crypt-v2-verify cmd : Run command cmd to verify the metadata of the\n"
+    "                  client-supplied tls-crypt-v2 client key\n"
     "--askpass [file]: Get PEM password from controlling tty before we daemonize.\n"
     "--auth-nocache  : Don't cache --askpass or --auth-user-pass passwords.\n"
     "--crl-verify crl ['dir']: Check peer certificate against a CRL.\n"
@@ -8065,6 +8067,11 @@ add_option(struct options *options,
             options->tls_crypt_v2_metadata = p[3];
         }
     }
+    else if (streq(p[0], "tls-crypt-v2-verify") && p[1] && !p[2])
+    {
+        VERIFY_PERMISSION(OPT_P_GENERAL);
+        options->tls_crypt_v2_verify_script = p[1];
+    }
     else if (streq(p[0], "key-method") && p[1] && !p[2])
     {
         int key_method;
src/openvpn/options.h +2 -0
index d5407b5b..2f93e44f 100644
@@ -576,6 +576,8 @@ struct options
     const char *tls_crypt_v2_genkey_file;
     const char *tls_crypt_v2_metadata;
 
+    const char *tls_crypt_v2_verify_script;
+
     /* Allow only one session */
     bool single_session;
 
src/openvpn/ssl.c +10 -6
index 9fc1efa3..d3951f68 100644
@@ -1541,14 +1541,15 @@ write_control_auth(struct tls_session *session,
 static bool
 read_control_auth(struct buffer *buf,
                   struct tls_wrap_ctx *ctx,
-                  const struct link_socket_actual *from)
+                  const struct link_socket_actual *from,
+                  const struct tls_options *opt)
 {
     struct gc_arena gc = gc_new();
     bool ret = false;
 
     const uint8_t opcode = *(BPTR(buf)) >> P_OPCODE_SHIFT;
     if (opcode == P_CONTROL_HARD_RESET_CLIENT_V3
-        && !tls_crypt_v2_extract_client_key(buf, ctx))
+        && !tls_crypt_v2_extract_client_key(buf, ctx, opt))
     {
         msg (D_TLS_ERRORS,
              "TLS Error: can not extract tls-crypt-v2 client key from %s",
@@ -3629,7 +3630,8 @@ tls_pre_decrypt(struct tls_multi *multi,
                     goto error;
                 }
 
-                if (!read_control_auth(buf, &session->tls_wrap, from))
+                if (!read_control_auth(buf, &session->tls_wrap, from,
+                                       session->opt))
                 {
                     goto error;
                 }
@@ -3682,7 +3684,8 @@ tls_pre_decrypt(struct tls_multi *multi,
                 if (op == P_CONTROL_SOFT_RESET_V1
                     && DECRYPT_KEY_ENABLED(multi, ks))
                 {
-                    if (!read_control_auth(buf, &session->tls_wrap, from))
+                    if (!read_control_auth(buf, &session->tls_wrap, from,
+                                           session->opt))
                     {
                         goto error;
                     }
@@ -3703,7 +3706,8 @@ tls_pre_decrypt(struct tls_multi *multi,
                         do_burst = true;
                     }
 
-                    if (!read_control_auth(buf, &session->tls_wrap, from))
+                    if (!read_control_auth(buf, &session->tls_wrap, from,
+                                           session->opt))
                     {
                         goto error;
                     }
@@ -3905,7 +3909,7 @@ tls_pre_decrypt_lite(const struct tls_auth_standalone *tas,
             bool status;
 
             /* HMAC test, if --tls-auth was specified */
-            status = read_control_auth(&newbuf, &tls_wrap_tmp, from);
+            status = read_control_auth(&newbuf, &tls_wrap_tmp, from, NULL);
             free_buf(&newbuf);
             if (tls_wrap_tmp.cleanup_key_ctx)
             {
src/openvpn/ssl_common.h +1 -0
index d744881c..923cd959 100644
@@ -293,6 +293,7 @@ struct tls_options
     bool ncp_enabled;
 
     bool tls_crypt_v2;
+    const char *tls_crypt_v2_verify_script;
 
     /** TLS handshake wrapping state */
     struct tls_wrap_ctx tls_wrap;
src/openvpn/tls_crypt.c +68 -1

92 lines not on this page · raw message

src/openvpn/tls_crypt.h +2 -1
index 6521144e..5d9d29d6 100644
@@ -193,7 +193,8 @@ void tls_crypt_v2_init_client_key(struct key_ctx_bi *key,
  * @returns true if a key was successfully extracted.
  */
 bool tls_crypt_v2_extract_client_key(struct buffer *buf,
-                                     struct tls_wrap_ctx *ctx);
+                                     struct tls_wrap_ctx *ctx,
+                                     const struct tls_options *opt);
 
 /**
  * Generate a tls-crypt-v2 server key, and write to file.
tests/unit_tests/openvpn/Makefile.am +9 -3
index b51973fa..80eca766 100644
@@ -6,7 +6,10 @@ if HAVE_LD_WRAP_SUPPORT
 check_PROGRAMS += argv_testdriver buffer_testdriver
 endif
 
-check_PROGRAMS += crypto_testdriver packet_id_testdriver tls_crypt_testdriver
+check_PROGRAMS += crypto_testdriver packet_id_testdriver
+if HAVE_LD_WRAP_SUPPORT
+check_PROGRAMS += tls_crypt_testdriver
+endif
 
 TESTS = $(check_PROGRAMS)
 
@@ -60,14 +63,17 @@ packet_id_testdriver_SOURCES = test_packet_id.c mock_msg.c \
 tls_crypt_testdriver_CFLAGS  = @TEST_CFLAGS@ \
 	-I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \
 	$(OPTIONAL_CRYPTO_CFLAGS) $(OPTIONAL_PKCS11_HELPER_CFLAGS)
-tls_crypt_testdriver_LDFLAGS = @TEST_LDFLAGS@ \
+tls_crypt_testdriver_LDFLAGS = @TEST_LDFLAGS@ -Wl,--wrap=parse_line \
 	$(OPTIONAL_CRYPTO_LIBS)
 tls_crypt_testdriver_SOURCES = test_tls_crypt.c mock_msg.c \
+	$(openvpn_srcdir)/argv.c \
 	$(openvpn_srcdir)/base64.c \
 	$(openvpn_srcdir)/buffer.c \
 	$(openvpn_srcdir)/crypto.c \
 	$(openvpn_srcdir)/crypto_mbedtls.c \
 	$(openvpn_srcdir)/crypto_openssl.c \
+	$(openvpn_srcdir)/env_set.c \
 	$(openvpn_srcdir)/otime.c \
 	$(openvpn_srcdir)/packet_id.c \
-	$(openvpn_srcdir)/platform.c
+	$(openvpn_srcdir)/platform.c \
+	$(openvpn_srcdir)/run_command.c
tests/unit_tests/openvpn/test_tls_crypt.c +16 -0
index f53255cd..b7a5359f 100644
@@ -43,8 +43,24 @@
 
 #define TESTBUF_SIZE            128
 
+/* Defines for use in the tests and the mock parse_line() */
+#define PATH1       "/s p a c e"
+#define PATH2       "/foo bar/baz"
+#define PARAM1      "param1"
+#define PARAM2      "param two"
+
 const char plaintext_short[1];
 
+int
+__wrap_parse_line(const char *line, char **p, const int n, const char *file,
+                  const int line_num, int msglevel, struct gc_arena *gc)
+{
+    p[0] = PATH1 PATH2;
+    p[1] = PARAM1;
+    p[2] = PARAM2;
+    return 3;
+}
+
 struct test_tls_crypt_context {
     struct crypto_options co;
     struct key_type kt;
Signature · 1 line
2.17.1
Acked-by on v2 1/9
#44 lore raw

[Openvpn-devel] [PATCH applied] Re: Move file-related functions from misc.c to platform.c

In reply to Steffan Karger

"only moving stuff around (+renaming), reason given makes sense, passes t_client and cmocka tests"

Your patch has been applied to the master branch.

commit b7bea782f3356dbd82613dc8f38fd4ef0bc714ca

Author: Steffan Karger
Date:   Wed Jul 4 19:53:56 2018 +0200
     Move file-related functions from misc.c to platform.c

     Signed-off-by: Steffan Karger <[email protected]...>
     Acked-by: Gert Doering <[email protected]...>
     Message-Id: <[email protected]...>
     URL: https://www.mail-archive.com/[email protected]/msg17208.html
     Signed-off-by: Gert Doering <[email protected]...>

--
kind regards,

Gert Doering

#45 lore raw

PATCH v3 1/7 Introduce buffer_write_file()

In reply to Steffan Karger

Rewrite buf_write_string_file to buffer_write_file, which is simpler to use and can deal with not-null-terminated strings. Mostly implemented so this can be easily reused for tls-crypt-v2 (client) key files.

---
v3: split change out of "generate client key", reuse in write_key_file()

 src/openvpn/buffer.c | 31 ++++++++++++++++++++++++-------
 src/openvpn/buffer.h |  7 ++-----
 src/openvpn/crypto.c | 30 +++++-------------------------
 3 files changed, 31 insertions(+), 37 deletions(-)
src/openvpn/buffer.c +24 -7
index 0972139..20e2b9c 100644
@@ -343,16 +343,33 @@ convert_to_one_line(struct buffer *buf)
     }
 }
 
-/* NOTE: requires that string be null terminated */
-void
-buf_write_string_file(const struct buffer *buf, const char *filename, int fd)
+bool
+buffer_write_file(const char *filename, const struct buffer *buf)
 {
-    const int len = strlen((char *) BPTR(buf));
-    const int size = write(fd, BPTR(buf), len);
-    if (size != len)
+    bool ret = false;
+    int fd = platform_open(filename, O_CREAT | O_TRUNC | O_WRONLY,
+                           S_IRUSR | S_IWUSR);
+    if (fd == -1)
+    {
+        msg(M_ERRNO, "Cannot open file '%s' for write", filename);
+        goto cleanup;
+    }
+
+    const int size = write(fd, BPTR(buf), BLEN(buf));
+    if (size != BLEN(buf))
     {
-        msg(M_ERR, "Write error on file '%s'", filename);
+        msg(M_ERRNO, "Write error on file '%s'", filename);
+        goto cleanup;
+    }
+
+    ret = true;
+cleanup:
+    if (close(fd))
+    {
+        msg(M_ERRNO, "Close error on file %s", filename);
+        ret = false;
     }
+    return ret;
 }
 
 /*
src/openvpn/buffer.h +2 -5
index 6b6025e..180dd56 100644
@@ -469,11 +469,8 @@ const char *skip_leading_whitespace(const char *str);
 
 void string_null_terminate(char *str, int len, int capacity);
 
-/*
- * Write string in buf to file descriptor fd.
- * NOTE: requires that string be null terminated.
- */
-void buf_write_string_file(const struct buffer *buf, const char *filename, int fd);
+/** Write buffer contents to file */
+bool buffer_write_file(const char *filename, const struct buffer *buf);
 
 /*
  * write a string to the end of a buffer that was
src/openvpn/crypto.c +5 -25
index 5381ef2..35bd243 100644
@@ -1427,27 +1427,19 @@ write_key_file(const int nkeys, const char *filename)
 {
     struct gc_arena gc = gc_new();
 
-    int fd, i;
-    int nbits = 0;
+    int nbits = nkeys * sizeof(struct key) * 8;
 
     /* must be large enough to hold full key file */
     struct buffer out = alloc_buf_gc(2048, &gc);
-    struct buffer nbits_head_text = alloc_buf_gc(128, &gc);
 
     /* how to format the ascii file representation of key */
     const int bytes_per_line = 16;
 
-    /* open key file */
-    fd = platform_open(filename, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR);
-
-    if (fd == -1)
-    {
-        msg(M_ERR, "Cannot open shared secret file '%s' for write", filename);
-    }
-
+    /* write header */
+    buf_printf(&out, "#\n# %d bit OpenVPN static key\n#\n", nbits);
     buf_printf(&out, "%s\n", static_key_head);
 
-    for (i = 0; i < nkeys; ++i)
+    for (int i = 0; i < nkeys; ++i)
     {
         struct key key;
         char *fmt;
@@ -1463,9 +1455,6 @@ write_key_file(const int nkeys, const char *filename)
                             "\n",
                             &gc);
 
-        /* increment random bits counter */
-        nbits += sizeof(key) * 8;
-
         /* write to holding buffer */
         buf_printf(&out, "%s\n", fmt);
 
@@ -1476,17 +1465,8 @@ write_key_file(const int nkeys, const char *filename)
 
     buf_printf(&out, "%s\n", static_key_foot);
 
-    /* write number of bits */
-    buf_printf(&nbits_head_text, "#\n# %d bit OpenVPN static key\n#\n", nbits);
-    buf_write_string_file(&nbits_head_text, filename, fd);
-
     /* write key file, now formatted in out, to file */
-    buf_write_string_file(&out, filename, fd);
-
-    if (close(fd))
-    {
-        msg(M_ERR, "Close error on shared secret file %s", filename);
-    }
+    buffer_write_file(filename, &out);
 
     /* zero memory which held file content (memory will be freed by GC) */
     buf_clear(&out);
Signature · 1 line
2.7.4
#46 lore raw

PATCH v3 2/7 tls-crypt-v2: add specification to doc/

This is a preliminary description of tls-crypt-v2. It should give a good impression about the reasoning and design behind tls-crypt-v2, but might need some polishing and updating.

---
v3: Include length in WKc

 doc/tls-crypt-v2.txt | 170 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 170 insertions(+)
 create mode 100644 doc/tls-crypt-v2.txt
doc/tls-crypt-v2.txt new file +170 -0
new file mode 100644
index 0000000..cc6453c
@@ -0,0 +1,170 @@
+Client-specific tls-crypt keys (--tls-crypt-v2)
+===============================================
+
+This document describes the ``--tls-crypt-v2`` option, which enables OpenVPN
+to use client-specific ``--tls-crypt`` keys.
+
+Rationale
+---------
+
+``--tls-auth`` and ``tls-crypt`` use a pre-shared group key, which is shared
+among all clients and servers in an OpenVPN deployment.  If any client or
+server is compromised, the attacker will have access to this shared key, and it
+will no longer provide any security.  To reduce the risk of loosing pre-shared
+keys, ``tls-crypt-v2`` adds the ability to supply each client with a unique
+tls-crypt key.  This allows large organisations and VPN providers to profit
+from the same DoS and TLS stack protection that small deployments can already
+achieve using ``tls-auth`` or ``tls-crypt``.
+
+Also, for ``tls-crypt``, even if all these peers succeed in keeping the key
+secret, the key lifetime is limited to roughly 8000 years, divided by the
+number of clients (see the ``--tls-crypt`` section of the man page).  Using
+client-specific keys, we lift this lifetime requirement to roughly 8000 years
+for each client key (which "Should Be Enough For Everybody (tm)").
+
+
+Introduction
+------------
+
+``tls-crypt-v2`` uses an encrypted cookie mechanism to introduce
+client-specific tls-crypt keys without introducing a lot of server-side state.
+The client-specific key is encrypted using a server key.  The server key is the
+same for all servers in a group.  When a client connects, it first sends the
+encrypted key to the server, such that the server can decrypt the key and all
+messages can thereafter be encrypted using the client-specific key.
+
+A wrapped (encrypted and authenticated) client-specific key can also contain
+metadata.  The metadata is wrapped together with the key, and can be used to
+allow servers to identify clients and/or key validity.  This allows the server
+to abort the connection immediately after receiving the first packet, rather
+than performing an entire TLS handshake.  Aborting the connection this early
+greatly improves the DoS resilience and reduces attack service against
+malicious clients that have the ``tls-crypt`` or ``tls-auth`` key.  This is
+particularly relevant for large deployments (think lost key or disgruntled
+employee) and VPN providers (clients are not trusted).
+
+To allow for a smooth transition, ``tls-crypt-v2`` is designed such that a
+server can enable both ``tls-crypt-v2`` and either ``tls-crypt`` or
+``tls-auth``.  This is achieved by introducing a P_CONTROL_HARD_RESET_CLIENT_V3
+opcode, that indicates that the client wants to use ``tls-crypt-v2`` for the
+current connection.
+
+For an exact specification and more details, read the Implementation section.
+
+
+Implementation
+--------------
+
+When setting up a tls-crypt-v2 group (similar to generating a tls-crypt or
+tls-auth key previously):
+
+1. Generate a tls-crypt-v2 server key using OpenVPN's ``--genkey``.  This key
+   contains 4 512-bit keys, of which we use:
+
+   * the first 256 bits of key 1 as AES-256-CTR encryption key ``Ke``
+   * the first 256 bits of key 2 as HMAC-SHA-256 authentication key ``Ka``
+
+2. Add the tls-crypt-v2 server key to all server configs
+   (``tls-crypt-v2 /path/to/server.key``)
+
+
+When provisioning a client, create a client-specific tls-crypt key:
+
+1. Generate 2048 bits client-specific key ``Kc``
+2. Optionally generate metadata
+3. Create a wrapped client key ``WKc``, using the same nonce-misuse-resistant
+   SIV conruction we use for tls-crypt:
+
+   ``len = len(Kc || metadata)`` (16 bit, network byte order)
+
+   ``T = HMAC-SHA256(Ka, len || Kc || metadata)``
+
+   ``IV = 128 most significant bits of T``
+
+   ``WKc = T || AES-256-CTR(Ke, IV, Kc || metadata || len)``
+
+4. Create a tls-crypt-v2 client key: PEM-encode ``Kc || WKc`` and store in a
+   file, using the header ``-----BEGIN OpenVPN tls-crypt-v2 client key-----``
+   and the footer ``-----END OpenVPN tls-crypt-v2 client key-----``.  (The PEM
+   format is simple, and following PEM allows us to use the crypto lib function
+   for en/decoding.)
+5. Add the tls-crypt-v2 client key to the client config
+   (``tls-crypt-v2 /path/to/client-specific.key``)
+
+
+When setting up the openvpn connection:
+
+1. The client reads the tls-crypt-v2 key from its config, and:
+
+   1. loads ``Kc`` as its tls-crypt key,
+   2. stores ``WKc`` in memory for sending to the server.
+
+2. To start the connection, the client creates a P_CONTROL_HARD_RESET_CLIENT_V3
+   message, wraps it with tls-crypt using ``Kc`` as the key, and appends
+   ``WKc``.  (``WKc`` must not be encrypted, to prevent a chicken-and-egg
+   problem.)
+
+3. The server receives the P_CONTROL_HARD_RESET_CLIENT_V3 message, and
+
+   1. reads the WKc length field from the end of the message, and extracts WKc
+      from the message
+   2. unwraps ``WKc``
+   3. uses unwrapped ``Kc`` to verify the remaining
+      P_CONTROL_HARD_RESET_CLIENT_V3 message's (encryption and) authentication.
+
+   The message is dropped and no error response is sent when either 3.1, 3.2 or
+   3.3 fails (DoS protection).
+
+4. Server optionally checks metadata using a --tls-crypt-v2-verify script
+
+   Metadata could for example contain the users certificate serial, such that
+   the incoming connection can be verified against a CRL, or a notAfter
+   timestamp that limits the key's validity period.
+
+   This allows early abort of connection, *before* we expose any of the
+   notoriously dangerous TLS, X.509 and ASN.1 parsers and thereby reduces the
+   attack surface of the server.
+
+   The metadata is checked *after* the OpenVPN three-way handshake has
+   completed, to prevent DoS attacks.  (That is, once the client has proved to
+   the server that it possesses Kc, by authenticating a packet that contains the
+   session ID picked by the server.)
+
+   RFC: should the server send a 'key rejected' message if the key is e.g.
+   revoked or expired?  That allows for better client-side error reporting, but
+   also reduces the DoS resilience.
+
+6. Client and server use ``Kc`` for (un)wrapping any following control channel
+   messages.
+
+
+Considerations
+--------------
+
+To allow for a smooth transition, the server implementation allows
+``tls-crypt`` or ``tls-auth`` to be used simultaneously with ``tls-crypt-v2``.
+This specification does not allow simultaneously using ``tls-crypt-v2`` and
+connections without any control channel wrapping, because that would break DoS
+resilience.
+
+WKc includes a length field, so we leave the option for future extension of the
+P_CONTROL_HEAD_RESET_CLIENT_V3 message open.  (E.g. add payload to the reset to
+indicate low-level protocol features.)
+
+``tls-crypt-v2`` uses fixed crypto algorithms, because:
+
+ * The crypto is used before we can do any negotiation, so the algorithms have
+   to be predefined.
+ * The crypto primitives are chosen conservatively, making problems with these
+   primitives unlikely.
+ * Making anything configurable adds complexity, both in implementation and
+   usage.  We should not add anymore complexity than is absolutely necessary.
+
+Potential ``tls-crypt-v2`` risks:
+
+ * Slightly more work on first connection (``WKc`` unwrap + hard reset unwrap)
+   than with ``tls-crypt`` (hard reset unwrap) or ``tls-auth`` (hard reset auth).
+ * Flexible metadata allow mistakes
+   (So we should make it easy to do it right.  Provide tooling to create client
+   keys based on cert serial + CA fingerprint, provide script that uses CRL (if
+   available) to drop revoked keys.)
Signature · 1 line
2.7.4
#47 lore raw

Re: [Openvpn-devel] [PATCH v3 2/7] tls-crypt-v2: add specification to doc/

Hi,

Quoted 118 lines · Steffan Karger

This is a preliminary description of tls-crypt-v2. It should give a good impression about the reasoning and design behind tls-crypt-v2, but might need some polishing and updating.

---
v3: Include length in WKc

 doc/tls-crypt-v2.txt | 170 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 170 insertions(+)
 create mode 100644 doc/tls-crypt-v2.txt
doc/tls-crypt-v2.txt new file +100 -0
new file mode 100644
index 0000000..cc6453c
@@ -0,0 +1,170 @@
+Client-specific tls-crypt keys (--tls-crypt-v2)
+===============================================
+
+This document describes the ``--tls-crypt-v2`` option, which enables OpenVPN
+to use client-specific ``--tls-crypt`` keys.
+
+Rationale
+---------
+
+``--tls-auth`` and ``tls-crypt`` use a pre-shared group key, which is shared
+among all clients and servers in an OpenVPN deployment.  If any client or
+server is compromised, the attacker will have access to this shared key, and it
+will no longer provide any security.  To reduce the risk of loosing pre-shared
+keys, ``tls-crypt-v2`` adds the ability to supply each client with a unique
+tls-crypt key.  This allows large organisations and VPN providers to profit
+from the same DoS and TLS stack protection that small deployments can already
+achieve using ``tls-auth`` or ``tls-crypt``.
+
+Also, for ``tls-crypt``, even if all these peers succeed in keeping the key
+secret, the key lifetime is limited to roughly 8000 years, divided by the
+number of clients (see the ``--tls-crypt`` section of the man page).  Using
+client-specific keys, we lift this lifetime requirement to roughly 8000 years
+for each client key (which "Should Be Enough For Everybody (tm)").
+
+
+Introduction
+------------
+
+``tls-crypt-v2`` uses an encrypted cookie mechanism to introduce
+client-specific tls-crypt keys without introducing a lot of server-side state.
+The client-specific key is encrypted using a server key.  The server key is the
+same for all servers in a group.  When a client connects, it first sends the
+encrypted key to the server, such that the server can decrypt the key and all
+messages can thereafter be encrypted using the client-specific key.
+
+A wrapped (encrypted and authenticated) client-specific key can also contain
+metadata.  The metadata is wrapped together with the key, and can be used to
+allow servers to identify clients and/or key validity.  This allows the server
+to abort the connection immediately after receiving the first packet, rather
+than performing an entire TLS handshake.  Aborting the connection this early
+greatly improves the DoS resilience and reduces attack service against
+malicious clients that have the ``tls-crypt`` or ``tls-auth`` key.  This is
+particularly relevant for large deployments (think lost key or disgruntled
+employee) and VPN providers (clients are not trusted).
+
+To allow for a smooth transition, ``tls-crypt-v2`` is designed such that a
+server can enable both ``tls-crypt-v2`` and either ``tls-crypt`` or
+``tls-auth``.  This is achieved by introducing a P_CONTROL_HARD_RESET_CLIENT_V3
+opcode, that indicates that the client wants to use ``tls-crypt-v2`` for the
+current connection.
+
+For an exact specification and more details, read the Implementation section.
+
+
+Implementation
+--------------
+
+When setting up a tls-crypt-v2 group (similar to generating a tls-crypt or
+tls-auth key previously):
+
+1. Generate a tls-crypt-v2 server key using OpenVPN's ``--genkey``.  This key
+   contains 4 512-bit keys, of which we use:
+
+   * the first 256 bits of key 1 as AES-256-CTR encryption key ``Ke``
+   * the first 256 bits of key 2 as HMAC-SHA-256 authentication key ``Ka``
+
+2. Add the tls-crypt-v2 server key to all server configs
+   (``tls-crypt-v2 /path/to/server.key``)
+
+
+When provisioning a client, create a client-specific tls-crypt key:
+
+1. Generate 2048 bits client-specific key ``Kc``
+2. Optionally generate metadata
+3. Create a wrapped client key ``WKc``, using the same nonce-misuse-resistant
+   SIV conruction we use for tls-crypt:
+
+   ``len = len(Kc || metadata)`` (16 bit, network byte order)
+
+   ``T = HMAC-SHA256(Ka, len || Kc || metadata)``
+
+   ``IV = 128 most significant bits of T``
+
+   ``WKc = T || AES-256-CTR(Ke, IV, Kc || metadata || len)``
+
+4. Create a tls-crypt-v2 client key: PEM-encode ``Kc || WKc`` and store in a
+   file, using the header ``-----BEGIN OpenVPN tls-crypt-v2 client key-----``
+   and the footer ``-----END OpenVPN tls-crypt-v2 client key-----``.  (The PEM
+   format is simple, and following PEM allows us to use the crypto lib function
+   for en/decoding.)
+5. Add the tls-crypt-v2 client key to the client config
+   (``tls-crypt-v2 /path/to/client-specific.key``)
+
+
+When setting up the openvpn connection:
+
+1. The client reads the tls-crypt-v2 key from its config, and:
+
+   1. loads ``Kc`` as its tls-crypt key,
+   2. stores ``WKc`` in memory for sending to the server.

doc/tls-crypt-v2.txt line 110

Quoted 10 lines
doc/tls-crypt-v2.txt new file +10 -0
+
+2. To start the connection, the client creates a P_CONTROL_HARD_RESET_CLIENT_V3
+   message, wraps it with tls-crypt using ``Kc`` as the key, and appends
+   ``WKc``.  (``WKc`` must not be encrypted, to prevent a chicken-and-egg
+   problem.)
+
+3. The server receives the P_CONTROL_HARD_RESET_CLIENT_V3 message, and
+
+   1. reads the WKc length field from the end of the message, and extracts WKc
+      from the message

I think this is not possible until after point 2, because the length is encrypted inside WKc, so we must unwrap the latter first, no?

However, if we append something new, how do we know before hand how long WKc is? Maybe you intended to "len" right after T? so that it could immediately be retrieved before doing any unwrapping?

However, it feels to me like we are slowly moving towards a TLV (type-length-value) approach, with the type implicitly represented by the order of the additional payloads.

Although I like this approach (as it saves packet type IDs and attempts to keep format backwards compatible) I have the feeling this adds useless complexity to part of the code which is currently meant to be DoS resistant.

Do you think we really need this potential (since we currently don't use the length) complexity? Or should we rather keep it as simple as possible until we have any real plan to introduce something more complex? (and then we can easily add another RESET_Vx with this feature)

doc/tls-crypt-v2.txt line 122

Quoted 12 lines
doc/tls-crypt-v2.txt new file +12 -0
+   2. unwraps ``WKc``
+   3. uses unwrapped ``Kc`` to verify the remaining
+      P_CONTROL_HARD_RESET_CLIENT_V3 message's (encryption and) authentication.
+
+   The message is dropped and no error response is sent when either 3.1, 3.2 or
+   3.3 fails (DoS protection).
+
+4. Server optionally checks metadata using a --tls-crypt-v2-verify script
+
+   Metadata could for example contain the users certificate serial, such that
+   the incoming connection can be verified against a CRL, or a notAfter
+   timestamp that limits the key's validity period.

Am I wrong or at some point we agreed on having some "format" for the metadata container?
This way openvpn could decide if the payload is something to forward to the verify script or if it is something to be handled internally? Or did we drop this mechanism (can't remember right now)?

doc/tls-crypt-v2.txt line 135

Quoted 13 lines
doc/tls-crypt-v2.txt new file +13 -0
+
+   This allows early abort of connection, *before* we expose any of the
+   notoriously dangerous TLS, X.509 and ASN.1 parsers and thereby reduces the
+   attack surface of the server.
+
+   The metadata is checked *after* the OpenVPN three-way handshake has
+   completed, to prevent DoS attacks.  (That is, once the client has proved to
+   the server that it possesses Kc, by authenticating a packet that contains the
+   session ID picked by the server.)
+
+   RFC: should the server send a 'key rejected' message if the key is e.g.
+   revoked or expired?  That allows for better client-side error reporting, but
+   also reduces the DoS resilience.

I am "pro silence" for two reasons:
1) reuce attack surface
2) hide OpenVPN as much as possible - it should not reply anything in case of potential malicious connection.

Quoted 25 lines
doc/tls-crypt-v2.txt new file +25 -0
+
+6. Client and server use ``Kc`` for (un)wrapping any following control channel
+   messages.
+
+
+Considerations
+--------------
+
+To allow for a smooth transition, the server implementation allows
+``tls-crypt`` or ``tls-auth`` to be used simultaneously with ``tls-crypt-v2``.
+This specification does not allow simultaneously using ``tls-crypt-v2`` and
+connections without any control channel wrapping, because that would break DoS
+resilience.
+
+WKc includes a length field, so we leave the option for future extension of the
+P_CONTROL_HEAD_RESET_CLIENT_V3 message open.  (E.g. add payload to the reset to
+indicate low-level protocol features.)
+
+``tls-crypt-v2`` uses fixed crypto algorithms, because:
+
+ * The crypto is used before we can do any negotiation, so the algorithms have
+   to be predefined.
+ * The crypto primitives are chosen conservatively, making problems with these
+   primitives unlikely.
+ * Making anything configurable adds complexity, both in implementation and

doc/tls-crypt-v2.txt line 170

Quoted 10 lines
doc/tls-crypt-v2.txt new file +10 -0
+   usage.  We should not add anymore complexity than is absolutely necessary.
+
+Potential ``tls-crypt-v2`` risks:
+
+ * Slightly more work on first connection (``WKc`` unwrap + hard reset unwrap)
+   than with ``tls-crypt`` (hard reset unwrap) or ``tls-auth`` (hard reset auth).
+ * Flexible metadata allow mistakes
+   (So we should make it easy to do it right.  Provide tooling to create client
+   keys based on cert serial + CA fingerprint, provide script that uses CRL (if
+   available) to drop revoked keys.)

just a thought: how do we handle the process running the verify script? Might users "bomb" with connections to keep openvpn busy spawning processes and running them?

Cheers,

Quoted 1 line
Signature · 1 line
Antonio Quartulli
#48 lore raw

Re: [Openvpn-devel] [PATCH v3 2/7] tls-crypt-v2: add specification to doc/

Hi,

Quoted 133 lines · Antonio Quartulli
Quoted 118 lines · Steffan Karger

This is a preliminary description of tls-crypt-v2. It should give a good impression about the reasoning and design behind tls-crypt-v2, but might need some polishing and updating.

---
v3: Include length in WKc

 doc/tls-crypt-v2.txt | 170 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 170 insertions(+)
 create mode 100644 doc/tls-crypt-v2.txt
doc/tls-crypt-v2.txt new file +100 -0
new file mode 100644
index 0000000..cc6453c
@@ -0,0 +1,170 @@
+Client-specific tls-crypt keys (--tls-crypt-v2)
+===============================================
+
+This document describes the ``--tls-crypt-v2`` option, which enables OpenVPN
+to use client-specific ``--tls-crypt`` keys.
+
+Rationale
+---------
+
+``--tls-auth`` and ``tls-crypt`` use a pre-shared group key, which is shared
+among all clients and servers in an OpenVPN deployment.  If any client or
+server is compromised, the attacker will have access to this shared key, and it
+will no longer provide any security.  To reduce the risk of loosing pre-shared
+keys, ``tls-crypt-v2`` adds the ability to supply each client with a unique
+tls-crypt key.  This allows large organisations and VPN providers to profit
+from the same DoS and TLS stack protection that small deployments can already
+achieve using ``tls-auth`` or ``tls-crypt``.
+
+Also, for ``tls-crypt``, even if all these peers succeed in keeping the key
+secret, the key lifetime is limited to roughly 8000 years, divided by the
+number of clients (see the ``--tls-crypt`` section of the man page).  Using
+client-specific keys, we lift this lifetime requirement to roughly 8000 years
+for each client key (which "Should Be Enough For Everybody (tm)").
+
+
+Introduction
+------------
+
+``tls-crypt-v2`` uses an encrypted cookie mechanism to introduce
+client-specific tls-crypt keys without introducing a lot of server-side state.
+The client-specific key is encrypted using a server key.  The server key is the
+same for all servers in a group.  When a client connects, it first sends the
+encrypted key to the server, such that the server can decrypt the key and all
+messages can thereafter be encrypted using the client-specific key.
+
+A wrapped (encrypted and authenticated) client-specific key can also contain
+metadata.  The metadata is wrapped together with the key, and can be used to
+allow servers to identify clients and/or key validity.  This allows the server
+to abort the connection immediately after receiving the first packet, rather
+than performing an entire TLS handshake.  Aborting the connection this early
+greatly improves the DoS resilience and reduces attack service against
+malicious clients that have the ``tls-crypt`` or ``tls-auth`` key.  This is
+particularly relevant for large deployments (think lost key or disgruntled
+employee) and VPN providers (clients are not trusted).
+
+To allow for a smooth transition, ``tls-crypt-v2`` is designed such that a
+server can enable both ``tls-crypt-v2`` and either ``tls-crypt`` or
+``tls-auth``.  This is achieved by introducing a P_CONTROL_HARD_RESET_CLIENT_V3
+opcode, that indicates that the client wants to use ``tls-crypt-v2`` for the
+current connection.
+
+For an exact specification and more details, read the Implementation section.
+
+
+Implementation
+--------------
+
+When setting up a tls-crypt-v2 group (similar to generating a tls-crypt or
+tls-auth key previously):
+
+1. Generate a tls-crypt-v2 server key using OpenVPN's ``--genkey``.  This key
+   contains 4 512-bit keys, of which we use:
+
+   * the first 256 bits of key 1 as AES-256-CTR encryption key ``Ke``
+   * the first 256 bits of key 2 as HMAC-SHA-256 authentication key ``Ka``
+
+2. Add the tls-crypt-v2 server key to all server configs
+   (``tls-crypt-v2 /path/to/server.key``)
+
+
+When provisioning a client, create a client-specific tls-crypt key:
+
+1. Generate 2048 bits client-specific key ``Kc``
+2. Optionally generate metadata
+3. Create a wrapped client key ``WKc``, using the same nonce-misuse-resistant
+   SIV conruction we use for tls-crypt:
+
+   ``len = len(Kc || metadata)`` (16 bit, network byte order)
+
+   ``T = HMAC-SHA256(Ka, len || Kc || metadata)``
+
+   ``IV = 128 most significant bits of T``
+
+   ``WKc = T || AES-256-CTR(Ke, IV, Kc || metadata || len)``
+
+4. Create a tls-crypt-v2 client key: PEM-encode ``Kc || WKc`` and store in a
+   file, using the header ``-----BEGIN OpenVPN tls-crypt-v2 client key-----``
+   and the footer ``-----END OpenVPN tls-crypt-v2 client key-----``.  (The PEM
+   format is simple, and following PEM allows us to use the crypto lib function
+   for en/decoding.)
+5. Add the tls-crypt-v2 client key to the client config
+   (``tls-crypt-v2 /path/to/client-specific.key``)
+
+
+When setting up the openvpn connection:
+
+1. The client reads the tls-crypt-v2 key from its config, and:
+
+   1. loads ``Kc`` as its tls-crypt key,
+   2. stores ``WKc`` in memory for sending to the server.

doc/tls-crypt-v2.txt line 110

Quoted 10 lines
doc/tls-crypt-v2.txt new file +10 -0
+
+2. To start the connection, the client creates a P_CONTROL_HARD_RESET_CLIENT_V3
+   message, wraps it with tls-crypt using ``Kc`` as the key, and appends
+   ``WKc``.  (``WKc`` must not be encrypted, to prevent a chicken-and-egg
+   problem.)
+
+3. The server receives the P_CONTROL_HARD_RESET_CLIENT_V3 message, and
+
+   1. reads the WKc length field from the end of the message, and extracts WKc
+      from the message

I think this is not possible until after point 2, because the length is encrypted inside WKc, so we must unwrap the latter first, no?

Quoted 3 lines

However, if we append something new, how do we know before hand how long WKc is? Maybe you intended to "len" right after T? so that it could immediately be retrieved before doing any unwrapping?

Hrmpf, len should not have been inside the encrypted blob. The line above should have read:

   ``WKc = T || AES-256-CTR(Ke, IV, Kc || metadata) || len``
Quoted 9 lines

However, it feels to me like we are slowly moving towards a TLV (type-length-value) approach, with the type implicitly represented by the order of the additional payloads.

Although I like this approach (as it saves packet type IDs and attempts to keep format backwards compatible) I have the feeling this adds useless complexity to part of the code which is currently meant to be DoS resistant.

Quoted 4 lines

Do you think we really need this potential (since we currently don't use the length) complexity? Or should we rather keep it as simple as possible until we have any real plan to introduce something more complex? (and then we can easily add another RESET_Vx with this feature)

The trick here is that we have "a v3 reset packet", which might be just what it is now or any new format we come up with, concatenated with a WKc. By including the length of WKc as plaintext at the end of WKc itself, we can easily read it from the end and truncate the data to end up with just the packet. This allows us to postpone the discussion about protocol extension of the packets themselves to when we actually need that.

And yes, I think we are going to need this to be able to improve the control channel performance at some point without a hard protocol switch. Also, I think that we'll need to do that to cope with heavier (e.g. post-quantum) key exchanges or larger configurations.

Adding another RESET_Vx to achieve this makes it harder to make the protocol upgrade transparent to the user, because older clients will simply not reply to the new opcodes. If we extend the payload packet otoh, older clients (at least openvpn2, don't know what v3 does) will just ignore the payload. We can turn that into a handshake by having newer servers reply with payload in the reset response, and use that to determine which protocol extensions the server support. But as said before, just including the length gives to the time to think this through.

All in all, I think adding the length to WKc is such a simple addition that it is easily justified by the flexibility we get in return.

Quoted 10 lines
Quoted 10 lines
doc/tls-crypt-v2.txt new file +10 -0
+   2. unwraps ``WKc``
+   3. uses unwrapped ``Kc`` to verify the remaining
+      P_CONTROL_HARD_RESET_CLIENT_V3 message's (encryption and) authentication.
+
+   The message is dropped and no error response is sent when either 3.1, 3.2 or
+   3.3 fails (DoS protection).
+
+4. Server optionally checks metadata using a --tls-crypt-v2-verify script
+
+   Metadata could for example contain the users certificate serial, such that
Quoted 8 lines

doc/tls-crypt-v2.txt line 122

Quoted 2 lines
doc/tls-crypt-v2.txt new file +2 -0
+   the incoming connection can be verified against a CRL, or a notAfter
+   timestamp that limits the key's validity period.

Am I wrong or at some point we agreed on having some "format" for the metadata container?
This way openvpn could decide if the payload is something to forward to the verify script or if it is something to be handled internally? Or did we drop this mechanism (can't remember right now)?

Yes and no. We did agree on introducing the metadata type, and the implementation does that. If no metadata is supplied when generation the key, the current implementation defaults to adding a key generation timestamp.

For verification however, we don't act on the type. We just pass the type to the external script through the metadata_type env var. This was done to not pull in too much extra work, as it allows us to later implement internal handling if we want to.

But, none of this was documented properly here, so I'll update the doc.

Quoted 10 lines
Quoted 10 lines
doc/tls-crypt-v2.txt new file +10 -0
+
+   This allows early abort of connection, *before* we expose any of the
+   notoriously dangerous TLS, X.509 and ASN.1 parsers and thereby reduces the
+   attack surface of the server.
+
+   The metadata is checked *after* the OpenVPN three-way handshake has
+   completed, to prevent DoS attacks.  (That is, once the client has proved to
+   the server that it possesses Kc, by authenticating a packet that contains the
+   session ID picked by the server.)
+
Quoted 8 lines

doc/tls-crypt-v2.txt line 135

Quoted 3 lines
doc/tls-crypt-v2.txt new file +3 -0
+   RFC: should the server send a 'key rejected' message if the key is e.g.
+   revoked or expired?  That allows for better client-side error reporting, but
+   also reduces the DoS resilience.

I am "pro silence" for two reasons:
1) reuce attack surface
2) hide OpenVPN as much as possible - it should not reply anything in case of potential malicious connection.

Me too - let's go that way. I'll update the doc.

Quoted 31 lines
Quoted 31 lines
doc/tls-crypt-v2.txt new file +31 -0
+
+6. Client and server use ``Kc`` for (un)wrapping any following control channel
+   messages.
+
+
+Considerations
+--------------
+
+To allow for a smooth transition, the server implementation allows
+``tls-crypt`` or ``tls-auth`` to be used simultaneously with ``tls-crypt-v2``.
+This specification does not allow simultaneously using ``tls-crypt-v2`` and
+connections without any control channel wrapping, because that would break DoS
+resilience.
+
+WKc includes a length field, so we leave the option for future extension of the
+P_CONTROL_HEAD_RESET_CLIENT_V3 message open.  (E.g. add payload to the reset to
+indicate low-level protocol features.)
+
+``tls-crypt-v2`` uses fixed crypto algorithms, because:
+
+ * The crypto is used before we can do any negotiation, so the algorithms have
+   to be predefined.
+ * The crypto primitives are chosen conservatively, making problems with these
+   primitives unlikely.
+ * Making anything configurable adds complexity, both in implementation and
+   usage.  We should not add anymore complexity than is absolutely necessary.
+
+Potential ``tls-crypt-v2`` risks:
+
+ * Slightly more work on first connection (``WKc`` unwrap + hard reset unwrap)
+   than with ``tls-crypt`` (hard reset unwrap) or ``tls-auth`` (hard reset auth).
Quoted 8 lines

doc/tls-crypt-v2.txt line 170

Quoted 4 lines
doc/tls-crypt-v2.txt new file +4 -0
+ * Flexible metadata allow mistakes
+   (So we should make it easy to do it right.  Provide tooling to create client
+   keys based on cert serial + CA fingerprint, provide script that uses CRL (if
+   available) to drop revoked keys.)

just a thought: how do we handle the process running the verify script? Might users "bomb" with connections to keep openvpn busy spawning processes and running them?

Only users with a valid (both plain text and wrapped) tls-crypt-v2 key can do that, because they need to complete the three-way handshake. That's why metadata verification is done only after the handshake (proof-of-possession) completes and not immediately after receiving (and unwrapping) WKc. There's not much more we can do without introducing even more crypto tricks (like, proof-of-work or so). Still, it should be a better than doing an entire TLS handshake (including all the attack surface there) :-)

Thanks!
-Steffan

#49 lore raw

PATCH v4 1/7 Introduce buffer_write_file()

Rewrite buf_write_string_file to buffer_write_file, which is simpler to use and can deal with not-null-terminated strings. Mostly implemented so this can be easily reused for tls-crypt-v2 (client) key files.

---
v3: split change out of "generate client key", reuse in write_key_file()
v4: improve doxygen and error handling

 src/openvpn/buffer.c | 31 ++++++++++++++++++++++++-------
 src/openvpn/buffer.h | 12 ++++++++----
 src/openvpn/crypto.c | 33 ++++++---------------------------
 src/openvpn/crypto.h |  5 +++++
 src/openvpn/init.c   |  4 ++++
 5 files changed, 47 insertions(+), 38 deletions(-)
src/openvpn/buffer.c +24 -7
index 0972139..7f540d7 100644
@@ -343,16 +343,33 @@ convert_to_one_line(struct buffer *buf)
     }
 }
 
-/* NOTE: requires that string be null terminated */
-void
-buf_write_string_file(const struct buffer *buf, const char *filename, int fd)
+bool
+buffer_write_file(const char *filename, const struct buffer *buf)
 {
-    const int len = strlen((char *) BPTR(buf));
-    const int size = write(fd, BPTR(buf), len);
-    if (size != len)
+    bool ret = false;
+    int fd = platform_open(filename, O_CREAT | O_TRUNC | O_WRONLY,
+                           S_IRUSR | S_IWUSR);
+    if (fd == -1)
+    {
+        msg(M_ERRNO, "Cannot open file '%s' for write", filename);
+        goto cleanup;
+    }
+
+    const int size = write(fd, BPTR(buf), BLEN(buf));
+    if (size != BLEN(buf))
     {
-        msg(M_ERR, "Write error on file '%s'", filename);
+        msg(M_ERRNO, "Write error on file '%s'", filename);
+        goto cleanup;
+    }
+
+    ret = true;
+cleanup:
+    if (close(fd) < 0)
+    {
+        msg(M_ERRNO, "Close error on file %s", filename);
+        ret = false;
     }
+    return ret;
 }
 
 /*
src/openvpn/buffer.h +8 -4
index 6b6025e..704129a 100644
@@ -469,11 +469,15 @@ const char *skip_leading_whitespace(const char *str);
 
 void string_null_terminate(char *str, int len, int capacity);
 
-/*
- * Write string in buf to file descriptor fd.
- * NOTE: requires that string be null terminated.
+/**
+ * Write buffer contents to file.
+ *
+ * @param filename  The filename to write the buffer to.
+ * @param buf       Thu buffer to write to the file.
+ *
+ * @return true on success, false otherwise.
  */
-void buf_write_string_file(const struct buffer *buf, const char *filename, int fd);
+bool buffer_write_file(const char *filename, const struct buffer *buf);
 
 /*
  * write a string to the end of a buffer that was
src/openvpn/crypto.c +6 -27
index 5381ef2..c8d95f3 100644
@@ -1418,36 +1418,24 @@ read_key_file(struct key2 *key2, const char *file, const unsigned int flags)
     gc_free(&gc);
 }
 
-/*
- * Write key to file, return number of random bits
- * written.
- */
 int
 write_key_file(const int nkeys, const char *filename)
 {
     struct gc_arena gc = gc_new();
 
-    int fd, i;
-    int nbits = 0;
+    int nbits = nkeys * sizeof(struct key) * 8;
 
     /* must be large enough to hold full key file */
     struct buffer out = alloc_buf_gc(2048, &gc);
-    struct buffer nbits_head_text = alloc_buf_gc(128, &gc);
 
     /* how to format the ascii file representation of key */
     const int bytes_per_line = 16;
 
-    /* open key file */
-    fd = platform_open(filename, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR);
-
-    if (fd == -1)
-    {
-        msg(M_ERR, "Cannot open shared secret file '%s' for write", filename);
-    }
-
+    /* write header */
+    buf_printf(&out, "#\n# %d bit OpenVPN static key\n#\n", nbits);
     buf_printf(&out, "%s\n", static_key_head);
 
-    for (i = 0; i < nkeys; ++i)
+    for (int i = 0; i < nkeys; ++i)
     {
         struct key key;
         char *fmt;
@@ -1463,9 +1451,6 @@ write_key_file(const int nkeys, const char *filename)
                             "\n",
                             &gc);
 
-        /* increment random bits counter */
-        nbits += sizeof(key) * 8;
-
         /* write to holding buffer */
         buf_printf(&out, "%s\n", fmt);
 
@@ -1476,16 +1461,10 @@ write_key_file(const int nkeys, const char *filename)
 
     buf_printf(&out, "%s\n", static_key_foot);
 
-    /* write number of bits */
-    buf_printf(&nbits_head_text, "#\n# %d bit OpenVPN static key\n#\n", nbits);
-    buf_write_string_file(&nbits_head_text, filename, fd);
-
     /* write key file, now formatted in out, to file */
-    buf_write_string_file(&out, filename, fd);
-
-    if (close(fd))
+    if(!buffer_write_file(filename, &out))
     {
-        msg(M_ERR, "Close error on shared secret file %s", filename);
+        nbits = -1;
     }
 
     /* zero memory which held file content (memory will be freed by GC) */
src/openvpn/crypto.h +5 -0
index 9417fa1..0dc597f 100644
@@ -271,6 +271,11 @@ struct crypto_options
 #define RKF_INLINE       (1<<1)
 void read_key_file(struct key2 *key2, const char *file, const unsigned int flags);
 
+/**
+ * Write nkeys 1024-bits keys to file.
+ *
+ * @returns number of random bits written, or -1 on failure.
+ */
 int write_key_file(const int nkeys, const char *filename);
 
 int read_passphrase_hash(const char *passphrase_file,
src/openvpn/init.c +4 -0
index f432106..2933d55 100644
@@ -1041,6 +1041,10 @@ do_genkey(const struct options *options)
         }
 
         nbits_written = write_key_file(2, options->shared_secret_file);
+        if (nbits_written < 0)
+        {
+            msg(M_FATAL, "Failed to write key file");
+        }
 
         msg(D_GENKEY | M_NOPREFIX,
             "Randomly generated %d bit key written to %s", nbits_written,
Signature · 1 line
2.7.4
#50 lore raw

Re: [Openvpn-devel] [PATCH v4 1/7] Introduce buffer_write_file()

Hi,

Quoted 30 lines · Steffan Karger

Rewrite buf_write_string_file to buffer_write_file, which is simpler to use and can deal with not-null-terminated strings. Mostly implemented so this can be easily reused for tls-crypt-v2 (client) key files.

---
v3: split change out of "generate client key", reuse in write_key_file()
v4: improve doxygen and error handling

 src/openvpn/buffer.c | 31 ++++++++++++++++++++++++-------
 src/openvpn/buffer.h | 12 ++++++++----
 src/openvpn/crypto.c | 33 ++++++---------------------------
 src/openvpn/crypto.h |  5 +++++
 src/openvpn/init.c   |  4 ++++
 5 files changed, 47 insertions(+), 38 deletions(-)
src/openvpn/buffer.c +2 -3
index 0972139..7f540d7 100644
@@ -343,16 +343,33 @@ convert_to_one_line(struct buffer *buf)
     }
 }
 
-/* NOTE: requires that string be null terminated */
-void
-buf_write_string_file(const struct buffer *buf, const char *filename, int fd)
+bool
+buffer_write_file(const char *filename, const struct buffer *buf)
 {

src/openvpn/buffer.c convert_to_one_line(struct buffer *buf) · line 355

Quoted 10 lines
src/openvpn/buffer.c +7 -3
-    const int len = strlen((char *) BPTR(buf));
-    const int size = write(fd, BPTR(buf), len);
-    if (size != len)
+    bool ret = false;
+    int fd = platform_open(filename, O_CREAT | O_TRUNC | O_WRONLY,
+                           S_IRUSR | S_IWUSR);
+    if (fd == -1)
+    {
+        msg(M_ERRNO, "Cannot open file '%s' for write", filename);
+        goto cleanup;

While running some tests I realized that a failure in open() will cause a double error because close() is performed with fd = -1. The result is:

$ ./src/openvpn/openvpn --genkey --secret /root/test.key
Fri Aug  3 17:35:46 2018 Cannot open file '/root/test.key' for write:
Permission denied (errno=13)
Fri Aug  3 17:35:46 2018 Close error on file /root/test.key: Bad file
descriptor (errno=9)
Fri Aug  3 17:35:46 2018 Failed to write key file
Fri Aug  3 17:35:46 2018 Exiting due to fatal error

As you can see we have two distinct errors, with the second being caused by the first one.

I think this is one of those cases where in case of failure you just return false rather than jumping to cleanup, because nothing has been initialized yet.

The double error is harmless, but not very clean, especially because, no matter why open() failed, errno will always be changed to EBADF.

(I think this also means that you can get rid of the label at all and slightly restyle the code)

Cheers,

Signature · 1 line
Antonio Quartulli
#51 lore raw

PATCH v5 1/7 Introduce buffer_write_file()

Rewrite buf_write_string_file to buffer_write_file, which is simpler to use and can deal with not-null-terminated strings. Mostly implemented so this can be easily reused for tls-crypt-v2 (client) key files.

---
v3: split change out of "generate client key", reuse in write_key_file()
v4: improve doxygen and error handling (thanks to ordex)
v5: do not print close error if open fails (thanks to ordex)

 src/openvpn/buffer.c | 31 ++++++++++++++++++++++++-------
 src/openvpn/buffer.h | 12 ++++++++----
 src/openvpn/crypto.c | 33 ++++++---------------------------
 src/openvpn/crypto.h |  5 +++++
 src/openvpn/init.c   |  4 ++++
 5 files changed, 47 insertions(+), 38 deletions(-)
src/openvpn/buffer.c +24 -7
index 0972139..7630ff7 100644
@@ -343,16 +343,33 @@ convert_to_one_line(struct buffer *buf)
     }
 }
 
-/* NOTE: requires that string be null terminated */
-void
-buf_write_string_file(const struct buffer *buf, const char *filename, int fd)
+bool
+buffer_write_file(const char *filename, const struct buffer *buf)
 {
-    const int len = strlen((char *) BPTR(buf));
-    const int size = write(fd, BPTR(buf), len);
-    if (size != len)
+    bool ret = false;
+    int fd = platform_open(filename, O_CREAT | O_TRUNC | O_WRONLY,
+                           S_IRUSR | S_IWUSR);
+    if (fd == -1)
+    {
+        msg(M_ERRNO, "Cannot open file '%s' for write", filename);
+        return false;
+    }
+
+    const int size = write(fd, BPTR(buf), BLEN(buf));
+    if (size != BLEN(buf))
     {
-        msg(M_ERR, "Write error on file '%s'", filename);
+        msg(M_ERRNO, "Write error on file '%s'", filename);
+        goto cleanup;
+    }
+
+    ret = true;
+cleanup:
+    if (close(fd) < 0)
+    {
+        msg(M_ERRNO, "Close error on file %s", filename);
+        ret = false;
     }
+    return ret;
 }
 
 /*
src/openvpn/buffer.h +8 -4
index 6b6025e..704129a 100644
@@ -469,11 +469,15 @@ const char *skip_leading_whitespace(const char *str);
 
 void string_null_terminate(char *str, int len, int capacity);
 
-/*
- * Write string in buf to file descriptor fd.
- * NOTE: requires that string be null terminated.
+/**
+ * Write buffer contents to file.
+ *
+ * @param filename  The filename to write the buffer to.
+ * @param buf       Thu buffer to write to the file.
+ *
+ * @return true on success, false otherwise.
  */
-void buf_write_string_file(const struct buffer *buf, const char *filename, int fd);
+bool buffer_write_file(const char *filename, const struct buffer *buf);
 
 /*
  * write a string to the end of a buffer that was
src/openvpn/crypto.c +6 -27
index 5381ef2..c8d95f3 100644
@@ -1418,36 +1418,24 @@ read_key_file(struct key2 *key2, const char *file, const unsigned int flags)
     gc_free(&gc);
 }
 
-/*
- * Write key to file, return number of random bits
- * written.
- */
 int
 write_key_file(const int nkeys, const char *filename)
 {
     struct gc_arena gc = gc_new();
 
-    int fd, i;
-    int nbits = 0;
+    int nbits = nkeys * sizeof(struct key) * 8;
 
     /* must be large enough to hold full key file */
     struct buffer out = alloc_buf_gc(2048, &gc);
-    struct buffer nbits_head_text = alloc_buf_gc(128, &gc);
 
     /* how to format the ascii file representation of key */
     const int bytes_per_line = 16;
 
-    /* open key file */
-    fd = platform_open(filename, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR);
-
-    if (fd == -1)
-    {
-        msg(M_ERR, "Cannot open shared secret file '%s' for write", filename);
-    }
-
+    /* write header */
+    buf_printf(&out, "#\n# %d bit OpenVPN static key\n#\n", nbits);
     buf_printf(&out, "%s\n", static_key_head);
 
-    for (i = 0; i < nkeys; ++i)
+    for (int i = 0; i < nkeys; ++i)
     {
         struct key key;
         char *fmt;
@@ -1463,9 +1451,6 @@ write_key_file(const int nkeys, const char *filename)
                             "\n",
                             &gc);
 
-        /* increment random bits counter */
-        nbits += sizeof(key) * 8;
-
         /* write to holding buffer */
         buf_printf(&out, "%s\n", fmt);
 
@@ -1476,16 +1461,10 @@ write_key_file(const int nkeys, const char *filename)
 
     buf_printf(&out, "%s\n", static_key_foot);
 
-    /* write number of bits */
-    buf_printf(&nbits_head_text, "#\n# %d bit OpenVPN static key\n#\n", nbits);
-    buf_write_string_file(&nbits_head_text, filename, fd);
-
     /* write key file, now formatted in out, to file */
-    buf_write_string_file(&out, filename, fd);
-
-    if (close(fd))
+    if(!buffer_write_file(filename, &out))
     {
-        msg(M_ERR, "Close error on shared secret file %s", filename);
+        nbits = -1;
     }
 
     /* zero memory which held file content (memory will be freed by GC) */
src/openvpn/crypto.h +5 -0
index 9417fa1..0dc597f 100644
@@ -271,6 +271,11 @@ struct crypto_options
 #define RKF_INLINE       (1<<1)
 void read_key_file(struct key2 *key2, const char *file, const unsigned int flags);
 
+/**
+ * Write nkeys 1024-bits keys to file.
+ *
+ * @returns number of random bits written, or -1 on failure.
+ */
 int write_key_file(const int nkeys, const char *filename);
 
 int read_passphrase_hash(const char *passphrase_file,
src/openvpn/init.c +4 -0
index f432106..2933d55 100644
@@ -1041,6 +1041,10 @@ do_genkey(const struct options *options)
         }
 
         nbits_written = write_key_file(2, options->shared_secret_file);
+        if (nbits_written < 0)
+        {
+            msg(M_FATAL, "Failed to write key file");
+        }
 
         msg(D_GENKEY | M_NOPREFIX,
             "Randomly generated %d bit key written to %s", nbits_written,
Signature · 1 line
2.7.4
#52 lore raw

Re: [Openvpn-devel] [PATCH v5 1/7] Introduce buffer_write_file()

Hi,

One typo inline

Quoted 68 lines · Steffan Karger

Rewrite buf_write_string_file to buffer_write_file, which is simpler to use and can deal with not-null-terminated strings. Mostly implemented so this can be easily reused for tls-crypt-v2 (client) key files.

---
v3: split change out of "generate client key", reuse in write_key_file()
v4: improve doxygen and error handling (thanks to ordex)
v5: do not print close error if open fails (thanks to ordex)

  src/openvpn/buffer.c | 31 ++++++++++++++++++++++++-------
  src/openvpn/buffer.h | 12 ++++++++----
  src/openvpn/crypto.c | 33 ++++++---------------------------
  src/openvpn/crypto.h |  5 +++++
  src/openvpn/init.c   |  4 ++++
  5 files changed, 47 insertions(+), 38 deletions(-)
src/openvpn/buffer.c +24 -7
index 0972139..7630ff7 100644
@@ -343,16 +343,33 @@ convert_to_one_line(struct buffer *buf)
      }
  }
 
-/* NOTE: requires that string be null terminated */
-void
-buf_write_string_file(const struct buffer *buf, const char *filename, int fd)
+bool
+buffer_write_file(const char *filename, const struct buffer *buf)
  {
-    const int len = strlen((char *) BPTR(buf));
-    const int size = write(fd, BPTR(buf), len);
-    if (size != len)
+    bool ret = false;
+    int fd = platform_open(filename, O_CREAT | O_TRUNC | O_WRONLY,
+                           S_IRUSR | S_IWUSR);
+    if (fd == -1)
+    {
+        msg(M_ERRNO, "Cannot open file '%s' for write", filename);
+        return false;
+    }
+
+    const int size = write(fd, BPTR(buf), BLEN(buf));
+    if (size != BLEN(buf))
      {
-        msg(M_ERR, "Write error on file '%s'", filename);
+        msg(M_ERRNO, "Write error on file '%s'", filename);
+        goto cleanup;
+    }
+
+    ret = true;
+cleanup:
+    if (close(fd) < 0)
+    {
+        msg(M_ERRNO, "Close error on file %s", filename);
+        ret = false;
      }
+    return ret;
  }
 
  /*
src/openvpn/buffer.h +0 -0
index 6b6025e..704129a 100644
@@ -469,11 +469,15 @@ const char *skip_leading_whitespace(const char *str);
 

src/openvpn/buffer.h const char *skip_leading_whitespace(const char *str); · line 476

Quoted 10 lines
src/openvpn/buffer.h +5 -3
  void string_null_terminate(char *str, int len, int capacity);
 
-/*
- * Write string in buf to file descriptor fd.
- * NOTE: requires that string be null terminated.
+/**
+ * Write buffer contents to file.
+ *
+ * @param filename  The filename to write the buffer to.
+ * @param buf       Thu buffer to write to the file.

Thu --> The

Acked-by Tested-by on v5 1/7
#53 lore raw

Re: [Openvpn-devel] [PATCH v5 1/7] Introduce buffer_write_file()

In reply to Steffan Karger

Hi,

Quoted 5 lines · Steffan Karger

Rewrite buf_write_string_file to buffer_write_file, which is simpler to use and can deal with not-null-terminated strings. Mostly implemented so this can be easily reused for tls-crypt-v2 (client) key files.

All looks good now! Thanks!

Key generation (success and failure) and basic connection work as expected. 'make check' performed on various platforms, but no buildbot test has been executed.

Cheers,

Signature · 1 line
Antonio Quartulli
#54 lore raw

[Openvpn-devel] [PATCH applied] Re: Introduce buffer_write_file()

In reply to Steffan Karger

Your patch has been applied to the master branch.

(I have not run extra tests - no buildbot explosions are to be expected, and if Antonio says "key generation has been tested" that is about all the function does today. The code looks good, visually :) )

The "Thu" comment has been changed to "The". No code changes.

commit a8fa167941c548f1cbc82e4bbe5316518df015d4

Author: Steffan Karger
Date:   Mon Aug 6 10:02:33 2018 +0200
     Introduce buffer_write_file()

     Signed-off-by: Steffan Karger <[email protected]...>
     Acked-by: Antonio Quartulli <[email protected]...>
     Tested-by: Antonio Quartulli <[email protected]...>
     Message-Id: <[email protected]...>
     URL: https://www.mail-archive.com/[email protected]/msg17371.html
     Signed-off-by: Gert Doering <[email protected]...>

--
kind regards,

Gert Doering

#55 lore raw

PATCH v4 2/7 tls-crypt-v2: add specification to doc/

In reply to Steffan Karger

This is a preliminary description of tls-crypt-v2. It should give a good impression about the reasoning and design behind tls-crypt-v2, but might need some polishing and updating.

---
v3: Include length in WKc
v4: Clarify metadata handling

 doc/tls-crypt-v2.txt | 182 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 182 insertions(+)
 create mode 100644 doc/tls-crypt-v2.txt
doc/tls-crypt-v2.txt new file +182 -0
new file mode 100644
index 0000000..7b75710
@@ -0,0 +1,182 @@
+Client-specific tls-crypt keys (--tls-crypt-v2)
+===============================================
+
+This document describes the ``--tls-crypt-v2`` option, which enables OpenVPN
+to use client-specific ``--tls-crypt`` keys.
+
+Rationale
+---------
+
+``--tls-auth`` and ``tls-crypt`` use a pre-shared group key, which is shared
+among all clients and servers in an OpenVPN deployment.  If any client or
+server is compromised, the attacker will have access to this shared key, and it
+will no longer provide any security.  To reduce the risk of loosing pre-shared
+keys, ``tls-crypt-v2`` adds the ability to supply each client with a unique
+tls-crypt key.  This allows large organisations and VPN providers to profit
+from the same DoS and TLS stack protection that small deployments can already
+achieve using ``tls-auth`` or ``tls-crypt``.
+
+Also, for ``tls-crypt``, even if all these peers succeed in keeping the key
+secret, the key lifetime is limited to roughly 8000 years, divided by the
+number of clients (see the ``--tls-crypt`` section of the man page).  Using
+client-specific keys, we lift this lifetime requirement to roughly 8000 years
+for each client key (which "Should Be Enough For Everybody (tm)").
+
+
+Introduction
+------------
+
+``tls-crypt-v2`` uses an encrypted cookie mechanism to introduce
+client-specific tls-crypt keys without introducing a lot of server-side state.
+The client-specific key is encrypted using a server key.  The server key is the
+same for all servers in a group.  When a client connects, it first sends the
+encrypted key to the server, such that the server can decrypt the key and all
+messages can thereafter be encrypted using the client-specific key.
+
+A wrapped (encrypted and authenticated) client-specific key can also contain
+metadata.  The metadata is wrapped together with the key, and can be used to
+allow servers to identify clients and/or key validity.  This allows the server
+to abort the connection immediately after receiving the first packet, rather
+than performing an entire TLS handshake.  Aborting the connection this early
+greatly improves the DoS resilience and reduces attack service against
+malicious clients that have the ``tls-crypt`` or ``tls-auth`` key.  This is
+particularly relevant for large deployments (think lost key or disgruntled
+employee) and VPN providers (clients are not trusted).
+
+To allow for a smooth transition, ``tls-crypt-v2`` is designed such that a
+server can enable both ``tls-crypt-v2`` and either ``tls-crypt`` or
+``tls-auth``.  This is achieved by introducing a P_CONTROL_HARD_RESET_CLIENT_V3
+opcode, that indicates that the client wants to use ``tls-crypt-v2`` for the
+current connection.
+
+For an exact specification and more details, read the Implementation section.
+
+
+Implementation
+--------------
+
+When setting up a tls-crypt-v2 group (similar to generating a tls-crypt or
+tls-auth key previously):
+
+1. Generate a tls-crypt-v2 server key using OpenVPN's ``--genkey``.  This key
+   contains 4 512-bit keys, of which we use:
+
+   * the first 256 bits of key 1 as AES-256-CTR encryption key ``Ke``
+   * the first 256 bits of key 2 as HMAC-SHA-256 authentication key ``Ka``
+
+2. Add the tls-crypt-v2 server key to all server configs
+   (``tls-crypt-v2 /path/to/server.key``)
+
+
+When provisioning a client, create a client-specific tls-crypt key:
+
+1. Generate 2048 bits client-specific key ``Kc``
+
+2. Optionally generate metadata
+
+   The first byte of the metadata determines the type.  The initial
+   implementation supports the following types:
+
+   0x00 (USER):         User-defined free-form data.
+   0x01 (TIMESTAMP):    64-bit network order unix timestamp of key generation.
+
+   The timestamp can be used to reject too-old tls-crypt-v2 client keys.
+
+   User metadata could for example contain the users certificate serial, such
+   that the incoming connection can be verified against a CRL.
+
+   If no metadata is supplied during key generation, openvpn defaults to the
+   TIMESTAMP metadata type.
+
+3. Create a wrapped client key ``WKc``, using the same nonce-misuse-resistant
+   SIV conruction we use for tls-crypt:
+
+   ``len = len(Kc || metadata)`` (16 bit, network byte order)
+
+   ``T = HMAC-SHA256(Ka, len || Kc || metadata)``
+
+   ``IV = 128 most significant bits of T``
+
+   ``WKc = T || AES-256-CTR(Ke, IV, Kc || metadata) || len``
+
+4. Create a tls-crypt-v2 client key: PEM-encode ``Kc || WKc`` and store in a
+   file, using the header ``-----BEGIN OpenVPN tls-crypt-v2 client key-----``
+   and the footer ``-----END OpenVPN tls-crypt-v2 client key-----``.  (The PEM
+   format is simple, and following PEM allows us to use the crypto lib function
+   for en/decoding.)
+
+5. Add the tls-crypt-v2 client key to the client config
+   (``tls-crypt-v2 /path/to/client-specific.key``)
+
+
+When setting up the openvpn connection:
+
+1. The client reads the tls-crypt-v2 key from its config, and:
+
+   1. loads ``Kc`` as its tls-crypt key,
+   2. stores ``WKc`` in memory for sending to the server.
+
+2. To start the connection, the client creates a P_CONTROL_HARD_RESET_CLIENT_V3
+   message, wraps it with tls-crypt using ``Kc`` as the key, and appends
+   ``WKc``.  (``WKc`` must not be encrypted, to prevent a chicken-and-egg
+   problem.)
+
+3. The server receives the P_CONTROL_HARD_RESET_CLIENT_V3 message, and
+
+   1. reads the WKc length field from the end of the message, and extracts WKc
+      from the message
+   2. unwraps ``WKc``
+   3. uses unwrapped ``Kc`` to verify the remaining
+      P_CONTROL_HARD_RESET_CLIENT_V3 message's (encryption and) authentication.
+
+   The message is dropped and no error response is sent when either 3.1, 3.2 or
+   3.3 fails (DoS protection).
+
+4. Server optionally checks metadata using a --tls-crypt-v2-verify script
+
+   This allows early abort of connection, *before* we expose any of the
+   notoriously dangerous TLS, X.509 and ASN.1 parsers and thereby reduces the
+   attack surface of the server.
+
+   The metadata is checked *after* the OpenVPN three-way handshake has
+   completed, to prevent DoS attacks.  (That is, once the client has proved to
+   the server that it possesses Kc, by authenticating a packet that contains the
+   session ID picked by the server.)
+
+   A server should not send back any error messages if metadata verification
+   fails, to reduce attack surface and maximize DoS resilience.
+
+6. Client and server use ``Kc`` for (un)wrapping any following control channel
+   messages.
+
+
+Considerations
+--------------
+
+To allow for a smooth transition, the server implementation allows
+``tls-crypt`` or ``tls-auth`` to be used simultaneously with ``tls-crypt-v2``.
+This specification does not allow simultaneously using ``tls-crypt-v2`` and
+connections without any control channel wrapping, because that would break DoS
+resilience.
+
+WKc includes a length field, so we leave the option for future extension of the
+P_CONTROL_HEAD_RESET_CLIENT_V3 message open.  (E.g. add payload to the reset to
+indicate low-level protocol features.)
+
+``tls-crypt-v2`` uses fixed crypto algorithms, because:
+
+ * The crypto is used before we can do any negotiation, so the algorithms have
+   to be predefined.
+ * The crypto primitives are chosen conservatively, making problems with these
+   primitives unlikely.
+ * Making anything configurable adds complexity, both in implementation and
+   usage.  We should not add anymore complexity than is absolutely necessary.
+
+Potential ``tls-crypt-v2`` risks:
+
+ * Slightly more work on first connection (``WKc`` unwrap + hard reset unwrap)
+   than with ``tls-crypt`` (hard reset unwrap) or ``tls-auth`` (hard reset auth).
+ * Flexible metadata allow mistakes
+   (So we should make it easy to do it right.  Provide tooling to create client
+   keys based on cert serial + CA fingerprint, provide script that uses CRL (if
+   available) to drop revoked keys.)
Signature · 1 line
2.7.4
#56 lore raw

Re: [Openvpn-devel] [PATCH v4 2/7] tls-crypt-v2: add specification to doc/

Hi,

as I spotted an error I decided to spell check this.

Three comments in line.

Quoted 22 lines · Steffan Karger

This is a preliminary description of tls-crypt-v2. It should give a good impression about the reasoning and design behind tls-crypt-v2, but might need some polishing and updating.

---
v3: Include length in WKc
v4: Clarify metadata handling

  doc/tls-crypt-v2.txt | 182 +++++++++++++++++++++++++++++++++++++++++++++++++++
  1 file changed, 182 insertions(+)

create mode 100644 doc/tls-crypt-v2.txt

doc/tls-crypt-v2.txt new file +3 -0
new file mode 100644
index 0000000..7b75710
@@ -0,0 +1,182 @@
+Client-specific tls-crypt keys (--tls-crypt-v2)
+===============================================
+

doc/tls-crypt-v2.txt line 13

Quoted 10 lines
doc/tls-crypt-v2.txt new file +10 -0
+This document describes the ``--tls-crypt-v2`` option, which enables OpenVPN
+to use client-specific ``--tls-crypt`` keys.
+
+Rationale
+---------
+
+``--tls-auth`` and ``tls-crypt`` use a pre-shared group key, which is shared
+among all clients and servers in an OpenVPN deployment.  If any client or
+server is compromised, the attacker will have access to this shared key, and it
+will no longer provide any security.  To reduce the risk of loosing pre-shared

loosing --> losing (losing is the correct spelling)

doc/tls-crypt-v2.txt

Quoted 8 lines
doc/tls-crypt-v2.txt new file +8 -0
+keys, ``tls-crypt-v2`` adds the ability to supply each client with a unique
+tls-crypt key.  This allows large organisations and VPN providers to profit
+from the same DoS and TLS stack protection that small deployments can already
+achieve using ``tls-auth`` or ``tls-crypt``.
+
+Also, for ``tls-crypt``, even if all these peers succeed in keeping the key
+secret, the key lifetime is limited to roughly 8000 years, divided by the
+number of clients (see the ``--tls-crypt`` section of the man page). 

Using

Quoted 61 lines
doc/tls-crypt-v2.txt new file +61 -0
+client-specific keys, we lift this lifetime requirement to roughly 8000 years
+for each client key (which "Should Be Enough For Everybody (tm)").
+
+
+Introduction
+------------
+
+``tls-crypt-v2`` uses an encrypted cookie mechanism to introduce
+client-specific tls-crypt keys without introducing a lot of server-side state.
+The client-specific key is encrypted using a server key.  The server key is the
+same for all servers in a group.  When a client connects, it first sends the
+encrypted key to the server, such that the server can decrypt the key and all
+messages can thereafter be encrypted using the client-specific key.
+
+A wrapped (encrypted and authenticated) client-specific key can also contain
+metadata.  The metadata is wrapped together with the key, and can be used to
+allow servers to identify clients and/or key validity.  This allows the server
+to abort the connection immediately after receiving the first packet, rather
+than performing an entire TLS handshake.  Aborting the connection this early
+greatly improves the DoS resilience and reduces attack service against
+malicious clients that have the ``tls-crypt`` or ``tls-auth`` key. This is
+particularly relevant for large deployments (think lost key or disgruntled
+employee) and VPN providers (clients are not trusted).
+
+To allow for a smooth transition, ``tls-crypt-v2`` is designed such that a
+server can enable both ``tls-crypt-v2`` and either ``tls-crypt`` or
+``tls-auth``.  This is achieved by introducing a P_CONTROL_HARD_RESET_CLIENT_V3
+opcode, that indicates that the client wants to use ``tls-crypt-v2`` for the
+current connection.
+
+For an exact specification and more details, read the Implementation section.
+
+
+Implementation
+--------------
+
+When setting up a tls-crypt-v2 group (similar to generating a tls-crypt or
+tls-auth key previously):
+
+1. Generate a tls-crypt-v2 server key using OpenVPN's ``--genkey``. This key
+   contains 4 512-bit keys, of which we use:
+
+   * the first 256 bits of key 1 as AES-256-CTR encryption key ``Ke``
+   * the first 256 bits of key 2 as HMAC-SHA-256 authentication key ``Ka``
+
+2. Add the tls-crypt-v2 server key to all server configs
+   (``tls-crypt-v2 /path/to/server.key``)
+
+
+When provisioning a client, create a client-specific tls-crypt key:
+
+1. Generate 2048 bits client-specific key ``Kc``
+
+2. Optionally generate metadata
+
+   The first byte of the metadata determines the type.  The initial
+   implementation supports the following types:
+
+   0x00 (USER):         User-defined free-form data.
+   0x01 (TIMESTAMP):    64-bit network order unix timestamp of key generation.
+

doc/tls-crypt-v2.txt line 92

Quoted 10 lines
doc/tls-crypt-v2.txt new file +10 -0
+   The timestamp can be used to reject too-old tls-crypt-v2 client keys.
+
+   User metadata could for example contain the users certificate serial, such
+   that the incoming connection can be verified against a CRL.
+
+   If no metadata is supplied during key generation, openvpn defaults to the
+   TIMESTAMP metadata type.
+
+3. Create a wrapped client key ``WKc``, using the same nonce-misuse-resistant
+   SIV conruction we use for tls-crypt:

conruction --> construction

doc/tls-crypt-v2.txt

Quoted 12 lines
doc/tls-crypt-v2.txt new file +12 -0
+
+   ``len = len(Kc || metadata)`` (16 bit, network byte order)
+
+   ``T = HMAC-SHA256(Ka, len || Kc || metadata)``
+
+   ``IV = 128 most significant bits of T``
+
+   ``WKc = T || AES-256-CTR(Ke, IV, Kc || metadata) || len``
+
+4. Create a tls-crypt-v2 client key: PEM-encode ``Kc || WKc`` and store in a
+   file, using the header ``-----BEGIN OpenVPN tls-crypt-v2 client key-----``
+   and the footer ``-----END OpenVPN tls-crypt-v2 client key-----``. 

(The PEM

Quoted 59 lines
doc/tls-crypt-v2.txt new file +59 -0
+   format is simple, and following PEM allows us to use the crypto lib function
+   for en/decoding.)
+
+5. Add the tls-crypt-v2 client key to the client config
+   (``tls-crypt-v2 /path/to/client-specific.key``)
+
+
+When setting up the openvpn connection:
+
+1. The client reads the tls-crypt-v2 key from its config, and:
+
+   1. loads ``Kc`` as its tls-crypt key,
+   2. stores ``WKc`` in memory for sending to the server.
+
+2. To start the connection, the client creates a P_CONTROL_HARD_RESET_CLIENT_V3
+   message, wraps it with tls-crypt using ``Kc`` as the key, and appends
+   ``WKc``.  (``WKc`` must not be encrypted, to prevent a chicken-and-egg
+   problem.)
+
+3. The server receives the P_CONTROL_HARD_RESET_CLIENT_V3 message, and
+
+   1. reads the WKc length field from the end of the message, and extracts WKc
+      from the message
+   2. unwraps ``WKc``
+   3. uses unwrapped ``Kc`` to verify the remaining
+      P_CONTROL_HARD_RESET_CLIENT_V3 message's (encryption and) authentication.
+
+   The message is dropped and no error response is sent when either 3.1, 3.2 or
+   3.3 fails (DoS protection).
+
+4. Server optionally checks metadata using a --tls-crypt-v2-verify script
+
+   This allows early abort of connection, *before* we expose any of the
+   notoriously dangerous TLS, X.509 and ASN.1 parsers and thereby reduces the
+   attack surface of the server.
+
+   The metadata is checked *after* the OpenVPN three-way handshake has
+   completed, to prevent DoS attacks.  (That is, once the client has proved to
+   the server that it possesses Kc, by authenticating a packet that contains the
+   session ID picked by the server.)
+
+   A server should not send back any error messages if metadata verification
+   fails, to reduce attack surface and maximize DoS resilience.
+
+6. Client and server use ``Kc`` for (un)wrapping any following control channel
+   messages.
+
+
+Considerations
+--------------
+
+To allow for a smooth transition, the server implementation allows
+``tls-crypt`` or ``tls-auth`` to be used simultaneously with ``tls-crypt-v2``.
+This specification does not allow simultaneously using ``tls-crypt-v2`` and
+connections without any control channel wrapping, because that would break DoS
+resilience.
+
+WKc includes a length field, so we leave the option for future extension of the
+P_CONTROL_HEAD_RESET_CLIENT_V3 message open.  (E.g. add payload to the reset to

doc/tls-crypt-v2.txt line 173

Quoted 10 lines
doc/tls-crypt-v2.txt new file +10 -0
+indicate low-level protocol features.)
+
+``tls-crypt-v2`` uses fixed crypto algorithms, because:
+
+ * The crypto is used before we can do any negotiation, so the algorithms have
+   to be predefined.
+ * The crypto primitives are chosen conservatively, making problems with these
+   primitives unlikely.
+ * Making anything configurable adds complexity, both in implementation and
+   usage.  We should not add anymore complexity than is absolutely necessary.

Technicality: anymore --> any more (your call but that is correct)

Quoted 10 lines
doc/tls-crypt-v2.txt new file +9 -0
+
+Potential ``tls-crypt-v2`` risks:
+
+ * Slightly more work on first connection (``WKc`` unwrap + hard reset unwrap)
+   than with ``tls-crypt`` (hard reset unwrap) or ``tls-auth`` (hard reset auth).
+ * Flexible metadata allow mistakes
+   (So we should make it easy to do it right.  Provide tooling to create client
+   keys based on cert serial + CA fingerprint, provide script that uses CRL (if
+   available) to drop revoked keys.)
#57 lore raw

Re: [Openvpn-devel] [PATCH v4 2/7] tls-crypt-v2: add specification to doc/

Hi,

Quoted 3 lines · tincanteksup

as I spotted an error I decided to spell check this.

Three comments in line.

Thanks! I'll include fixes in v5.

-Steffan

#58 lore raw

PATCH v5 2/7 tls-crypt-v2: add specification to doc/

In reply to tincanteksup

This is a preliminary description of tls-crypt-v2. It should give a good impression about the reasoning and design behind tls-crypt-v2, but might need some polishing and updating.

---
v3: Include length in WKc
v4: Clarify metadata handling
v5: Typo fixes (thanks tincanteksup)

 doc/tls-crypt-v2.txt | 182 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 182 insertions(+)
 create mode 100644 doc/tls-crypt-v2.txt
doc/tls-crypt-v2.txt new file +182 -0
new file mode 100644
index 0000000..ec13443
@@ -0,0 +1,182 @@
+Client-specific tls-crypt keys (--tls-crypt-v2)
+===============================================
+
+This document describes the ``--tls-crypt-v2`` option, which enables OpenVPN
+to use client-specific ``--tls-crypt`` keys.
+
+Rationale
+---------
+
+``--tls-auth`` and ``tls-crypt`` use a pre-shared group key, which is shared
+among all clients and servers in an OpenVPN deployment.  If any client or
+server is compromised, the attacker will have access to this shared key, and it
+will no longer provide any security.  To reduce the risk of losing pre-shared
+keys, ``tls-crypt-v2`` adds the ability to supply each client with a unique
+tls-crypt key.  This allows large organisations and VPN providers to profit
+from the same DoS and TLS stack protection that small deployments can already
+achieve using ``tls-auth`` or ``tls-crypt``.
+
+Also, for ``tls-crypt``, even if all these peers succeed in keeping the key
+secret, the key lifetime is limited to roughly 8000 years, divided by the
+number of clients (see the ``--tls-crypt`` section of the man page).  Using
+client-specific keys, we lift this lifetime requirement to roughly 8000 years
+for each client key (which "Should Be Enough For Everybody (tm)").
+
+
+Introduction
+------------
+
+``tls-crypt-v2`` uses an encrypted cookie mechanism to introduce
+client-specific tls-crypt keys without introducing a lot of server-side state.
+The client-specific key is encrypted using a server key.  The server key is the
+same for all servers in a group.  When a client connects, it first sends the
+encrypted key to the server, such that the server can decrypt the key and all
+messages can thereafter be encrypted using the client-specific key.
+
+A wrapped (encrypted and authenticated) client-specific key can also contain
+metadata.  The metadata is wrapped together with the key, and can be used to
+allow servers to identify clients and/or key validity.  This allows the server
+to abort the connection immediately after receiving the first packet, rather
+than performing an entire TLS handshake.  Aborting the connection this early
+greatly improves the DoS resilience and reduces attack service against
+malicious clients that have the ``tls-crypt`` or ``tls-auth`` key.  This is
+particularly relevant for large deployments (think lost key or disgruntled
+employee) and VPN providers (clients are not trusted).
+
+To allow for a smooth transition, ``tls-crypt-v2`` is designed such that a
+server can enable both ``tls-crypt-v2`` and either ``tls-crypt`` or
+``tls-auth``.  This is achieved by introducing a P_CONTROL_HARD_RESET_CLIENT_V3
+opcode, that indicates that the client wants to use ``tls-crypt-v2`` for the
+current connection.
+
+For an exact specification and more details, read the Implementation section.
+
+
+Implementation
+--------------
+
+When setting up a tls-crypt-v2 group (similar to generating a tls-crypt or
+tls-auth key previously):
+
+1. Generate a tls-crypt-v2 server key using OpenVPN's ``--genkey``.  This key
+   contains 4 512-bit keys, of which we use:
+
+   * the first 256 bits of key 1 as AES-256-CTR encryption key ``Ke``
+   * the first 256 bits of key 2 as HMAC-SHA-256 authentication key ``Ka``
+
+2. Add the tls-crypt-v2 server key to all server configs
+   (``tls-crypt-v2 /path/to/server.key``)
+
+
+When provisioning a client, create a client-specific tls-crypt key:
+
+1. Generate 2048 bits client-specific key ``Kc``
+
+2. Optionally generate metadata
+
+   The first byte of the metadata determines the type.  The initial
+   implementation supports the following types:
+
+   0x00 (USER):         User-defined free-form data.
+   0x01 (TIMESTAMP):    64-bit network order unix timestamp of key generation.
+
+   The timestamp can be used to reject too-old tls-crypt-v2 client keys.
+
+   User metadata could for example contain the users certificate serial, such
+   that the incoming connection can be verified against a CRL.
+
+   If no metadata is supplied during key generation, openvpn defaults to the
+   TIMESTAMP metadata type.
+
+3. Create a wrapped client key ``WKc``, using the same nonce-misuse-resistant
+   SIV construction we use for tls-crypt:
+
+   ``len = len(Kc || metadata)`` (16 bit, network byte order)
+
+   ``T = HMAC-SHA256(Ka, len || Kc || metadata)``
+
+   ``IV = 128 most significant bits of T``
+
+   ``WKc = T || AES-256-CTR(Ke, IV, Kc || metadata) || len``
+
+4. Create a tls-crypt-v2 client key: PEM-encode ``Kc || WKc`` and store in a
+   file, using the header ``-----BEGIN OpenVPN tls-crypt-v2 client key-----``
+   and the footer ``-----END OpenVPN tls-crypt-v2 client key-----``.  (The PEM
+   format is simple, and following PEM allows us to use the crypto lib function
+   for en/decoding.)
+
+5. Add the tls-crypt-v2 client key to the client config
+   (``tls-crypt-v2 /path/to/client-specific.key``)
+
+
+When setting up the openvpn connection:
+
+1. The client reads the tls-crypt-v2 key from its config, and:
+
+   1. loads ``Kc`` as its tls-crypt key,
+   2. stores ``WKc`` in memory for sending to the server.
+
+2. To start the connection, the client creates a P_CONTROL_HARD_RESET_CLIENT_V3
+   message, wraps it with tls-crypt using ``Kc`` as the key, and appends
+   ``WKc``.  (``WKc`` must not be encrypted, to prevent a chicken-and-egg
+   problem.)
+
+3. The server receives the P_CONTROL_HARD_RESET_CLIENT_V3 message, and
+
+   1. reads the WKc length field from the end of the message, and extracts WKc
+      from the message
+   2. unwraps ``WKc``
+   3. uses unwrapped ``Kc`` to verify the remaining
+      P_CONTROL_HARD_RESET_CLIENT_V3 message's (encryption and) authentication.
+
+   The message is dropped and no error response is sent when either 3.1, 3.2 or
+   3.3 fails (DoS protection).
+
+4. Server optionally checks metadata using a --tls-crypt-v2-verify script
+
+   This allows early abort of connection, *before* we expose any of the
+   notoriously dangerous TLS, X.509 and ASN.1 parsers and thereby reduces the
+   attack surface of the server.
+
+   The metadata is checked *after* the OpenVPN three-way handshake has
+   completed, to prevent DoS attacks.  (That is, once the client has proved to
+   the server that it possesses Kc, by authenticating a packet that contains the
+   session ID picked by the server.)
+
+   A server should not send back any error messages if metadata verification
+   fails, to reduce attack surface and maximize DoS resilience.
+
+6. Client and server use ``Kc`` for (un)wrapping any following control channel
+   messages.
+
+
+Considerations
+--------------
+
+To allow for a smooth transition, the server implementation allows
+``tls-crypt`` or ``tls-auth`` to be used simultaneously with ``tls-crypt-v2``.
+This specification does not allow simultaneously using ``tls-crypt-v2`` and
+connections without any control channel wrapping, because that would break DoS
+resilience.
+
+WKc includes a length field, so we leave the option for future extension of the
+P_CONTROL_HEAD_RESET_CLIENT_V3 message open.  (E.g. add payload to the reset to
+indicate low-level protocol features.)
+
+``tls-crypt-v2`` uses fixed crypto algorithms, because:
+
+ * The crypto is used before we can do any negotiation, so the algorithms have
+   to be predefined.
+ * The crypto primitives are chosen conservatively, making problems with these
+   primitives unlikely.
+ * Making anything configurable adds complexity, both in implementation and
+   usage.  We should not add any more complexity than is absolutely necessary.
+
+Potential ``tls-crypt-v2`` risks:
+
+ * Slightly more work on first connection (``WKc`` unwrap + hard reset unwrap)
+   than with ``tls-crypt`` (hard reset unwrap) or ``tls-auth`` (hard reset auth).
+ * Flexible metadata allow mistakes
+   (So we should make it easy to do it right.  Provide tooling to create client
+   keys based on cert serial + CA fingerprint, provide script that uses CRL (if
+   available) to drop revoked keys.)
Signature · 1 line
2.7.4
#59 lore raw

PATCH v3 3/7 tls-crypt-v2: generate client keys

In reply to Steffan Karger

As a first step towards a full tls-crypt-v2 implementation, add functionality to generate tls-crypt-v2 client keys.

---
v3: Include length in WKc

 doc/openvpn.8           |  51 +++++++++
 src/openvpn/init.c      |  35 +++++-
 src/openvpn/integer.h   |  10 ++
 src/openvpn/options.c   |  66 ++++++++++-
 src/openvpn/options.h   |  14 +++
 src/openvpn/tls_crypt.c | 288 ++++++++++++++++++++++++++++++++++++++++++++++++
 src/openvpn/tls_crypt.h |  81 ++++++++++++--
 tests/t_lpback.sh       |  40 ++++++-
 8 files changed, 565 insertions(+), 20 deletions(-)
doc/openvpn.8 +51 -0
index f01b48b..597c0c4 100644
@@ -5248,6 +5248,57 @@ degrading to the same security as using
 That is, the control channel still benefits from the extra protection against
 active man\-in\-the\-middle\-attacks and DoS attacks, but may no longer offer
 extra privacy and post\-quantum security on top of what TLS itself offers.
+
+For large setups or setups where clients are not trusted, consider using
+.B \-\-tls\-crypt\-v2
+instead.  That uses per\-client unique keys, and thereby improves the bounds to
+\fR'rotate a client key at least once per 8000 years'.
+.\"*********************************************************
+.TP
+.B \-\-tls\-crypt\-v2 keyfile
+
+Use client\-specific tls\-crypt keys.
+
+For clients,
+.B keyfile
+is a client\-specific tls\-crypt key.  Such a key can be generated using the
+.B \-\-tls\-crypt\-v2\-genkey
+option.
+
+For servers,
+.B keyfile
+is used to unwrap client\-specific keys supplied by the client during connection
+setup.  This key must be the same as the key used to generate the
+client\-specific key (see
+.B \-\-tls\-crypt\-v2\-genkey\fR).
+
+On servers, this option can be used together with the
+.B \-\-tls\-auth
+or
+.B \-\-tls\-crypt
+option.  In that case, the server will detect whether the client is using
+client\-specific keys, and automatically select the right mode.
+.\"*********************************************************
+.TP
+.B \-\-tls\-crypt\-v2\-genkey client|server keyfile [metadata]
+
+If the first parameter equals "server", generate a \-\-tls\-crypt\-v2 server
+key and store the key in
+.B keyfile\fR.
+
+
+If the first parameter equals "client", generate a \-\-tls\-crypt\-v2 client
+key, and store the key in
+.B keyfile\fR.
+
+If supplied, include the supplied
+.B metadata
+in the wrapped client key.  This metadata must be supplied in base64\-encoded
+form.  The metadata must be at most 735 bytes long (980 bytes in base64).
+
+.B TODO
+Metadata handling is not yet implemented.  This text will be updated by the
+commit that introduces metadata handling.
 .\"*********************************************************
 .TP
 .B \-\-askpass [file]
src/openvpn/init.c +30 -5
index f432106..ef7b422 100644
@@ -1028,6 +1028,11 @@ print_openssl_info(const struct options *options)
 bool
 do_genkey(const struct options *options)
 {
+    /* should we disable paging? */
+    if (options->mlock && (options->genkey || options->tls_crypt_v2_genkey_file))
+    {
+        platform_mlockall(true);
+    }
     if (options->genkey)
     {
         int nbits_written;
@@ -1035,11 +1040,6 @@ do_genkey(const struct options *options)
         notnull(options->shared_secret_file,
                 "shared secret output file (--secret)");
 
-        if (options->mlock)     /* should we disable paging? */
-        {
-            platform_mlockall(true);
-        }
-
         nbits_written = write_key_file(2, options->shared_secret_file);
 
         msg(D_GENKEY | M_NOPREFIX,
@@ -1047,6 +1047,31 @@ do_genkey(const struct options *options)
             options->shared_secret_file);
         return true;
     }
+    if (options->tls_crypt_v2_genkey_type)
+    {
+        if(!strcmp(options->tls_crypt_v2_genkey_type, "server"))
+        {
+            tls_crypt_v2_write_server_key_file(options->tls_crypt_v2_genkey_file);
+            return true;
+        }
+        else if (options->tls_crypt_v2_genkey_type
+                 && !strcmp(options->tls_crypt_v2_genkey_type, "client"))
+        {
+            if (!options->tls_crypt_v2_file)
+            {
+                msg(M_USAGE, "--tls-crypt-v2-gen-client-key requires a server key to be set via --tls-crypt-v2");
+            }
+
+            tls_crypt_v2_write_client_key_file(options->tls_crypt_v2_genkey_file,
+                    options->tls_crypt_v2_metadata, options->tls_crypt_v2_file,
+                    options->tls_crypt_v2_inline);
+            return true;
+        }
+        else
+        {
+            msg(M_USAGE, "--tls-crypt-v2-genkey type should be \"client\" or \"server\"");
+        }
+    }
     return false;
 }
 
src/openvpn/integer.h +10 -0
index a7e19d3..b1ae0ed 100644
@@ -26,6 +26,16 @@
 
 #include "error.h"
 
+#ifndef htonll
+#define htonll(x) ((1==htonl(1)) ? (x) : \
+                  ((uint64_t)htonl((x) & 0xFFFFFFFF) << 32) | htonl((x) >> 32))
+#endif
+
+#ifndef ntohll
+#define ntohll(x) ((1==ntohl(1)) ? (x) : \
+                  ((uint64_t)ntohl((x) & 0xFFFFFFFF) << 32) | ntohl((x) >> 32))
+#endif
+
 /*
  * min/max functions
  */
src/openvpn/options.c +62 -4

125 lines not on this page · raw message

src/openvpn/options.h +14 -0
index acbd108..3d2c770 100644
@@ -139,6 +139,11 @@ struct connection_entry
     /* Shared secret used for TLS control channel authenticated encryption */
     const char *tls_crypt_file;
     const char *tls_crypt_inline;
+
+    /* Client-specific secret or server key used for TLS control channel
+     * authenticated encryption v2 */
+    const char *tls_crypt_v2_file;
+    const char *tls_crypt_v2_inline;
 };
 
 struct remote_entry
@@ -576,6 +581,15 @@ struct options
     const char *tls_crypt_file;
     const char *tls_crypt_inline;
 
+    /* Client-specific secret or server key used for TLS control channel
+     * authenticated encryption v2 */
+    const char *tls_crypt_v2_file;
+    const char *tls_crypt_v2_inline;
+
+    const char *tls_crypt_v2_genkey_type;
+    const char *tls_crypt_v2_genkey_file;
+    const char *tls_crypt_v2_metadata;
+
     /* Allow only one session */
     bool single_session;
 
src/openvpn/tls_crypt.c +288 -0

302 lines not on this page · raw message

src/openvpn/tls_crypt.h +73 -8

129 lines not on this page · raw message

tests/t_lpback.sh +37 -3
index 2052c62..fb43211 100755
@@ -21,8 +21,8 @@
 
 set -eu
 top_builddir="${top_builddir:-..}"
-trap "rm -f key.$$ log.$$ ; trap 0 ; exit 77" 1 2 15
-trap "rm -f key.$$ log.$$ ; exit 1" 0 3
+trap "rm -f key.$$ tc-server-key.$$ tc-client-key.$$ log.$$ ; trap 0 ; exit 77" 1 2 15
+trap "rm -f key.$$ tc-server-key.$$ tc-client-key.$$ log.$$ ; exit 1" 0 3
 
 # Get list of supported ciphers from openvpn --show-ciphers output
 CIPHERS=$(${top_builddir}/src/openvpn/openvpn --show-ciphers | \
@@ -55,6 +55,40 @@ do
     fi
 done
 
-rm key.$$ log.$$
+echo -n "Testing tls-crypt-v2 server key generation..."
+"${top_builddir}/src/openvpn/openvpn" \
+    --tls-crypt-v2-genkey server tc-server-key.$$ >log.$$ 2>&1
+if [ $? != 0 ] ; then
+    echo "FAILED"
+    cat log.$$
+    e=1
+else
+    echo "OK"
+fi
+
+echo -n "Testing tls-crypt-v2 key generation (no metadata)..."
+"${top_builddir}/src/openvpn/openvpn" --tls-crypt-v2 tc-server-key.$$ \
+    --tls-crypt-v2-genkey client tc-client-key.$$ >log.$$ 2>&1
+if [ $? != 0 ] ; then
+    echo "FAILED"
+    cat log.$$
+    e=1
+else
+    echo "OK"
+fi
+
+echo -n "Testing tls-crypt-v2 key generation (max length metadata)..."
+"${top_builddir}/src/openvpn/openvpn" --tls-crypt-v2 tc-server-key.$$ \
+    --tls-crypt-v2-genkey client tc-client-key.$$ \
+    $(head -c732 /dev/zero | base64 -w0) >log.$$ 2>&1
+if [ $? != 0 ] ; then
+    echo "FAILED"
+    cat log.$$
+    e=1
+else
+    echo "OK"
+fi
+
+rm key.$$ tc-server-key.$$ tc-client-key.$$ log.$$
 trap 0
 exit $e
Signature · 1 line
2.7.4
#60 lore raw

Re: [Openvpn-devel] [PATCH v3 3/7] tls-crypt-v2: generate client keys

Hi,

as I spotted an error I decided to spell check this.

Two comments in line.

src/openvpn/options.c options_postprocess_verify_ce(const struct options *options, const struct connec

Quoted 210 lines · Steffan Karger

As a first step towards a full tls-crypt-v2 implementation, add functionality to generate tls-crypt-v2 client keys.

---
v3: Include length in WKc

  doc/openvpn.8           |  51 +++++++++
  src/openvpn/init.c      |  35 +++++-
  src/openvpn/integer.h   |  10 ++
  src/openvpn/options.c   |  66 ++++++++++-
  src/openvpn/options.h   |  14 +++
  src/openvpn/tls_crypt.c | 288 ++++++++++++++++++++++++++++++++++++++++++++++++
  src/openvpn/tls_crypt.h |  81 ++++++++++++--
  tests/t_lpback.sh       |  40 ++++++-
  8 files changed, 565 insertions(+), 20 deletions(-)
doc/openvpn.8 +51 -0
index f01b48b..597c0c4 100644
@@ -5248,6 +5248,57 @@ degrading to the same security as using
  That is, the control channel still benefits from the extra protection against
  active man\-in\-the\-middle\-attacks and DoS attacks, but may no longer offer
  extra privacy and post\-quantum security on top of what TLS itself offers.
+
+For large setups or setups where clients are not trusted, consider using
+.B \-\-tls\-crypt\-v2
+instead.  That uses per\-client unique keys, and thereby improves the bounds to
+\fR'rotate a client key at least once per 8000 years'.
+.\"*********************************************************
+.TP
+.B \-\-tls\-crypt\-v2 keyfile
+
+Use client\-specific tls\-crypt keys.
+
+For clients,
+.B keyfile
+is a client\-specific tls\-crypt key.  Such a key can be generated using the
+.B \-\-tls\-crypt\-v2\-genkey
+option.
+
+For servers,
+.B keyfile
+is used to unwrap client\-specific keys supplied by the client during connection
+setup.  This key must be the same as the key used to generate the
+client\-specific key (see
+.B \-\-tls\-crypt\-v2\-genkey\fR).
+
+On servers, this option can be used together with the
+.B \-\-tls\-auth
+or
+.B \-\-tls\-crypt
+option.  In that case, the server will detect whether the client is using
+client\-specific keys, and automatically select the right mode.
+.\"*********************************************************
+.TP
+.B \-\-tls\-crypt\-v2\-genkey client|server keyfile [metadata]
+
+If the first parameter equals "server", generate a \-\-tls\-crypt\-v2 server
+key and store the key in
+.B keyfile\fR.
+
+
+If the first parameter equals "client", generate a \-\-tls\-crypt\-v2 client
+key, and store the key in
+.B keyfile\fR.
+
+If supplied, include the supplied
+.B metadata
+in the wrapped client key.  This metadata must be supplied in base64\-encoded
+form.  The metadata must be at most 735 bytes long (980 bytes in base64).
+
+.B TODO
+Metadata handling is not yet implemented.  This text will be updated by the
+commit that introduces metadata handling.
  .\"*********************************************************
  .TP
  .B \-\-askpass [file]
src/openvpn/init.c +30 -5
index f432106..ef7b422 100644
@@ -1028,6 +1028,11 @@ print_openssl_info(const struct options *options)
  bool
  do_genkey(const struct options *options)
  {
+    /* should we disable paging? */
+    if (options->mlock && (options->genkey || options->tls_crypt_v2_genkey_file))
+    {
+        platform_mlockall(true);
+    }
      if (options->genkey)
      {
          int nbits_written;
@@ -1035,11 +1040,6 @@ do_genkey(const struct options *options)
          notnull(options->shared_secret_file,
                  "shared secret output file (--secret)");
 
-        if (options->mlock)     /* should we disable paging? */
-        {
-            platform_mlockall(true);
-        }
-
          nbits_written = write_key_file(2, options->shared_secret_file);
 
          msg(D_GENKEY | M_NOPREFIX,
@@ -1047,6 +1047,31 @@ do_genkey(const struct options *options)
              options->shared_secret_file);
          return true;
      }
+    if (options->tls_crypt_v2_genkey_type)
+    {
+        if(!strcmp(options->tls_crypt_v2_genkey_type, "server"))
+        {
+ tls_crypt_v2_write_server_key_file(options->tls_crypt_v2_genkey_file);
+            return true;
+        }
+        else if (options->tls_crypt_v2_genkey_type
+                 && !strcmp(options->tls_crypt_v2_genkey_type, "client"))
+        {
+            if (!options->tls_crypt_v2_file)
+            {
+                msg(M_USAGE, "--tls-crypt-v2-gen-client-key requires a server key to be set via --tls-crypt-v2");
+            }
+
+ tls_crypt_v2_write_client_key_file(options->tls_crypt_v2_genkey_file,
+                    options->tls_crypt_v2_metadata, options->tls_crypt_v2_file,
+                    options->tls_crypt_v2_inline);
+            return true;
+        }
+        else
+        {
+            msg(M_USAGE, "--tls-crypt-v2-genkey type should be \"client\" or \"server\"");
+        }
+    }
      return false;
  }
 
src/openvpn/integer.h +10 -0
index a7e19d3..b1ae0ed 100644
@@ -26,6 +26,16 @@
 
  #include "error.h"
 
+#ifndef htonll
+#define htonll(x) ((1==htonl(1)) ? (x) : \
+                  ((uint64_t)htonl((x) & 0xFFFFFFFF) << 32) | htonl((x) >> 32))
+#endif
+
+#ifndef ntohll
+#define ntohll(x) ((1==ntohl(1)) ? (x) : \
+                  ((uint64_t)ntohl((x) & 0xFFFFFFFF) << 32) | ntohl((x) >> 32))
+#endif
+
  /*
   * min/max functions
   */
src/openvpn/options.c +17 -0
index 61fa983..acab042 100644
@@ -622,6 +622,13 @@ static const char usage_message[] =
      "                  attacks on the TLS stack and DoS attacks.\n"
      "                  key (required) provides the pre-shared key file.\n"
      "                  see --secret option for more info.\n"
+    "--tls-crypt-v2 key : For clients: use key as a client-specific tls-crypt key.\n"
+    "                  For servers: use key to decrypt client-specific keys.  For\n"
+    "                  key generation (--tls-crypt-v2-genkey): use key to\n"
+    "                  encrypt generated client-specific key.  (See --tls-crypt.)\n"
+    "--tls-crypt-v2-genkey client|server keyfile [base64 metadata]: Generate a\n"
+    "                  fresh tls-crypt-v2 client or server key, and store to\n"
+    "                  keyfile.  If supplied, include metadata in wrapped key.\n"
      "--askpass [file]: Get PEM password from controlling tty before we daemonize.\n"
      "--auth-nocache  : Don't cache --askpass or --auth-user-pass passwords.\n"
      "--crl-verify crl ['dir']: Check peer certificate against a CRL.\n"
@@ -1512,6 +1519,7 @@ show_connection_entry(const struct connection_entry *o)
      SHOW_PARM(key_direction, keydirection2ascii(o->key_direction, false, true),
                "%s");
      SHOW_STR(tls_crypt_file);
+    SHOW_STR(tls_crypt_v2_file);
  }
 
 
@@ -1792,6 +1800,10 @@ show_settings(const struct options *o)
      SHOW_BOOL(push_peer_info);
      SHOW_BOOL(tls_exit);
 
+    SHOW_STR(tls_crypt_v2_genkey_type);
+    SHOW_STR(tls_crypt_v2_genkey_file);
+    SHOW_STR(tls_crypt_v2_metadata);
+
  #ifdef ENABLE_PKCS11
      {
          int i;
@@ -2730,6 +2742,11 @@ options_postprocess_verify_ce(const struct options *options, const struct connec
          {
              msg(M_USAGE, "--tls-auth and --tls-crypt are mutually exclusive");
          }
+        if (options->tls_client && ((ce->tls_auth_file && ce->tls_crypt_v2_file)
+                || (ce->tls_crypt_file && ce->tls_crypt_v2_file)))
+        {
+            msg(M_USAGE, "--tls-auth, --tls-crypt and --tls-crypt-v2 are mutually exclusive in client mode");
+        }
      }
      else
      {
@@ -2764,6 +2781,7 @@ options_postprocess_verify_ce(const struct options *options, const struct connec

connec --> connect ? (This is code so I just highlight for your consideration, I don't know)

Quoted 523 lines
src/openvpn/options.c +45 -4
          MUST_BE_UNDEF(transition_window);
          MUST_BE_UNDEF(tls_auth_file);
          MUST_BE_UNDEF(tls_crypt_file);
+        MUST_BE_UNDEF(tls_crypt_v2_file);
          MUST_BE_UNDEF(single_session);
          MUST_BE_UNDEF(push_peer_info);
          MUST_BE_UNDEF(tls_exit);
@@ -2873,12 +2891,12 @@ options_postprocess_mutate_ce(struct options *o, struct connection_entry *ce)
      }
 
      /*
-     * Set per-connection block tls-auth/crypt fields if undefined.
+     * Set per-connection block tls-auth/crypt/crypto-v2 fields if undefined.
       *
-     * At the end only one of the two will be really set because the parser
-     * logic prevents configurations where both are set.
+     * At the end only one of these will be really set because the parser
+     * logic prevents configurations where more are set.
       */
-    if (!ce->tls_auth_file && !ce->tls_crypt_file)
+    if (!ce->tls_auth_file && !ce->tls_crypt_file && !ce->tls_crypt_v2_file)
      {
          ce->tls_auth_file = o->tls_auth_file;
          ce->tls_auth_file_inline = o->tls_auth_file_inline;
@@ -2886,6 +2904,9 @@ options_postprocess_mutate_ce(struct options *o, struct connection_entry *ce)
 
          ce->tls_crypt_file = o->tls_crypt_file;
          ce->tls_crypt_inline = o->tls_crypt_inline;
+
+        ce->tls_crypt_v2_file = o->tls_crypt_v2_file;
+        ce->tls_crypt_v2_inline = o->tls_crypt_v2_inline;
      }
 
      /* pre-cache tls-auth/crypt key file if persist-key was specified and keys
@@ -3342,9 +3363,15 @@ options_postprocess_filechecks(struct options *options)
          errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE|CHKACC_PRIVATE,
                                    ce->tls_crypt_file, R_OK, "--tls-crypt");
 
+        errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE|CHKACC_PRIVATE,
+                                  ce->tls_crypt_v2_file, R_OK,
+                                  "--tls-crypt-v2");
      }
 
      errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE|CHKACC_PRIVATE,
+                              options->tls_crypt_v2_genkey_file, R_OK,
+                              "--tls-crypt-v2-genkey");
+    errs |= check_file_access(CHKACC_FILE|CHKACC_INLINE|CHKACC_PRIVATE,
                                options->shared_secret_file, R_OK, "--secret");
 
      errs |= check_file_access(CHKACC_DIRPATH|CHKACC_FILEXSTWR,
@@ -8118,6 +8145,37 @@ add_option(struct options *options,
 
          }
      }
+    else if (streq(p[0], "tls-crypt-v2") && p[1] && !p[3])
+    {
+        VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_CONNECTION);
+        if (permission_mask & OPT_P_GENERAL)
+        {
+            if (streq(p[1], INLINE_FILE_TAG) && p[2])
+            {
+                options->tls_crypt_v2_inline = p[2];
+            }
+            options->tls_crypt_v2_file = p[1];
+        }
+        else if (permission_mask & OPT_P_CONNECTION)
+        {
+            if (streq(p[1], INLINE_FILE_TAG) && p[2])
+            {
+                options->ce.tls_crypt_v2_inline = p[2];
+            }
+            options->ce.tls_crypt_v2_file = p[1];
+
+        }
+    }
+    else if (streq(p[0], "tls-crypt-v2-genkey") && p[2] && !p[4])
+    {
+        VERIFY_PERMISSION(OPT_P_GENERAL);
+        options->tls_crypt_v2_genkey_type = p[1];
+        options->tls_crypt_v2_genkey_file = p[2];
+        if (p[3])
+        {
+            options->tls_crypt_v2_metadata = p[3];
+        }
+    }
      else if (streq(p[0], "key-method") && p[1] && !p[2])
      {
          int key_method;
src/openvpn/options.h +14 -0
index acbd108..3d2c770 100644
@@ -139,6 +139,11 @@ struct connection_entry
      /* Shared secret used for TLS control channel authenticated encryption */
      const char *tls_crypt_file;
      const char *tls_crypt_inline;
+
+    /* Client-specific secret or server key used for TLS control channel
+     * authenticated encryption v2 */
+    const char *tls_crypt_v2_file;
+    const char *tls_crypt_v2_inline;
  };
 
  struct remote_entry
@@ -576,6 +581,15 @@ struct options
      const char *tls_crypt_file;
      const char *tls_crypt_inline;
 
+    /* Client-specific secret or server key used for TLS control channel
+     * authenticated encryption v2 */
+    const char *tls_crypt_v2_file;
+    const char *tls_crypt_v2_inline;
+
+    const char *tls_crypt_v2_genkey_type;
+    const char *tls_crypt_v2_genkey_file;
+    const char *tls_crypt_v2_metadata;
+
      /* Allow only one session */
      bool single_session;
 
src/openvpn/tls_crypt.c +288 -0
index 36ead84..103a4fc 100644
@@ -29,11 +29,21 @@
 
  #include "syshead.h"
 
+#include "base64.h"
  #include "crypto.h"
+#include "platform.h"
  #include "session_id.h"
 
  #include "tls_crypt.h"
 
+const char *tls_crypt_v2_cli_pem_name = "OpenVPN tls-crypt-v2 client key";
+const char *tls_crypt_v2_srv_pem_name = "OpenVPN tls-crypt-v2 server key";
+
+/** Metadata contains user-specified data */
+static const uint8_t TLS_CRYPT_METADATA_TYPE_USER           = 0x00;
+/** Metadata contains a 64-bit unix timestamp in network byte order */
+static const uint8_t TLS_CRYPT_METADATA_TYPE_TIMESTAMP      = 0x01;
+
  static struct key_type
  tls_crypt_kt(void)
  {
@@ -264,3 +274,281 @@ error_exit:
      gc_free(&gc);
      return false;
  }
+
+static inline bool
+tls_crypt_v2_read_keyfile(struct buffer *key, const char *pem_name,
+                          const char *key_file, const char *key_inline)
+{
+    bool ret = false;
+    struct buffer key_pem = { 0 };
+    struct gc_arena gc = gc_new();
+
+    if (strcmp(key_file, INLINE_FILE_TAG))
+    {
+        key_pem = buffer_read_from_file(key_file, &gc);
+        if (!buf_valid(&key_pem))
+        {
+            msg(M_WARN, "ERROR: failed to read tls-crypt-v2 key file (%s)",
+                key_file);
+            goto cleanup;
+        }
+    }
+    else
+    {
+        buf_set_read(&key_pem, (const void *)key_inline, strlen(key_inline));
+    }
+
+    if (!crypto_pem_decode(pem_name, key, &key_pem))
+    {
+        msg(M_WARN, "ERROR: tls-crypt-v2 pem decode failed");
+        goto cleanup;
+    }
+
+    ret = true;
+cleanup:
+    if (strcmp(key_file, INLINE_FILE_TAG))
+    {
+        buf_clear(&key_pem);
+    }
+    gc_free(&gc);
+    return ret;
+}
+
+static inline void
+tls_crypt_v2_load_client_key(struct key_ctx_bi *key, const struct key2 *key2,
+                             bool tls_server)
+{
+    const int key_direction = tls_server ?
+                              KEY_DIRECTION_NORMAL : KEY_DIRECTION_INVERSE;
+    struct key_type kt = tls_crypt_kt();
+    if (!kt.cipher || !kt.digest)
+    {
+        msg (M_FATAL, "ERROR: --tls-crypt not supported");
+    }
+    init_key_ctx_bi(key, key2, key_direction, &kt,
+                    "Control Channel Encryption");
+}
+
+void
+tls_crypt_v2_init_client_key(struct key_ctx_bi *key, struct buffer *wkc_buf,
+                             const char *key_file, const char *key_inline)
+{
+    struct buffer client_key = alloc_buf(TLS_CRYPT_V2_CLIENT_KEY_LEN
+                                         + TLS_CRYPT_V2_MAX_WKC_LEN);
+
+    if (!tls_crypt_v2_read_keyfile(&client_key, tls_crypt_v2_cli_pem_name,
+                                   key_file, key_inline))
+    {
+        msg(M_FATAL, "ERROR: invalid tls-crypt-v2 client key format");
+    }
+
+    struct key2 key2;
+    if (!buf_read(&client_key, &key2.keys, sizeof(key2.keys)))
+    {
+        msg (M_FATAL, "ERROR: not enough data in tls-crypt-v2 client key");
+    }
+
+    tls_crypt_v2_load_client_key(key, &key2, false);
+    secure_memzero(&key2, sizeof(key2));
+
+    *wkc_buf = client_key;
+}
+
+void
+tls_crypt_v2_init_server_key(struct key_ctx *key_ctx, bool encrypt,
+                             const char *key_file, const char *key_inline)
+{
+    struct key srv_key;
+    struct buffer srv_key_buf;
+
+    buf_set_write(&srv_key_buf, (void *) &srv_key, sizeof(srv_key));
+    if (!tls_crypt_v2_read_keyfile(&srv_key_buf, tls_crypt_v2_srv_pem_name,
+                                   key_file, key_inline))
+    {
+        msg(M_FATAL, "ERROR: invalid tls-crypt-v2 server key format");
+    }
+
+    struct key_type kt = tls_crypt_kt();
+    if (!kt.cipher || !kt.digest)
+    {
+        msg (M_FATAL, "ERROR: --tls-crypt not supported");
+    }
+    init_key_ctx(key_ctx, &srv_key, &kt, encrypt, "tls-crypt-v2 server key");
+    secure_memzero(&srv_key, sizeof(srv_key));
+}
+
+static bool
+tls_crypt_v2_wrap_client_key(struct buffer *wkc,
+                             const struct key2 *src_key,
+                             const struct buffer *src_metadata,
+                             struct key_ctx *server_key, struct gc_arena *gc)
+{
+    cipher_ctx_t *cipher_ctx = server_key->cipher;
+    struct buffer work = alloc_buf_gc(TLS_CRYPT_V2_MAX_WKC_LEN
+                                      + cipher_ctx_block_size(cipher_ctx), gc);
+
+    /* Calculate auth tag and synthetic IV */
+    uint8_t *tag = buf_write_alloc(&work, TLS_CRYPT_TAG_SIZE);
+    if (!tag)
+    {
+        msg (M_WARN, "ERROR: could not write tag");
+        return false;
+    }
+    uint16_t net_len = htons(sizeof(src_key->keys) + BLEN(src_metadata));
+    hmac_ctx_t *hmac_ctx = server_key->hmac;
+    hmac_ctx_reset(hmac_ctx);
+    hmac_ctx_update(hmac_ctx, (void*)&net_len, sizeof(net_len));
+    hmac_ctx_update(hmac_ctx, (void*)src_key->keys, sizeof(src_key->keys));
+    hmac_ctx_update(hmac_ctx, BPTR(src_metadata), BLEN(src_metadata));
+    hmac_ctx_final(hmac_ctx, tag);
+
+    dmsg(D_CRYPTO_DEBUG, "TLS-CRYPT WRAP TAG: %s",
+         format_hex(tag, TLS_CRYPT_TAG_SIZE, 0, gc));
+
+    /* Use the 128 most significant bits of the tag as IV */
+    ASSERT(cipher_ctx_reset(cipher_ctx, tag));
+
+    /* Overflow check (OpenSSL requires an extra block in the dst buffer) */
+    if (buf_forward_capacity(&work) < (sizeof(src_key->keys)
+                                       + BLEN(src_metadata)
+                                       + sizeof(net_len)
+                                       + cipher_ctx_block_size(cipher_ctx)))
+    {
+        msg (M_WARN, "ERROR: could not crypt: insufficient space in dst");
+        return false;
+    }
+
+    /* Encrypt */
+    int outlen = 0;
+    ASSERT(cipher_ctx_update(cipher_ctx, BEND(&work), &outlen,
+                             (void*)src_key->keys, sizeof(src_key->keys)));
+    ASSERT(buf_inc_len(&work, outlen));
+    ASSERT(cipher_ctx_update(cipher_ctx, BEND(&work), &outlen,
+                             BPTR(src_metadata), BLEN(src_metadata)));
+    ASSERT(buf_inc_len(&work, outlen));
+    ASSERT(cipher_ctx_final(cipher_ctx, BEND(&work), &outlen));
+    ASSERT(buf_inc_len(&work, outlen));
+    ASSERT(buf_write(&work, &net_len, sizeof(net_len)));
+
+    return buf_copy(wkc, &work);
+}
+
+void
+tls_crypt_v2_write_server_key_file(const char *filename)
+{
+    struct gc_arena gc = gc_new();
+    struct key server_key = { 0 };
+    struct buffer server_key_buf = clear_buf();
+    struct buffer server_key_pem = clear_buf();
+
+    if (!rand_bytes((void *)&server_key, sizeof(server_key)))
+    {
+        msg(M_NONFATAL, "ERROR: could not generate random key");
+        goto cleanup;
+    }
+    buf_set_read(&server_key_buf, (void *) &server_key, sizeof(server_key));
+    if (!crypto_pem_encode(tls_crypt_v2_srv_pem_name, &server_key_pem,
+                           &server_key_buf, &gc))
+    {
+        msg(M_WARN, "ERROR: could not PEM-encode client key");
+        goto cleanup;
+    }
+
+    if (!buffer_write_file(filename, &server_key_pem))
+    {
+        msg(M_ERR, "ERROR: could not write server key file");
+        goto cleanup;
+    }
+
+cleanup:
+    secure_memzero(&server_key, sizeof(server_key));
+    buf_clear(&server_key_pem);
+    gc_free(&gc);
+    return;
+}
+
+void
+tls_crypt_v2_write_client_key_file(const char *filename, const char *b64_metadata,
+                                   const char *server_key_file,
+                                   const char *server_key_inline)
+{
+    struct gc_arena gc = gc_new();
+    struct key_ctx server_key = { 0 };
+    struct buffer client_key_pem = { 0 };
+    struct buffer dst = alloc_buf_gc(TLS_CRYPT_V2_CLIENT_KEY_LEN
+                                     + TLS_CRYPT_V2_MAX_WKC_LEN, &gc);
+
+    struct key2 client_key = { 2 };
+    if (!rand_bytes((void*)client_key.keys, sizeof(client_key.keys)))
+    {
+        msg(M_FATAL, "ERROR: could not generate random key");
+        goto cleanup;
+    }
+    ASSERT(buf_write(&dst, client_key.keys, sizeof(client_key.keys)));
+
+    struct buffer metadata = alloc_buf_gc(TLS_CRYPT_V2_MAX_METADATA_LEN, &gc);
+    if (b64_metadata)
+    {
+        if (TLS_CRYPT_V2_MAX_B64_METADATA_LEN < strlen(b64_metadata))
+        {
+            msg(M_FATAL,
+                "ERROR: metadata too long (%d bytes, max %u bytes)",
+                (int) strlen(b64_metadata), TLS_CRYPT_V2_MAX_B64_METADATA_LEN);
+        }
+        ASSERT(buf_write(&metadata, &TLS_CRYPT_METADATA_TYPE_USER, 1));
+        int decoded_len = openvpn_base64_decode(b64_metadata, BPTR(&metadata),
+                                                BCAP(&metadata));
+        if (decoded_len < 0)
+        {
+            msg(M_FATAL, "ERROR: failed to base64 decode provided metadata");
+            goto cleanup;
+        }
+        ASSERT(buf_inc_len(&metadata, decoded_len));
+    }
+    else
+    {
+        int64_t timestamp = htonll(now);
+        ASSERT(buf_write(&metadata, &TLS_CRYPT_METADATA_TYPE_TIMESTAMP, 1));
+        ASSERT(buf_write(&metadata, &timestamp, sizeof(timestamp)));
+    }
+
+    tls_crypt_v2_init_server_key(&server_key, true, server_key_file,
+                                 server_key_inline);
+    if (!tls_crypt_v2_wrap_client_key(&dst, &client_key, &metadata, &server_key,
+                                      &gc))
+    {
+        msg (M_FATAL, "ERROR: could not wrap generated client key");
+        goto cleanup;
+    }
+
+    /* PEM-encode Kc || WKc */
+    if (!crypto_pem_encode(tls_crypt_v2_cli_pem_name, &client_key_pem, &dst,
+                           &gc))
+    {
+        msg(M_FATAL, "ERROR: could not PEM-encode client key");
+        goto cleanup;
+    }
+
+    if (!buffer_write_file(filename, &client_key_pem))
+    {
+        msg(M_FATAL, "ERROR: could not write client key file");
+        goto cleanup;
+    }
+
+    /* Sanity check: load client key (as "client") */
+    struct key_ctx_bi test_client_key;
+    struct buffer test_wrapped_client_key;
+    msg (D_GENKEY, "Testing client-side key loading...");
+    tls_crypt_v2_init_client_key(&test_client_key, &test_wrapped_client_key,
+                                 filename, NULL);
+    free_key_ctx_bi(&test_client_key);
+    free_buf(&test_wrapped_client_key);
+
+cleanup:
+    secure_memzero(&client_key, sizeof(client_key));
+    free_key_ctx(&server_key);
+    buf_clear(&client_key_pem);
+    buf_clear(&dst);
+
+    gc_free(&gc);
+}
src/openvpn/tls_crypt.h +33 -8
index 067758c..6513836 100644
@@ -22,15 +22,13 @@
   */
 
  /**
- * @defgroup tls_crypt Control channel encryption (--tls-crypt)
+ * @defgroup tls_crypt Control channel encryption (--tls-crypt, --tls-crypt-v2)
   * @ingroup control_tls
   * @{
   *
- * @par
   * Control channel encryption uses a pre-shared static key (like the --tls-auth
   * key) to encrypt control channel packets.
   *
- * @par
   * Encrypting control channel packets has three main advantages:
   *  - It provides more privacy by hiding the certificate used for the TLS
   *    connection.
@@ -38,11 +36,20 @@
   *  - It provides "poor-man's" post-quantum security, against attackers who
   *    will never know the pre-shared key (i.e. no forward secrecy).
   *
- * @par Specification
+ * --tls-crypt uses a tls-auth-style group key, where all servers and clients
+ * share the same group key.  --tls-crypt-v2 adds support for client-specific
+ * keys, where all servers share the same client-key encryption key, and each
+ * clients receives a unique client key, both in plaintext and in encrypted
+ * form.  When connecting to a server, the client sends the encrypted key to
+ * the server in the first packet (P_CONTROL_HARD_RESET_CLIENT_V3). The server
+ * then decrypts that key, and both parties can use the same client-specific
+ * key for tls-crypt packets.  See doc/tls-crypt-v2.txt for more details.
+ *
+ * @par On-the-wire tls-crypt packet specification
+ * @parblock
   * Control channel encryption is based on the SIV construction [0], to achieve
   * nonce misuse-resistant authenticated encryption:
   *
- * @par
   * \code{.unparsed}
   * msg      = control channel plaintext
   * header   = opcode (1 byte) || session_id (8 bytes) || packet_id (8 bytes)
@@ -57,18 +64,17 @@
   * output   = Header || Tag || Ciph
   * \endcode
   *
- * @par
   * This boils down to the following on-the-wire packet format:
   *
- * @par
   * \code{.unparsed}
   * - opcode - || - session_id - || - packet_id - || auth_tag || * payload *
   * \endcode
   *
- * @par
   * Where
   * <tt>- XXX -</tt> means authenticated, and
   * <tt>* XXX *</tt> means authenticated and encrypted.
+ *
+ * @endparblock
   */
 
  #ifndef TLSCRYPT_H
@@ -86,6 +92,15 @@
  #define TLS_CRYPT_OFF_TAG (TLS_CRYPT_OFF_PID + TLS_CRYPT_PID_SIZE)
  #define TLS_CRYPT_OFF_CT (TLS_CRYPT_OFF_TAG + TLS_CRYPT_TAG_SIZE)
 
+#define TLS_CRYPT_V2_MAX_WKC_LEN (1024)
+#define TLS_CRYPT_V2_CLIENT_KEY_LEN (2048/8)
+#define TLS_CRYPT_V2_SERVER_KEY_LEN (sizeof(struct key))
+#define TLS_CRYPT_V2_TAG_SIZE (TLS_CRYPT_TAG_SIZE)
+#define TLS_CRYPT_V2_MAX_METADATA_LEN (unsigned) (TLS_CRYPT_V2_MAX_WKC_LEN \
+         - (TLS_CRYPT_V2_CLIENT_KEY_LEN + TLS_CRYPT_V2_TAG_SIZE + sizeof(uint16_t)))
+#define TLS_CRYPT_V2_MAX_B64_METADATA_LEN \
+         ((((TLS_CRYPT_V2_MAX_METADATA_LEN - 1) * 8) + 5) / 6)
+
  /**
   * Initialize a key_ctx_bi structure for use with --tls-crypt.
   *
@@ -138,6 +153,56 @@ bool tls_crypt_wrap(const struct buffer *src, struct buffer *dst,
  bool tls_crypt_unwrap(const struct buffer *src, struct buffer *dst,
                        struct crypto_options *opt);
 
+/**
+ * Initialize a tls-crypt-v2 server key (used to encrypt/decrypt client keys).
+ *
+ * @param key           Key structure to be initialized.  Must be non-NULL.
+ * @parem encrypt       If true, initialize the key structure for encryption,
+ *                      otherwise for decryption.
+ * @param key_file      File path of the key file to load, or INLINE tag.
+ * @param key_inline    Inline key file contents (or NULL if not inline).
+ */
+void tls_crypt_v2_init_server_key(struct key_ctx *key_ctx, bool encrypt,

src/openvpn/tls_crypt.h bool tls_crypt_wrap(const struct buffer *src, struct buffer *dst, · line 175

Quoted 10 lines
src/openvpn/tls_crypt.h +10 -0
+                                  const char *key_file, const char *key_inline);
+
+/**
+ * Initialize a tls-crypt-v2 client key.
+ *
+ * @param key               Key structure to be initialized with the client
+ *                          key.
+ * @param wrapped_key_buf   Returns buffer containing the wrapped key that will
+ *                          be sent to the server when connecting. Caller must
+ *                          free this buffer when no longer neede.

neede --> needed

Quoted 91 lines
src/openvpn/tls_crypt.h +30 -0
+ * @param key_file          File path of the key file to load, or INLINE tag.
+ * @param key_inline        Inline key file contents (or NULL if not inline).
+ */
+void tls_crypt_v2_init_client_key(struct key_ctx_bi *key,
+                                  struct buffer *wrapped_key_buf,
+                                  const char *key_file,
+                                  const char *key_inline);
+
+/**
+ * Generate a tls-crypt-v2 server key, and write to file.
+ *
+ * @param filename          Filename of the server key file to create.
+ */
+void tls_crypt_v2_write_server_key_file(const char *filename);
+
+/**
+ * Generate a tls-crypt-v2 client key, and write to file.
+ *
+ * @param filename          Filename of the client key file to create.
+ * @param b64_metadata      Base64 metadata to be included in the client key.
+ * @param server_key_file   File path of the server key to use for wrapping the
+ *                          client key, or INLINE tag.
+ * @param server_key_inline Inline server key file contents (or NULL if not
+ *                          inline).
+ */
+void tls_crypt_v2_write_client_key_file(const char *filename,
+                                        const char *b64_metadata,
+                                        const char *key_file,
+                                        const char *key_inline);
+
  /** @} */
 
  #endif /* TLSCRYPT_H */
tests/t_lpback.sh +37 -3
index 2052c62..fb43211 100755
@@ -21,8 +21,8 @@
 
  set -eu
  top_builddir="${top_builddir:-..}"
-trap "rm -f key.$$ log.$$ ; trap 0 ; exit 77" 1 2 15
-trap "rm -f key.$$ log.$$ ; exit 1" 0 3
+trap "rm -f key.$$ tc-server-key.$$ tc-client-key.$$ log.$$ ; trap 0 ; exit 77" 1 2 15
+trap "rm -f key.$$ tc-server-key.$$ tc-client-key.$$ log.$$ ; exit 1" 0 3
 
  # Get list of supported ciphers from openvpn --show-ciphers output
  CIPHERS=$(${top_builddir}/src/openvpn/openvpn --show-ciphers | \
@@ -55,6 +55,40 @@ do
      fi
  done
 
-rm key.$$ log.$$
+echo -n "Testing tls-crypt-v2 server key generation..."
+"${top_builddir}/src/openvpn/openvpn" \
+    --tls-crypt-v2-genkey server tc-server-key.$$ >log.$$ 2>&1
+if [ $? != 0 ] ; then
+    echo "FAILED"
+    cat log.$$
+    e=1
+else
+    echo "OK"
+fi
+
+echo -n "Testing tls-crypt-v2 key generation (no metadata)..."
+"${top_builddir}/src/openvpn/openvpn" --tls-crypt-v2 tc-server-key.$$ \
+    --tls-crypt-v2-genkey client tc-client-key.$$ >log.$$ 2>&1
+if [ $? != 0 ] ; then
+    echo "FAILED"
+    cat log.$$
+    e=1
+else
+    echo "OK"
+fi
+
+echo -n "Testing tls-crypt-v2 key generation (max length metadata)..."
+"${top_builddir}/src/openvpn/openvpn" --tls-crypt-v2 tc-server-key.$$ \
+    --tls-crypt-v2-genkey client tc-client-key.$$ \
+    $(head -c732 /dev/zero | base64 -w0) >log.$$ 2>&1
+if [ $? != 0 ] ; then
+    echo "FAILED"
+    cat log.$$
+    e=1
+else
+    echo "OK"
+fi
+
+rm key.$$ tc-server-key.$$ tc-client-key.$$ log.$$
  trap 0
  exit $e
#61 lore raw

Re: [Openvpn-devel] [PATCH v3 3/7] tls-crypt-v2: generate client keys

Hi,

Quoted 7 lines · tincanteksup

Review options_postprocess_verify_ce(const struct

Quoted 1 line · Steffan Karger
(quoted diff) +0 -0
@@ -2764,6 +2781,7 @@ options_postprocess_verify_ce(const struct

options *options, const struct connec

connec --> connect ? (This is code so I just highlight for your consideration, I don't know)

This is just the patch file truncating a line. The code says "const struct connection_entry *ce".

Quoted 5 lines

src/openvpn/tls_crypt.h bool tls_crypt_wrap(const struct buffer *src, struct buffer *dst,

Quoted 4 lines
src/openvpn/tls_crypt.h +4 -0
+/**
+ * Initialize a tls-crypt-v2 client key.
+ *
+ * @param key               Key structure to be initialized with the

client

Quoted 8 lines

Review options_postprocess_verify_ce(const struct · line 2786

Quoted 2 lines
(quoted diff) +2 -0
+ *                          key.
+ * @param wrapped_key_buf   Returns buffer containing the wrapped key

that will

Review options_postprocess_verify_ce(const struct · line 2787

Quoted 1 line
(quoted diff) +1 -0
+ *                          be sent to the server when connecting.

Caller must

Quoted 1 line

+ *                          free this buffer when no longer neede.

neede --> needed

Yes, will fix in v4!

Thanks,
-Steffan

#62 lore raw

PATCH v3 4/7 tls-crypt-v2: add unwrap_client_key

In reply to Steffan Karger

Add helper functions to unwrap tls-crypt-v2 client keys.

---
v3: Include length in WKc

 src/openvpn/buffer.h                      |   7 +
 src/openvpn/tls_crypt.c                   | 120 ++++++++++++++
 tests/unit_tests/openvpn/test_tls_crypt.c | 253 +++++++++++++++++++++++++++---
 3 files changed, 360 insertions(+), 20 deletions(-)
src/openvpn/buffer.h +7 -0
index 180dd56..0c730ae 100644
@@ -830,6 +830,13 @@ buf_read_u32(struct buffer *buf, bool *good)
     }
 }
 
+/** Return true if buffer contents are equal */
+static inline bool
+buf_equal(const struct buffer *a, const struct buffer *b)
+{
+    return BLEN(a) == BLEN(b) && 0 == memcmp(BPTR(a), BPTR(b), BLEN(a));
+}
+
 /**
  * Compare src buffer contents with match.
  * *NOT* constant time. Do not use when comparing HMACs.
src/openvpn/tls_crypt.c +120 -0

132 lines not on this page · raw message

tests/unit_tests/openvpn/test_tls_crypt.c +233 -20

334 lines not on this page · raw message

Signature · 1 line
2.7.4
#63 lore raw

PATCH v3 5/7 tls-crypt-v2: add P_CONTROL_HARD_RESET_CLIENT_V3 opcode

In reply to Steffan Karger

Not used yet, but prepare for sending and receiving tls-crypt-v2 handshake messages.

---
v3: rebase on curent master / v3 patch set

 src/openvpn/ps.c         |  3 ++-
 src/openvpn/ssl.c        | 23 ++++++++++++++++++-----
 src/openvpn/ssl.h        |  5 ++++-
 src/openvpn/ssl_common.h |  2 ++
 4 files changed, 26 insertions(+), 7 deletions(-)
src/openvpn/ps.c +2 -1
index 25ab374..adec072 100644
@@ -985,7 +985,8 @@ is_openvpn_protocol(const struct buffer *buf)
     {
         return p[0] == 0
                && p[1] >= 14
-               && p[2] == (P_CONTROL_HARD_RESET_CLIENT_V2<<P_OPCODE_SHIFT);
+               && (p[2] == (P_CONTROL_HARD_RESET_CLIENT_V2<<P_OPCODE_SHIFT)
+                   || p[2] == (P_CONTROL_HARD_RESET_CLIENT_V3<<P_OPCODE_SHIFT));
     }
     else if (len >= 2)
     {
src/openvpn/ssl.c +18 -5
index dcb5445..757754f 100644
@@ -792,6 +792,9 @@ packet_opcode_name(int op)
         case P_CONTROL_HARD_RESET_SERVER_V2:
             return "P_CONTROL_HARD_RESET_SERVER_V2";
 
+        case P_CONTROL_HARD_RESET_CLIENT_V3:
+            return "P_CONTROL_HARD_RESET_CLIENT_V3";
+
         case P_CONTROL_SOFT_RESET_V1:
             return "P_CONTROL_SOFT_RESET_V1";
 
@@ -864,7 +867,8 @@ is_hard_reset(int op, int key_method)
 
     if (!key_method || key_method >= 2)
     {
-        if (op == P_CONTROL_HARD_RESET_CLIENT_V2 || op == P_CONTROL_HARD_RESET_SERVER_V2)
+        if (op == P_CONTROL_HARD_RESET_CLIENT_V2 || op == P_CONTROL_HARD_RESET_SERVER_V2
+            || op == P_CONTROL_HARD_RESET_CLIENT_V3)
         {
             return true;
         }
@@ -1095,8 +1099,15 @@ tls_session_init(struct tls_multi *multi, struct tls_session *session)
     }
     else /* session->opt->key_method >= 2 */
     {
-        session->initial_opcode = session->opt->server ?
-                                  P_CONTROL_HARD_RESET_SERVER_V2 : P_CONTROL_HARD_RESET_CLIENT_V2;
+        if (session->opt->server)
+        {
+            session->initial_opcode = P_CONTROL_HARD_RESET_SERVER_V2;
+        }
+        else
+        {
+            session->initial_opcode = session->opt->tls_crypt_v2 ?
+                    P_CONTROL_HARD_RESET_CLIENT_V3 : P_CONTROL_HARD_RESET_CLIENT_V2;
+        }
     }
 
     /* Initialize control channel authentication parameters */
@@ -3427,7 +3438,8 @@ tls_pre_decrypt(struct tls_multi *multi,
             {
                 /* verify client -> server or server -> client connection */
                 if (((op == P_CONTROL_HARD_RESET_CLIENT_V1
-                      || op == P_CONTROL_HARD_RESET_CLIENT_V2) && !multi->opt.server)
+                      || op == P_CONTROL_HARD_RESET_CLIENT_V2
+                      || op == P_CONTROL_HARD_RESET_CLIENT_V3) && !multi->opt.server)
                     || ((op == P_CONTROL_HARD_RESET_SERVER_V1
                          || op == P_CONTROL_HARD_RESET_SERVER_V2) && multi->opt.server))
                 {
@@ -3812,7 +3824,8 @@ tls_pre_decrypt_lite(const struct tls_auth_standalone *tas,
         /* this packet is from an as-yet untrusted source, so
          * scrutinize carefully */
 
-        if (op != P_CONTROL_HARD_RESET_CLIENT_V2)
+        if (op != P_CONTROL_HARD_RESET_CLIENT_V2
+            && op != P_CONTROL_HARD_RESET_CLIENT_V3)
         {
             /*
              * This can occur due to bogus data or DoS packets.
src/openvpn/ssl.h +4 -1
index 72227d9..852debd 100644
@@ -63,9 +63,12 @@
 #define P_CONTROL_HARD_RESET_CLIENT_V2 7     /* initial key from client, forget previous state */
 #define P_CONTROL_HARD_RESET_SERVER_V2 8     /* initial key from server, forget previous state */
 
+/* indicates key_method >= 2 and client-specific tls-crypt key */
+#define P_CONTROL_HARD_RESET_CLIENT_V3 10    /* initial key from client, forget previous state */
+
 /* define the range of legal opcodes */
 #define P_FIRST_OPCODE                 1
-#define P_LAST_OPCODE                  9
+#define P_LAST_OPCODE                  10
 
 /*
  * Set the max number of acknowledgments that can "hitch a ride" on an outgoing
src/openvpn/ssl_common.h +2 -0
index 08ef6ff..e3c852a 100644
@@ -286,6 +286,8 @@ struct tls_options
     const char *config_authname;
     bool ncp_enabled;
 
+    bool tls_crypt_v2;
+
     /** TLS handshake wrapping state */
     struct tls_wrap_ctx tls_wrap;
 
Signature · 1 line
2.7.4
#64 lore raw

PATCH v3 6/7 tls-crypt-v2: implement tls-crypt-v2 handshake

In reply to Steffan Karger

This makes clients send-and-use, and servers receive-unwrap-and-use tls-crypt-v2 client keys, which completes the on-the-wire work.

---
v3: include length in WKc, rebase on curent master / v3 patch set

 src/openvpn/init.c                        | 41 +++++++++++++++-
 src/openvpn/openvpn.h                     |  2 +
 src/openvpn/options.c                     |  6 +--
 src/openvpn/ssl.c                         | 79 ++++++++++++++++++++++++-------
 src/openvpn/ssl_common.h                  |  6 +++
 src/openvpn/tls_crypt.c                   | 57 ++++++++++++++++++++++
 src/openvpn/tls_crypt.h                   | 14 ++++++
 tests/unit_tests/openvpn/Makefile.am      |  2 +-
 tests/unit_tests/openvpn/test_tls_crypt.c |  6 +++
 9 files changed, 192 insertions(+), 21 deletions(-)
src/openvpn/init.c +40 -1
index ef7b422..01667c3 100644
@@ -2435,6 +2435,11 @@ key_schedule_free(struct key_schedule *ks, bool free_ssl_ctx)
     if (tls_ctx_initialised(&ks->ssl_ctx) && free_ssl_ctx)
     {
         tls_ctx_free(&ks->ssl_ctx);
+	/* XXX TODO merge conflict here - is this still correct? */
+        free_key_ctx_bi(&ks->tls_wrap_key);
+        free_key_ctx(&ks->tls_crypt_v2_server_key);
+        buf_clear(&ks->tls_crypt_v2_wkc);
+        free_buf(&ks->tls_crypt_v2_wkc);
     }
     CLEAR(*ks);
 }
@@ -2618,6 +2623,24 @@ do_init_crypto_tls_c1(struct context *c)
         /* initialize tls-auth/crypt key */
         do_init_tls_wrap_key(c);
 
+        /* tls-crypt with client-specific keys (--tls-crypt-v2) */
+        if (options->tls_crypt_v2_file)
+        {
+            if (options->tls_server)
+            {
+                tls_crypt_v2_init_server_key(&c->c1.ks.tls_crypt_v2_server_key,
+                                             true, options->tls_crypt_v2_file,
+                                             options->tls_crypt_v2_inline);
+            }
+            else
+            {
+                tls_crypt_v2_init_client_key(&c->c1.ks.tls_wrap_key,
+                                             &c->c1.ks.tls_crypt_v2_wkc,
+                                             options->tls_crypt_v2_file,
+                                             options->tls_crypt_v2_inline);
+            }
+        }
+
         c->c1.ciphername = options->ciphername;
         c->c1.authname = options->authname;
         c->c1.keysize = options->keysize;
@@ -2843,13 +2866,28 @@ do_init_crypto_tls(struct context *c, const unsigned int flags)
     }
 
     /* TLS handshake encryption (--tls-crypt) */
-    if (options->ce.tls_crypt_file)
+    if (options->ce.tls_crypt_file
+        || (options->ce.tls_crypt_v2_file && options->tls_client))
     {
         to.tls_wrap.mode = TLS_WRAP_CRYPT;
         to.tls_wrap.opt.key_ctx_bi = c->c1.ks.tls_wrap_key;
         to.tls_wrap.opt.pid_persist = &c->c1.pid_persist;
         to.tls_wrap.opt.flags |= CO_PACKET_ID_LONG_FORM;
         tls_crypt_adjust_frame_parameters(&to.frame);
+
+        if (options->tls_crypt_v2_file)
+        {
+            to.tls_wrap.tls_crypt_v2_wkc = &c->c1.ks.tls_crypt_v2_wkc;
+        }
+    }
+
+    if (options->tls_crypt_v2_file)
+    {
+        to.tls_crypt_v2 = true;
+        if (options->tls_server)
+        {
+            to.tls_wrap.tls_crypt_v2_server_key = c->c1.ks.tls_crypt_v2_server_key;
+        }
     }
 
     /* If we are running over TCP, allow for
@@ -4386,6 +4424,7 @@ inherit_context_child(struct context *dest,
     dest->c1.ks.ssl_ctx = src->c1.ks.ssl_ctx;
     dest->c1.ks.tls_wrap_key = src->c1.ks.tls_wrap_key;
     dest->c1.ks.tls_auth_key_type = src->c1.ks.tls_auth_key_type;
+    dest->c1.ks.tls_crypt_v2_server_key = src->c1.ks.tls_crypt_v2_server_key;
     /* inherit pre-NCP ciphers */
     dest->c1.ciphername = src->c1.ciphername;
     dest->c1.authname = src->c1.authname;
src/openvpn/openvpn.h +2 -0
index c5c767c..96cff87 100644
@@ -66,6 +66,8 @@ struct key_schedule
     /* optional TLS control channel wrapping */
     struct key_type tls_auth_key_type;
     struct key_ctx_bi tls_wrap_key;
+    struct key_ctx tls_crypt_v2_server_key;
+    struct buffer tls_crypt_v2_wkc;             /**< Wrapped client key */
 };
 
 /*
src/openvpn/options.c +3 -3
index acab042..8ce5cf4 100644
@@ -2742,10 +2742,10 @@ options_postprocess_verify_ce(const struct options *options, const struct connec
         {
             msg(M_USAGE, "--tls-auth and --tls-crypt are mutually exclusive");
         }
-        if (options->tls_client && ((ce->tls_auth_file && ce->tls_crypt_v2_file)
-                || (ce->tls_crypt_file && ce->tls_crypt_v2_file)))
+        if (options->client && options->ce.tls_crypt_v2_file
+            && (options->ce.tls_auth_file || options->ce.tls_crypt_file))
         {
-            msg(M_USAGE, "--tls-auth, --tls-crypt and --tls-crypt-v2 are mutually exclusive in client mode");
+            msg(M_USAGE, "--tls-crypt-v2, --tls-auth and --tls-crypt are mutually exclusive in client mode");
         }
     }
     else
src/openvpn/ssl.c +63 -16

126 lines not on this page · raw message

src/openvpn/ssl_common.h +6 -0
index e3c852a..d744881 100644
@@ -213,6 +213,12 @@ struct tls_wrap_ctx
     } mode;                     /**< Control channel wrapping mode */
     struct crypto_options opt;  /**< Crypto state */
     struct buffer work;         /**< Work buffer (only for --tls-crypt) */
+    struct key_ctx tls_crypt_v2_server_key;  /**< Decrypts client keys */
+    const struct buffer *tls_crypt_v2_wkc;   /**< Wrapped client key,
+                                                  sent to server */
+    struct buffer tls_crypt_v2_metadata;     /**< Received from client */
+    bool cleanup_key_ctx;                    /**< opt.key_ctx_bi is owned by
+                                                  this context */
 };
 
 /*
src/openvpn/tls_crypt.c +57 -0

69 lines not on this page · raw message

src/openvpn/tls_crypt.h +14 -0
index 6513836..edee590 100644
@@ -83,6 +83,7 @@
 #include "buffer.h"
 #include "crypto.h"
 #include "session_id.h"
+#include "ssl_common.h"
 
 #define TLS_CRYPT_TAG_SIZE (256/8)
 #define TLS_CRYPT_PID_SIZE (sizeof(packet_id_type) + sizeof(net_time_t))
@@ -182,6 +183,19 @@ void tls_crypt_v2_init_client_key(struct key_ctx_bi *key,
                                   const char *key_inline);
 
 /**
+ * Extract a tls-crypt-v2 client key from a P_CONTROL_HARD_RESET_CLIENT_V3
+ * message, and load the key into the supplied tls wrap context.
+ *
+ * @param buf   Buffer containing a received P_CONTROL_HARD_RESET_CLIENT_V3
+ *              message.
+ * @param ctx   tls-wrap context to be initialized with the client key.
+ *
+ * @returns true if a key was successfully extracted.
+ */
+bool tls_crypt_v2_extract_client_key(struct buffer *buf,
+                                     struct tls_wrap_ctx *ctx);
+
+/**
  * Generate a tls-crypt-v2 server key, and write to file.
  *
  * @param filename          Filename of the server key file to create.
tests/unit_tests/openvpn/Makefile.am +1 -1
index 0f7f86b..6e7fa20 100644
@@ -58,7 +58,7 @@ packet_id_testdriver_SOURCES = test_packet_id.c mock_msg.c \
 
 tls_crypt_testdriver_CFLAGS  = @TEST_CFLAGS@ \
 	-I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \
-	$(OPTIONAL_CRYPTO_CFLAGS)
+	$(OPTIONAL_CRYPTO_CFLAGS) $(OPTIONAL_PKCS11_HELPER_CFLAGS)
 tls_crypt_testdriver_LDFLAGS = @TEST_LDFLAGS@ \
 	$(OPTIONAL_CRYPTO_LIBS)
 tls_crypt_testdriver_SOURCES = test_tls_crypt.c mock_msg.c \
tests/unit_tests/openvpn/test_tls_crypt.c +6 -0
index f53255c..22c7486 100644
@@ -345,6 +345,12 @@ tls_crypt_v2_wrap_unwrap_max_metadata(void **state) {
     assert_true(0 == memcmp(ctx->client_key2.keys, unwrapped_client_key2.keys,
                             sizeof(ctx->client_key2.keys)));
     assert_true(buf_equal(&ctx->metadata, &unwrap_metadata));
+
+    struct tls_wrap_ctx wrap_ctx = {
+            .mode = TLS_WRAP_CRYPT,
+            .tls_crypt_v2_server_key = ctx->server_keys.encrypt,
+    };
+    assert_true(tls_crypt_v2_extract_client_key(&ctx->wkc, &wrap_ctx));
 }
 
 /**
Signature · 1 line
2.7.4
#65 lore raw

PATCH v3 7/7 tls-crypt-v2: add script hook to verify metadata

In reply to Steffan Karger

To allow rejecting incoming connections very early in the handshake, add a --tls-crypt-v2-verify option that allows administators to run an external command to verify the metadata from the client key. See doc/tls-crypt-v2.txt for more details.

Because of the extra dependencies, this requires adding a mock parse_line() to the tls-crypt unit tests.

---
v3: rebase on curent master / v3 patch set

 Changes.rst                               | 12 ++++++
 doc/openvpn.8                             | 35 ++++++++++++++--
 src/openvpn/init.c                        |  1 +
 src/openvpn/options.c                     |  7 ++++
 src/openvpn/options.h                     |  2 +
 src/openvpn/ssl.c                         | 16 ++++---
 src/openvpn/ssl_common.h                  |  1 +
 src/openvpn/tls_crypt.c                   | 69 ++++++++++++++++++++++++++++++-
 src/openvpn/tls_crypt.h                   |  3 +-
 tests/unit_tests/openvpn/Makefile.am      | 12 ++++--
 tests/unit_tests/openvpn/test_tls_crypt.c | 18 +++++++-
 11 files changed, 161 insertions(+), 15 deletions(-)
Changes.rst +12 -0
index a6090cf..e77b3d7 100644
@@ -1,3 +1,15 @@
+Overview of changes in 2.5
+==========================
+
+New features
+------------
+Client-specific tls-crypt keys (``--tls-crypt-v2``)
+    ``tls-crypt-v2`` adds the ability to supply each client with a unique
+    tls-crypt key.  This allows large organisations and VPN providers to profit
+    from the same DoS and TLS stack protection that small deployments can
+    already achieve using ``tls-auth`` or ``tls-crypt``.
+
+
 Overview of changes in 2.4
 ==========================
 
doc/openvpn.8 +32 -3
index 597c0c4..cb61a53 100644
@@ -5296,9 +5296,38 @@ If supplied, include the supplied
 in the wrapped client key.  This metadata must be supplied in base64\-encoded
 form.  The metadata must be at most 735 bytes long (980 bytes in base64).
 
-.B TODO
-Metadata handling is not yet implemented.  This text will be updated by the
-commit that introduces metadata handling.
+If no metadata is supplied, OpenVPN will use a 64-bit unix timestamp
+representing the current time in UTC, encoded in network order, as metadata for
+the generated key.
+
+Servers can use
+.B \-\-tls\-crypt\-v2\-verify
+to specify a metadata verification command.
+.\"*********************************************************
+.TP
+.B \-\-tls\-crypt\-v2\-verify cmd
+
+Run command
+.B cmd
+to verify the metadata of the client-specific tls-crypt-v2 key of a connecting
+client.  This allows server administrators to reject client connections, before
+exposing the TLS stack (including the notoriously dangerous X.509 and ASN.1
+stacks) to the connecting client.
+
+OpenVPN supplies the following env vars to the command:
+.RS
+.IP \[bu] 2
+script_type is set to "tls-crypt-v2-verify"
+.IP \[bu]
+metadata_type is set to "0" is the metadata was user supplied, or "1" if it's a
+64-bit unix timestamp representing the key creation time.
+.IP \[bu]
+metadata_file contains the filename of a temporary file that contains the client
+metadata.
+.RE
+
+.IP
+The command can reject the connection by exitingwith a non-zero exit code.
 .\"*********************************************************
 .TP
 .B \-\-askpass [file]
src/openvpn/init.c +1 -0
index 01667c3..a349d42 100644
@@ -2887,6 +2887,7 @@ do_init_crypto_tls(struct context *c, const unsigned int flags)
         if (options->tls_server)
         {
             to.tls_wrap.tls_crypt_v2_server_key = c->c1.ks.tls_crypt_v2_server_key;
+            to.tls_crypt_v2_verify_script = c->options.tls_crypt_v2_verify_script;
         }
     }
 
src/openvpn/options.c +7 -0
index 8ce5cf4..bd324fd 100644
@@ -629,6 +629,8 @@ static const char usage_message[] =
     "--tls-crypt-v2-genkey client|server keyfile [base64 metadata]: Generate a\n"
     "                  fresh tls-crypt-v2 client or server key, and store to\n"
     "                  keyfile.  If supplied, include metadata in wrapped key.\n"
+    "--tls-crypt-v2-verify cmd : Run command cmd to verify the metadata of the\n"
+    "                  client-supplied tls-crypt-v2 client key\n"
     "--askpass [file]: Get PEM password from controlling tty before we daemonize.\n"
     "--auth-nocache  : Don't cache --askpass or --auth-user-pass passwords.\n"
     "--crl-verify crl ['dir']: Check peer certificate against a CRL.\n"
@@ -8176,6 +8178,11 @@ add_option(struct options *options,
             options->tls_crypt_v2_metadata = p[3];
         }
     }
+    else if (streq(p[0], "tls-crypt-v2-verify") && p[1] && !p[2])
+    {
+        VERIFY_PERMISSION(OPT_P_GENERAL);
+        options->tls_crypt_v2_verify_script = p[1];
+    }
     else if (streq(p[0], "key-method") && p[1] && !p[2])
     {
         int key_method;
src/openvpn/options.h +2 -0
index 3d2c770..d5ceb18 100644
@@ -590,6 +590,8 @@ struct options
     const char *tls_crypt_v2_genkey_file;
     const char *tls_crypt_v2_metadata;
 
+    const char *tls_crypt_v2_verify_script;
+
     /* Allow only one session */
     bool single_session;
 
src/openvpn/ssl.c +10 -6
index dcae279..4cf7619 100644
@@ -1541,14 +1541,15 @@ write_control_auth(struct tls_session *session,
 static bool
 read_control_auth(struct buffer *buf,
                   struct tls_wrap_ctx *ctx,
-                  const struct link_socket_actual *from)
+                  const struct link_socket_actual *from,
+                  const struct tls_options *opt)
 {
     struct gc_arena gc = gc_new();
     bool ret = false;
 
     const uint8_t opcode = *(BPTR(buf)) >> P_OPCODE_SHIFT;
     if (opcode == P_CONTROL_HARD_RESET_CLIENT_V3
-        && !tls_crypt_v2_extract_client_key(buf, ctx))
+        && !tls_crypt_v2_extract_client_key(buf, ctx, opt))
     {
         msg (D_TLS_ERRORS,
              "TLS Error: can not extract tls-crypt-v2 client key from %s",
@@ -3629,7 +3630,8 @@ tls_pre_decrypt(struct tls_multi *multi,
                     goto error;
                 }
 
-                if (!read_control_auth(buf, &session->tls_wrap, from))
+                if (!read_control_auth(buf, &session->tls_wrap, from,
+                                       session->opt))
                 {
                     goto error;
                 }
@@ -3682,7 +3684,8 @@ tls_pre_decrypt(struct tls_multi *multi,
                 if (op == P_CONTROL_SOFT_RESET_V1
                     && DECRYPT_KEY_ENABLED(multi, ks))
                 {
-                    if (!read_control_auth(buf, &session->tls_wrap, from))
+                    if (!read_control_auth(buf, &session->tls_wrap, from,
+                                           session->opt))
                     {
                         goto error;
                     }
@@ -3703,7 +3706,8 @@ tls_pre_decrypt(struct tls_multi *multi,
                         do_burst = true;
                     }
 
-                    if (!read_control_auth(buf, &session->tls_wrap, from))
+                    if (!read_control_auth(buf, &session->tls_wrap, from,
+                                           session->opt))
                     {
                         goto error;
                     }
@@ -3905,7 +3909,7 @@ tls_pre_decrypt_lite(const struct tls_auth_standalone *tas,
             bool status;
 
             /* HMAC test, if --tls-auth was specified */
-            status = read_control_auth(&newbuf, &tls_wrap_tmp, from);
+            status = read_control_auth(&newbuf, &tls_wrap_tmp, from, NULL);
             free_buf(&newbuf);
             if (tls_wrap_tmp.cleanup_key_ctx)
             {
src/openvpn/ssl_common.h +1 -0
index d744881..923cd95 100644
@@ -293,6 +293,7 @@ struct tls_options
     bool ncp_enabled;
 
     bool tls_crypt_v2;
+    const char *tls_crypt_v2_verify_script;
 
     /** TLS handshake wrapping state */
     struct tls_wrap_ctx tls_wrap;
src/openvpn/tls_crypt.c +68 -1

92 lines not on this page · raw message

src/openvpn/tls_crypt.h +2 -1
index edee590..3dc2661 100644
@@ -193,7 +193,8 @@ void tls_crypt_v2_init_client_key(struct key_ctx_bi *key,
  * @returns true if a key was successfully extracted.
  */
 bool tls_crypt_v2_extract_client_key(struct buffer *buf,
-                                     struct tls_wrap_ctx *ctx);
+                                     struct tls_wrap_ctx *ctx,
+                                     const struct tls_options *opt);
 
 /**
  * Generate a tls-crypt-v2 server key, and write to file.
tests/unit_tests/openvpn/Makefile.am +9 -3
index 6e7fa20..e5ffba9 100644
@@ -6,7 +6,10 @@ if HAVE_LD_WRAP_SUPPORT
 check_PROGRAMS += argv_testdriver buffer_testdriver
 endif
 
-check_PROGRAMS += crypto_testdriver packet_id_testdriver tls_crypt_testdriver
+check_PROGRAMS += crypto_testdriver packet_id_testdriver
+if HAVE_LD_WRAP_SUPPORT
+check_PROGRAMS += tls_crypt_testdriver
+endif
 
 TESTS = $(check_PROGRAMS)
 
@@ -59,14 +62,17 @@ packet_id_testdriver_SOURCES = test_packet_id.c mock_msg.c \
 tls_crypt_testdriver_CFLAGS  = @TEST_CFLAGS@ \
 	-I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \
 	$(OPTIONAL_CRYPTO_CFLAGS) $(OPTIONAL_PKCS11_HELPER_CFLAGS)
-tls_crypt_testdriver_LDFLAGS = @TEST_LDFLAGS@ \
+tls_crypt_testdriver_LDFLAGS = @TEST_LDFLAGS@ -Wl,--wrap=parse_line \
 	$(OPTIONAL_CRYPTO_LIBS)
 tls_crypt_testdriver_SOURCES = test_tls_crypt.c mock_msg.c \
+	$(openvpn_srcdir)/argv.c \
 	$(openvpn_srcdir)/base64.c \
 	$(openvpn_srcdir)/buffer.c \
 	$(openvpn_srcdir)/crypto.c \
 	$(openvpn_srcdir)/crypto_mbedtls.c \
 	$(openvpn_srcdir)/crypto_openssl.c \
+	$(openvpn_srcdir)/env_set.c \
 	$(openvpn_srcdir)/otime.c \
 	$(openvpn_srcdir)/packet_id.c \
-	$(openvpn_srcdir)/platform.c
+	$(openvpn_srcdir)/platform.c \
+	$(openvpn_srcdir)/run_command.c
tests/unit_tests/openvpn/test_tls_crypt.c +17 -1
index 22c7486..897adce 100644
@@ -43,8 +43,24 @@
 
 #define TESTBUF_SIZE            128
 
+/* Defines for use in the tests and the mock parse_line() */
+#define PATH1       "/s p a c e"
+#define PATH2       "/foo bar/baz"
+#define PARAM1      "param1"
+#define PARAM2      "param two"
+
 const char plaintext_short[1];
 
+int
+__wrap_parse_line(const char *line, char **p, const int n, const char *file,
+                  const int line_num, int msglevel, struct gc_arena *gc)
+{
+    p[0] = PATH1 PATH2;
+    p[1] = PARAM1;
+    p[2] = PARAM2;
+    return 3;
+}
+
 struct test_tls_crypt_context {
     struct crypto_options co;
     struct key_type kt;
@@ -350,7 +366,7 @@ tls_crypt_v2_wrap_unwrap_max_metadata(void **state) {
             .mode = TLS_WRAP_CRYPT,
             .tls_crypt_v2_server_key = ctx->server_keys.encrypt,
     };
-    assert_true(tls_crypt_v2_extract_client_key(&ctx->wkc, &wrap_ctx));
+    assert_true(tls_crypt_v2_extract_client_key(&ctx->wkc, &wrap_ctx, NULL));
 }
 
 /**
Signature · 1 line
2.7.4
#66 lore raw

Re: [Openvpn-devel] [PATCH v3 1/7] Introduce buffer_write_file()

In reply to Steffan Karger

Hi,

Quoted 18 lines · Steffan Karger

Rewrite buf_write_string_file to buffer_write_file, which is simpler to use and can deal with not-null-terminated strings. Mostly implemented so this can be easily reused for tls-crypt-v2 (client) key files.

---
v3: split change out of "generate client key", reuse in write_key_file()

 src/openvpn/buffer.c | 31 ++++++++++++++++++++++++-------
 src/openvpn/buffer.h |  7 ++-----
 src/openvpn/crypto.c | 30 +++++-------------------------
 3 files changed, 31 insertions(+), 37 deletions(-)
src/openvpn/buffer.c +0 -0
index 0972139..20e2b9c 100644

src/openvpn/buffer.c convert_to_one_line(struct buffer *buf) · line 347

Quoted 8 lines
src/openvpn/buffer.c +2 -3
@@ -343,16 +343,33 @@ convert_to_one_line(struct buffer *buf)
     }
 }
 
-/* NOTE: requires that string be null terminated */
-void
-buf_write_string_file(const struct buffer *buf, const char *filename, int fd)
+bool
+buffer_write_file(const char *filename, const struct buffer *buf)

Since you are removing the old comments from both function declaration and definition, why not adding some little doxygen style doc?

I see you are also changing the way this method works (now it takes only the filename instead of a fd), but given that we currently use it only once in our code (for now), I think this change is ok and actually makes the logic more self-contained (no need for the caller to deal with the fd at all).

Quoted 14 lines
src/openvpn/buffer.c +10 -3
 {
-    const int len = strlen((char *) BPTR(buf));
-    const int size = write(fd, BPTR(buf), len);
-    if (size != len)
+    bool ret = false;
+    int fd = platform_open(filename, O_CREAT | O_TRUNC | O_WRONLY,
+                           S_IRUSR | S_IWUSR);
+    if (fd == -1)
+    {
+        msg(M_ERRNO, "Cannot open file '%s' for write", filename);
+        goto cleanup;
+    }
+
+    const int size = write(fd, BPTR(buf), BLEN(buf));

src/openvpn/buffer.c convert_to_one_line(struct buffer *buf) · line 367

Quoted 10 lines
src/openvpn/buffer.c +8 -1
+    if (size != BLEN(buf))
     {
-        msg(M_ERR, "Write error on file '%s'", filename);
+        msg(M_ERRNO, "Write error on file '%s'", filename);
+        goto cleanup;
+    }
+
+    ret = true;
+cleanup:
+    if (close(fd))

nitpikcing: I'd dare to say that in the rest of the code we prefer to use "func() < 0" as check, to make it more explicit that we are looking for errors (my personal feeling is that when i see something like your expression I always need to double check "does this function return something positive too? what in case of errors?").

On top of that consistency is always good :-)

Quoted 82 lines
src/openvpn/buffer.c +4 -0
+    {
+        msg(M_ERRNO, "Close error on file %s", filename);
+        ret = false;
     }
+    return ret;
 }
 
 /*
src/openvpn/buffer.h +2 -5
index 6b6025e..180dd56 100644
@@ -469,11 +469,8 @@ const char *skip_leading_whitespace(const char *str);
 
 void string_null_terminate(char *str, int len, int capacity);
 
-/*
- * Write string in buf to file descriptor fd.
- * NOTE: requires that string be null terminated.
- */
-void buf_write_string_file(const struct buffer *buf, const char *filename, int fd);
+/** Write buffer contents to file */
+bool buffer_write_file(const char *filename, const struct buffer *buf);
 
 /*
  * write a string to the end of a buffer that was
src/openvpn/crypto.c +4 -19
index 5381ef2..35bd243 100644
@@ -1427,27 +1427,19 @@ write_key_file(const int nkeys, const char *filename)
 {
     struct gc_arena gc = gc_new();
 
-    int fd, i;
-    int nbits = 0;
+    int nbits = nkeys * sizeof(struct key) * 8;
 
     /* must be large enough to hold full key file */
     struct buffer out = alloc_buf_gc(2048, &gc);
-    struct buffer nbits_head_text = alloc_buf_gc(128, &gc);
 
     /* how to format the ascii file representation of key */
     const int bytes_per_line = 16;
 
-    /* open key file */
-    fd = platform_open(filename, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR);
-
-    if (fd == -1)
-    {
-        msg(M_ERR, "Cannot open shared secret file '%s' for write", filename);
-    }
-
+    /* write header */
+    buf_printf(&out, "#\n# %d bit OpenVPN static key\n#\n", nbits);
     buf_printf(&out, "%s\n", static_key_head);
 
-    for (i = 0; i < nkeys; ++i)
+    for (int i = 0; i < nkeys; ++i)
     {
         struct key key;
         char *fmt;
@@ -1463,9 +1455,6 @@ write_key_file(const int nkeys, const char *filename)
                             "\n",
                             &gc);
 
-        /* increment random bits counter */
-        nbits += sizeof(key) * 8;
-
         /* write to holding buffer */
         buf_printf(&out, "%s\n", fmt);
 
@@ -1476,17 +1465,8 @@ write_key_file(const int nkeys, const char *filename)
 
     buf_printf(&out, "%s\n", static_key_foot);
 
-    /* write number of bits */
-    buf_printf(&nbits_head_text, "#\n# %d bit OpenVPN static key\n#\n", nbits);
-    buf_write_string_file(&nbits_head_text, filename, fd);
-
     /* write key file, now formatted in out, to file */

src/openvpn/crypto.c write_key_file(const int nkeys, const char *filename) · line 1472

Quoted 10 lines
src/openvpn/crypto.c +1 -6
-    buf_write_string_file(&out, filename, fd);
-
-    if (close(fd))
-    {
-        msg(M_ERR, "Close error on shared secret file %s", filename);
-    }
+    buffer_write_file(filename, &out);
 
     /* zero memory which held file content (memory will be freed by GC) */
     buf_clear(&out);

I like this change especially because the writing function now returns a boolean reporting its success or failure. How about adding some more checks in the calling chain so that such status can be used?

As of now, if the writing function fails, openvpn will print an error message, but won't really return "a failure" (as in returning non-zero). What do you think?

Cheers,

Signature · 1 line
Antonio Quartulli
#67 lore raw

Re: [Openvpn-devel] [PATCH v3 1/7] Introduce buffer_write_file()

Hi,

Thanks for the review. Responses inline.

Quoted 9 lines · Antonio Quartulli
Quoted 8 lines · Steffan Karger
src/openvpn/buffer.c +0 -0
index 0972139..20e2b9c 100644
@@ -343,16 +343,33 @@ convert_to_one_line(struct buffer *buf)
     }
 }
 
Quoted 8 lines

src/openvpn/buffer.c convert_to_one_line(struct buffer *buf) · line 347

Quoted 5 lines
src/openvpn/buffer.c +2 -3
-/* NOTE: requires that string be null terminated */
-void
-buf_write_string_file(const struct buffer *buf, const char *filename, int fd)
+bool
+buffer_write_file(const char *filename, const struct buffer *buf)

Since you are removing the old comments from both function declaration and definition, why not adding some little doxygen style doc?

In the .h, I added the following doxygen:

+/** Write buffer contents to file */
+bool buffer_write_file(const char *filename, const struct buffer *buf);

I'll expand it to explicitly state the return values, but I don't think there's much more to say.

Quoted 5 lines

I see you are also changing the way this method works (now it takes only the filename instead of a fd), but given that we currently use it only once in our code (for now), I think this change is ok and actually makes the logic more self-contained (no need for the caller to deal with the fd at all).

Yeah, that's the idea :) I was duplicating the surrounding code and decided I didn't like that.

Quoted 31 lines
Quoted 14 lines
src/openvpn/buffer.c +10 -3
 {
-    const int len = strlen((char *) BPTR(buf));
-    const int size = write(fd, BPTR(buf), len);
-    if (size != len)
+    bool ret = false;
+    int fd = platform_open(filename, O_CREAT | O_TRUNC | O_WRONLY,
+                           S_IRUSR | S_IWUSR);
+    if (fd == -1)
+    {
+        msg(M_ERRNO, "Cannot open file '%s' for write", filename);
+        goto cleanup;
+    }
+
+    const int size = write(fd, BPTR(buf), BLEN(buf));

src/openvpn/buffer.c convert_to_one_line(struct buffer *buf) · line 367

Quoted 10 lines
src/openvpn/buffer.c +8 -1
+    if (size != BLEN(buf))
     {
-        msg(M_ERR, "Write error on file '%s'", filename);
+        msg(M_ERRNO, "Write error on file '%s'", filename);
+        goto cleanup;
+    }
+
+    ret = true;
+cleanup:
+    if (close(fd))

nitpikcing: I'd dare to say that in the rest of the code we prefer to use "func() < 0" as check, to make it more explicit that we are looking for errors (my personal feeling is that when i see something like your expression I always need to double check "does this function return something positive too? what in case of errors?").

Quoted 1 line

On top of that consistency is always good :-)

Ok, will change.

Quoted 97 lines
Quoted 82 lines
src/openvpn/buffer.c +4 -0
+    {
+        msg(M_ERRNO, "Close error on file %s", filename);
+        ret = false;
     }
+    return ret;
 }
 
 /*
src/openvpn/buffer.h +2 -5
index 6b6025e..180dd56 100644
@@ -469,11 +469,8 @@ const char *skip_leading_whitespace(const char *str);
 
 void string_null_terminate(char *str, int len, int capacity);
 
-/*
- * Write string in buf to file descriptor fd.
- * NOTE: requires that string be null terminated.
- */
-void buf_write_string_file(const struct buffer *buf, const char *filename, int fd);
+/** Write buffer contents to file */
+bool buffer_write_file(const char *filename, const struct buffer *buf);
 
 /*
  * write a string to the end of a buffer that was
src/openvpn/crypto.c +4 -19
index 5381ef2..35bd243 100644
@@ -1427,27 +1427,19 @@ write_key_file(const int nkeys, const char *filename)
 {
     struct gc_arena gc = gc_new();
 
-    int fd, i;
-    int nbits = 0;
+    int nbits = nkeys * sizeof(struct key) * 8;
 
     /* must be large enough to hold full key file */
     struct buffer out = alloc_buf_gc(2048, &gc);
-    struct buffer nbits_head_text = alloc_buf_gc(128, &gc);
 
     /* how to format the ascii file representation of key */
     const int bytes_per_line = 16;
 
-    /* open key file */
-    fd = platform_open(filename, O_CREAT | O_TRUNC | O_WRONLY, S_IRUSR | S_IWUSR);
-
-    if (fd == -1)
-    {
-        msg(M_ERR, "Cannot open shared secret file '%s' for write", filename);
-    }
-
+    /* write header */
+    buf_printf(&out, "#\n# %d bit OpenVPN static key\n#\n", nbits);
     buf_printf(&out, "%s\n", static_key_head);
 
-    for (i = 0; i < nkeys; ++i)
+    for (int i = 0; i < nkeys; ++i)
     {
         struct key key;
         char *fmt;
@@ -1463,9 +1455,6 @@ write_key_file(const int nkeys, const char *filename)
                             "\n",
                             &gc);
 
-        /* increment random bits counter */
-        nbits += sizeof(key) * 8;
-
         /* write to holding buffer */
         buf_printf(&out, "%s\n", fmt);
 
@@ -1476,17 +1465,8 @@ write_key_file(const int nkeys, const char *filename)
 
     buf_printf(&out, "%s\n", static_key_foot);
 
-    /* write number of bits */
-    buf_printf(&nbits_head_text, "#\n# %d bit OpenVPN static key\n#\n", nbits);
-    buf_write_string_file(&nbits_head_text, filename, fd);
-
     /* write key file, now formatted in out, to file */

src/openvpn/crypto.c write_key_file(const int nkeys, const char *filename) · line 1472

Quoted 10 lines
src/openvpn/crypto.c +1 -6
-    buf_write_string_file(&out, filename, fd);
-
-    if (close(fd))
-    {
-        msg(M_ERR, "Close error on shared secret file %s", filename);
-    }
+    buffer_write_file(filename, &out);
 
     /* zero memory which held file content (memory will be freed by GC) */
     buf_clear(&out);

I like this change especially because the writing function now returns a boolean reporting its success or failure. How about adding some more checks in the calling chain so that such status can be used?

Quoted 3 lines

As of now, if the writing function fails, openvpn will print an error message, but won't really return "a failure" (as in returning non-zero). What do you think?

Absolutely right, will improve error handling and make openvpn return non-zero.

-Steffan

#68 lore raw

PATCH v4 3/7 tls-crypt-v2: generate client keys

In reply to Steffan Karger

As a first step towards a full tls-crypt-v2 implementation, add functionality to generate tls-crypt-v2 client keys.

---
v3: Include length in WKc
v4: Fix option verification (use ce->tls_*, not options->ce.tls_*)

 doc/openvpn.8           |  51 +++++++++
 src/openvpn/init.c      |  35 +++++-
 src/openvpn/integer.h   |  10 ++
 src/openvpn/options.c   |  66 ++++++++++-
 src/openvpn/options.h   |  14 +++
 src/openvpn/tls_crypt.c | 288 ++++++++++++++++++++++++++++++++++++++++++++++++
 src/openvpn/tls_crypt.h |  81 ++++++++++++--
 tests/t_lpback.sh       |  40 ++++++-
 8 files changed, 565 insertions(+), 20 deletions(-)
doc/openvpn.8 +51 -0
index de1a192..21e52a5 100644
@@ -5248,6 +5248,57 @@ degrading to the same security as using
 That is, the control channel still benefits from the extra protection against
 active man\-in\-the\-middle\-attacks and DoS attacks, but may no longer offer
 extra privacy and post\-quantum security on top of what TLS itself offers.
+
+For large setups or setups where clients are not trusted, consider using
+.B \-\-tls\-crypt\-v2
+instead.  That uses per\-client unique keys, and thereby improves the bounds to
+\fR'rotate a client key at least once per 8000 years'.
+.\"*********************************************************
+.TP
+.B \-\-tls\-crypt\-v2 keyfile
+
+Use client\-specific tls\-crypt keys.
+
+For clients,
+.B keyfile
+is a client\-specific tls\-crypt key.  Such a key can be generated using the
+.B \-\-tls\-crypt\-v2\-genkey
+option.
+
+For servers,
+.B keyfile
+is used to unwrap client\-specific keys supplied by the client during connection
+setup.  This key must be the same as the key used to generate the
+client\-specific key (see
+.B \-\-tls\-crypt\-v2\-genkey\fR).
+
+On servers, this option can be used together with the
+.B \-\-tls\-auth
+or
+.B \-\-tls\-crypt
+option.  In that case, the server will detect whether the client is using
+client\-specific keys, and automatically select the right mode.
+.\"*********************************************************
+.TP
+.B \-\-tls\-crypt\-v2\-genkey client|server keyfile [metadata]
+
+If the first parameter equals "server", generate a \-\-tls\-crypt\-v2 server
+key and store the key in
+.B keyfile\fR.
+
+
+If the first parameter equals "client", generate a \-\-tls\-crypt\-v2 client
+key, and store the key in
+.B keyfile\fR.
+
+If supplied, include the supplied
+.B metadata
+in the wrapped client key.  This metadata must be supplied in base64\-encoded
+form.  The metadata must be at most 735 bytes long (980 bytes in base64).
+
+.B TODO
+Metadata handling is not yet implemented.  This text will be updated by the
+commit that introduces metadata handling.
 .\"*********************************************************
 .TP
 .B \-\-askpass [file]
src/openvpn/init.c +30 -5
index 2933d55..9de9d4a 100644
@@ -1028,6 +1028,11 @@ print_openssl_info(const struct options *options)
 bool
 do_genkey(const struct options *options)
 {
+    /* should we disable paging? */
+    if (options->mlock && (options->genkey || options->tls_crypt_v2_genkey_file))
+    {
+        platform_mlockall(true);
+    }
     if (options->genkey)
     {
         int nbits_written;
@@ -1035,11 +1040,6 @@ do_genkey(const struct options *options)
         notnull(options->shared_secret_file,
                 "shared secret output file (--secret)");
 
-        if (options->mlock)     /* should we disable paging? */
-        {
-            platform_mlockall(true);
-        }
-
         nbits_written = write_key_file(2, options->shared_secret_file);
         if (nbits_written < 0)
         {
@@ -1051,6 +1051,31 @@ do_genkey(const struct options *options)
             options->shared_secret_file);
         return true;
     }
+    if (options->tls_crypt_v2_genkey_type)
+    {
+        if(!strcmp(options->tls_crypt_v2_genkey_type, "server"))
+        {
+            tls_crypt_v2_write_server_key_file(options->tls_crypt_v2_genkey_file);
+            return true;
+        }
+        else if (options->tls_crypt_v2_genkey_type
+                 && !strcmp(options->tls_crypt_v2_genkey_type, "client"))
+        {
+            if (!options->tls_crypt_v2_file)
+            {
+                msg(M_USAGE, "--tls-crypt-v2-gen-client-key requires a server key to be set via --tls-crypt-v2");
+            }
+
+            tls_crypt_v2_write_client_key_file(options->tls_crypt_v2_genkey_file,
+                    options->tls_crypt_v2_metadata, options->tls_crypt_v2_file,
+                    options->tls_crypt_v2_inline);
+            return true;
+        }
+        else
+        {
+            msg(M_USAGE, "--tls-crypt-v2-genkey type should be \"client\" or \"server\"");
+        }
+    }
     return false;
 }
 
src/openvpn/integer.h +10 -0
index a7e19d3..b1ae0ed 100644
@@ -26,6 +26,16 @@
 
 #include "error.h"
 
+#ifndef htonll
+#define htonll(x) ((1==htonl(1)) ? (x) : \
+                  ((uint64_t)htonl((x) & 0xFFFFFFFF) << 32) | htonl((x) >> 32))
+#endif
+
+#ifndef ntohll
+#define ntohll(x) ((1==ntohl(1)) ? (x) : \
+                  ((uint64_t)ntohl((x) & 0xFFFFFFFF) << 32) | ntohl((x) >> 32))
+#endif
+
 /*
  * min/max functions
  */
src/openvpn/options.c +62 -4

125 lines not on this page · raw message

src/openvpn/options.h +14 -0
index acbd108..3d2c770 100644
@@ -139,6 +139,11 @@ struct connection_entry
     /* Shared secret used for TLS control channel authenticated encryption */
     const char *tls_crypt_file;
     const char *tls_crypt_inline;
+
+    /* Client-specific secret or server key used for TLS control channel
+     * authenticated encryption v2 */
+    const char *tls_crypt_v2_file;
+    const char *tls_crypt_v2_inline;
 };
 
 struct remote_entry
@@ -576,6 +581,15 @@ struct options
     const char *tls_crypt_file;
     const char *tls_crypt_inline;
 
+    /* Client-specific secret or server key used for TLS control channel
+     * authenticated encryption v2 */
+    const char *tls_crypt_v2_file;
+    const char *tls_crypt_v2_inline;
+
+    const char *tls_crypt_v2_genkey_type;
+    const char *tls_crypt_v2_genkey_file;
+    const char *tls_crypt_v2_metadata;
+
     /* Allow only one session */
     bool single_session;
 
src/openvpn/tls_crypt.c +288 -0

302 lines not on this page · raw message

src/openvpn/tls_crypt.h +73 -8

129 lines not on this page · raw message

tests/t_lpback.sh +37 -3
index 2052c62..fb43211 100755
@@ -21,8 +21,8 @@
 
 set -eu
 top_builddir="${top_builddir:-..}"
-trap "rm -f key.$$ log.$$ ; trap 0 ; exit 77" 1 2 15
-trap "rm -f key.$$ log.$$ ; exit 1" 0 3
+trap "rm -f key.$$ tc-server-key.$$ tc-client-key.$$ log.$$ ; trap 0 ; exit 77" 1 2 15
+trap "rm -f key.$$ tc-server-key.$$ tc-client-key.$$ log.$$ ; exit 1" 0 3
 
 # Get list of supported ciphers from openvpn --show-ciphers output
 CIPHERS=$(${top_builddir}/src/openvpn/openvpn --show-ciphers | \
@@ -55,6 +55,40 @@ do
     fi
 done
 
-rm key.$$ log.$$
+echo -n "Testing tls-crypt-v2 server key generation..."
+"${top_builddir}/src/openvpn/openvpn" \
+    --tls-crypt-v2-genkey server tc-server-key.$$ >log.$$ 2>&1
+if [ $? != 0 ] ; then
+    echo "FAILED"
+    cat log.$$
+    e=1
+else
+    echo "OK"
+fi
+
+echo -n "Testing tls-crypt-v2 key generation (no metadata)..."
+"${top_builddir}/src/openvpn/openvpn" --tls-crypt-v2 tc-server-key.$$ \
+    --tls-crypt-v2-genkey client tc-client-key.$$ >log.$$ 2>&1
+if [ $? != 0 ] ; then
+    echo "FAILED"
+    cat log.$$
+    e=1
+else
+    echo "OK"
+fi
+
+echo -n "Testing tls-crypt-v2 key generation (max length metadata)..."
+"${top_builddir}/src/openvpn/openvpn" --tls-crypt-v2 tc-server-key.$$ \
+    --tls-crypt-v2-genkey client tc-client-key.$$ \
+    $(head -c732 /dev/zero | base64 -w0) >log.$$ 2>&1
+if [ $? != 0 ] ; then
+    echo "FAILED"
+    cat log.$$
+    e=1
+else
+    echo "OK"
+fi
+
+rm key.$$ tc-server-key.$$ tc-client-key.$$ log.$$
 trap 0
 exit $e
Signature · 1 line
2.7.4
#69 lore raw

PATCH v4 4/7 tls-crypt-v2: add unwrap_client_key

Add helper functions to unwrap tls-crypt-v2 client keys.

---
v3: Include length in WKc
v4: Rebase on v4 patch set

 src/openvpn/buffer.h                      |   7 +
 src/openvpn/tls_crypt.c                   | 120 ++++++++++++++
 tests/unit_tests/openvpn/test_tls_crypt.c | 253 +++++++++++++++++++++++++++---
 3 files changed, 360 insertions(+), 20 deletions(-)
src/openvpn/buffer.h +7 -0
index cb9e9df..e17ce07 100644
@@ -837,6 +837,13 @@ buf_read_u32(struct buffer *buf, bool *good)
     }
 }
 
+/** Return true if buffer contents are equal */
+static inline bool
+buf_equal(const struct buffer *a, const struct buffer *b)
+{
+    return BLEN(a) == BLEN(b) && 0 == memcmp(BPTR(a), BPTR(b), BLEN(a));
+}
+
 /**
  * Compare src buffer contents with match.
  * *NOT* constant time. Do not use when comparing HMACs.
src/openvpn/tls_crypt.c +120 -0

132 lines not on this page · raw message

tests/unit_tests/openvpn/test_tls_crypt.c +233 -20

334 lines not on this page · raw message

Signature · 1 line
2.7.4
#70 lore raw

PATCH v4 5/7 tls-crypt-v2: add P_CONTROL_HARD_RESET_CLIENT_V3 opcode

In reply to Steffan Karger

Not used yet, but prepare for sending and receiving tls-crypt-v2 handshake messages.

---
v3: rebase on curent master / v3 patch set
v4: rebase on v4 patch set

 src/openvpn/ps.c         |  3 ++-
 src/openvpn/ssl.c        | 23 ++++++++++++++++++-----
 src/openvpn/ssl.h        |  5 ++++-
 src/openvpn/ssl_common.h |  2 ++
 4 files changed, 26 insertions(+), 7 deletions(-)
src/openvpn/ps.c +2 -1
index 25ab374..adec072 100644
@@ -985,7 +985,8 @@ is_openvpn_protocol(const struct buffer *buf)
     {
         return p[0] == 0
                && p[1] >= 14
-               && p[2] == (P_CONTROL_HARD_RESET_CLIENT_V2<<P_OPCODE_SHIFT);
+               && (p[2] == (P_CONTROL_HARD_RESET_CLIENT_V2<<P_OPCODE_SHIFT)
+                   || p[2] == (P_CONTROL_HARD_RESET_CLIENT_V3<<P_OPCODE_SHIFT));
     }
     else if (len >= 2)
     {
src/openvpn/ssl.c +18 -5
index dcb5445..757754f 100644
@@ -792,6 +792,9 @@ packet_opcode_name(int op)
         case P_CONTROL_HARD_RESET_SERVER_V2:
             return "P_CONTROL_HARD_RESET_SERVER_V2";
 
+        case P_CONTROL_HARD_RESET_CLIENT_V3:
+            return "P_CONTROL_HARD_RESET_CLIENT_V3";
+
         case P_CONTROL_SOFT_RESET_V1:
             return "P_CONTROL_SOFT_RESET_V1";
 
@@ -864,7 +867,8 @@ is_hard_reset(int op, int key_method)
 
     if (!key_method || key_method >= 2)
     {
-        if (op == P_CONTROL_HARD_RESET_CLIENT_V2 || op == P_CONTROL_HARD_RESET_SERVER_V2)
+        if (op == P_CONTROL_HARD_RESET_CLIENT_V2 || op == P_CONTROL_HARD_RESET_SERVER_V2
+            || op == P_CONTROL_HARD_RESET_CLIENT_V3)
         {
             return true;
         }
@@ -1095,8 +1099,15 @@ tls_session_init(struct tls_multi *multi, struct tls_session *session)
     }
     else /* session->opt->key_method >= 2 */
     {
-        session->initial_opcode = session->opt->server ?
-                                  P_CONTROL_HARD_RESET_SERVER_V2 : P_CONTROL_HARD_RESET_CLIENT_V2;
+        if (session->opt->server)
+        {
+            session->initial_opcode = P_CONTROL_HARD_RESET_SERVER_V2;
+        }
+        else
+        {
+            session->initial_opcode = session->opt->tls_crypt_v2 ?
+                    P_CONTROL_HARD_RESET_CLIENT_V3 : P_CONTROL_HARD_RESET_CLIENT_V2;
+        }
     }
 
     /* Initialize control channel authentication parameters */
@@ -3427,7 +3438,8 @@ tls_pre_decrypt(struct tls_multi *multi,
             {
                 /* verify client -> server or server -> client connection */
                 if (((op == P_CONTROL_HARD_RESET_CLIENT_V1
-                      || op == P_CONTROL_HARD_RESET_CLIENT_V2) && !multi->opt.server)
+                      || op == P_CONTROL_HARD_RESET_CLIENT_V2
+                      || op == P_CONTROL_HARD_RESET_CLIENT_V3) && !multi->opt.server)
                     || ((op == P_CONTROL_HARD_RESET_SERVER_V1
                          || op == P_CONTROL_HARD_RESET_SERVER_V2) && multi->opt.server))
                 {
@@ -3812,7 +3824,8 @@ tls_pre_decrypt_lite(const struct tls_auth_standalone *tas,
         /* this packet is from an as-yet untrusted source, so
          * scrutinize carefully */
 
-        if (op != P_CONTROL_HARD_RESET_CLIENT_V2)
+        if (op != P_CONTROL_HARD_RESET_CLIENT_V2
+            && op != P_CONTROL_HARD_RESET_CLIENT_V3)
         {
             /*
              * This can occur due to bogus data or DoS packets.
src/openvpn/ssl.h +4 -1
index 72227d9..852debd 100644
@@ -63,9 +63,12 @@
 #define P_CONTROL_HARD_RESET_CLIENT_V2 7     /* initial key from client, forget previous state */
 #define P_CONTROL_HARD_RESET_SERVER_V2 8     /* initial key from server, forget previous state */
 
+/* indicates key_method >= 2 and client-specific tls-crypt key */
+#define P_CONTROL_HARD_RESET_CLIENT_V3 10    /* initial key from client, forget previous state */
+
 /* define the range of legal opcodes */
 #define P_FIRST_OPCODE                 1
-#define P_LAST_OPCODE                  9
+#define P_LAST_OPCODE                  10
 
 /*
  * Set the max number of acknowledgments that can "hitch a ride" on an outgoing
src/openvpn/ssl_common.h +2 -0
index 08ef6ff..e3c852a 100644
@@ -286,6 +286,8 @@ struct tls_options
     const char *config_authname;
     bool ncp_enabled;
 
+    bool tls_crypt_v2;
+
     /** TLS handshake wrapping state */
     struct tls_wrap_ctx tls_wrap;
 
Signature · 1 line
2.7.4
#71 lore raw

PATCH v4 6/7 tls-crypt-v2: implement tls-crypt-v2 handshake

In reply to Steffan Karger

This makes clients send-and-use, and servers receive-unwrap-and-use tls-crypt-v2 client keys, which completes the on-the-wire work.

---
v3: include length in WKc, rebase on curent master / v3 patch set
v4: resolve merge conflict (fixes memory leak)

 src/openvpn/init.c                        | 39 ++++++++++++++-
 src/openvpn/openvpn.h                     |  2 +
 src/openvpn/ssl.c                         | 79 ++++++++++++++++++++++++-------
 src/openvpn/ssl_common.h                  |  6 +++
 src/openvpn/tls_crypt.c                   | 57 ++++++++++++++++++++++
 src/openvpn/tls_crypt.h                   | 14 ++++++
 tests/unit_tests/openvpn/Makefile.am      |  2 +-
 tests/unit_tests/openvpn/test_tls_crypt.c |  6 +++
 8 files changed, 187 insertions(+), 18 deletions(-)
src/openvpn/init.c +38 -1
index 9de9d4a..6a556ae 100644
@@ -2439,6 +2439,7 @@ key_schedule_free(struct key_schedule *ks, bool free_ssl_ctx)
     if (tls_ctx_initialised(&ks->ssl_ctx) && free_ssl_ctx)
     {
         tls_ctx_free(&ks->ssl_ctx);
+        free_key_ctx(&ks->tls_crypt_v2_server_key);
     }
     CLEAR(*ks);
 }
@@ -2622,6 +2623,24 @@ do_init_crypto_tls_c1(struct context *c)
         /* initialize tls-auth/crypt key */
         do_init_tls_wrap_key(c);
 
+        /* tls-crypt with client-specific keys (--tls-crypt-v2) */
+        if (options->tls_crypt_v2_file)
+        {
+            if (options->tls_server)
+            {
+                tls_crypt_v2_init_server_key(&c->c1.ks.tls_crypt_v2_server_key,
+                                             true, options->tls_crypt_v2_file,
+                                             options->tls_crypt_v2_inline);
+            }
+            else
+            {
+                tls_crypt_v2_init_client_key(&c->c1.ks.tls_wrap_key,
+                                             &c->c1.ks.tls_crypt_v2_wkc,
+                                             options->tls_crypt_v2_file,
+                                             options->tls_crypt_v2_inline);
+            }
+        }
+
         c->c1.ciphername = options->ciphername;
         c->c1.authname = options->authname;
         c->c1.keysize = options->keysize;
@@ -2847,13 +2866,28 @@ do_init_crypto_tls(struct context *c, const unsigned int flags)
     }
 
     /* TLS handshake encryption (--tls-crypt) */
-    if (options->ce.tls_crypt_file)
+    if (options->ce.tls_crypt_file
+        || (options->ce.tls_crypt_v2_file && options->tls_client))
     {
         to.tls_wrap.mode = TLS_WRAP_CRYPT;
         to.tls_wrap.opt.key_ctx_bi = c->c1.ks.tls_wrap_key;
         to.tls_wrap.opt.pid_persist = &c->c1.pid_persist;
         to.tls_wrap.opt.flags |= CO_PACKET_ID_LONG_FORM;
         tls_crypt_adjust_frame_parameters(&to.frame);
+
+        if (options->tls_crypt_v2_file)
+        {
+            to.tls_wrap.tls_crypt_v2_wkc = &c->c1.ks.tls_crypt_v2_wkc;
+        }
+    }
+
+    if (options->tls_crypt_v2_file)
+    {
+        to.tls_crypt_v2 = true;
+        if (options->tls_server)
+        {
+            to.tls_wrap.tls_crypt_v2_server_key = c->c1.ks.tls_crypt_v2_server_key;
+        }
     }
 
     /* If we are running over TCP, allow for
@@ -3457,6 +3491,8 @@ do_close_free_key_schedule(struct context *c, bool free_ssl_ctx)
      */
     free_key_ctx_bi(&c->c1.ks.tls_wrap_key);
     CLEAR(c->c1.ks.tls_wrap_key);
+    buf_clear(&c->c1.ks.tls_crypt_v2_wkc);
+    free_buf(&c->c1.ks.tls_crypt_v2_wkc);
 
     if (!(c->sig->signal_received == SIGUSR1 && c->options.persist_key))
     {
@@ -4390,6 +4426,7 @@ inherit_context_child(struct context *dest,
     dest->c1.ks.ssl_ctx = src->c1.ks.ssl_ctx;
     dest->c1.ks.tls_wrap_key = src->c1.ks.tls_wrap_key;
     dest->c1.ks.tls_auth_key_type = src->c1.ks.tls_auth_key_type;
+    dest->c1.ks.tls_crypt_v2_server_key = src->c1.ks.tls_crypt_v2_server_key;
     /* inherit pre-NCP ciphers */
     dest->c1.ciphername = src->c1.ciphername;
     dest->c1.authname = src->c1.authname;
src/openvpn/openvpn.h +2 -0
index c5c767c..96cff87 100644
@@ -66,6 +66,8 @@ struct key_schedule
     /* optional TLS control channel wrapping */
     struct key_type tls_auth_key_type;
     struct key_ctx_bi tls_wrap_key;
+    struct key_ctx tls_crypt_v2_server_key;
+    struct buffer tls_crypt_v2_wkc;             /**< Wrapped client key */
 };
 
 /*
src/openvpn/ssl.c +63 -16

126 lines not on this page · raw message

src/openvpn/ssl_common.h +6 -0
index e3c852a..d744881 100644
@@ -213,6 +213,12 @@ struct tls_wrap_ctx
     } mode;                     /**< Control channel wrapping mode */
     struct crypto_options opt;  /**< Crypto state */
     struct buffer work;         /**< Work buffer (only for --tls-crypt) */
+    struct key_ctx tls_crypt_v2_server_key;  /**< Decrypts client keys */
+    const struct buffer *tls_crypt_v2_wkc;   /**< Wrapped client key,
+                                                  sent to server */
+    struct buffer tls_crypt_v2_metadata;     /**< Received from client */
+    bool cleanup_key_ctx;                    /**< opt.key_ctx_bi is owned by
+                                                  this context */
 };
 
 /*
src/openvpn/tls_crypt.c +57 -0

69 lines not on this page · raw message

src/openvpn/tls_crypt.h +14 -0
index 931e7d3..94c95e1 100644
@@ -83,6 +83,7 @@
 #include "buffer.h"
 #include "crypto.h"
 #include "session_id.h"
+#include "ssl_common.h"
 
 #define TLS_CRYPT_TAG_SIZE (256/8)
 #define TLS_CRYPT_PID_SIZE (sizeof(packet_id_type) + sizeof(net_time_t))
@@ -182,6 +183,19 @@ void tls_crypt_v2_init_client_key(struct key_ctx_bi *key,
                                   const char *key_inline);
 
 /**
+ * Extract a tls-crypt-v2 client key from a P_CONTROL_HARD_RESET_CLIENT_V3
+ * message, and load the key into the supplied tls wrap context.
+ *
+ * @param buf   Buffer containing a received P_CONTROL_HARD_RESET_CLIENT_V3
+ *              message.
+ * @param ctx   tls-wrap context to be initialized with the client key.
+ *
+ * @returns true if a key was successfully extracted.
+ */
+bool tls_crypt_v2_extract_client_key(struct buffer *buf,
+                                     struct tls_wrap_ctx *ctx);
+
+/**
  * Generate a tls-crypt-v2 server key, and write to file.
  *
  * @param filename          Filename of the server key file to create.
tests/unit_tests/openvpn/Makefile.am +1 -1
index 0f7f86b..6e7fa20 100644
@@ -58,7 +58,7 @@ packet_id_testdriver_SOURCES = test_packet_id.c mock_msg.c \
 
 tls_crypt_testdriver_CFLAGS  = @TEST_CFLAGS@ \
 	-I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \
-	$(OPTIONAL_CRYPTO_CFLAGS)
+	$(OPTIONAL_CRYPTO_CFLAGS) $(OPTIONAL_PKCS11_HELPER_CFLAGS)
 tls_crypt_testdriver_LDFLAGS = @TEST_LDFLAGS@ \
 	$(OPTIONAL_CRYPTO_LIBS)
 tls_crypt_testdriver_SOURCES = test_tls_crypt.c mock_msg.c \
tests/unit_tests/openvpn/test_tls_crypt.c +6 -0
index f53255c..22c7486 100644
@@ -345,6 +345,12 @@ tls_crypt_v2_wrap_unwrap_max_metadata(void **state) {
     assert_true(0 == memcmp(ctx->client_key2.keys, unwrapped_client_key2.keys,
                             sizeof(ctx->client_key2.keys)));
     assert_true(buf_equal(&ctx->metadata, &unwrap_metadata));
+
+    struct tls_wrap_ctx wrap_ctx = {
+            .mode = TLS_WRAP_CRYPT,
+            .tls_crypt_v2_server_key = ctx->server_keys.encrypt,
+    };
+    assert_true(tls_crypt_v2_extract_client_key(&ctx->wkc, &wrap_ctx));
 }
 
 /**
Signature · 1 line
2.7.4
#72 lore raw

PATCH v4 7/7 tls-crypt-v2: add script hook to verify metadata

In reply to Steffan Karger

To allow rejecting incoming connections very early in the handshake, add a --tls-crypt-v2-verify option that allows administators to run an external command to verify the metadata from the client key. See doc/tls-crypt-v2.txt for more details.

Because of the extra dependencies, this requires adding a mock parse_line() to the tls-crypt unit tests.

---
v3: rebase on curent master / v3 patch set
v4: fix memory leak (metadata buffer was not free'd for tls_wrap_tmp)

 Changes.rst                               | 12 ++++++
 doc/openvpn.8                             | 35 ++++++++++++++--
 src/openvpn/init.c                        |  1 +
 src/openvpn/options.c                     |  7 ++++
 src/openvpn/options.h                     |  2 +
 src/openvpn/ssl.c                         | 17 +++++---
 src/openvpn/ssl_common.h                  |  1 +
 src/openvpn/tls_crypt.c                   | 69 ++++++++++++++++++++++++++++++-
 src/openvpn/tls_crypt.h                   |  3 +-
 tests/unit_tests/openvpn/Makefile.am      | 12 ++++--
 tests/unit_tests/openvpn/test_tls_crypt.c | 18 +++++++-
 11 files changed, 162 insertions(+), 15 deletions(-)
Changes.rst +12 -0
index a6090cf..e77b3d7 100644
@@ -1,3 +1,15 @@
+Overview of changes in 2.5
+==========================
+
+New features
+------------
+Client-specific tls-crypt keys (``--tls-crypt-v2``)
+    ``tls-crypt-v2`` adds the ability to supply each client with a unique
+    tls-crypt key.  This allows large organisations and VPN providers to profit
+    from the same DoS and TLS stack protection that small deployments can
+    already achieve using ``tls-auth`` or ``tls-crypt``.
+
+
 Overview of changes in 2.4
 ==========================
 
doc/openvpn.8 +32 -3
index 21e52a5..9843fd8 100644
@@ -5296,9 +5296,38 @@ If supplied, include the supplied
 in the wrapped client key.  This metadata must be supplied in base64\-encoded
 form.  The metadata must be at most 735 bytes long (980 bytes in base64).
 
-.B TODO
-Metadata handling is not yet implemented.  This text will be updated by the
-commit that introduces metadata handling.
+If no metadata is supplied, OpenVPN will use a 64-bit unix timestamp
+representing the current time in UTC, encoded in network order, as metadata for
+the generated key.
+
+Servers can use
+.B \-\-tls\-crypt\-v2\-verify
+to specify a metadata verification command.
+.\"*********************************************************
+.TP
+.B \-\-tls\-crypt\-v2\-verify cmd
+
+Run command
+.B cmd
+to verify the metadata of the client-specific tls-crypt-v2 key of a connecting
+client.  This allows server administrators to reject client connections, before
+exposing the TLS stack (including the notoriously dangerous X.509 and ASN.1
+stacks) to the connecting client.
+
+OpenVPN supplies the following env vars to the command:
+.RS
+.IP \[bu] 2
+script_type is set to "tls-crypt-v2-verify"
+.IP \[bu]
+metadata_type is set to "0" is the metadata was user supplied, or "1" if it's a
+64-bit unix timestamp representing the key creation time.
+.IP \[bu]
+metadata_file contains the filename of a temporary file that contains the client
+metadata.
+.RE
+
+.IP
+The command can reject the connection by exitingwith a non-zero exit code.
 .\"*********************************************************
 .TP
 .B \-\-askpass [file]
src/openvpn/init.c +1 -0
index 6a556ae..3e7dfdb 100644
@@ -2887,6 +2887,7 @@ do_init_crypto_tls(struct context *c, const unsigned int flags)
         if (options->tls_server)
         {
             to.tls_wrap.tls_crypt_v2_server_key = c->c1.ks.tls_crypt_v2_server_key;
+            to.tls_crypt_v2_verify_script = c->options.tls_crypt_v2_verify_script;
         }
     }
 
src/openvpn/options.c +7 -0
index e19e42d..7c9bb5c 100644
@@ -629,6 +629,8 @@ static const char usage_message[] =
     "--tls-crypt-v2-genkey client|server keyfile [base64 metadata]: Generate a\n"
     "                  fresh tls-crypt-v2 client or server key, and store to\n"
     "                  keyfile.  If supplied, include metadata in wrapped key.\n"
+    "--tls-crypt-v2-verify cmd : Run command cmd to verify the metadata of the\n"
+    "                  client-supplied tls-crypt-v2 client key\n"
     "--askpass [file]: Get PEM password from controlling tty before we daemonize.\n"
     "--auth-nocache  : Don't cache --askpass or --auth-user-pass passwords.\n"
     "--crl-verify crl ['dir']: Check peer certificate against a CRL.\n"
@@ -8179,6 +8181,11 @@ add_option(struct options *options,
             options->tls_crypt_v2_metadata = p[3];
         }
     }
+    else if (streq(p[0], "tls-crypt-v2-verify") && p[1] && !p[2])
+    {
+        VERIFY_PERMISSION(OPT_P_GENERAL);
+        options->tls_crypt_v2_verify_script = p[1];
+    }
     else if (streq(p[0], "key-method") && p[1] && !p[2])
     {
         int key_method;
src/openvpn/options.h +2 -0
index 3d2c770..d5ceb18 100644
@@ -590,6 +590,8 @@ struct options
     const char *tls_crypt_v2_genkey_file;
     const char *tls_crypt_v2_metadata;
 
+    const char *tls_crypt_v2_verify_script;
+
     /* Allow only one session */
     bool single_session;
 
src/openvpn/ssl.c +11 -6
index dcae279..9e0b283 100644
@@ -1541,14 +1541,15 @@ write_control_auth(struct tls_session *session,
 static bool
 read_control_auth(struct buffer *buf,
                   struct tls_wrap_ctx *ctx,
-                  const struct link_socket_actual *from)
+                  const struct link_socket_actual *from,
+                  const struct tls_options *opt)
 {
     struct gc_arena gc = gc_new();
     bool ret = false;
 
     const uint8_t opcode = *(BPTR(buf)) >> P_OPCODE_SHIFT;
     if (opcode == P_CONTROL_HARD_RESET_CLIENT_V3
-        && !tls_crypt_v2_extract_client_key(buf, ctx))
+        && !tls_crypt_v2_extract_client_key(buf, ctx, opt))
     {
         msg (D_TLS_ERRORS,
              "TLS Error: can not extract tls-crypt-v2 client key from %s",
@@ -3629,7 +3630,8 @@ tls_pre_decrypt(struct tls_multi *multi,
                     goto error;
                 }
 
-                if (!read_control_auth(buf, &session->tls_wrap, from))
+                if (!read_control_auth(buf, &session->tls_wrap, from,
+                                       session->opt))
                 {
                     goto error;
                 }
@@ -3682,7 +3684,8 @@ tls_pre_decrypt(struct tls_multi *multi,
                 if (op == P_CONTROL_SOFT_RESET_V1
                     && DECRYPT_KEY_ENABLED(multi, ks))
                 {
-                    if (!read_control_auth(buf, &session->tls_wrap, from))
+                    if (!read_control_auth(buf, &session->tls_wrap, from,
+                                           session->opt))
                     {
                         goto error;
                     }
@@ -3703,7 +3706,8 @@ tls_pre_decrypt(struct tls_multi *multi,
                         do_burst = true;
                     }
 
-                    if (!read_control_auth(buf, &session->tls_wrap, from))
+                    if (!read_control_auth(buf, &session->tls_wrap, from,
+                                           session->opt))
                     {
                         goto error;
                     }
@@ -3905,8 +3909,9 @@ tls_pre_decrypt_lite(const struct tls_auth_standalone *tas,
             bool status;
 
             /* HMAC test, if --tls-auth was specified */
-            status = read_control_auth(&newbuf, &tls_wrap_tmp, from);
+            status = read_control_auth(&newbuf, &tls_wrap_tmp, from, NULL);
             free_buf(&newbuf);
+            free_buf(&tls_wrap_tmp.tls_crypt_v2_metadata);
             if (tls_wrap_tmp.cleanup_key_ctx)
             {
                 free_key_ctx_bi(&tls_wrap_tmp.opt.key_ctx_bi);
src/openvpn/ssl_common.h +1 -0
index d744881..923cd95 100644
@@ -293,6 +293,7 @@ struct tls_options
     bool ncp_enabled;
 
     bool tls_crypt_v2;
+    const char *tls_crypt_v2_verify_script;
 
     /** TLS handshake wrapping state */
     struct tls_wrap_ctx tls_wrap;
src/openvpn/tls_crypt.c +68 -1

92 lines not on this page · raw message

src/openvpn/tls_crypt.h +2 -1
index 94c95e1..9cf0569 100644
@@ -193,7 +193,8 @@ void tls_crypt_v2_init_client_key(struct key_ctx_bi *key,
  * @returns true if a key was successfully extracted.
  */
 bool tls_crypt_v2_extract_client_key(struct buffer *buf,
-                                     struct tls_wrap_ctx *ctx);
+                                     struct tls_wrap_ctx *ctx,
+                                     const struct tls_options *opt);
 
 /**
  * Generate a tls-crypt-v2 server key, and write to file.
tests/unit_tests/openvpn/Makefile.am +9 -3
index 6e7fa20..e5ffba9 100644
@@ -6,7 +6,10 @@ if HAVE_LD_WRAP_SUPPORT
 check_PROGRAMS += argv_testdriver buffer_testdriver
 endif
 
-check_PROGRAMS += crypto_testdriver packet_id_testdriver tls_crypt_testdriver
+check_PROGRAMS += crypto_testdriver packet_id_testdriver
+if HAVE_LD_WRAP_SUPPORT
+check_PROGRAMS += tls_crypt_testdriver
+endif
 
 TESTS = $(check_PROGRAMS)
 
@@ -59,14 +62,17 @@ packet_id_testdriver_SOURCES = test_packet_id.c mock_msg.c \
 tls_crypt_testdriver_CFLAGS  = @TEST_CFLAGS@ \
 	-I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir) \
 	$(OPTIONAL_CRYPTO_CFLAGS) $(OPTIONAL_PKCS11_HELPER_CFLAGS)
-tls_crypt_testdriver_LDFLAGS = @TEST_LDFLAGS@ \
+tls_crypt_testdriver_LDFLAGS = @TEST_LDFLAGS@ -Wl,--wrap=parse_line \
 	$(OPTIONAL_CRYPTO_LIBS)
 tls_crypt_testdriver_SOURCES = test_tls_crypt.c mock_msg.c \
+	$(openvpn_srcdir)/argv.c \
 	$(openvpn_srcdir)/base64.c \
 	$(openvpn_srcdir)/buffer.c \
 	$(openvpn_srcdir)/crypto.c \
 	$(openvpn_srcdir)/crypto_mbedtls.c \
 	$(openvpn_srcdir)/crypto_openssl.c \
+	$(openvpn_srcdir)/env_set.c \
 	$(openvpn_srcdir)/otime.c \
 	$(openvpn_srcdir)/packet_id.c \
-	$(openvpn_srcdir)/platform.c
+	$(openvpn_srcdir)/platform.c \
+	$(openvpn_srcdir)/run_command.c
tests/unit_tests/openvpn/test_tls_crypt.c +17 -1
index 22c7486..897adce 100644
@@ -43,8 +43,24 @@
 
 #define TESTBUF_SIZE            128
 
+/* Defines for use in the tests and the mock parse_line() */
+#define PATH1       "/s p a c e"
+#define PATH2       "/foo bar/baz"
+#define PARAM1      "param1"
+#define PARAM2      "param two"
+
 const char plaintext_short[1];
 
+int
+__wrap_parse_line(const char *line, char **p, const int n, const char *file,
+                  const int line_num, int msglevel, struct gc_arena *gc)
+{
+    p[0] = PATH1 PATH2;
+    p[1] = PARAM1;
+    p[2] = PARAM2;
+    return 3;
+}
+
 struct test_tls_crypt_context {
     struct crypto_options co;
     struct key_type kt;
@@ -350,7 +366,7 @@ tls_crypt_v2_wrap_unwrap_max_metadata(void **state) {
             .mode = TLS_WRAP_CRYPT,
             .tls_crypt_v2_server_key = ctx->server_keys.encrypt,
     };
-    assert_true(tls_crypt_v2_extract_client_key(&ctx->wkc, &wrap_ctx));
+    assert_true(tls_crypt_v2_extract_client_key(&ctx->wkc, &wrap_ctx, NULL));
 }
 
 /**
Signature · 1 line
2.7.4
#73 lore raw

Re: [Openvpn-devel] [PATCH v4 7/7] tls-crypt-v2: add script hook to verify metadata

Hi,

just a reminder about \- in openvpn.8
and one missing space

hope this helps :-)

Quoted 16 lines · Steffan Karger

To allow rejecting incoming connections very early in the handshake, add a --tls-crypt-v2-verify option that allows administators to run an external command to verify the metadata from the client key. See doc/tls-crypt-v2.txt for more details.

Because of the extra dependencies, this requires adding a mock parse_line() to the tls-crypt unit tests.

---
v3: rebase on curent master / v3 patch set
v4: fix memory leak (metadata buffer was not free'd for tls_wrap_tmp)

  Changes.rst                               | 12 ++++++
  doc/openvpn.8                             | 35 ++++++++++++++--

<snip>

doc/openvpn.8 If supplied, include the supplied · line 5299

Quoted 13 lines
doc/openvpn.8 +1 -3
index 21e52a5..9843fd8 100644
@@ -5296,9 +5296,38 @@ If supplied, include the supplied
  in the wrapped client key.  This metadata must be supplied in base64\-encoded
  form.  The metadata must be at most 735 bytes long (980 bytes in base64).
 
-.B TODO
-Metadata handling is not yet implemented.  This text will be updated by the
-commit that introduces metadata handling.
+If no metadata is supplied, OpenVPN will use a 64-bit unix timestamp
                                                    ^

doc/openvpn.8 If supplied, include the supplied · line 5312

Quoted 13 lines
doc/openvpn.8 +13 -0
+representing the current time in UTC, encoded in network order, as metadata for
+the generated key.
+
+Servers can use
+.B \-\-tls\-crypt\-v2\-verify
+to specify a metadata verification command.
+.\"*********************************************************
+.TP
+.B \-\-tls\-crypt\-v2\-verify cmd
+
+Run command
+.B cmd
+to verify the metadata of the client-specific tls-crypt-v2 key of a connecting

^ ^ ^

doc/openvpn.8 If supplied, include the supplied · line 5320

Quoted 8 lines
doc/openvpn.8 +8 -0
+client.  This allows server administrators to reject client connections, before
+exposing the TLS stack (including the notoriously dangerous X.509 and ASN.1
+stacks) to the connecting client.
+
+OpenVPN supplies the following env vars to the command:
+.RS
+.IP \[bu] 2
+script_type is set to "tls-crypt-v2-verify"
                             ^     ^  ^

doc/openvpn.8 If supplied, include the supplied · line 5323

Quoted 3 lines
doc/openvpn.8 +3 -0
+.IP \[bu]
+metadata_type is set to "0" is the metadata was user supplied, or "1" if it's a
+64-bit unix timestamp representing the key creation time.
     ^

doc/openvpn.8 If supplied, include the supplied · line 5330

Quoted 7 lines
doc/openvpn.8 +7 -0
+.IP \[bu]
+metadata_file contains the filename of a temporary file that contains the client
+metadata.
+.RE
+
+.IP
+The command can reject the connection by exitingwith a non-zero exit code.

^ space missing

Quoted 3 lines
doc/openvpn.8 +0 -0
  .\"*********************************************************
  .TP
  .B \-\-askpass [file]
#74 lore raw

PATCH v6 2/7 tls-crypt-v2: add specification to doc/

In reply to Steffan Karger

This is a preliminary description of tls-crypt-v2. It should give a good impression about the reasoning and design behind tls-crypt-v2, but might need some polishing and updating.

---
v3: Include length in WKc
v4: Clarify metadata handling
v5: Typo fixes (thanks tincanteksup)
v6: Change WKc length to ciphertext length (was plaintext), textual fixes

 doc/tls-crypt-v2.txt | 189 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 189 insertions(+)
 create mode 100644 doc/tls-crypt-v2.txt
doc/tls-crypt-v2.txt new file +189 -0
new file mode 100644
index 0000000..582a2b8
@@ -0,0 +1,189 @@
+Client-specific tls-crypt keys (--tls-crypt-v2)
+===============================================
+
+This document describes the ``--tls-crypt-v2`` option, which enables OpenVPN
+to use client-specific ``--tls-crypt`` keys.
+
+Rationale
+---------
+
+``--tls-auth`` and ``tls-crypt`` use a pre-shared group key, which is shared
+among all clients and servers in an OpenVPN deployment.  If any client or
+server is compromised, the attacker will have access to this shared key, and it
+will no longer provide any security.  To reduce the risk of losing pre-shared
+keys, ``tls-crypt-v2`` adds the ability to supply each client with a unique
+tls-crypt key.  This allows large organisations and VPN providers to profit
+from the same DoS and TLS stack protection that small deployments can already
+achieve using ``tls-auth`` or ``tls-crypt``.
+
+Also, for ``tls-crypt``, even if all these peers succeed in keeping the key
+secret, the key lifetime is limited to roughly 8000 years, divided by the
+number of clients (see the ``--tls-crypt`` section of the man page).  Using
+client-specific keys, we lift this lifetime requirement to roughly 8000 years
+for each client key (which "Should Be Enough For Everybody (tm)").
+
+
+Introduction
+------------
+
+``tls-crypt-v2`` uses an encrypted cookie mechanism to introduce
+client-specific tls-crypt keys without introducing a lot of server-side state.
+The client-specific key is encrypted using a server key.  The server key is the
+same for all servers in a group.  When a client connects, it first sends the
+encrypted key to the server, such that the server can decrypt the key and all
+messages can thereafter be encrypted using the client-specific key.
+
+A wrapped (encrypted and authenticated) client-specific key can also contain
+metadata.  The metadata is wrapped together with the key, and can be used to
+allow servers to identify clients and/or key validity.  This allows the server
+to abort the connection immediately after receiving the first packet, rather
+than performing an entire TLS handshake.  Aborting the connection this early
+greatly improves the DoS resilience and reduces attack service against
+malicious clients that have the ``tls-crypt`` or ``tls-auth`` key.  This is
+particularly relevant for large deployments (think lost key or disgruntled
+employee) and VPN providers (clients are not trusted).
+
+To allow for a smooth transition, ``tls-crypt-v2`` is designed such that a
+server can enable both ``tls-crypt-v2`` and either ``tls-crypt`` or
+``tls-auth``.  This is achieved by introducing a P_CONTROL_HARD_RESET_CLIENT_V3
+opcode, that indicates that the client wants to use ``tls-crypt-v2`` for the
+current connection.
+
+For an exact specification and more details, read the Implementation section.
+
+
+Implementation
+--------------
+
+When setting up a tls-crypt-v2 group (similar to generating a tls-crypt or
+tls-auth key previously):
+
+1. Generate a tls-crypt-v2 server key using OpenVPN's ``--genkey``.  This key
+   contains 2 512-bit keys, of which we use:
+
+   * the first 256 bits of key 1 as AES-256-CTR encryption key ``Ke``
+   * the first 256 bits of key 2 as HMAC-SHA-256 authentication key ``Ka``
+
+   This fomat is similar to the format for regular ``tls-crypt``/``tls-auth``
+   and data channel keys, which allows us to reuse code.
+
+2. Add the tls-crypt-v2 server key to all server configs
+   (``tls-crypt-v2 /path/to/server.key``)
+
+
+When provisioning a client, create a client-specific tls-crypt key:
+
+1. Generate 2048 bits client-specific key ``Kc``
+
+2. Optionally generate metadata
+
+   The first byte of the metadata determines the type.  The initial
+   implementation supports the following types:
+
+   0x00 (USER):         User-defined free-form data.
+   0x01 (TIMESTAMP):    64-bit network order unix timestamp of key generation.
+
+   The timestamp can be used to reject too-old tls-crypt-v2 client keys.
+
+   User metadata could for example contain the users certificate serial, such
+   that the incoming connection can be verified against a CRL.
+
+   If no metadata is supplied during key generation, openvpn defaults to the
+   TIMESTAMP metadata type.
+
+3. Create a wrapped client key ``WKc``, using the same nonce-misuse-resistant
+   SIV construction we use for tls-crypt:
+
+   ``len = len(WKc)`` (16 bit, network byte order)
+
+   ``T = HMAC-SHA256(Ka, len || Kc || metadata)``
+
+   ``IV = 128 most significant bits of T``
+
+   ``WKc = T || AES-256-CTR(Ke, IV, Kc || metadata) || len``
+
+   Note that the length of ``WKc`` can be computed before composing ``WKc``,
+   because the length of each component is known (and AES-256-CTR does not add
+   any padding).
+
+4. Create a tls-crypt-v2 client key: PEM-encode ``Kc || WKc`` and store in a
+   file, using the header ``-----BEGIN OpenVPN tls-crypt-v2 client key-----``
+   and the footer ``-----END OpenVPN tls-crypt-v2 client key-----``.  (The PEM
+   format is simple, and following PEM allows us to use the crypto lib function
+   for en/decoding.)
+
+5. Add the tls-crypt-v2 client key to the client config
+   (``tls-crypt-v2 /path/to/client-specific.key``)
+
+
+When setting up the openvpn connection:
+
+1. The client reads the tls-crypt-v2 key from its config, and:
+
+   1. loads ``Kc`` as its tls-crypt key,
+   2. stores ``WKc`` in memory for sending to the server.
+
+2. To start the connection, the client creates a P_CONTROL_HARD_RESET_CLIENT_V3
+   message, wraps it with tls-crypt using ``Kc`` as the key, and appends
+   ``WKc``.  (``WKc`` must not be encrypted, to prevent a chicken-and-egg
+   problem.)
+
+3. The server receives the P_CONTROL_HARD_RESET_CLIENT_V3 message, and
+
+   1. reads the WKc length field from the end of the message, and extracts WKc
+      from the message
+   2. unwraps ``WKc``
+   3. uses unwrapped ``Kc`` to verify the remaining
+      P_CONTROL_HARD_RESET_CLIENT_V3 message's (encryption and) authentication.
+
+   The message is dropped and no error response is sent when either 3.1, 3.2 or
+   3.3 fails (DoS protection).
+
+4. Server optionally checks metadata using a --tls-crypt-v2-verify script
+
+   This allows early abort of connection, *before* we expose any of the
+   notoriously dangerous TLS, X.509 and ASN.1 parsers and thereby reduces the
+   attack surface of the server.
+
+   The metadata is checked *after* the OpenVPN three-way handshake has
+   completed, to prevent DoS attacks.  (That is, once the client has proved to
+   the server that it possesses Kc, by authenticating a packet that contains the
+   session ID picked by the server.)
+
+   A server should not send back any error messages if metadata verification
+   fails, to reduce attack surface and maximize DoS resilience.
+
+6. Client and server use ``Kc`` for (un)wrapping any following control channel
+   messages.
+
+
+Considerations
+--------------
+
+To allow for a smooth transition, the server implementation allows
+``tls-crypt`` or ``tls-auth`` to be used simultaneously with ``tls-crypt-v2``.
+This specification does not allow simultaneously using ``tls-crypt-v2`` and
+connections without any control channel wrapping, because that would break DoS
+resilience.
+
+WKc includes a length field, so we leave the option for future extension of the
+P_CONTROL_HEAD_RESET_CLIENT_V3 message open.  (E.g. add payload to the reset to
+indicate low-level protocol features.)
+
+``tls-crypt-v2`` uses fixed crypto algorithms, because:
+
+ * The crypto is used before we can do any negotiation, so the algorithms have
+   to be predefined.
+ * The crypto primitives are chosen conservatively, making problems with these
+   primitives unlikely.
+ * Making anything configurable adds complexity, both in implementation and
+   usage.  We should not add any more complexity than is absolutely necessary.
+
+Potential ``tls-crypt-v2`` risks:
+
+ * Slightly more work on first connection (``WKc`` unwrap + hard reset unwrap)
+   than with ``tls-crypt`` (hard reset unwrap) or ``tls-auth`` (hard reset auth).
+ * Flexible metadata allow mistakes
+   (So we should make it easy to do it right.  Provide tooling to create client
+   keys based on cert serial + CA fingerprint, provide script that uses CRL (if
+   available) to drop revoked keys.)
Signature · 1 line
2.7.4
#75 lore raw

PATCH v6 3/7 tls-crypt-v2: generate tls-crypt-v2 keys

In reply to Steffan Karger

As a first step towards a full tls-crypt-v2 implementation, add functionality to generate tls-crypt-v2 client and server keys.

---
v3: Include length in WKc
v4: Fix option verification (use ce->tls_*, not options->ce.tls_*)
v5: Rebase on patch set v5
v6:
- Reword commit message
- Fix base64 length calculation
- Change WKc length to ciphertext length (instead of plaintext length)

 doc/openvpn.8           |  51 +++++++++
 src/openvpn/base64.h    |   4 +
 src/openvpn/init.c      |  33 +++++-
 src/openvpn/integer.h   |  10 ++
 src/openvpn/options.c   |  69 +++++++++++-
 src/openvpn/options.h   |  14 +++
 src/openvpn/tls_crypt.c | 290 ++++++++++++++++++++++++++++++++++++++++++++++++
 src/openvpn/tls_crypt.h |  83 ++++++++++++--
 tests/t_lpback.sh       |  40 ++++++-
 9 files changed, 574 insertions(+), 20 deletions(-)
doc/openvpn.8 +51 -0
index e94ab76..490bbca 100644
@@ -5248,6 +5248,57 @@ degrading to the same security as using
 That is, the control channel still benefits from the extra protection against
 active man\-in\-the\-middle\-attacks and DoS attacks, but may no longer offer
 extra privacy and post\-quantum security on top of what TLS itself offers.
+
+For large setups or setups where clients are not trusted, consider using
+.B \-\-tls\-crypt\-v2
+instead.  That uses per\-client unique keys, and thereby improves the bounds to
+\fR'rotate a client key at least once per 8000 years'.
+.\"*********************************************************
+.TP
+.B \-\-tls\-crypt\-v2 keyfile
+
+Use client\-specific tls\-crypt keys.
+
+For clients,
+.B keyfile
+is a client\-specific tls\-crypt key.  Such a key can be generated using the
+.B \-\-tls\-crypt\-v2\-genkey
+option.
+
+For servers,
+.B keyfile
+is used to unwrap client\-specific keys supplied by the client during connection
+setup.  This key must be the same as the key used to generate the
+client\-specific key (see
+.B \-\-tls\-crypt\-v2\-genkey\fR).
+
+On servers, this option can be used together with the
+.B \-\-tls\-auth
+or
+.B \-\-tls\-crypt
+option.  In that case, the server will detect whether the client is using
+client\-specific keys, and automatically select the right mode.
+.\"*********************************************************
+.TP
+.B \-\-tls\-crypt\-v2\-genkey client|server keyfile [metadata]
+
+If the first parameter equals "server", generate a \-\-tls\-crypt\-v2 server
+key and store the key in
+.B keyfile\fR.
+
+
+If the first parameter equals "client", generate a \-\-tls\-crypt\-v2 client
+key, and store the key in
+.B keyfile\fR.
+
+If supplied, include the supplied
+.B metadata
+in the wrapped client key.  This metadata must be supplied in base64\-encoded
+form.  The metadata must be at most 735 bytes long (980 bytes in base64).
+
+.B TODO
+Metadata handling is not yet implemented.  This text will be updated by the
+commit that introduces metadata handling.
 .\"*********************************************************
 .TP
 .B \-\-askpass [file]
src/openvpn/base64.h +4 -0
index 5679bc9..f49860f 100644
@@ -34,6 +34,10 @@
 #ifndef _BASE64_H_
 #define _BASE64_H_
 
+/** Compute resulting base64 length.  6 bits per byte, padded to 4 bytes. */
+#define OPENVPN_BASE64_LENGTH(binary_length) \
+    ((((8 * binary_length) / 6) + 3) & ~3)
+
 int openvpn_base64_encode(const void *data, int size, char **str);
 
 int openvpn_base64_decode(const char *str, void *data, int size);
src/openvpn/init.c +28 -5
index 52c64da..ade1035 100644
@@ -1051,6 +1051,11 @@ print_openssl_info(const struct options *options)
 bool
 do_genkey(const struct options *options)
 {
+    /* should we disable paging? */
+    if (options->mlock && (options->genkey || options->tls_crypt_v2_genkey_file))
+    {
+        platform_mlockall(true);
+    }
     if (options->genkey)
     {
         int nbits_written;
@@ -1058,11 +1063,6 @@ do_genkey(const struct options *options)
         notnull(options->shared_secret_file,
                 "shared secret output file (--secret)");
 
-        if (options->mlock)     /* should we disable paging? */
-        {
-            platform_mlockall(true);
-        }
-
         nbits_written = write_key_file(2, options->shared_secret_file);
         if (nbits_written < 0)
         {
@@ -1074,6 +1074,29 @@ do_genkey(const struct options *options)
             options->shared_secret_file);
         return true;
     }
+    if (options->tls_crypt_v2_genkey_type)
+    {
+        if(!strcmp(options->tls_crypt_v2_genkey_type, "server"))
+        {
+            tls_crypt_v2_write_server_key_file(options->tls_crypt_v2_genkey_file);
+            return true;
+        }
+        if (options->tls_crypt_v2_genkey_type
+                 && !strcmp(options->tls_crypt_v2_genkey_type, "client"))
+        {
+            if (!options->tls_crypt_v2_file)
+            {
+                msg(M_USAGE, "--tls-crypt-v2-gen-client-key requires a server key to be set via --tls-crypt-v2");
+            }
+
+            tls_crypt_v2_write_client_key_file(options->tls_crypt_v2_genkey_file,
+                    options->tls_crypt_v2_metadata, options->tls_crypt_v2_file,
+                    options->tls_crypt_v2_inline);
+            return true;
+        }
+
+        msg(M_USAGE, "--tls-crypt-v2-genkey type should be \"client\" or \"server\"");
+    }
     return false;
 }
 
src/openvpn/integer.h +10 -0
index a7e19d3..b1ae0ed 100644
@@ -26,6 +26,16 @@
 
 #include "error.h"
 
+#ifndef htonll
+#define htonll(x) ((1==htonl(1)) ? (x) : \
+                  ((uint64_t)htonl((x) & 0xFFFFFFFF) << 32) | htonl((x) >> 32))
+#endif
+
+#ifndef ntohll
+#define ntohll(x) ((1==ntohl(1)) ? (x) : \
+                  ((uint64_t)ntohl((x) & 0xFFFFFFFF) << 32) | ntohl((x) >> 32))
+#endif
+
 /*
  * min/max functions
  */
src/openvpn/options.c +65 -4

128 lines not on this page · raw message

src/openvpn/options.h +14 -0
index acbd108..3d2c770 100644
@@ -139,6 +139,11 @@ struct connection_entry
     /* Shared secret used for TLS control channel authenticated encryption */
     const char *tls_crypt_file;
     const char *tls_crypt_inline;
+
+    /* Client-specific secret or server key used for TLS control channel
+     * authenticated encryption v2 */
+    const char *tls_crypt_v2_file;
+    const char *tls_crypt_v2_inline;
 };
 
 struct remote_entry
@@ -576,6 +581,15 @@ struct options
     const char *tls_crypt_file;
     const char *tls_crypt_inline;
 
+    /* Client-specific secret or server key used for TLS control channel
+     * authenticated encryption v2 */
+    const char *tls_crypt_v2_file;
+    const char *tls_crypt_v2_inline;
+
+    const char *tls_crypt_v2_genkey_type;
+    const char *tls_crypt_v2_genkey_file;
+    const char *tls_crypt_v2_metadata;
+
     /* Allow only one session */
     bool single_session;
 
src/openvpn/tls_crypt.c +290 -0

304 lines not on this page · raw message

src/openvpn/tls_crypt.h +75 -8

136 lines not on this page · raw message

tests/t_lpback.sh +37 -3
index 2052c62..fb43211 100755
@@ -21,8 +21,8 @@
 
 set -eu
 top_builddir="${top_builddir:-..}"
-trap "rm -f key.$$ log.$$ ; trap 0 ; exit 77" 1 2 15
-trap "rm -f key.$$ log.$$ ; exit 1" 0 3
+trap "rm -f key.$$ tc-server-key.$$ tc-client-key.$$ log.$$ ; trap 0 ; exit 77" 1 2 15
+trap "rm -f key.$$ tc-server-key.$$ tc-client-key.$$ log.$$ ; exit 1" 0 3
 
 # Get list of supported ciphers from openvpn --show-ciphers output
 CIPHERS=$(${top_builddir}/src/openvpn/openvpn --show-ciphers | \
@@ -55,6 +55,40 @@ do
     fi
 done
 
-rm key.$$ log.$$
+echo -n "Testing tls-crypt-v2 server key generation..."
+"${top_builddir}/src/openvpn/openvpn" \
+    --tls-crypt-v2-genkey server tc-server-key.$$ >log.$$ 2>&1
+if [ $? != 0 ] ; then
+    echo "FAILED"
+    cat log.$$
+    e=1
+else
+    echo "OK"
+fi
+
+echo -n "Testing tls-crypt-v2 key generation (no metadata)..."
+"${top_builddir}/src/openvpn/openvpn" --tls-crypt-v2 tc-server-key.$$ \
+    --tls-crypt-v2-genkey client tc-client-key.$$ >log.$$ 2>&1
+if [ $? != 0 ] ; then
+    echo "FAILED"
+    cat log.$$
+    e=1
+else
+    echo "OK"
+fi
+
+echo -n "Testing tls-crypt-v2 key generation (max length metadata)..."
+"${top_builddir}/src/openvpn/openvpn" --tls-crypt-v2 tc-server-key.$$ \
+    --tls-crypt-v2-genkey client tc-client-key.$$ \
+    $(head -c732 /dev/zero | base64 -w0) >log.$$ 2>&1
+if [ $? != 0 ] ; then
+    echo "FAILED"
+    cat log.$$
+    e=1
+else
+    echo "OK"
+fi
+
+rm key.$$ tc-server-key.$$ tc-client-key.$$ log.$$
 trap 0
 exit $e
Signature · 1 line
2.7.4
#76 lore raw

PATCH v6 4/7 tls-crypt-v2: add unwrap_client_key

In reply to Steffan Karger

Add helper functions to unwrap tls-crypt-v2 client keys.

---
v3: Include length in WKc
v4: Rebase on v4 patch set
v5: Rebase on v5 patch set
v6: Change WKc length to ciphertext length (was plaintext)

 src/openvpn/buffer.h                      |   7 +
 src/openvpn/tls_crypt.c                   | 121 ++++++++++++++
 tests/unit_tests/openvpn/test_tls_crypt.c | 254 +++++++++++++++++++++++++++---
 3 files changed, 362 insertions(+), 20 deletions(-)
src/openvpn/buffer.h +7 -0
index cb9e9df..e17ce07 100644
@@ -837,6 +837,13 @@ buf_read_u32(struct buffer *buf, bool *good)
     }
 }
 
+/** Return true if buffer contents are equal */
+static inline bool
+buf_equal(const struct buffer *a, const struct buffer *b)
+{
+    return BLEN(a) == BLEN(b) && 0 == memcmp(BPTR(a), BPTR(b), BLEN(a));
+}
+
 /**
  * Compare src buffer contents with match.
  * *NOT* constant time. Do not use when comparing HMACs.
src/openvpn/tls_crypt.c +121 -0

133 lines not on this page · raw message

tests/unit_tests/openvpn/test_tls_crypt.c +234 -20

335 lines not on this page · raw message

Signature · 1 line
2.7.4
#77 lore raw

PATCH v6 5/7 tls-crypt-v2: add P_CONTROL_HARD_RESET_CLIENT_V3 opcode

In reply to Steffan Karger

Not used yet, but prepare for sending and receiving tls-crypt-v2 handshake messages.

---
v3: rebase on curent master / v3 patch set
v4: rebase on v4 patch set
v5: rebase on v5 patch set
v6: rebase on v6 patch set

 src/openvpn/ps.c         |  3 ++-
 src/openvpn/ssl.c        | 23 ++++++++++++++++++-----
 src/openvpn/ssl.h        |  5 ++++-
 src/openvpn/ssl_common.h |  2 ++
 4 files changed, 26 insertions(+), 7 deletions(-)
src/openvpn/ps.c +2 -1
index 25ab374..2089e6b 100644
@@ -985,7 +985,8 @@ is_openvpn_protocol(const struct buffer *buf)
     {
         return p[0] == 0
                && p[1] >= 14
-               && p[2] == (P_CONTROL_HARD_RESET_CLIENT_V2<<P_OPCODE_SHIFT);
+               && (p[2] == (P_CONTROL_HARD_RESET_CLIENT_V2 << P_OPCODE_SHIFT)
+                   || p[2] == (P_CONTROL_HARD_RESET_CLIENT_V3 << P_OPCODE_SHIFT));
     }
     else if (len >= 2)
     {
src/openvpn/ssl.c +18 -5
index 455adfb..344447d 100644
@@ -784,6 +784,9 @@ packet_opcode_name(int op)
         case P_CONTROL_HARD_RESET_SERVER_V2:
             return "P_CONTROL_HARD_RESET_SERVER_V2";
 
+        case P_CONTROL_HARD_RESET_CLIENT_V3:
+            return "P_CONTROL_HARD_RESET_CLIENT_V3";
+
         case P_CONTROL_SOFT_RESET_V1:
             return "P_CONTROL_SOFT_RESET_V1";
 
@@ -856,7 +859,8 @@ is_hard_reset(int op, int key_method)
 
     if (!key_method || key_method >= 2)
     {
-        if (op == P_CONTROL_HARD_RESET_CLIENT_V2 || op == P_CONTROL_HARD_RESET_SERVER_V2)
+        if (op == P_CONTROL_HARD_RESET_CLIENT_V2 || op == P_CONTROL_HARD_RESET_SERVER_V2
+            || op == P_CONTROL_HARD_RESET_CLIENT_V3)
         {
             return true;
         }
@@ -1087,8 +1091,15 @@ tls_session_init(struct tls_multi *multi, struct tls_session *session)
     }
     else /* session->opt->key_method >= 2 */
     {
-        session->initial_opcode = session->opt->server ?
-                                  P_CONTROL_HARD_RESET_SERVER_V2 : P_CONTROL_HARD_RESET_CLIENT_V2;
+        if (session->opt->server)
+        {
+            session->initial_opcode = P_CONTROL_HARD_RESET_SERVER_V2;
+        }
+        else
+        {
+            session->initial_opcode = session->opt->tls_crypt_v2 ?
+                    P_CONTROL_HARD_RESET_CLIENT_V3 : P_CONTROL_HARD_RESET_CLIENT_V2;
+        }
     }
 
     /* Initialize control channel authentication parameters */
@@ -3419,7 +3430,8 @@ tls_pre_decrypt(struct tls_multi *multi,
             {
                 /* verify client -> server or server -> client connection */
                 if (((op == P_CONTROL_HARD_RESET_CLIENT_V1
-                      || op == P_CONTROL_HARD_RESET_CLIENT_V2) && !multi->opt.server)
+                      || op == P_CONTROL_HARD_RESET_CLIENT_V2
+                      || op == P_CONTROL_HARD_RESET_CLIENT_V3) && !multi->opt.server)
                     || ((op == P_CONTROL_HARD_RESET_SERVER_V1
                          || op == P_CONTROL_HARD_RESET_SERVER_V2) && multi->opt.server))
                 {
@@ -3804,7 +3816,8 @@ tls_pre_decrypt_lite(const struct tls_auth_standalone *tas,
         /* this packet is from an as-yet untrusted source, so
          * scrutinize carefully */
 
-        if (op != P_CONTROL_HARD_RESET_CLIENT_V2)
+        if (op != P_CONTROL_HARD_RESET_CLIENT_V2
+            && op != P_CONTROL_HARD_RESET_CLIENT_V3)
         {
             /*
              * This can occur due to bogus data or DoS packets.
src/openvpn/ssl.h +4 -1
index 72227d9..852debd 100644
@@ -63,9 +63,12 @@
 #define P_CONTROL_HARD_RESET_CLIENT_V2 7     /* initial key from client, forget previous state */
 #define P_CONTROL_HARD_RESET_SERVER_V2 8     /* initial key from server, forget previous state */
 
+/* indicates key_method >= 2 and client-specific tls-crypt key */
+#define P_CONTROL_HARD_RESET_CLIENT_V3 10    /* initial key from client, forget previous state */
+
 /* define the range of legal opcodes */
 #define P_FIRST_OPCODE                 1
-#define P_LAST_OPCODE                  9
+#define P_LAST_OPCODE                  10
 
 /*
  * Set the max number of acknowledgments that can "hitch a ride" on an outgoing
src/openvpn/ssl_common.h +2 -0
index 08ef6ff..e3c852a 100644
@@ -286,6 +286,8 @@ struct tls_options
     const char *config_authname;
     bool ncp_enabled;
 
+    bool tls_crypt_v2;
+
     /** TLS handshake wrapping state */
     struct tls_wrap_ctx tls_wrap;
 
Signature · 1 line
2.7.4
#78 lore raw

PATCH v6 6/7 tls-crypt-v2: implement tls-crypt-v2 handshake

In reply to Steffan Karger

This makes clients send-and-use, and servers receive-unwrap-and-use tls-crypt-v2 client keys, which completes the on-the-wire work.

---
v3: include length in WKc, rebase on curent master / v3 patch set
v4: resolve merge conflict (fixes memory leak)
v5: rebase on v5 patch set
v6: rebase on v6 patch set

 src/openvpn/init.c                        | 39 ++++++++++++++-
 src/openvpn/openvpn.h                     |  2 +
 src/openvpn/ssl.c                         | 79 ++++++++++++++++++++++++-------
 src/openvpn/ssl_common.h                  |  6 +++
 src/openvpn/tls_crypt.c                   | 57 ++++++++++++++++++++++
 src/openvpn/tls_crypt.h                   | 14 ++++++
 tests/unit_tests/openvpn/test_tls_crypt.c |  6 +++
 7 files changed, 186 insertions(+), 17 deletions(-)
src/openvpn/init.c +38 -1
index ade1035..c2e33ee 100644
@@ -2463,6 +2463,7 @@ key_schedule_free(struct key_schedule *ks, bool free_ssl_ctx)
     if (tls_ctx_initialised(&ks->ssl_ctx) && free_ssl_ctx)
     {
         tls_ctx_free(&ks->ssl_ctx);
+        free_key_ctx(&ks->tls_crypt_v2_server_key);
     }
     CLEAR(*ks);
 }
@@ -2646,6 +2647,24 @@ do_init_crypto_tls_c1(struct context *c)
         /* initialize tls-auth/crypt key */
         do_init_tls_wrap_key(c);
 
+        /* tls-crypt with client-specific keys (--tls-crypt-v2) */
+        if (options->tls_crypt_v2_file)
+        {
+            if (options->tls_server)
+            {
+                tls_crypt_v2_init_server_key(&c->c1.ks.tls_crypt_v2_server_key,
+                                             true, options->tls_crypt_v2_file,
+                                             options->tls_crypt_v2_inline);
+            }
+            else
+            {
+                tls_crypt_v2_init_client_key(&c->c1.ks.tls_wrap_key,
+                                             &c->c1.ks.tls_crypt_v2_wkc,
+                                             options->tls_crypt_v2_file,
+                                             options->tls_crypt_v2_inline);
+            }
+        }
+
 #if 0 /* was: #if ENABLE_INLINE_FILES --  Note that enabling this code will break restarts */
         if (options->priv_key_file_inline)
         {
@@ -2862,13 +2881,28 @@ do_init_crypto_tls(struct context *c, const unsigned int flags)
     }
 
     /* TLS handshake encryption (--tls-crypt) */
-    if (options->ce.tls_crypt_file)
+    if (options->ce.tls_crypt_file
+        || (options->ce.tls_crypt_v2_file && options->tls_client))
     {
         to.tls_wrap.mode = TLS_WRAP_CRYPT;
         to.tls_wrap.opt.key_ctx_bi = c->c1.ks.tls_wrap_key;
         to.tls_wrap.opt.pid_persist = &c->c1.pid_persist;
         to.tls_wrap.opt.flags |= CO_PACKET_ID_LONG_FORM;
         tls_crypt_adjust_frame_parameters(&to.frame);
+
+        if (options->tls_crypt_v2_file)
+        {
+            to.tls_wrap.tls_crypt_v2_wkc = &c->c1.ks.tls_crypt_v2_wkc;
+        }
+    }
+
+    if (options->tls_crypt_v2_file)
+    {
+        to.tls_crypt_v2 = true;
+        if (options->tls_server)
+        {
+            to.tls_wrap.tls_crypt_v2_server_key = c->c1.ks.tls_crypt_v2_server_key;
+        }
     }
 
     /* If we are running over TCP, allow for
@@ -3472,6 +3506,8 @@ do_close_free_key_schedule(struct context *c, bool free_ssl_ctx)
      */
     free_key_ctx_bi(&c->c1.ks.tls_wrap_key);
     CLEAR(c->c1.ks.tls_wrap_key);
+    buf_clear(&c->c1.ks.tls_crypt_v2_wkc);
+    free_buf(&c->c1.ks.tls_crypt_v2_wkc);
 
     if (!(c->sig->signal_received == SIGUSR1 && c->options.persist_key))
     {
@@ -4407,6 +4443,7 @@ inherit_context_child(struct context *dest,
     dest->c1.ks.ssl_ctx = src->c1.ks.ssl_ctx;
     dest->c1.ks.tls_wrap_key = src->c1.ks.tls_wrap_key;
     dest->c1.ks.tls_auth_key_type = src->c1.ks.tls_auth_key_type;
+    dest->c1.ks.tls_crypt_v2_server_key = src->c1.ks.tls_crypt_v2_server_key;
     /* inherit pre-NCP ciphers */
     dest->c1.ciphername = src->c1.ciphername;
     dest->c1.authname = src->c1.authname;
src/openvpn/openvpn.h +2 -0
index bc972f2..d11f61d 100644
@@ -66,6 +66,8 @@ struct key_schedule
     /* optional TLS control channel wrapping */
     struct key_type tls_auth_key_type;
     struct key_ctx_bi tls_wrap_key;
+    struct key_ctx tls_crypt_v2_server_key;
+    struct buffer tls_crypt_v2_wkc;             /**< Wrapped client key */
 };
 
 /*
src/openvpn/ssl.c +63 -16

126 lines not on this page · raw message

src/openvpn/ssl_common.h +6 -0
index e3c852a..d744881 100644
@@ -213,6 +213,12 @@ struct tls_wrap_ctx
     } mode;                     /**< Control channel wrapping mode */
     struct crypto_options opt;  /**< Crypto state */
     struct buffer work;         /**< Work buffer (only for --tls-crypt) */
+    struct key_ctx tls_crypt_v2_server_key;  /**< Decrypts client keys */
+    const struct buffer *tls_crypt_v2_wkc;   /**< Wrapped client key,
+                                                  sent to server */
+    struct buffer tls_crypt_v2_metadata;     /**< Received from client */
+    bool cleanup_key_ctx;                    /**< opt.key_ctx_bi is owned by
+                                                  this context */
 };
 
 /*
src/openvpn/tls_crypt.c +57 -0

69 lines not on this page · raw message

src/openvpn/tls_crypt.h +14 -0
index f888c79..edf297a 100644
@@ -84,6 +84,7 @@
 #include "buffer.h"
 #include "crypto.h"
 #include "session_id.h"
+#include "ssl_common.h"
 
 #define TLS_CRYPT_TAG_SIZE (256/8)
 #define TLS_CRYPT_PID_SIZE (sizeof(packet_id_type) + sizeof(net_time_t))
@@ -184,6 +185,19 @@ void tls_crypt_v2_init_client_key(struct key_ctx_bi *key,
                                   const char *key_inline);
 
 /**
+ * Extract a tls-crypt-v2 client key from a P_CONTROL_HARD_RESET_CLIENT_V3
+ * message, and load the key into the supplied tls wrap context.
+ *
+ * @param buf   Buffer containing a received P_CONTROL_HARD_RESET_CLIENT_V3
+ *              message.
+ * @param ctx   tls-wrap context to be initialized with the client key.
+ *
+ * @returns true if a key was successfully extracted.
+ */
+bool tls_crypt_v2_extract_client_key(struct buffer *buf,
+                                     struct tls_wrap_ctx *ctx);
+
+/**
  * Generate a tls-crypt-v2 server key, and write to file.
  *
  * @param filename          Filename of the server key file to create.
tests/unit_tests/openvpn/test_tls_crypt.c +6 -0
index d499c4e..6cdf4ed 100644
@@ -346,6 +346,12 @@ tls_crypt_v2_wrap_unwrap_max_metadata(void **state) {
     assert_true(0 == memcmp(ctx->client_key2.keys, unwrapped_client_key2.keys,
                             sizeof(ctx->client_key2.keys)));
     assert_true(buf_equal(&ctx->metadata, &unwrap_metadata));
+
+    struct tls_wrap_ctx wrap_ctx = {
+            .mode = TLS_WRAP_CRYPT,
+            .tls_crypt_v2_server_key = ctx->server_keys.encrypt,
+    };
+    assert_true(tls_crypt_v2_extract_client_key(&ctx->wkc, &wrap_ctx));
 }
 
 /**
Signature · 1 line
2.7.4