Skip to content

Commit

Permalink
Implement epoch key data format
Browse files Browse the repository at this point in the history
With DCO and possible future hardware assisted OpenVPN acceleration we
are approaching the point where 32 bit IVs are not cutting it any more,
especially if we are limiting the IVs to the safe limits of AES-GCM where
the limit is more 2^29.

To illustrate the problem, some back of the envelope math here:

If we want to keep the current 3600s renegotiation interval and have
a safety margin of 25% (when we trigger renegotiation) we have about
3.2 million packets (2*32 * 0.7) to work with. That translates to
about 835k packets per second. Currently, implementation trigger the
renegotiation at 0xff00000000 or at 7/8 of the AEAD usage limit.

With 1300 Byte packets that translates into 8-9 Gbit/s. That is far
from unrealistic any more. Current DCO implementations are already in
spitting distance to that or might even reach (for a single client
connection) that if you have extremely fast
single core performance CPU.

With the AEAD usage limit, these limits are almost a factor of 8 lower
so with the limit becomes 1-2 GBit/s. This is already reached without
DCO on some platforms.

This introduces the epoch data format for AEAD data channel
ciphers in TLS mode ciphers. No effort has been made to support
larger packet counters in any other scenario since those are all legacy.
This uses the same approach of epoch keys as (D)TLS 1.3 does and switches
the data channel regularly for affected AEAD ciphers when reaching the
usage limit.

For Chacha20-Poly1305, which does not suffer the same problems as AES-GCM,
the full 48 bit of packet counter are used only after that the same logic
to switch to a new key as with AES-GCM is done.

Change-Id: I00751c42cb04e30205ba8e6584530831e0d143c5
Signed-off-by: Arne Schwabe <[email protected]>
  • Loading branch information
schwabe committed Nov 11, 2024
1 parent f649a3c commit d546c8d
Show file tree
Hide file tree
Showing 14 changed files with 444 additions and 61 deletions.
9 changes: 8 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,7 @@ if (BUILD_TESTING)

target_sources(test_auth_token PRIVATE
src/openvpn/base64.c
src/openvpn/crypto_epoch.c
src/openvpn/crypto_mbedtls.c
src/openvpn/crypto_openssl.c
src/openvpn/crypto.c
Expand Down Expand Up @@ -733,9 +734,10 @@ if (BUILD_TESTING)
tests/unit_tests/openvpn/mock_win32_execve.c
src/openvpn/argv.c
src/openvpn/base64.c
src/openvpn/crypto.c
src/openvpn/crypto_epoch.c
src/openvpn/crypto_mbedtls.c
src/openvpn/crypto_openssl.c
src/openvpn/crypto.c
src/openvpn/cryptoapi.c
src/openvpn/env_set.c
src/openvpn/mss.c
Expand All @@ -761,6 +763,7 @@ if (BUILD_TESTING)
)

