Skip to contentSkip to conversation

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

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

lore
Share & tools
lore
#79 lore raw

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

In reply to Steffan Karger

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

Because of the extra dependencies, this requires adding a mock parse_line() to the tls-crypt unit tests. Also, this turns tls_wrap_free into a static inline function, so that we don't need to compile in ssl.c (and all of it's dependencies) with the unit tests.

---
v3: rebase on curent master / v3 patch set
v4: fix memory leak (metadata buffer was not free'd for tls_wrap_tmp)
v5: rebase on v5 patch set, textual fixes in man page
v6: fix memory leak in unit tests, code style fixes

 Changes.rst                               |  6 +++
 doc/openvpn.8                             | 35 ++++++++++++++--
 src/openvpn/init.c                        |  1 +
 src/openvpn/options.c                     |  7 ++++
 src/openvpn/options.h                     |  2 +
 src/openvpn/ssl.c                         | 34 +++++----------
 src/openvpn/ssl.h                         | 18 ++++++++
 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 | 19 ++++++++-
 12 files changed, 175 insertions(+), 32 deletions(-)
Changes.rst +6 -0
index 70ce2e1..a7429b1 100644
@@ -3,6 +3,12 @@ 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``.
+
 ChaCha20-Poly1305 cipher support
     Added support for using the ChaCha20-Poly1305 cipher in the OpenVPN data
     channel.
doc/openvpn.8 +32 -3
index 490bbca..9b171c2 100644
@@ -5296,9 +5296,38 @@ If supplied, include the supplied
 in the wrapped client key.  This metadata must be supplied in base64\-encoded
 form.  The metadata must be at most 735 bytes long (980 bytes in base64).
 
-.B TODO
-Metadata handling is not yet implemented.  This text will be updated by the
-commit that introduces metadata handling.
+If no metadata is supplied, OpenVPN will use a 64\-bit unix timestamp
+representing the current time in UTC, encoded in network order, as metadata for
+the generated key.
+
+Servers can use
+.B \-\-tls\-crypt\-v2\-verify
+to specify a metadata verification command.
+.\"*********************************************************
+.TP
+.B \-\-tls\-crypt\-v2\-verify cmd
+
+Run command
+.B cmd
+to verify the metadata of the client\-specific tls\-crypt\-v2 key of a
+connecting client.  This allows server administrators to reject client
+connections, before exposing the TLS stack (including the notoriously dangerous
+X.509 and ASN.1 stacks) to the connecting client.
+
+OpenVPN supplies the following env vars to the command:
+.RS
+.IP \[bu] 2
+script_type is set to "tls\-crypt\-v2\-verify"
+.IP \[bu]
+metadata_type is set to "0" if 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 exiting with a non-zero exit code.
 .\"*********************************************************
 .TP
 .B \-\-askpass [file]
src/openvpn/init.c +1 -0
index c2e33ee..00da51b 100644
@@ -2902,6 +2902,7 @@ do_init_crypto_tls(struct context *c, const unsigned int flags)
         if (options->tls_server)
         {
             to.tls_wrap.tls_crypt_v2_server_key = c->c1.ks.tls_crypt_v2_server_key;
+            to.tls_crypt_v2_verify_script = c->options.tls_crypt_v2_verify_script;
         }
     }
 
src/openvpn/options.c +7 -0
index fa638f1..6e3f040 100644
@@ -629,6 +629,8 @@ static const char usage_message[] =
     "--tls-crypt-v2-genkey client|server keyfile [base64 metadata]: Generate a\n"
     "                  fresh tls-crypt-v2 client or server key, and store to\n"
     "                  keyfile.  If supplied, include metadata in wrapped key.\n"
+    "--tls-crypt-v2-verify cmd : Run command cmd to verify the metadata of the\n"
+    "                  client-supplied tls-crypt-v2 client key\n"
     "--askpass [file]: Get PEM password from controlling tty before we daemonize.\n"
     "--auth-nocache  : Don't cache --askpass or --auth-user-pass passwords.\n"
     "--crl-verify crl ['dir']: Check peer certificate against a CRL.\n"
