Browse code

tls-crypt-v2: add script hook to verify metadata

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.

Signed-off-by: Antonio Quartulli <antonio@openvpn.net>
Signed-off-by: Steffan Karger <steffan.karger@fox-it.com>
Acked-by: Antonio Quartulli <antonio@openvpn.net>
Message-Id: <1540208715-14044-6-git-send-email-steffan.karger@fox-it.com>
URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg17789.html
Signed-off-by: David Sommerseth <davids@openvpn.net>

Steffan Karger authored on 2018/10/22 20:45:15
Showing 12 changed files
... ...
@@ -3,6 +3,12 @@ Overview of changes in 2.5
3 3
 
4 4
 New features
5 5
 ------------
6
+Client-specific tls-crypt keys (``--tls-crypt-v2``)
7
+    ``tls-crypt-v2`` adds the ability to supply each client with a unique
8
+    tls-crypt key.  This allows large organisations and VPN providers to profit
9
+    from the same DoS and TLS stack protection that small deployments can
10
+    already achieve using ``tls-auth`` or ``tls-crypt``.
11
+
6 12
 ChaCha20-Poly1305 cipher support
7 13
     Added support for using the ChaCha20-Poly1305 cipher in the OpenVPN data
8 14
     channel.
... ...
@@ -5310,9 +5310,38 @@ If supplied, include the supplied
5310 5310
 in the wrapped client key.  This metadata must be supplied in base64\-encoded
5311 5311
 form.  The metadata must be at most 735 bytes long (980 bytes in base64).
5312 5312
 
5313
-.B TODO
5314
-Metadata handling is not yet implemented.  This text will be updated by the
5315
-commit that introduces metadata handling.
5313
+If no metadata is supplied, OpenVPN will use a 64\-bit unix timestamp
5314
+representing the current time in UTC, encoded in network order, as metadata for
5315
+the generated key.
5316
+
5317
+Servers can use
5318
+.B \-\-tls\-crypt\-v2\-verify
5319
+to specify a metadata verification command.
5320
+.\"*********************************************************
5321
+.TP
5322
+.B \-\-tls\-crypt\-v2\-verify cmd
5323
+
5324
+Run command
5325
+.B cmd
5326
+to verify the metadata of the client\-specific tls\-crypt\-v2 key of a
5327
+connecting client.  This allows server administrators to reject client
5328
+connections, before exposing the TLS stack (including the notoriously dangerous
5329
+X.509 and ASN.1 stacks) to the connecting client.
5330
+
5331
+OpenVPN supplies the following env vars to the command:
5332
+.RS
5333
+.IP \[bu] 2
5334
+script_type is set to "tls\-crypt\-v2\-verify"
5335
+.IP \[bu]
5336
+metadata_type is set to "0" if the metadata was user supplied, or "1" if it's a
5337
+64\-bit unix timestamp representing the key creation time.
5338
+.IP \[bu]
5339
+metadata_file contains the filename of a temporary file that contains the client
5340
+metadata.
5341
+.RE
5342
+
5343
+.IP
5344
+The command can reject the connection by exiting with a non-zero exit code.
5316 5345
 .\"*********************************************************
5317 5346
 .TP
5318 5347
 .B \-\-askpass [file]
... ...
@@ -2903,6 +2903,7 @@ do_init_crypto_tls(struct context *c, const unsigned int flags)
2903 2903
         if (options->tls_server)
2904 2904
         {
2905 2905
             to.tls_wrap.tls_crypt_v2_server_key = c->c1.ks.tls_crypt_v2_server_key;
2906
+            to.tls_crypt_v2_verify_script = c->options.tls_crypt_v2_verify_script;
2906 2907
         }
2907 2908
     }
2908 2909
 
... ...
@@ -629,6 +629,8 @@ static const char usage_message[] =
629 629
     "--tls-crypt-v2-genkey client|server keyfile [base64 metadata]: Generate a\n"