target_sources(test_ncp PRIVATE
src/openvpn/crypto_epoch.c
src/openvpn/crypto_mbedtls.c
src/openvpn/crypto_openssl.c
src/openvpn/crypto.c
Expand All @@ -782,6 +785,7 @@ if (BUILD_TESTING)
tests/unit_tests/openvpn/mock_win32_execve.c
src/openvpn/argv.c
src/openvpn/base64.c
src/openvpn/crypto_epoch.c
src/openvpn/crypto_mbedtls.c
src/openvpn/crypto_openssl.c
src/openvpn/crypto.c
Expand Down Expand Up @@ -835,9 +839,11 @@ if (BUILD_TESTING)
target_compile_options(test_networking PRIVATE -UNDEBUG)
target_sources(test_networking PRIVATE
src/openvpn/networking_sitnl.c
src/openvpn/crypto_epoch.c
src/openvpn/crypto_mbedtls.c
src/openvpn/crypto_openssl.c
src/openvpn/crypto.c
src/openvpn/crypto_epoch.c
src/openvpn/otime.c
src/openvpn/packet_id.c
)
Expand All @@ -853,6 +859,7 @@ if (BUILD_TESTING)
tests/unit_tests/openvpn/mock_win32_execve.c
src/openvpn/argv.c
src/openvpn/base64.c
src/openvpn/crypto_epoch.c
src/openvpn/crypto_mbedtls.c
src/openvpn/crypto_openssl.c
src/openvpn/crypto.c
Expand Down
137 changes: 102 additions & 35 deletions src/openvpn/crypto.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
#include <string.h>

#include "crypto.h"
#include "crypto_epoch.h"
#include "packet_id.h"
#include "error.h"
#include "integer.h"
#include "platform.h"
Expand Down Expand Up @@ -68,7 +70,15 @@ openvpn_encrypt_aead(struct buffer *buf, struct buffer work,
{
struct gc_arena gc;
int outlen = 0;
const bool use_epoch_data_format = opt->flags & CO_EPOCH_DATA_KEY_FORMAT;

if (use_epoch_data_format)
{
epoch_check_send_iterate(opt);
}

const struct key_ctx *ctx = &opt->key_ctx_bi.encrypt;

uint8_t *mac_out = NULL;
const int mac_len = OPENVPN_AEAD_TAG_LENGTH;

Expand All @@ -89,14 +99,24 @@ openvpn_encrypt_aead(struct buffer *buf, struct buffer work,
buf_set_write(&iv_buffer, iv, iv_len);

/* IV starts with packet id to make the IV unique for packet */
if (!packet_id_write(&opt->packet_id.send, &iv_buffer, false, false))
if (use_epoch_data_format)
{
msg(D_CRYPT_ERRORS, "ENCRYPT ERROR: packet ID roll over");
goto err;
if (!packet_id_write_epoch(&opt->packet_id.send, ctx->epoch, &iv_buffer))
{
msg(D_CRYPT_ERRORS, "ENCRYPT ERROR: packet ID roll over");
goto err;
}
}
else
{
if (!packet_id_write(&opt->packet_id.send, &iv_buffer, false, false))
{
msg(D_CRYPT_ERRORS, "ENCRYPT ERROR: packet ID roll over");
goto err;
}
}

/* Write packet id part of IV to work buffer */
ASSERT(buf_write(&work, iv, packet_id_size(false)));
ASSERT(buf_write(&work, iv, buf_len(&iv_buffer)));

/* Remainder of IV consists of implicit part (unique per session)
* XOR of packet and implicit IV */
Expand Down Expand Up @@ -128,7 +148,7 @@ openvpn_encrypt_aead(struct buffer *buf, struct buffer work,
dmsg(D_PACKET_CONTENT, "ENCRYPT AD: %s",
format_hex(BPTR(&work), BLEN(&work), 0, &gc));

if (!(opt->flags & CO_EPOCH_DATA_KEY_FORMAT))
if (!use_epoch_data_format)
{
/* Reserve space for authentication tag */
mac_out = buf_write_alloc(&work, mac_len);
Expand All @@ -149,7 +169,7 @@ openvpn_encrypt_aead(struct buffer *buf, struct buffer work,
ASSERT(buf_inc_len(&work, outlen));

/* if the tag is at end the end, allocate it now */
if (opt->flags & CO_EPOCH_DATA_KEY_FORMAT)
if (use_epoch_data_format)
{
/* Reserve space for authentication tag */
mac_out = buf_write_alloc(&work, mac_len);
Expand Down Expand Up @@ -365,14 +385,35 @@ cipher_get_aead_limits(const char *ciphername)

bool
crypto_check_replay(struct crypto_options *opt,
const struct packet_id_net *pin, const char *error_prefix,
const struct packet_id_net *pin, uint16_t epoch,
const char *error_prefix,
struct gc_arena *gc)
{
bool ret = false;
packet_id_reap_test(&opt->packet_id.rec);
if (packet_id_test(&opt->packet_id.rec, pin))
struct packet_id_rec *recv;

if (epoch == 0 || opt->key_ctx_bi.decrypt.epoch == epoch)
{
recv = &opt->packet_id.rec;
}
else if (epoch == opt->epoch_retiring_data_receive_key.epoch)
{
recv = &opt->epoch_retiring_key_pid_recv;
}
else
{
/* We have an epoch that is neither current or old recv key but
* is authenticated, ie we need to move to a new current recv key */
msg(D_GENKEY, "Received data packet with new epoch %d. Updating "
"receive key", epoch);
epoch_replace_update_recv_key(opt, epoch);
recv = &opt->packet_id.rec;
}

packet_id_reap_test(recv);
if (packet_id_test(recv, pin))
{
packet_id_add(&opt->packet_id.rec, pin);
packet_id_add(recv, pin);
if (opt->pid_persist && (opt->flags & CO_PACKET_ID_LONG_FORM))
{
packet_id_persist_save_obj(opt->pid_persist, &opt->packet_id);
Expand Down Expand Up @@ -408,8 +449,9 @@ openvpn_decrypt_aead(struct buffer *buf, struct buffer work,
static const char error_prefix[] = "AEAD Decrypt error";
struct packet_id_net pin = { 0 };
const struct key_ctx *ctx = &opt->key_ctx_bi.decrypt;
int outlen;
struct gc_arena gc;
const bool use_epoch_data_format = opt->flags & CO_EPOCH_DATA_KEY_FORMAT;
const int tag_size = OPENVPN_AEAD_TAG_LENGTH;

gc_init(&gc);

Expand All @@ -428,20 +470,58 @@ openvpn_decrypt_aead(struct buffer *buf, struct buffer work,
/* IV and Packet ID required for this mode */
ASSERT(packet_id_initialized(&opt->packet_id));

/* Ensure that the packet size is long enough */
int min_packet_len = packet_id_size(false) + tag_size + 1;

if (use_epoch_data_format)
{
min_packet_len += sizeof(uint32_t);
}

if (buf->len < min_packet_len)
{
CRYPT_ERROR("missing IV info, missing tag or no payload");
}

uint16_t epoch = 0;
/* Combine IV from explicit part from packet and implicit part from context */
{
uint8_t iv[OPENVPN_MAX_IV_LENGTH] = { 0 };
const int iv_len = cipher_ctx_iv_length(ctx->cipher);
const size_t packet_iv_len = packet_id_size(false);

if (buf->len < packet_id_size(false))
/* Read packet id. For epoch data format also lookup the epoch key
* to be able to use the implicit IV of the correct decryption key */
if (use_epoch_data_format)
{
CRYPT_ERROR("missing IV info");
}
/* packet ID format is 16 bit epoch + 48 per epoch packet-counter */
const size_t packet_iv_len = sizeof(uint64_t);

memcpy(iv, BPTR(buf), packet_iv_len);
/* copy the epoch-counter part into the IV */
memcpy(iv, BPTR(buf), packet_iv_len);

/* Remainder of IV consists of implicit part (unique per session)
epoch = packet_id_read_epoch(&pin, buf);
if (epoch == 0)
{
CRYPT_ERROR("error reading packet-id");
}
ctx = epoch_lookup_decrypt_key(opt, epoch);
if (!ctx)
{
CRYPT_ERROR("data packet with unknown epoch");
}
}
else
{
const size_t packet_iv_len = packet_id_size(false);
/* Packet ID form is a 32 bit packet counter */
memcpy(iv, BPTR(buf), packet_iv_len);
if (!packet_id_read(&pin, buf, false))
{
CRYPT_ERROR("error reading packet-id");
}
}

/* Remainder of IV consists of implicit part (unique per session/epoch key)
* XOR of packet counter and implicit IV */
for (int i = 0; i < iv_len; i++)
{
Expand All @@ -457,25 +537,12 @@ openvpn_decrypt_aead(struct buffer *buf, struct buffer work,
}
}

/* Read packet ID from packet */
if (!packet_id_read(&pin, buf, false))
{
CRYPT_ERROR("error reading packet-id");
}

/* keep the tag value to feed in later */
const int tag_size = OPENVPN_AEAD_TAG_LENGTH;
if (buf->len < tag_size + 1)
{
CRYPT_ERROR("missing tag or no payload");
}

const int ad_size = BPTR(buf) - ad_start;

uint8_t *tag_ptr = NULL;
int data_len = 0;

if (opt->flags & CO_EPOCH_DATA_KEY_FORMAT)
if (use_epoch_data_format)
{
data_len = BLEN(buf) - tag_size;
tag_ptr = BPTR(buf) + data_len;
Expand All @@ -496,13 +563,13 @@ openvpn_decrypt_aead(struct buffer *buf, struct buffer work,
CRYPT_ERROR("potential buffer overflow");
}


/* feed in tag and the authenticated data */
ASSERT(cipher_ctx_update_ad(ctx->cipher, ad_start, ad_size));
dmsg(D_PACKET_CONTENT, "DECRYPT AD: %s",
format_hex(ad_start, ad_size, 0, &gc));

/* Decrypt and authenticate packet */
int outlen;
if (!cipher_ctx_update(ctx->cipher, BPTR(&work), &outlen, BPTR(buf),
data_len))
{
Expand All @@ -525,7 +592,7 @@ openvpn_decrypt_aead(struct buffer *buf, struct buffer work,
dmsg(D_PACKET_CONTENT, "DECRYPT TO: %s",
format_hex(BPTR(&work), BLEN(&work), 80, &gc));

if (!crypto_check_replay(opt, &pin, error_prefix, &gc))
if (!crypto_check_replay(opt, &pin, epoch, error_prefix, &gc))
{
goto error_exit;
}
Expand Down Expand Up @@ -696,7 +763,7 @@ openvpn_decrypt_v1(struct buffer *buf, struct buffer work,
}
}

if (have_pin && !crypto_check_replay(opt, &pin, error_prefix, &gc))
if (have_pin && !crypto_check_replay(opt, &pin, 0, error_prefix, &gc))
{
goto error_exit;
}
Expand Down
1 change: 1 addition & 0 deletions src/openvpn/crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,7 @@ bool openvpn_decrypt(struct buffer *buf, struct buffer work,
*/
bool crypto_check_replay(struct crypto_options *opt,
const struct packet_id_net *pin,
uint16_t epoch,
const char *error_prefix,
struct gc_arena *gc);

Expand Down
2 changes: 1 addition & 1 deletion src/openvpn/crypto_backend.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
#include "basic.h"
#include "buffer.h"

/* TLS uses a tag of 128 bytes, let's do the same for OpenVPN */
/* TLS uses a tag of 128 bits, let's do the same for OpenVPN */
#define OPENVPN_AEAD_TAG_LENGTH 16

/* Maximum cipher block size (bytes) */
Expand Down
14 changes: 14 additions & 0 deletions src/openvpn/dco.h
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,15 @@ int dco_get_peer_stats(struct context *c);
*/
const char *dco_get_supported_ciphers(void);

/**
* Return whether the dco implementation supports the new protocol features of
* a 64 bit packet counter and AEAD tag at the end.
*/
static inline bool
dco_supports_epoch_data(struct context *c)
{
return false;
}
#else /* if defined(ENABLE_DCO) */

typedef void *dco_context_t;
Expand Down Expand Up @@ -380,5 +389,10 @@ dco_get_supported_ciphers(void)
return "";
}

static inline bool
dco_supports_epoch_data(struct context *c)
{
return false;
}
#endif /* defined(ENABLE_DCO) */
#endif /* ifndef DCO_H */
22 changes: 22 additions & 0 deletions src/openvpn/init.c
Original file line number Diff line number Diff line change
Expand Up @@ -2761,6 +2761,19 @@ do_deferred_options(struct context *c, const unsigned int found)
}
}

/* Ensure that for epoch data format is only enabled if also data v2
* is enabled */
bool epoch_data = (c->options.imported_protocol_flags & CO_EPOCH_DATA_KEY_FORMAT);
bool datav2_enabled = (c->options.peer_id >= 0 && c->options.peer_id < MAX_PEER_ID);

if (epoch_data && !datav2_enabled)
{
msg(D_PUSH_ERRORS, "OPTIONS ERROR: Epoch key data format tag requires "
"data v2 (peer-id) to be enabled.");
return false;
}


if (found & OPT_P_PUSH_MTU)
{
/* MTU has changed, check that the pushed MTU is small enough to
Expand Down Expand Up @@ -3357,6 +3370,15 @@ do_init_crypto_tls(struct context *c, const unsigned int flags)
to.push_peer_info_detail = 1;
}

/* Check if the DCO drivers support the epoch data format */
if (dco_enabled(options))
{
to.data_epoch_supported = dco_supports_epoch_data(c);
}
else
{
to.data_epoch_supported = true;
}

/* should we not xmit any packets until we get an initial
* response from client? */
Expand Down
6 changes: 6 additions & 0 deletions src/openvpn/multi.c
Original file line number Diff line number Diff line change
Expand Up @@ -1849,6 +1849,12 @@ multi_client_set_protocol_options(struct context *c)
}
#endif

if (tls_multi->session[TM_ACTIVE].opt->data_epoch_supported
&& (proto & IV_PROTO_DATA_EPOCH))
{
o->imported_protocol_flags |= CO_EPOCH_DATA_KEY_FORMAT;
}

if (proto & IV_PROTO_CC_EXIT_NOTIFY)
{
o->imported_protocol_flags |= CO_USE_CC_EXIT_NOTIFY;
Expand Down
Loading

0 comments on commit d546c8d

Please sign in to comment.