@@ -8185,6 +8187,11 @@ add_option(struct options *options,
             options->tls_crypt_v2_metadata = p[3];
         }
     }
+    else if (streq(p[0], "tls-crypt-v2-verify") && p[1] && !p[2])
+    {
+        VERIFY_PERMISSION(OPT_P_GENERAL);
+        options->tls_crypt_v2_verify_script = p[1];
+    }
     else if (streq(p[0], "key-method") && p[1] && !p[2])
     {
         int key_method;
src/openvpn/options.h +2 -0
index 3d2c770..d5ceb18 100644
@@ -590,6 +590,8 @@ struct options
     const char *tls_crypt_v2_genkey_file;
     const char *tls_crypt_v2_metadata;
 
+    const char *tls_crypt_v2_verify_script;
+
     /* Allow only one session */
     bool single_session;
 
src/openvpn/ssl.c +11 -23
index 3d991e1..b6705c9 100644
@@ -1047,23 +1047,6 @@ tls_session_user_pass_enabled(struct tls_session *session)
 /** @addtogroup control_processor
  *  @{ */
 
-/** Free the elements of a tls_wrap_ctx structure */
-static void tls_wrap_free(struct tls_wrap_ctx *tls_wrap)
-{
-    if (packet_id_initialized(&tls_wrap->opt.packet_id))
-    {
-        packet_id_free(&tls_wrap->opt.packet_id);
-    }
-
-    if (tls_wrap->cleanup_key_ctx)
-    {
-        free_key_ctx_bi(&tls_wrap->opt.key_ctx_bi);
-    }
-
-    free_buf(&tls_wrap->tls_crypt_v2_metadata);
-    free_buf(&tls_wrap->work);
-}
-
 /** @name Functions for initialization and cleanup of tls_session structures
  *  @{ */
 
@@ -1533,14 +1516,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",
@@ -3621,7 +3605,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;
                 }
@@ -3674,7 +3659,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;
                     }
@@ -3695,7 +3681,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;
                     }
@@ -3897,8 +3884,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.h +18 -0
index 852debd..5fa8217 100644
@@ -527,6 +527,24 @@ bool tls_item_in_cipher_list(const char *item, const char *list);
  * inline functions
  */
 
