Skip to content

Commit c6e24fa

Browse files
syzzercron2
authored andcommitted
Add control channel encryption (--tls-crypt)
This adds a --tls-crypt option, which uses a pre-shared static key (like the --tls-auth key) to encrypt control channel packets. Encrypting control channel packets has three main advantages: * It provides more privacy by hiding the certificate used for the TLS connection. * It is harder to identify OpenVPN traffic as such. * It provides "poor-man's" post-quantum security, against attackers who will never know the pre-shared key (i.e. no forward secrecy). Control channel packet encryption --------------------------------- We propose to use the following encryption method, based on the SIV construction [0], to achieve nonce misuse-resistant authenticated encryption: msg = control channel plaintext header = opcode (1 byte) || session_id (8 bytes) || packet_id (8 bytes) Ka = authentication key (256 bits) Ke = encryption key (256 bits) (Ka and Ke are pre-shared keys, like with --tls-auth) auth_tag = HMAC-SHA256(Ka, header || msg) IV = 128 most-significant bits of auth_tag ciph = AES256-CTR(Ke, IV, msg) output = Header || Tag || Ciph This boils down to the following on-the-wire packet format: -opcode- || -session_id- || -packet_id- || auth_tag || * payload * Where - XXX - means authenticated, and * XXX * means authenticated and encrypted. Which is very similar to the current tls-auth packet format, and has the same overhead as "--tls-auth" with "--auth SHA256". The use of a nonce misuse-resistant authenticated encryption scheme allows us to worry less about the risks of nonce collisions. This is important, because in contrast with the data channel in TLS mode, we will not be able to rotate tls-crypt keys often or fully guarantee nonce uniqueness. For non misuse-resistant modes such as GCM [1], [2], the data channel in TLS mode only has to ensure that the packet counter never rolls over, while tls-crypt would have to provide nonce uniqueness over all control channel packets sent by all clients, for the lifetime of the tls-crypt key. Unlike with tls-auth, no --key-direction has to be specified for tls-crypt. TLS servers always use key direction 1, and TLS clients always use key direction 2, which means that client->server traffic and server->client traffic always use different keys, without requiring configuration. Using fixed, secure, encryption and authentication algorithms makes both implementation and configuration easier. If we ever want to, we can extend this to support other crypto primitives. Since tls-crypt should provide privacy as well as DoS protection, these should not be made negotiable. Security considerations: ------------------------ tls-crypt is a best-effort mechanism that aims to provide as much privacy and security as possible, while staying as simple as possible. The following are some security considerations for this scheme. 1. The same tls-crypt key is potentially shared by a lot of peers, so it is quite likely to get compromised. Once an attacker acquires the tls-crypt key, this mechanism no longer provides any security against the attacker. 2. Since many peers potentially use the tls-crypt key for a long time, a lot of data might be encrypted under the tls-crypt key. This leads to two potential problems: * The "opcode || session id || packet id" combination might collide. This might happen in larger setups, because the session id contains just 64 bits or random. Using the uniqueness requirement from the GCM spec [3] (a collision probability of less than 2^(-32)), uniqueness is achieved when using the tls-crypt key for at most 2^16 (65536) connections per process start. (The packet id includes the daemon start time in the packet ID, which should be different after stopping and (re)starting OpenPVN.) And if a collision happens, an attacker can *only* learn whether colliding packets contain the same plaintext. Attackers will not be able to learn anything else about the plaintext (unless the attacker knows the plaintext of one of these packets, of course). Since the impact is limited, I consider this an acceptable remaining risk. * The IVs used in encryption might collide. When two IVs collide, an attacker can learn the xor of the two plaintexts by xorring the ciphertexts. This is a serious loss of confidentiality. The IVs are 128-bit, so when HMAC-SHA256 is a secure PRF (an assumption that must also hold for TLS), and we use the same uniqueness requirement from [3], this limits the total amount of control channel messages for all peers in the setup to 2^48. Assuming a large setup of 2^16 (65536) clients, and a (conservative) number of 2^16 control channel packets per connection on average, this means that clients may set up 2^16 connections on average. I think these numbers are reasonable. (I have a follow-up proposal to use client-specific tls-auth/tls-crypt keys to partially mitigate these issues, but let's tackle this patch first.) References: ----------- [0] Rogaway & Shrimpton, A Provable-Security Treatment of the Key-Wrap Problem, 2006 (https://www.iacr.org/archive/eurocrypt2006/40040377/40040377.pdf) [1] Ferguson, Authentication weaknesses in GCM, 2005 (http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/comments/CWC-GCM/Ferg uson2.pdf) [2] Joux, Authentication Failures in NIST version of GCM, 2006 (http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/comments/800-38_Serie s-Drafts/GCM/Joux_comments.pdf) [3] Dworking, Recommendation for Block Cipher Modes of Operation: Galois/Counter Mode (GCM) and GMAC, 2007 (http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf) Patch history: -------------- v2 - processed Arne's review comments: * Error out early with a clear error message when AES-256-CTR or HMAC-SHA-256 are not supported by the crypto library. * Clarify that cipher_ctx_reset() sets the IV. v3 - actually add error messages promised in v2... Signed-off-by: Steffan Karger <steffan.karger@fox-it.com> Acked-by: Arne Schwabe <arne@rfc2549.org> Message-Id: <1479216586-20078-1-git-send-email-steffan.karger@fox-it.com> URL: https://www.mail-archive.com/openvpn-devel@lists.sourceforge.net/msg13069.html Signed-off-by: Gert Doering <gert@greenie.muc.de>
1 parent 1de37f3 commit c6e24fa

File tree

14 files changed

+612
-78
lines changed

14 files changed

+612
-78
lines changed

Changes.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,11 @@ AIX platform support
111111
AIX platform support has been added. The support only includes tap
112112
devices since AIX does not provide tun interface.
113113

114+
Control channel encryption (``--tls-crypt``)
115+
Use a pre-shared static key (like the ``--tls-auth`` key) to encrypt control
116+
channel packets. Provides more privacy, some obfuscation and poor-man's
117+
post-quantum security.
118+
114119

115120
Deprecated features
116121
-------------------

doc/openvpn.8

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3471,8 +3471,10 @@ DoS scenario, legitimate connections might also be refused.
34713471
For the best protection against DoS attacks in server mode,
34723472
use
34733473
.B \-\-proto udp
3474-
and
3475-
.B \-\-tls\-auth.
3474+
and either
3475+
.B \-\-tls\-auth
3476+
or
3477+
.B \-\-tls\-crypt\fR.
34763478
.\"*********************************************************
34773479
.TP
34783480
.B \-\-learn\-address cmd
@@ -4938,8 +4940,8 @@ Exit on TLS negotiation failure.
49384940
.\"*********************************************************
49394941
.TP
49404942
.B \-\-tls\-auth file [direction]
4941-
Add an additional layer of HMAC authentication on top of the TLS
4942-
control channel to protect against DoS attacks.
4943+
Add an additional layer of HMAC authentication on top of the TLS control channel
4944+
to mitigate DoS attacks and attacks on the TLS stack.
49434945

49444946
In a nutshell,
49454947
.B \-\-tls\-auth
@@ -5010,12 +5012,39 @@ option which will keep OpenVPN's replay protection state
50105012
in a file so that it is not lost across restarts.
50115013

50125014
It should be emphasized that this feature is optional and that the
5013-
passphrase/key file used with
5015+
key file used with
50145016
.B \-\-tls\-auth
50155017
gives a peer nothing more than the power to initiate a TLS
50165018
handshake. It is not used to encrypt or authenticate any tunnel data.
50175019
.\"*********************************************************
50185020
.TP
5021+
.B \-\-tls\-crypt keyfile
5022+
5023+
Encrypt and authenticate all control channel packets with the key from
5024+
.B keyfile.
5025+
(See
5026+
.B \-\-tls\-auth
5027+
for more background.)
5028+
5029+
Encrypting (and authenticating) control channel packets:
5030+
.RS
5031+
.IP \[bu] 2
5032+
provides more privacy by hiding the certificate used for the TLS connection,
5033+
.IP \[bu]
5034+
makes it harder to identify OpenVPN traffic as such,
5035+
.IP \[bu]
5036+
provides "poor-man's" post-quantum security, against attackers who will never
5037+
know the pre-shared key (i.e. no forward secrecy).
5038+
.RE
5039+
5040+
.IP
5041+
In contrast to
5042+
.B \-\-tls\-auth\fR,
5043+
.B \-\-tls\-crypt
5044+
does *not* require the user to set
5045+
.B \-\-key\-direction\fR.
5046+
.\"*********************************************************
5047+
.TP
50195048
.B \-\-askpass [file]
50205049
Get certificate password from console or
50215050
.B file

src/openvpn/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ openvpn_SOURCES = \
113113
ssl_verify_mbedtls.c ssl_verify_mbedtls.h \
114114
status.c status.h \
115115
syshead.h \
116+
tls_crypt.c tls_crypt.h \
116117
tun.c tun.h \
117118
win32.h win32.c \
118119
cryptoapi.h cryptoapi.c

src/openvpn/crypto.c

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
* packet compression.
77
*
88
* Copyright (C) 2002-2010 OpenVPN Technologies, Inc. <sales@openvpn.net>
9-
* Copyright (C) 2010 Fox Crypto B.V. <openvpn@fox-it.com>
9+
* Copyright (C) 2010-2016 Fox Crypto B.V. <openvpn@fox-it.com>
1010
*
1111
* This program is free software; you can redistribute it and/or modify
1212
* it under the terms of the GNU General Public License version 2
@@ -63,9 +63,6 @@
6363
* happen unless the frame parameters are wrong.
6464
*/
6565

66-
#define CRYPT_ERROR(format) \
67-
do { msg (D_CRYPT_ERRORS, "%s: " format, error_prefix); goto error_exit; } while (false)
68-
6966
static void
7067
openvpn_encrypt_aead (struct buffer *buf, struct buffer work,
7168
struct crypto_options *opt) {
@@ -326,17 +323,7 @@ openvpn_encrypt (struct buffer *buf, struct buffer work,
326323
}
327324
}
328325

329-
/**
330-
* Check packet ID for replay, and perform replay administration.
331-
*
332-
* @param opt Crypto options for this packet, contains replay state.
333-
* @param pin Packet ID read from packet.
334-
* @param error_prefix Prefix to use when printing error messages.
335-
* @param gc Garbage collector to use.
336-
*
337-
* @return true if packet ID is validated to be not a replay, false otherwise.
338-
*/
339-
static bool crypto_check_replay(struct crypto_options *opt,
326+
bool crypto_check_replay(struct crypto_options *opt,
340327
const struct packet_id_net *pin, const char *error_prefix,
341328
struct gc_arena *gc) {
342329
bool ret = false;

src/openvpn/crypto.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,9 @@ struct crypto_options
266266
* security operation functions. */
267267
};
268268

269+
#define CRYPT_ERROR(format) \
270+
do { msg (D_CRYPT_ERRORS, "%s: " format, error_prefix); goto error_exit; } while (false)
271+
269272
/**
270273
* Minimal IV length for AEAD mode ciphers (in bytes):
271274
* 4-byte packet id + 8 bytes implicit IV.
@@ -397,6 +400,21 @@ bool openvpn_decrypt (struct buffer *buf, struct buffer work,
397400

398401
/** @} name Functions for performing security operations on data channel packets */
399402

403+
/**
404+
* Check packet ID for replay, and perform replay administration.
405+
*
406+
* @param opt Crypto options for this packet, contains replay state.
407+
* @param pin Packet ID read from packet.
408+
* @param error_prefix Prefix to use when printing error messages.
409+
* @param gc Garbage collector to use.
410+
*
411+
* @return true if packet ID is validated to be not a replay, false otherwise.
412+
*/
413+
bool crypto_check_replay(struct crypto_options *opt,
414+
const struct packet_id_net *pin, const char *error_prefix,
415+
struct gc_arena *gc);
416+
417+
400418
/** Calculate crypto overhead and adjust frame to account for that */
401419
void crypto_adjust_frame_parameters(struct frame *frame,
402420
const struct key_type* kt,

src/openvpn/init.c

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
#include "ping.h"
4545
#include "mstats.h"
4646
#include "ssl_verify.h"
47+
#include "tls_crypt.h"
4748
#include "forward-inline.h"
4849

4950
#include "memdbg.h"
@@ -2078,7 +2079,7 @@ key_schedule_free (struct key_schedule *ks, bool free_ssl_ctx)
20782079
if (tls_ctx_initialised(&ks->ssl_ctx) && free_ssl_ctx)
20792080
{
20802081
tls_ctx_free (&ks->ssl_ctx);
2081-
free_key_ctx_bi (&ks->tls_auth_key);
2082+
free_key_ctx_bi (&ks->tls_wrap_key);
20822083
}
20832084
#endif /* ENABLE_CRYPTO */
20842085
CLEAR (*ks);
@@ -2232,11 +2233,17 @@ do_init_crypto_tls_c1 (struct context *c)
22322233
}
22332234

22342235
crypto_read_openvpn_key (&c->c1.ks.tls_auth_key_type,
2235-
&c->c1.ks.tls_auth_key, options->tls_auth_file,
2236+
&c->c1.ks.tls_wrap_key, options->tls_auth_file,
22362237
options->tls_auth_file_inline, options->key_direction,
22372238
"Control Channel Authentication", "tls-auth");
22382239
}
22392240

2241+
/* TLS handshake encryption+authentication (--tls-crypt) */
2242+
if (options->tls_crypt_file) {
2243+
tls_crypt_init_key (&c->c1.ks.tls_wrap_key, options->tls_crypt_file,
2244+
options->tls_crypt_inline, options->tls_server);
2245+
}
2246+
22402247
c->c1.ciphername = options->ciphername;
22412248
c->c1.authname = options->authname;
22422249
c->c1.keysize = options->keysize;
@@ -2421,14 +2428,25 @@ do_init_crypto_tls (struct context *c, const unsigned int flags)
24212428
/* TLS handshake authentication (--tls-auth) */
24222429
if (options->tls_auth_file)
24232430
{
2424-
to.tls_auth.key_ctx_bi = c->c1.ks.tls_auth_key;
2425-
to.tls_auth.pid_persist = &c->c1.pid_persist;
2426-
to.tls_auth.flags |= CO_PACKET_ID_LONG_FORM;
2431+
to.tls_wrap.mode = TLS_WRAP_AUTH;
2432+
to.tls_wrap.opt.key_ctx_bi = c->c1.ks.tls_wrap_key;
2433+
to.tls_wrap.opt.pid_persist = &c->c1.pid_persist;
2434+
to.tls_wrap.opt.flags |= CO_PACKET_ID_LONG_FORM;
24272435
crypto_adjust_frame_parameters (&to.frame,
24282436
&c->c1.ks.tls_auth_key_type,
24292437
false, true, true);
24302438
}
24312439

2440+
/* TLS handshake encryption (--tls-crypt) */
2441+
if (options->tls_crypt_file)
2442+
{
2443+
to.tls_wrap.mode = TLS_WRAP_CRYPT;
2444+
to.tls_wrap.opt.key_ctx_bi = c->c1.ks.tls_wrap_key;
2445+
to.tls_wrap.opt.pid_persist = &c->c1.pid_persist;
2446+
to.tls_wrap.opt.flags |= CO_PACKET_ID_LONG_FORM;
2447+
tls_crypt_adjust_frame_parameters (&to.frame);
2448+
}
2449+
24322450
/* If we are running over TCP, allow for
24332451
length prefix */
24342452
socket_adjust_frame_parameters (&to.frame, options->ce.proto);
@@ -3792,7 +3810,7 @@ inherit_context_child (struct context *dest,
37923810
dest->c1.ks.key_type = src->c1.ks.key_type;
37933811
/* inherit SSL context */
37943812
dest->c1.ks.ssl_ctx = src->c1.ks.ssl_ctx;
3795-
dest->c1.ks.tls_auth_key = src->c1.ks.tls_auth_key;
3813+
dest->c1.ks.tls_wrap_key = src->c1.ks.tls_wrap_key;
37963814
dest->c1.ks.tls_auth_key_type = src->c1.ks.tls_auth_key_type;
37973815
/* inherit pre-NCP ciphers */
37983816
dest->c1.ciphername = src->c1.ciphername;

src/openvpn/openvpn.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,9 @@ struct key_schedule
6565
/* our global SSL context */
6666
struct tls_root_ctx ssl_ctx;
6767

68-
/* optional authentication HMAC key for TLS control channel */
68+
/* optional TLS control channel wrapping */
6969
struct key_type tls_auth_key_type;
70-
struct key_ctx_bi tls_auth_key;
70+
struct key_ctx_bi tls_wrap_key;
7171
#else /* ENABLE_CRYPTO */
7272
int dummy;
7373
#endif /* ENABLE_CRYPTO */

src/openvpn/options.c

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -611,10 +611,17 @@ static const char usage_message[] =
611611
"--single-session: Allow only one session (reset state on restart).\n"
612612
"--tls-exit : Exit on TLS negotiation failure.\n"
613613
"--tls-auth f [d]: Add an additional layer of authentication on top of the TLS\n"
614-
" control channel to protect against DoS attacks.\n"
615-
" f (required) is a shared-secret passphrase file.\n"
614+
" control channel to protect against attacks on the TLS stack\n"
615+
" and DoS attacks.\n"
616+
" f (required) is a shared-secret key file.\n"
616617
" The optional d parameter controls key directionality,\n"
617618
" see --secret option for more info.\n"
619+
"--tls-crypt key : Add an additional layer of authenticated encryption on top\n"
620+
" of the TLS control channel to hide the TLS certificate,\n"
621+
" provide basic post-quantum security and protect against\n"
622+
" attacks on the TLS stack and DoS attacks.\n"
623+
" key (required) provides the pre-shared key file.\n"
624+
" see --secret option for more info.\n"
618625
"--askpass [file]: Get PEM password from controlling tty before we daemonize.\n"
619626
"--auth-nocache : Don't cache --askpass or --auth-user-pass passwords.\n"
620627
"--crl-verify crl ['dir']: Check peer certificate against a CRL.\n"
@@ -1710,6 +1717,7 @@ show_settings (const struct options *o)
17101717
SHOW_BOOL (tls_exit);
17111718

17121719
SHOW_STR (tls_auth_file);
1720+
SHOW_STR (tls_crypt_file);
17131721
#endif /* ENABLE_CRYPTO */
17141722

17151723
#ifdef ENABLE_PKCS11
@@ -2384,6 +2392,10 @@ options_postprocess_verify_ce (const struct options *options, const struct conne
23842392
notnull (options->priv_key_file, "private key file (--key) or PKCS#12 file (--pkcs12)");
23852393
}
23862394
}
2395+
if (options->tls_auth_file && options->tls_crypt_file)
2396+
{
2397+
msg (M_USAGE, "--tls-auth and --tls-crypt are mutually exclusive");
2398+
}
23872399
}
23882400
else
23892401
{
@@ -2415,6 +2427,7 @@ options_postprocess_verify_ce (const struct options *options, const struct conne
24152427
MUST_BE_UNDEF (handshake_window);
24162428
MUST_BE_UNDEF (transition_window);
24172429
MUST_BE_UNDEF (tls_auth_file);
2430+
MUST_BE_UNDEF (tls_crypt_file);
24182431
MUST_BE_UNDEF (single_session);
24192432
#ifdef ENABLE_PUSH_PEER_INFO
24202433
MUST_BE_UNDEF (push_peer_info);
@@ -2879,6 +2892,8 @@ options_postprocess_filechecks (struct options *options)
28792892

28802893
errs |= check_file_access (CHKACC_FILE|CHKACC_INLINE|CHKACC_PRIVATE,
28812894
options->tls_auth_file, R_OK, "--tls-auth");
2895+
errs |= check_file_access (CHKACC_FILE|CHKACC_INLINE|CHKACC_PRIVATE,
2896+
options->tls_crypt_file, R_OK, "--tls-crypt");
28822897
errs |= check_file_access (CHKACC_FILE|CHKACC_INLINE|CHKACC_PRIVATE,
28832898
options->shared_secret_file, R_OK, "--secret");
28842899
errs |= check_file_access (CHKACC_DIRPATH|CHKACC_FILEXSTWR,
@@ -3220,6 +3235,9 @@ options_string (const struct options *o,
32203235
{
32213236
if (o->tls_auth_file)
32223237
buf_printf (&out, ",tls-auth");
3238+
/* Not adding tls-crypt here, because we won't reach this code if
3239+
* tls-auth/tls-crypt does not match. Removing tls-auth here would
3240+
* break stuff, so leaving that in place. */
32233241

32243242
if (o->key_method > 1)
32253243
buf_printf (&out, ",key-method %d", o->key_method);
@@ -7242,6 +7260,15 @@ add_option (struct options *options,
72427260
}
72437261
options->tls_auth_file = p[1];
72447262
}
7263+
else if (streq (p[0], "tls-crypt") && p[1] && !p[3])
7264+
{
7265+
VERIFY_PERMISSION (OPT_P_GENERAL);
7266+
if (streq (p[1], INLINE_FILE_TAG) && p[2])
7267+
{
7268+
options->tls_crypt_inline = p[2];
7269+
}
7270+
options->tls_crypt_file = p[1];
7271+
}
72457272
else if (streq (p[0], "key-method") && p[1] && !p[2])
72467273
{
72477274
int key_method;

src/openvpn/options.h

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -558,10 +558,14 @@ struct options
558558
/* Old key allowed to live n seconds after new key goes active */
559559
int transition_window;
560560

561-
/* Special authentication MAC for TLS control channel */
562-
const char *tls_auth_file; /* shared secret */
561+
/* Shared secret used for TLS control channel authentication */
562+
const char *tls_auth_file;
563563
const char *tls_auth_file_inline;
564564

565+
/* Shared secret used for TLS control channel authenticated encryption */
566+
const char *tls_crypt_file;
567+
const char *tls_crypt_inline;
568+
565569
/* Allow only one session */
566570
bool single_session;
567571

0 commit comments

Comments
 (0)