630 630
     "                  fresh tls-crypt-v2 client or server key, and store to\n"
631 631
     "                  keyfile.  If supplied, include metadata in wrapped key.\n"
632
+    "--tls-crypt-v2-verify cmd : Run command cmd to verify the metadata of the\n"
633
+    "                  client-supplied tls-crypt-v2 client key\n"
632 634
     "--askpass [file]: Get PEM password from controlling tty before we daemonize.\n"
633 635
     "--auth-nocache  : Don't cache --askpass or --auth-user-pass passwords.\n"
634 636
     "--crl-verify crl ['dir']: Check peer certificate against a CRL.\n"
... ...
@@ -8131,6 +8133,11 @@ add_option(struct options *options,
8131 8131
             options->tls_crypt_v2_metadata = p[3];
8132 8132
         }
8133 8133
     }
8134
+    else if (streq(p[0], "tls-crypt-v2-verify") && p[1] && !p[2])
8135
+    {
8136
+        VERIFY_PERMISSION(OPT_P_GENERAL);
8137
+        options->tls_crypt_v2_verify_script = p[1];
8138
+    }
8134 8139
     else if (streq(p[0], "key-method") && p[1] && !p[2])
8135 8140
     {
8136 8141
         int key_method;
... ...
@@ -591,6 +591,8 @@ struct options
591 591
     const char *tls_crypt_v2_genkey_file;
592 592
     const char *tls_crypt_v2_metadata;
593 593
 
594
+    const char *tls_crypt_v2_verify_script;
595
+
594 596
     /* Allow only one session */
595 597
     bool single_session;
596 598
 
... ...
@@ -1048,23 +1048,6 @@ tls_session_user_pass_enabled(struct tls_session *session)
1048 1048
 /** @addtogroup control_processor
1049 1049
  *  @{ */
1050 1050
 
1051
-/** Free the elements of a tls_wrap_ctx structure */
1052
-static void tls_wrap_free(struct tls_wrap_ctx *tls_wrap)
1053
-{
1054
-    if (packet_id_initialized(&tls_wrap->opt.packet_id))
1055
-    {
1056
-        packet_id_free(&tls_wrap->opt.packet_id);
1057
-    }
1058
-
1059
-    if (tls_wrap->cleanup_key_ctx)
1060
-    {
1061
-        free_key_ctx_bi(&tls_wrap->opt.key_ctx_bi);
1062
-    }
1063
-
1064
-    free_buf(&tls_wrap->tls_crypt_v2_metadata);
1065
-    free_buf(&tls_wrap->work);
1066
-}
1067
-
1068 1051
 /** @name Functions for initialization and cleanup of tls_session structures
1069 1052
  *  @{ */
1070 1053
 
... ...
@@ -1534,14 +1517,15 @@ write_control_auth(struct tls_session *session,
1534 1534
 static bool
1535 1535
 read_control_auth(struct buffer *buf,
1536 1536
                   struct tls_wrap_ctx *ctx,
1537
-                  const struct link_socket_actual *from)
1537
+                  const struct link_socket_actual *from,
1538
+                  const struct tls_options *opt)
1538 1539
 {
1539 1540
     struct gc_arena gc = gc_new();
1540 1541
     bool ret = false;
1541 1542
 
1542 1543
     const uint8_t opcode = *(BPTR(buf)) >> P_OPCODE_SHIFT;
1543 1544
     if (opcode == P_CONTROL_HARD_RESET_CLIENT_V3
1544
-        && !tls_crypt_v2_extract_client_key(buf, ctx))
1545
+        && !tls_crypt_v2_extract_client_key(buf, ctx, opt))
1545 1546
     {
1546 1547
         msg (D_TLS_ERRORS,
1547 1548
              "TLS Error: can not extract tls-crypt-v2 client key from %s",
... ...
@@ -3622,7 +3606,8 @@ tls_pre_decrypt(struct tls_multi *multi,
3622 3622
                     goto error;
3623 3623
                 }
3624 3624
 
3625
-                if (!read_control_auth(buf, &session->tls_wrap, from))
3625
+                if (!read_control_auth(buf, &session->tls_wrap, from,
3626
+                                       session->opt))
3626 3627
                 {
3627 3628
                     goto error;
3628 3629
                 }
... ...
@@ -3675,7 +3660,8 @@ tls_pre_decrypt(struct tls_multi *multi,
3675 3675
                 if (op == P_CONTROL_SOFT_RESET_V1
3676 3676
                     && DECRYPT_KEY_ENABLED(multi, ks))
3677 3677
                 {
3678
-                    if (!read_control_auth(buf, &session->tls_wrap, from))
3678
+                    if (!read_control_auth(buf, &session->tls_wrap, from,
3679
+                                           session->opt))
3679 3680
                     {
3680 3681
                         goto error;
3681 3682
                     }
... ...
@@ -3696,7 +3682,8 @@ tls_pre_decrypt(struct tls_multi *multi,
3696 3696
                         do_burst = true;
3697 3697
                     }
3698 3698
 
3699
-                    if (!read_control_auth(buf, &session->tls_wrap, from))
3699
+                    if (!read_control_auth(buf, &session->tls_wrap, from,
3700
+                                           session->opt))
3700 3701
                     {
3701 3702
                         goto error;
3702 3703
                     }
... ...
@@ -3898,8 +3885,9 @@ tls_pre_decrypt_lite(const struct tls_auth_standalone *tas,
3898 3898
             bool status;
3899 3899
 
3900 3900
             /* HMAC test, if --tls-auth was specified */
3901
-            status = read_control_auth(&newbuf, &tls_wrap_tmp, from);
3901
+            status = read_control_auth(&newbuf, &tls_wrap_tmp, from, NULL);
3902 3902
             free_buf(&newbuf);
3903
+            free_buf(&tls_wrap_tmp.tls_crypt_v2_metadata);
3903 3904
             if (tls_wrap_tmp.cleanup_key_ctx)
3904 3905
             {
3905 3906
                 free_key_ctx_bi(&tls_wrap_tmp.opt.key_ctx_bi);
... ...
@@ -527,6 +527,24 @@ bool tls_item_in_cipher_list(const char *item, const char *list);
527 527
  * inline functions
528 528
  */
529 529
 
530
+/** Free the elements of a tls_wrap_ctx structure */
531
+static inline void
532
+tls_wrap_free(struct tls_wrap_ctx *tls_wrap)
533
+{
534
+    if (packet_id_initialized(&tls_wrap->opt.packet_id))
535
+    {
536
+        packet_id_free(&tls_wrap->opt.packet_id);
537
+    }
538
+
539
+    if (tls_wrap->cleanup_key_ctx)
540
+    {
541
+        free_key_ctx_bi(&tls_wrap->opt.key_ctx_bi);
542
+    }
543
+
544
+    free_buf(&tls_wrap->tls_crypt_v2_metadata);
545
+    free_buf(&tls_wrap->work);
546
+}
547
+
530 548
 static inline bool
531 549
 tls_initial_packet_received(const struct tls_multi *multi)
532 550
 {
... ...
@@ -293,6 +293,7 @@ struct tls_options
293 293
     bool ncp_enabled;
294 294
 
295 295
     bool tls_crypt_v2;
296
+    const char *tls_crypt_v2_verify_script;
296 297
 
297 298
     /** TLS handshake wrapping state */
298 299
     struct tls_wrap_ctx tls_wrap;
... ...
@@ -29,9 +29,11 @@
29 29
 
30 30
 #include "syshead.h"
31 31
 
32
+#include "argv.h"
32 33
 #include "base64.h"
33 34
 #include "crypto.h"
34 35
 #include "platform.h"
36
+#include "run_command.h"
35 37
 #include "session_id.h"
36 38
 #include "ssl.h"
37 39
 
... ...
@@ -544,9 +546,69 @@ error_exit:
544 544
     return ret;
545 545
 }
546 546
 
547
+static bool
548
+tls_crypt_v2_verify_metadata(const struct tls_wrap_ctx *ctx,
549
+                             const struct tls_options *opt)
550
+{
551
+    bool ret = false;
552
+    struct gc_arena gc = gc_new();
553
+    const char *tmp_file = NULL;
554
+    struct buffer metadata = ctx->tls_crypt_v2_metadata;
555
+    int metadata_type = buf_read_u8(&metadata);
556
+    if (metadata_type < 0)
557
+    {
558
+        msg(M_WARN, "ERROR: no metadata type");
559
+        goto cleanup;
560
+    }
561
+
562
+    tmp_file = platform_create_temp_file(opt->tmp_dir, "tls_crypt_v2_metadata_",
563
+                                         &gc);
564
+    if (!tmp_file || !buffer_write_file(tmp_file, &metadata))
565
+    {
566
+        msg(M_WARN, "ERROR: could not write metadata to file");
567
+        goto cleanup;
568
+    }
569
+
570
+    char metadata_type_str[4] = { 0 }; /* Max value: 255 */
571
+    openvpn_snprintf(metadata_type_str, sizeof(metadata_type_str),
572
+                     "%i", metadata_type);
573
+    struct env_set *es = env_set_create(NULL);
574
+    setenv_str(es, "script_type", "tls-crypt-v2-verify");
575
+    setenv_str(es, "metadata_type", metadata_type_str);
576
+    setenv_str(es, "metadata_file", tmp_file);
577
+
578
+    struct argv argv = argv_new();
579
+    argv_parse_cmd(&argv, opt->tls_crypt_v2_verify_script);
580
+    argv_msg_prefix(D_TLS_DEBUG, &argv, "Executing tls-crypt-v2-verify");
581
+
582
+    ret = openvpn_run_script(&argv, es, 0, "--tls-crypt-v2-verify");
583
+
584
+    argv_reset(&argv);
585
+    env_set_destroy(es);
586
+
587
+    if (!platform_unlink(tmp_file))
588
+    {
589
+        msg(M_WARN, "WARNING: failed to remove temp file '%s", tmp_file);
590
+    }
591
+
592
+    if (ret)
593
+    {
594
+        msg(D_HANDSHAKE, "TLS CRYPT V2 VERIFY SCRIPT OK");
595
+    }
596
+    else
597
+    {
598
+        msg(D_HANDSHAKE, "TLS CRYPT V2 VERIFY SCRIPT ERROR");
599
+    }
600
+
601
+cleanup:
602
+    gc_free(&gc);
603
+    return ret;
604
+}
605
+
547 606
 bool
548 607
 tls_crypt_v2_extract_client_key(struct buffer *buf,
549
-                                struct tls_wrap_ctx *ctx)
608
+                                struct tls_wrap_ctx *ctx,
609
+                                const struct tls_options *opt)
550 610
 {
551 611
     if (!ctx->tls_crypt_v2_server_key.cipher)
552 612
     {
... ...
@@ -597,6 +659,11 @@ tls_crypt_v2_extract_client_key(struct buffer *buf,
597 597
     /* Remove client key from buffer so tls-crypt code can unwrap message */
598 598
     ASSERT(buf_inc_len(buf, -(BLEN(&wrapped_client_key))));
599 599
 
600
+    if (opt && opt->tls_crypt_v2_verify_script)
601
+    {
602
+        return tls_crypt_v2_verify_metadata(ctx, opt);
603
+    }
604
+
600 605
     return true;
601 606
 }
602 607
 
... ...
@@ -195,7 +195,8 @@ void tls_crypt_v2_init_client_key(struct key_ctx_bi *key,
195 195
  * @returns true if a key was successfully extracted.
196 196
  */
197 197
 bool tls_crypt_v2_extract_client_key(struct buffer *buf,
198
-                                     struct tls_wrap_ctx *ctx);
198
+                                     struct tls_wrap_ctx *ctx,
199
+                                     const struct tls_options *opt);
199 200
 
200 201
 /**
201 202
  * Generate a tls-crypt-v2 server key, and write to file.
... ...
@@ -6,7 +6,10 @@ if HAVE_LD_WRAP_SUPPORT
6 6
 check_PROGRAMS += argv_testdriver buffer_testdriver
7 7
 endif
8 8
 
9
-check_PROGRAMS += crypto_testdriver packet_id_testdriver tls_crypt_testdriver
9
+check_PROGRAMS += crypto_testdriver packet_id_testdriver
10
+if HAVE_LD_WRAP_SUPPORT
11
+check_PROGRAMS += tls_crypt_testdriver
12
+endif
10 13
 
11 14
 TESTS = $(check_PROGRAMS)
12 15
 
... ...
@@ -52,13 +55,16 @@ packet_id_testdriver_SOURCES = test_packet_id.c mock_msg.c \
52 52
 
53 53
 tls_crypt_testdriver_CFLAGS  = @TEST_CFLAGS@ \
54 54
 	-I$(openvpn_includedir) -I$(compat_srcdir) -I$(openvpn_srcdir)
55
-tls_crypt_testdriver_LDFLAGS = @TEST_LDFLAGS@
55
+tls_crypt_testdriver_LDFLAGS = @TEST_LDFLAGS@ -Wl,--wrap=parse_line
56 56
 tls_crypt_testdriver_SOURCES = test_tls_crypt.c mock_msg.c \
57
+	$(openvpn_srcdir)/argv.c \
57 58
 	$(openvpn_srcdir)/base64.c \
58 59
 	$(openvpn_srcdir)/buffer.c \
59 60
 	$(openvpn_srcdir)/crypto.c \
60 61
 	$(openvpn_srcdir)/crypto_mbedtls.c \
61 62
 	$(openvpn_srcdir)/crypto_openssl.c \
63
+	$(openvpn_srcdir)/env_set.c \
62 64
 	$(openvpn_srcdir)/otime.c \
63 65
 	$(openvpn_srcdir)/packet_id.c \
64
-	$(openvpn_srcdir)/platform.c
66
+	$(openvpn_srcdir)/platform.c \
67
+	$(openvpn_srcdir)/run_command.c
... ...
@@ -43,8 +43,24 @@
43 43
 
44 44
 #define TESTBUF_SIZE            128
45 45
 
46
+/* Defines for use in the tests and the mock parse_line() */
47
+#define PATH1       "/s p a c e"
48
+#define PATH2       "/foo bar/baz"
49
+#define PARAM1      "param1"
50
+#define PARAM2      "param two"
51
+
46 52
 const char plaintext_short[1];
47 53
 
54
+int
55
+__wrap_parse_line(const char *line, char **p, const int n, const char *file,
56
+                  const int line_num, int msglevel, struct gc_arena *gc)
57
+{
58
+    p[0] = PATH1 PATH2;
59
+    p[1] = PARAM1;
60
+    p[2] = PARAM2;
61
+    return 3;
62
+}
63
+
48 64
 struct test_tls_crypt_context {
49 65
     struct crypto_options co;
50 66
     struct key_type kt;
... ...
@@ -351,7 +367,8 @@ tls_crypt_v2_wrap_unwrap_max_metadata(void **state) {
351 351
             .mode = TLS_WRAP_CRYPT,
352 352
             .tls_crypt_v2_server_key = ctx->server_keys.encrypt,
353 353
     };
354
-    assert_true(tls_crypt_v2_extract_client_key(&ctx->wkc, &wrap_ctx));
354
+    assert_true(tls_crypt_v2_extract_client_key(&ctx->wkc, &wrap_ctx, NULL));
355
+    tls_wrap_free(&wrap_ctx);
355 356
 }
356 357
 
357 358
 /**