+/** Free the elements of a tls_wrap_ctx structure */
+static inline void
+tls_wrap_free(struct tls_wrap_ctx *tls_wrap)
+{
+    if (packet_id_initialized(&tls_wrap->opt.packet_id))
+    {
+        packet_id_free(&tls_wrap->opt.packet_id);
+    }
+
+    if (tls_wrap->cleanup_key_ctx)
+    {
+        free_key_ctx_bi(&tls_wrap->opt.key_ctx_bi);
+    }
+
+    free_buf(&tls_wrap->tls_crypt_v2_metadata);
+    free_buf(&tls_wrap->work);
+}
+
 static inline bool
 tls_initial_packet_received(const struct tls_multi *multi)
 {
src/openvpn/ssl_common.h +1 -0
index d744881..923cd95 100644
@@ -293,6 +293,7 @@ struct tls_options
     bool ncp_enabled;
 
     bool tls_crypt_v2;
+    const char *tls_crypt_v2_verify_script;
 
     /** TLS handshake wrapping state */
     struct tls_wrap_ctx tls_wrap;
src/openvpn/tls_crypt.c +68 -1

92 lines not on this page · raw message

src/openvpn/tls_crypt.h +2 -1
index edf297a..9b5ea97 100644
@@ -195,7 +195,8 @@ void tls_crypt_v2_init_client_key(struct key_ctx_bi *key,
  * @returns true if a key was successfully extracted.
  */
 bool tls_crypt_v2_extract_client_key(struct buffer *buf,
-                                     struct tls_wrap_ctx *ctx);
+                                     struct tls_wrap_ctx *ctx,
+                                     const struct tls_options *opt);
 
 /**
  * Generate a tls-crypt-v2 server key, and write to file.
tests/unit_tests/openvpn/Makefile.am +9 -3
index 22a458a..b4304e3 100644
@@ -6,7 +6,10 @@ if HAVE_LD_WRAP_SUPPORT
 check_PROGRAMS += argv_testdriver buffer_testdriver
 endif
 
-check_PROGRAMS += crypto_testdriver packet_id_testdriver tls_crypt_testdriver
+check_PROGRAMS += crypto_testdriver packet_id_testdriver
+if HAVE_LD_WRAP_SUPPORT
+check_PROGRAMS += tls_crypt_testdriver
+endif
 
 TESTS = $(check_PROGRAMS)
 
@@ -52,13 +55,16 @@ 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)
-tls_crypt_testdriver_LDFLAGS = @TEST_LDFLAGS@
+tls_crypt_testdriver_LDFLAGS = @TEST_LDFLAGS@ -Wl,--wrap=parse_line
 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 +18 -1
index 6cdf4ed..62721e8 100644
@@ -43,8 +43,24 @@
 
 #define TESTBUF_SIZE            128
 
+/* Defines for use in the tests and the mock parse_line() */
+#define PATH1       "/s p a c e"
+#define PATH2       "/foo bar/baz"
+#define PARAM1      "param1"
+#define PARAM2      "param two"
+
 const char plaintext_short[1];
 
+int
+__wrap_parse_line(const char *line, char **p, const int n, const char *file,
+                  const int line_num, int msglevel, struct gc_arena *gc)
+{
+    p[0] = PATH1 PATH2;
+    p[1] = PARAM1;
+    p[2] = PARAM2;
+    return 3;
+}
+
 struct test_tls_crypt_context {
     struct crypto_options co;
     struct key_type kt;
@@ -351,7 +367,8 @@ 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));
+    tls_wrap_free(&wrap_ctx);
 }
 
 /**
Signature · 1 line
2.7.4
#80 lore raw

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

In reply to Steffan Karger

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

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

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

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

Two typos only .. probably can be fixed on the fly

Quoted 53 lines · Steffan Karger

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

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

  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 +31 -0
new file mode 100644
index 0000000..582a2b8
@@ -0,0 +1,189 @@
+Client-specific tls-crypt keys (--tls-crypt-v2)
+===============================================
+
+This document describes the ``--tls-crypt-v2`` option, which enables OpenVPN
+to use client-specific ``--tls-crypt`` keys.
+
+Rationale
+---------
+
+``--tls-auth`` and ``tls-crypt`` use a pre-shared group key, which is shared
+among all clients and servers in an OpenVPN deployment.  If any client or
+server is compromised, the attacker will have access to this shared key, and it
+will no longer provide any security.  To reduce the risk of losing pre-shared
+keys, ``tls-crypt-v2`` adds the ability to supply each client with a unique
+tls-crypt key.  This allows large organisations and VPN providers to profit
+from the same DoS and TLS stack protection that small deployments can already
+achieve using ``tls-auth`` or ``tls-crypt``.
+
+Also, for ``tls-crypt``, even if all these peers succeed in keeping the key
+secret, the key lifetime is limited to roughly 8000 years, divided by the
+number of clients (see the ``--tls-crypt`` section of the man page).  Using
+client-specific keys, we lift this lifetime requirement to roughly 8000 years
+for each client key (which "Should Be Enough For Everybody (tm)").
+
+
+Introduction
+------------
+
+``tls-crypt-v2`` uses an encrypted cookie mechanism to introduce
+client-specific tls-crypt keys without introducing a lot of server-side state.
+The client-specific key is encrypted using a server key.  The server key is the

doc/tls-crypt-v2.txt line 41

Quoted 10 lines
doc/tls-crypt-v2.txt new file +10 -0
+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

"attack surface"

Quoted 16 lines
doc/tls-crypt-v2.txt new file +16 -0
+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
+--------------
+

doc/tls-crypt-v2.txt line 67

Quoted 10 lines
doc/tls-crypt-v2.txt new file +10 -0
+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``

fomat -> format

Quoted 123 lines
doc/tls-crypt-v2.txt new file +122 -0
+   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.)
Acked-by on v7 2/7
#82 lore raw

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

In reply to Steffan Karger

Hi,

one little typ0 and one addition below:

Quoted 73 lines · Steffan Karger

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

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

 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 +51 -0
new file mode 100644
index 0000000..582a2b8
@@ -0,0 +1,189 @@
+Client-specific tls-crypt keys (--tls-crypt-v2)
+===============================================
+
+This document describes the ``--tls-crypt-v2`` option, which enables OpenVPN
+to use client-specific ``--tls-crypt`` keys.
+
+Rationale
+---------
+
+``--tls-auth`` and ``tls-crypt`` use a pre-shared group key, which is shared
+among all clients and servers in an OpenVPN deployment.  If any client or
+server is compromised, the attacker will have access to this shared key, and it
+will no longer provide any security.  To reduce the risk of losing pre-shared
+keys, ``tls-crypt-v2`` adds the ability to supply each client with a unique
+tls-crypt key.  This allows large organisations and VPN providers to profit
+from the same DoS and TLS stack protection that small deployments can already
+achieve using ``tls-auth`` or ``tls-crypt``.
+
+Also, for ``tls-crypt``, even if all these peers succeed in keeping the key
+secret, the key lifetime is limited to roughly 8000 years, divided by the
+number of clients (see the ``--tls-crypt`` section of the man page).  Using
+client-specific keys, we lift this lifetime requirement to roughly 8000 years
+for each client key (which "Should Be Enough For Everybody (tm)").
+
+
+Introduction
+------------
+
+``tls-crypt-v2`` uses an encrypted cookie mechanism to introduce
+client-specific tls-crypt keys without introducing a lot of server-side state.
+The client-specific key is encrypted using a server key.  The server key is the
+same for all servers in a group.  When a client connects, it first sends the
+encrypted key to the server, such that the server can decrypt the key and all
+messages can thereafter be encrypted using the client-specific key.
+
+A wrapped (encrypted and authenticated) client-specific key can also contain
+metadata.  The metadata is wrapped together with the key, and can be used to
+allow servers to identify clients and/or key validity.  This allows the server
+to abort the connection immediately after receiving the first packet, rather
+than performing an entire TLS handshake.  Aborting the connection this early
+greatly improves the DoS resilience and reduces attack service against
+malicious clients that have the ``tls-crypt`` or ``tls-auth`` key.  This is
+particularly relevant for large deployments (think lost key or disgruntled
+employee) and VPN providers (clients are not trusted).
+
+To allow for a smooth transition, ``tls-crypt-v2`` is designed such that a
+server can enable both ``tls-crypt-v2`` and either ``tls-crypt`` or
+``tls-auth``.  This is achieved by introducing a P_CONTROL_HARD_RESET_CLIENT_V3
+opcode, that indicates that the client wants to use ``tls-crypt-v2`` for the
+current connection.
+

doc/tls-crypt-v2.txt line 61

Quoted 10 lines
doc/tls-crypt-v2.txt new file +10 -0
+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

typ0:

``--genkey`` should be ``--tls-crypt-v2-genkey server``

Quoted 5 lines
doc/tls-crypt-v2.txt new file +5 -0
+   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``
+

doc/tls-crypt-v2.txt line 76

Quoted 10 lines
doc/tls-crypt-v2.txt new file +10 -0
+   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``

I would add something like the paragraph about the server key:

... using OpenVPN's ``--tls-crypt-v2-genkey client``

Quoted 104 lines
doc/tls-crypt-v2.txt new file +103 -0
+
+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

doc/tls-crypt-v2.txt line 189

Quoted 10 lines
doc/tls-crypt-v2.txt new file +10 -0
+   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.)

I hope they can be fixed on the fly. Other than that:

Signature · 1 line
Antonio Quartulli
#83 lore raw

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

In reply to Steffan Karger

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

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

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

128 lines not on this page · raw message

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

304 lines not on this page · raw message

src/openvpn/tls_crypt.h +75 -8

136 lines not on this page · raw message

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

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

In reply to Steffan Karger

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

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

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

133 lines not on this page · raw message

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

335 lines not on this page · raw message

Signature · 1 line
2.7.4
#87 lore raw

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

In reply to Steffan Karger

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

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

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

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

In reply to Steffan Karger

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

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

 src/openvpn/init.c                        | 39 ++++++++++++++-
 src/openvpn/openvpn.h                     |  2 +
 src/openvpn/ssl.c                         | 79 ++++++++++++++++++++++++-------
 src/openvpn/ssl_common.h                  |  6 +++
 src/openvpn/tls_crypt.c                   | 57 ++++++++++++++++++++++
 src/openvpn/tls_crypt.h                   | 14 ++++++
 tests/unit_tests/openvpn/test_tls_crypt.c |  6 +++
 7 files changed, 186 insertions(+), 17 deletions(-)
src/openvpn/init.c +38 -1
index 73153dc..8a8bb00 100644
@@ -2464,6 +2464,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);
 }
@@ -2647,6 +2648,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)
         {
@@ -2863,13 +2882,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
@@ -3473,6 +3507,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))
     {
@@ -4408,6 +4444,7 @@ inherit_context_child(struct context *dest,
     dest->c1.ks.ssl_ctx = src->c1.ks.ssl_ctx;
     dest->c1.ks.tls_wrap_key = src->c1.ks.tls_wrap_key;
     dest->c1.ks.tls_auth_key_type = src->c1.ks.tls_auth_key_type;
+    dest->c1.ks.tls_crypt_v2_server_key = src->c1.ks.tls_crypt_v2_server_key;
     /* inherit pre-NCP ciphers */
     dest->c1.ciphername = src->c1.ciphername;
     dest->c1.authname = src->c1.authname;
