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>
... | ... |
@@ -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; |
... | ... |
@@ -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 |
{ |
... | ... |
@@ -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 |
/** |