Client-specific tls-crypt keys (--tls-crypt-v2)
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
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
@@ -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
@@ -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
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.
Signed-off-by Steffan Karger <[email protected]...>--- 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.csrc/openvpn/crypto_backend.h +1
-0@@ -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
-0void 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@@ -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
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@@ -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
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
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.
Signed-off-by Steffan Karger <[email protected]...>--- 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.csrc/openvpn/crypto_backend.h +1
-0@@ -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@@ -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@@ -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@@ -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@@ -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_bufwithsrc_bufto 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_bufcontains well-formatted PEM data afterpem_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
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->lengthbytes and then writebptr->lengthbytes 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_bufcontains well-formatted PEM data afterpem_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_bufis larger thatsrc_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
PATCH 2/10 tls-crypt-v2: add specification to doc/
In reply to Steffan KargerThis 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
@@ -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
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.
Signed-off-by Steffan Karger <[email protected]...>--- doc/tls-crypt-v2.txt | 164 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 164 insertions(+) create mode 100644 doc/tls-crypt-v2.txtdoc/tls-crypt-v2.txt new file +3
-0@@ -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
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
@@ -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
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
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
PATCH 3/10 tls-crypt-v2: generate client keys
In reply to Steffan KargerAs 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 connecMUST_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
@@ -566,6 +566,15 @@ struct optionsconst 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
@@ -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
@@ -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
PATCH 4/10 tls-crypt-v2: add unwrap_client_key
In reply to Steffan KargerAdd 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
@@ -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
PATCH 5/10 tls-crypt-v2: add P_CONTROL_HARD_RESET_CLIENT_V3 opcode
In reply to Steffan KargerNot 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
@@ -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
@@ -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
@@ -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
@@ -286,6 +286,8 @@ struct tls_optionsconst 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
PATCH 6/10 tls-crypt-v2: implement tls-crypt-v2 handshake
In reply to Steffan KargerThis 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
PATCH 7/10 Move env helper functions into their own module/file
In reply to Steffan KargerTo 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
@@ -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
@@ -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
@@ -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
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 theenv_setfunctionality out into it's own file.Signed-off-by Antonio Quartulli <[email protected]...>Signed-off-by Steffan Karger <[email protected]...>
*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]...
[Openvpn-devel] [PATCH applied] Re: Move env helper functions into their own module/file
In reply to Steffan KargerDoes 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
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
Acked-by Gert Doering <[email protected]...>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.cand parts ended inenv_set.h, so "plain diff" betweenold-misc.candnew-env_set.cdidn't help much).One small catch: this collided with
06ad53e067d9a8bandsetenv_long_long()being changed from "%lld" to PRIi64 - so I changed the patch accordingly so that it still only moves lines frommisc.ctoenv_set.cYour patch has been applied to the master branch.
commit
0afe0ca5620d5ab7cec3380d40048dce2286f048Author: Steffan Karger Date: Fri Dec 8 13:07:47 2017 +0100Move 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]...
[Openvpn-devel] [PATCH applied] Re: Move env helper functions into their own module/file
In reply to Steffan KargerDoes 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
PATCH 8/10 Move file-related functions from misc.c to platform.c
In reply to Steffan KargerTo 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
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 toplatform.c(which is where other file-related functions already reside).Signed-off-by Steffan Karger <[email protected]...>
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.csrc/openvpn/init.c +1
-1@@ -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@@ -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@@ -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@@ -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@@ -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@@ -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@@ -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@@ -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@@ -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@@ -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@@ -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
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 toplatform.c(which is where other file-related functions already reside).Signed-off-by Steffan Karger <[email protected]...>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.cmess some more.
Thanks!
-Steffan
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
PATCH 9/10 Move execve/run_script helper functions to run_command.c
In reply to Steffan KargerFrom: 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
@@ -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
@@ -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 intbool 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 infree (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
@@ -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
@@ -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
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 newrun_command.cmodule.While at it, abstract away the
script_securityglobal variable.Signed-off-by Steffan Karger <[email protected]...>
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
PATCH 10/10 tls-crypt-v2: Add script hook to verify metadata
In reply to Steffan KargerTo 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -575,6 +575,8 @@ struct optionsconst 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
@@ -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
@@ -293,6 +293,7 @@ struct tls_optionsbool 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
@@ -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
@@ -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
@@ -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
PATCH v2 1/9 Move file-related functions from misc.c to platform.c
In reply to Steffan KargerFrom: 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
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
@@ -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
@@ -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 intbool 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 infree (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
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 newrun_command.cmodule.
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_securityglobal 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
Signed-off-by Steffan Karger <[email protected]...>---
[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
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_scriptvariable 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
[Openvpn-devel] [PATCH applied] Re: Move execve/run_script helper functions to run_command.c
In reply to Steffan KargerYour 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
PATCH v2 3/9 Add crypto_pem_{encode,decode}()
In reply to Steffan KargerFrom: 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
@@ -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
@@ -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
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
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_datain 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_testand everything seems to be working as expected.
Thanks!
-Steffan
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_datain 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_allocreturns 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
PATCH v3 2/9 Add crypto_pem_{encode,decode}()
In reply to Antonio QuartulliFrom: 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
@@ -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
@@ -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
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.
Signed-off-by Steffan Karger <[email protected]...>
Signature · 1 line
Antonio Quartulli
[Openvpn-devel] [PATCH applied] Re: Add crypto_pem_{encode, decode}()
In reply to Steffan KargerYour 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
PATCH v2 4/9 tls-crypt-v2: add specification to doc/
In reply to Steffan KargerFrom: 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
@@ -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
PATCH v2 5/9 tls-crypt-v2: generate client keys
In reply to Steffan KargerFrom: 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 connecMUST_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
@@ -567,6 +567,15 @@ struct optionsconst 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
@@ -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
PATCH v2 6/9 tls-crypt-v2: add unwrap_client_key
In reply to Steffan KargerFrom: 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
@@ -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
PATCH v2 7/9 tls-crypt-v2: add P_CONTROL_HARD_RESET_CLIENT_V3 opcode
In reply to Steffan KargerFrom: 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
@@ -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
@@ -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
@@ -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
@@ -286,6 +286,8 @@ struct tls_optionsconst 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
PATCH v2 8/9 tls-crypt-v2: implement tls-crypt-v2 handshake
In reply to Steffan KargerFrom: 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
PATCH v2 9/9 tls-crypt-v2: add script hook to verify metadata
In reply to Steffan KargerFrom: 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -576,6 +576,8 @@ struct optionsconst 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
@@ -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
@@ -293,6 +293,7 @@ struct tls_optionsbool 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
@@ -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
@@ -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
@@ -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
[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
PATCH v3 1/7 Introduce buffer_write_file()
In reply to Steffan KargerRewrite 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
@@ -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
@@ -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
@@ -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
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
@@ -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
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.
Signed-off-by Steffan Karger <[email protected]...>---
v3: Include length in WKcdoc/tls-crypt-v2.txt | 170 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 doc/tls-crypt-v2.txtdoc/tls-crypt-v2.txt new file +100
-0@@ -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
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.
Signed-off-by Steffan Karger <[email protected]...>---
v3: Include length in WKcdoc/tls-crypt-v2.txt | 170 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 doc/tls-crypt-v2.txtdoc/tls-crypt-v2.txt new file +100
-0@@ -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 messageI 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_Vxwith 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
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
Re: [Openvpn-devel] [PATCH v4 1/7] Introduce buffer_write_file()
Hi,
Quoted 30 lines · Steffan Karger
Rewrite
buf_write_string_filetobuffer_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.Signed-off-by Steffan Karger <[email protected]...>---
v3: split change out of "generate client key", reuse inwrite_key_file()
v4: improve doxygen and error handlingsrc/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@@ -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
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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
Re: [Openvpn-devel] [PATCH v5 1/7] Introduce buffer_write_file()
Hi,
One typo inline
Quoted 68 lines · Steffan Karger
Rewrite
buf_write_string_filetobuffer_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.Signed-off-by Steffan Karger <[email protected]...>---
v3: split change out of "generate client key", reuse inwrite_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@@ -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@@ -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
-3void 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
Re: [Openvpn-devel] [PATCH v5 1/7] Introduce buffer_write_file()
In reply to Steffan KargerHi,
Quoted 5 lines · Steffan Karger
Rewrite
buf_write_string_filetobuffer_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.Signed-off-by Steffan Karger <[email protected]...>
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
[Openvpn-devel] [PATCH applied] Re: Introduce buffer_write_file()
In reply to Steffan KargerYour 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
PATCH v4 2/7 tls-crypt-v2: add specification to doc/
In reply to Steffan KargerThis 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
@@ -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
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.
Signed-off-by Steffan Karger <[email protected]...>---
v3: Include length in WKc
v4: Clarify metadata handlingdoc/tls-crypt-v2.txt | 182 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 182 insertions(+)create mode 100644
doc/tls-crypt-v2.txtdoc/tls-crypt-v2.txt new file +3
-0@@ -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.)
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
PATCH v5 2/7 tls-crypt-v2: add specification to doc/
In reply to tincanteksupThis 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
@@ -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
PATCH v3 3/7 tls-crypt-v2: generate client keys
In reply to Steffan KargerAs 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
@@ -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
@@ -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
@@ -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
@@ -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 optionsconst 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
@@ -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
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.
Signed-off-by Steffan Karger <[email protected]...>---
v3: Include length in WKcdoc/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@@ -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@@ -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@@ -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@@ -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
-4MUST_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@@ -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 optionsconst 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@@ -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, ×tamp,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@@ -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@@ -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
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 structoptions *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 keythat 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
PATCH v3 4/7 tls-crypt-v2: add unwrap_client_key
In reply to Steffan KargerAdd 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
@@ -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
PATCH v3 5/7 tls-crypt-v2: add P_CONTROL_HARD_RESET_CLIENT_V3 opcode
In reply to Steffan KargerNot 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
@@ -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
@@ -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
@@ -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
@@ -286,6 +286,8 @@ struct tls_optionsconst 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
PATCH v3 6/7 tls-crypt-v2: implement tls-crypt-v2 handshake
In reply to Steffan KargerThis 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
PATCH v3 7/7 tls-crypt-v2: add script hook to verify metadata
In reply to Steffan KargerTo 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -590,6 +590,8 @@ struct optionsconst 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
@@ -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
@@ -293,6 +293,7 @@ struct tls_optionsbool 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
@@ -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
@@ -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
@@ -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
Re: [Openvpn-devel] [PATCH v3 1/7] Introduce buffer_write_file()
In reply to Steffan KargerHi,
Quoted 18 lines · Steffan Karger
Rewrite
buf_write_string_filetobuffer_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.Signed-off-by Steffan Karger <[email protected]...>---
v3: split change out of "generate client key", reuse inwrite_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
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@@ -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@@ -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
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@@ -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@@ -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@@ -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
PATCH v4 3/7 tls-crypt-v2: generate client keys
In reply to Steffan KargerAs 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
@@ -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
@@ -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
@@ -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
@@ -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 optionsconst 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
@@ -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
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
@@ -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
PATCH v4 5/7 tls-crypt-v2: add P_CONTROL_HARD_RESET_CLIENT_V3 opcode
In reply to Steffan KargerNot 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
@@ -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
@@ -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
@@ -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
@@ -286,6 +286,8 @@ struct tls_optionsconst 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
PATCH v4 6/7 tls-crypt-v2: implement tls-crypt-v2 handshake
In reply to Steffan KargerThis 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
PATCH v4 7/7 tls-crypt-v2: add script hook to verify metadata
In reply to Steffan KargerTo 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -590,6 +590,8 @@ struct optionsconst 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
@@ -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
@@ -293,6 +293,7 @@ struct tls_optionsbool 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
@@ -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
@@ -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
@@ -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
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.txtfor more details.Because of the extra dependencies, this requires adding a mock
parse_line()to the tls-crypt unit tests.Signed-off-by Antonio Quartulli <[email protected]...>Signed-off-by Steffan Karger <[email protected]...>---
v3: rebase on curent master / v3 patch set
v4: fix memory leak (metadata buffer was not free'd fortls_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@@ -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]
PATCH v6 2/7 tls-crypt-v2: add specification to doc/
In reply to Steffan KargerThis 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
@@ -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
PATCH v6 3/7 tls-crypt-v2: generate tls-crypt-v2 keys
In reply to Steffan KargerAs 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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 optionsconst 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
@@ -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
PATCH v6 4/7 tls-crypt-v2: add unwrap_client_key
In reply to Steffan KargerAdd 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
@@ -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
PATCH v6 5/7 tls-crypt-v2: add P_CONTROL_HARD_RESET_CLIENT_V3 opcode
In reply to Steffan KargerNot 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
@@ -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
@@ -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
@@ -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
@@ -286,6 +286,8 @@ struct tls_optionsconst 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
PATCH v6 6/7 tls-crypt-v2: implement tls-crypt-v2 handshake
In reply to Steffan KargerThis 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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