src/openvpn/openvpn.h +2 -0
index bc972f2..d11f61d 100644
@@ -66,6 +66,8 @@ struct key_schedule
     /* optional TLS control channel wrapping */
     struct key_type tls_auth_key_type;
     struct key_ctx_bi tls_wrap_key;
+    struct key_ctx tls_crypt_v2_server_key;
+    struct buffer tls_crypt_v2_wkc;             /**< Wrapped client key */
 };
 
 /*
src/openvpn/ssl.c +63 -16

126 lines not on this page · raw message

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

69 lines not on this page · raw message

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

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

In reply to Steffan Karger

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

Because of the extra dependencies, this requires adding a mock parse_line() to the tls-crypt unit tests. Also, this turns tls_wrap_free into a static inline function, so that we don't need to compile in ssl.c (and all of it's dependencies) with the unit tests.

---
v3: rebase on curent master / v3 patch set
v4: fix memory leak (metadata buffer was not free'd for tls_wrap_tmp)
v5: rebase on v5 patch set, textual fixes in man page
v6: fix memory leak in unit tests, code style fixes
v7: rebase on patch set v7

 Changes.rst                               |  6 +++
 doc/openvpn.8                             | 35 ++++++++++++++--
 src/openvpn/init.c                        |  1 +
 src/openvpn/options.c                     |  7 ++++
 src/openvpn/options.h                     |  2 +
 src/openvpn/ssl.c                         | 34 +++++----------
 src/openvpn/ssl.h                         | 18 ++++++++
 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 | 19 ++++++++-
 12 files changed, 175 insertions(+), 32 deletions(-)
Changes.rst +6 -0
index 70ce2e1..a7429b1 100644
@@ -3,6 +3,12 @@ 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``.
+
 ChaCha20-Poly1305 cipher support
     Added support for using the ChaCha20-Poly1305 cipher in the OpenVPN data
     channel.
doc/openvpn.8 +32 -3
index 56a71c9..94b5cc4 100644
@@ -5310,9 +5310,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" if 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 exiting with a non-zero exit code.
 .\"*********************************************************
 .TP
 .B \-\-askpass [file]
src/openvpn/init.c +1 -0
index 8a8bb00..882337b 100644
@@ -2903,6 +2903,7 @@ do_init_crypto_tls(struct context *c, const unsigned int flags)
         if (options->tls_server)
         {
             to.tls_wrap.tls_crypt_v2_server_key = c->c1.ks.tls_crypt_v2_server_key;
+            to.tls_crypt_v2_verify_script = c->options.tls_crypt_v2_verify_script;
         }
     }
 
src/openvpn/options.c +7 -0
index dfe5e6e..9e30d7d 100644
@@ -629,6 +629,8 @@ static const char usage_message[] =
     "--tls-crypt-v2-genkey client|server keyfile [base64 metadata]: Generate a\n"
     "                  fresh tls-crypt-v2 client or server key, and store to\n"
     "                  keyfile.  If supplied, include metadata in wrapped key.\n"
+    "--tls-crypt-v2-verify cmd : Run command cmd to verify the metadata of the\n"
+    "                  client-supplied tls-crypt-v2 client key\n"
     "--askpass [file]: Get PEM password from controlling tty before we daemonize.\n"
     "--auth-nocache  : Don't cache --askpass or --auth-user-pass passwords.\n"
     "--crl-verify crl ['dir']: Check peer certificate against a CRL.\n"
@@ -8131,6 +8133,11 @@ add_option(struct options *options,
             options->tls_crypt_v2_metadata = p[3];
         }
     }
+    else if (streq(p[0], "tls-crypt-v2-verify") && p[1] && !p[2])
+    {
+        VERIFY_PERMISSION(OPT_P_GENERAL);
+        options->tls_crypt_v2_verify_script = p[1];
+    }
     else if (streq(p[0], "key-method") && p[1] && !p[2])
     {
         int key_method;
src/openvpn/options.h +2 -0
index bc180ca..575777f 100644
@@ -591,6 +591,8 @@ struct options
     const char *tls_crypt_v2_genkey_file;
     const char *tls_crypt_v2_metadata;
 
+    const char *tls_crypt_v2_verify_script;
+
     /* Allow only one session */
     bool single_session;
 
src/openvpn/ssl.c +11 -23
index 86e7877..74b88ce 100644
@@ -1048,23 +1048,6 @@ tls_session_user_pass_enabled(struct tls_session *session)
 /** @addtogroup control_processor
  *  @{ */
 
-/** Free the elements of a tls_wrap_ctx structure */
-static void tls_wrap_free(struct tls_wrap_ctx *tls_wrap)
-{
-    if (packet_id_initialized(&tls_wrap->opt.packet_id))
-    {
-        packet_id_free(&tls_wrap->opt.packet_id);
-    }
-
-    if (tls_wrap->cleanup_key_ctx)
-    {
-        free_key_ctx_bi(&tls_wrap->opt.key_ctx_bi);
-    }
-
-    free_buf(&tls_wrap->tls_crypt_v2_metadata);
-    free_buf(&tls_wrap->work);
-}
-
 /** @name Functions for initialization and cleanup of tls_session structures
  *  @{ */
 
@@ -1534,14 +1517,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",
@@ -3622,7 +3606,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;
                 }
@@ -3675,7 +3660,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;
                     }
@@ -3696,7 +3682,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;
                     }
@@ -3898,8 +3885,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.h +18 -0
index 023330c..ac6fef7 100644
@@ -527,6 +527,24 @@ bool tls_item_in_cipher_list(const char *item, const char *list);
  * inline functions
  */
 
+/** Free the elements of a tls_wrap_ctx structure */
+static inline void
+tls_wrap_free(struct tls_wrap_ctx *tls_wrap)
+{
+    if (packet_id_initialized(&tls_wrap->opt.packet_id))
+    {
+        packet_id_free(&tls_wrap->opt.packet_id);
+    }
+
+    if (tls_wrap->cleanup_key_ctx)
+    {
+        free_key_ctx_bi(&tls_wrap->opt.key_ctx_bi);
+    }
+
+    free_buf(&tls_wrap->tls_crypt_v2_metadata);
+    free_buf(&tls_wrap->work);
+}
+
 static inline bool
 tls_initial_packet_received(const struct tls_multi *multi)
 {
src/openvpn/ssl_common.h +1 -0
index 2b3e8a1..7bf82b3 100644
@@ -293,6 +293,7 @@ struct tls_options
     bool ncp_enabled;
 
     bool tls_crypt_v2;
+    const char *tls_crypt_v2_verify_script;
 
     /** TLS handshake wrapping state */
     struct tls_wrap_ctx tls_wrap;
src/openvpn/tls_crypt.c +68 -1

92 lines not on this page · raw message

src/openvpn/tls_crypt.h +2 -1
index edf297a..9b5ea97 100644
@@ -195,7 +195,8 @@ void tls_crypt_v2_init_client_key(struct key_ctx_bi *key,
  * @returns true if a key was successfully extracted.
  */
 bool tls_crypt_v2_extract_client_key(struct buffer *buf,
-                                     struct tls_wrap_ctx *ctx);
+                                     struct tls_wrap_ctx *ctx,
+                                     const struct tls_options *opt);
 
 /**
  * Generate a tls-crypt-v2 server key, and write to file.
tests/unit_tests/openvpn/Makefile.am +9 -3
index 22a458a..b4304e3 100644
@@ -6,7 +6,10 @@ if HAVE_LD_WRAP_SUPPORT
 check_PROGRAMS += argv_testdriver buffer_testdriver
 endif
 
-check_PROGRAMS += crypto_testdriver packet_id_testdriver tls_crypt_testdriver
+check_PROGRAMS += crypto_testdriver packet_id_testdriver
+if HAVE_LD_WRAP_SUPPORT
+check_PROGRAMS += tls_crypt_testdriver
+endif
 
 TESTS = $(check_PROGRAMS)
 
@@ -52,13 +55,16 @@ 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)
-tls_crypt_testdriver_LDFLAGS = @TEST_LDFLAGS@
+tls_crypt_testdriver_LDFLAGS = @TEST_LDFLAGS@ -Wl,--wrap=parse_line
 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 +18 -1
index 6cdf4ed..62721e8 100644
@@ -43,8 +43,24 @@
 
 #define TESTBUF_SIZE            128
 
+/* Defines for use in the tests and the mock parse_line() */
+#define PATH1       "/s p a c e"
+#define PATH2       "/foo bar/baz"
+#define PARAM1      "param1"
+#define PARAM2      "param two"
+
 const char plaintext_short[1];
 
+int
+__wrap_parse_line(const char *line, char **p, const int n, const char *file,
+                  const int line_num, int msglevel, struct gc_arena *gc)
+{
+    p[0] = PATH1 PATH2;
+    p[1] = PARAM1;
+    p[2] = PARAM2;
+    return 3;
+}
+
 struct test_tls_crypt_context {
     struct crypto_options co;
     struct key_type kt;
@@ -351,7 +367,8 @@ 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));
+    tls_wrap_free(&wrap_ctx);
 }
 
 /**
Signature · 1 line
2.7.4
Acked-by on v7 7/7
#92 lore raw

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

Hi,

Quoted 5 lines · Steffan Karger

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

Quoted 7 lines

Because of the extra dependencies, this requires adding a mock parse_line() to the tls-crypt unit tests. Also, this turns tls_wrap_free into a static inline function, so that we don't need to compile in ssl.c (and all of it's dependencies) with the unit tests.

Signature · 1 line
Antonio Quartulli
#93 lore raw
In reply to Steffan Karger

Hi all,

I am happy to say that finally I couldn't find anything wrong in the last version of this patches.

Other then reviewing and providing feedback to Steffan, I have also tested the code in various setups, including the usage of custom metadata and its verify script.

On top of that, I used the documentation provided by Steffan to port this feature to the OpenVPN 3 codebase and I can say that my implementation happily co-operates with OpenVPN 2.

This makes me think that Steffan's code follows the doc properly (or that both implementations have the same mistakes :-P but it is quite unlikely).

I have ack'd the various patches one by one and I believe they are ready to be merged into master.

The ball now goes to the repo maintainers.

Regards,

Quoted 21 lines · Steffan Karger

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

------------------------------------------------------------------------------
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 · 1 line
Antonio Quartulli