Skip to content

Commit

Permalink
Implement support for larger packet counter sizes
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.

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.

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.

This introduces the 64bit packet counters for AEAD data channel
ciphers in TLS mode ciphers. No effort has been made to support
larger packet counters in any scenario since the other scenarios
are all legacy.

While we still keep the old --secret logic around we use the same
weird unix timestamp + packet counter format to avoid refactoring the
code now and again when we remove --secret code but DCO
implementations are free to use just a single 64 bit counter. One
other small downside of this approach is that when rollover happens
and we get reordering all the older packets are thrown away since
the distance between the packet before and after the rollover is
quite large as we probably jump forward more than 1s (or more than
2^32 packet ids) forward. But this is an obscure edge that we can
(currently) live with.

Change-Id: I01e258e97351b5aa4b9e561f5b35ddc2318569e2
  • Loading branch information
schwabe committed Jan 23, 2024
1 parent 90fe09c commit 0879715
Show file tree
Hide file tree
Showing 9 changed files with 95 additions and 19 deletions.
7 changes: 5 additions & 2 deletions src/openvpn/crypto.c
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ openvpn_encrypt_aead(struct buffer *buf, struct buffer work,
const struct key_ctx *ctx = &opt->key_ctx_bi.encrypt;
uint8_t *mac_out = NULL;
const int mac_len = OPENVPN_AEAD_TAG_LENGTH;
bool longiv = opt->flags & CO_64_BIT_PKT_ID;

/* IV, packet-ID and implicit IV required for this mode. */
ASSERT(ctx->cipher);
Expand All @@ -86,7 +87,7 @@ 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 (!packet_id_write(&opt->packet_id.send, &iv_buffer, longiv, false))
{
msg(D_CRYPT_ERRORS, "ENCRYPT ERROR: packet ID roll over");
goto err;
Expand Down Expand Up @@ -384,6 +385,8 @@ openvpn_decrypt_aead(struct buffer *buf, struct buffer work,
/* IV and Packet ID required for this mode */
ASSERT(packet_id_initialized(&opt->packet_id));

bool longiv = opt->flags & CO_64_BIT_PKT_ID;

/* Combine IV from explicit part from packet and implicit part from context */
{
uint8_t iv[OPENVPN_MAX_IV_LENGTH] = { 0 };
Expand All @@ -409,7 +412,7 @@ openvpn_decrypt_aead(struct buffer *buf, struct buffer work,
}

/* Read packet ID from packet */
if (!packet_id_read(&pin, buf, false))
if (!packet_id_read(&pin, buf, longiv))
{
CRYPT_ERROR("error reading packet-id");
}
Expand Down
5 changes: 5 additions & 0 deletions src/openvpn/crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,11 @@ struct crypto_options
/**< Bit-flag indicating that the AEAD tag is at the end of the
* packet.
*/
#define CO_64_BIT_PKT_ID (1<<9)
/**< Bit-flag indicating that we should use a 64 bit (8 byte) packet
* counter instead of the 32 bit that we normally use.
*/


unsigned int flags; /**< Bit-flags determining behavior of
* security operation functions. */
Expand Down
14 changes: 14 additions & 0 deletions src/openvpn/init.c
Original file line number Diff line number Diff line change
Expand Up @@ -2322,6 +2322,10 @@ tls_print_deferred_options_results(struct context *c)
{
buf_printf(&out, " aead-tag-end");
}
if (o->imported_protocol_flags & CO_64_BIT_PKT_ID)
{
buf_printf(&out, " pkt-id-64-bit");
}
}

if (buf_len(&out) > strlen(header))
Expand Down Expand Up @@ -3292,6 +3296,16 @@ do_init_crypto_tls(struct context *c, const unsigned int flags)
to.push_peer_info_detail = 1;
}

/* Check if the DCO drivers support the new 64bit packet counter and
* AEAD tag at the end */
if (dco_enabled(options))
{
to.data_v3_features_supported = false;
}
else
{
to.data_v3_features_supported = true
}

Check failure on line 3308 in src/openvpn/init.c

View workflow job for this annotation

GitHub Actions / gcc-mingw - x64 - OSSL

expected ‘;’ before ‘}’ token

Check failure on line 3308 in src/openvpn/init.c

View workflow job for this annotation

GitHub Actions / gcc-mingw - x86 - OSSL

expected ‘;’ before ‘}’ token

/* should we not xmit any packets until we get an initial
* response from client? */
Expand Down
4 changes: 4 additions & 0 deletions src/openvpn/options.c
Original file line number Diff line number Diff line change
Expand Up @@ -8783,6 +8783,10 @@ add_option(struct options *options,
{
options->imported_protocol_flags |= CO_AEAD_TAG_AT_THE_END;
}
else if (streq(p[j], "pkt-id-64-bit"))
{
options->imported_protocol_flags |= CO_64_BIT_PKT_ID;
}
else
{
msg(msglevel, "Unknown protocol-flags flag: %s", p[j]);
Expand Down
4 changes: 4 additions & 0 deletions src/openvpn/push.c
Original file line number Diff line number Diff line change
Expand Up @@ -874,6 +874,10 @@ prepare_push_reply(struct context *c, struct gc_arena *gc,
{
buf_printf(&proto_flags, " aead-tag-end");
}
if (o->imported_protocol_flags & CO_64_BIT_PKT_ID)
{
buf_printf(&proto_flags, " pkt-id-64-bit");
}

if (buf_len(&proto_flags) > 0)
{
Expand Down
19 changes: 14 additions & 5 deletions src/openvpn/ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -1372,13 +1372,15 @@ init_key_contexts(struct key_state *ks,
}
else
{
bool longiv = ks->crypto_options.flags & CO_64_BIT_PKT_ID;
init_key_ctx_bi(key, key2, key_direction, key_type, "Data Channel");
/* Initialize implicit IVs */
key_ctx_update_implicit_iv(&key->encrypt, key2->keys[(int)server].hmac,
MAX_HMAC_KEY_LENGTH);
key_ctx_update_implicit_iv(&key->encrypt,
key2->keys[(int)server].hmac,
MAX_HMAC_KEY_LENGTH, longiv);
key_ctx_update_implicit_iv(&key->decrypt,
key2->keys[1 - (int)server].hmac,
MAX_HMAC_KEY_LENGTH);
MAX_HMAC_KEY_LENGTH, longiv);
}
}

Expand Down Expand Up @@ -1516,14 +1518,15 @@ generate_key_expansion(struct tls_multi *multi, struct key_state *ks,
}

static void
key_ctx_update_implicit_iv(struct key_ctx *ctx, uint8_t *key, size_t key_len)
key_ctx_update_implicit_iv(struct key_ctx *ctx, uint8_t *key,
size_t key_len, bool longiv)
{
/* Only use implicit IV in AEAD cipher mode, where HMAC key is not used */
if (cipher_ctx_mode_aead(ctx->cipher))
{
size_t impl_iv_len = 0;
ASSERT(cipher_ctx_iv_length(ctx->cipher) >= OPENVPN_AEAD_MIN_IV_LEN);
impl_iv_len = cipher_ctx_iv_length(ctx->cipher) - sizeof(packet_id_type);
impl_iv_len = cipher_ctx_iv_length(ctx->cipher) - packet_id_size(longiv);
ASSERT(impl_iv_len <= OPENVPN_MAX_IV_LENGTH);
ASSERT(impl_iv_len <= key_len);
memcpy(ctx->implicit_iv, key, impl_iv_len);
Expand Down Expand Up @@ -1952,6 +1955,12 @@ push_peer_info(struct buffer *buf, struct tls_session *session)
iv_proto |= IV_PROTO_DYN_TLS_CRYPT;
#endif

/* support for AEAD tag at the end and 8 byte IV */
if (session->opt->data_v3_features_supported)
{
iv_proto |= IV_PROTO_DATA_V3;
}

buf_printf(&out, "IV_PROTO=%d\n", iv_proto);

if (session->opt->push_peer_info_detail > 1)
Expand Down
5 changes: 2 additions & 3 deletions src/openvpn/ssl_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,6 @@ struct tls_options

/* from command line */
bool single_session;
bool disable_occ;
int mode;
bool pull;
/**
Expand Down Expand Up @@ -364,6 +363,8 @@ struct tls_options
const char *config_ciphername;
const char *config_ncp_ciphers;

bool data_v3_features_supported; /**< dco supports new data channel features */

bool tls_crypt_v2;
const char *tls_crypt_v2_verify_script;

Expand Down Expand Up @@ -493,8 +494,6 @@ struct tls_session
*/
int key_id;

int limit_next; /* used for traffic shaping on the control channel */

int verify_maxlevel;

char *common_name;
Expand Down
6 changes: 6 additions & 0 deletions src/openvpn/ssl_ncp.c
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,12 @@ p2p_ncp_set_options(struct tls_multi *multi, struct tls_session *session)
session->opt->crypto_flags |= CO_USE_CC_EXIT_NOTIFY;
}

if (session->opt->data_v3_features_supported && (iv_proto_peer & IV_PROTO_DATA_V3))
{
session->opt->crypto_flags |= CO_AEAD_TAG_AT_THE_END;
session->opt->crypto_flags |= CO_64_BIT_PKT_ID;
}

#if defined(HAVE_EXPORT_KEYING_MATERIAL)
if (iv_proto_peer & IV_PROTO_TLS_KEY_EXPORT)
{
Expand Down
50 changes: 41 additions & 9 deletions tests/unit_tests/openvpn/test_ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,12 @@ init_implicit_iv(struct crypto_options *co)
{
cipher_ctx_t *cipher = co->key_ctx_bi.encrypt.cipher;


if (cipher_ctx_mode_aead(cipher))
{
size_t impl_iv_len = cipher_ctx_iv_length(cipher) - sizeof(packet_id_type);
bool longiv = co->flags & CO_64_BIT_PKT_ID;

size_t impl_iv_len = cipher_ctx_iv_length(cipher) - packet_id_size(longiv);
ASSERT(cipher_ctx_iv_length(cipher) <= OPENVPN_MAX_IV_LENGTH);
ASSERT(cipher_ctx_iv_length(cipher) >= OPENVPN_AEAD_MIN_IV_LEN);

Expand All @@ -142,6 +145,11 @@ init_implicit_iv(struct crypto_options *co)
memcpy(co->key_ctx_bi.decrypt.implicit_iv,
co->key_ctx_bi.encrypt.implicit_iv, OPENVPN_MAX_IV_LENGTH);
co->key_ctx_bi.decrypt.implicit_iv_len = impl_iv_len;

if (longiv)
{
co->flags |= CO_64_BIT_PKT_ID;
}
}
}

Expand Down Expand Up @@ -279,6 +287,25 @@ run_data_channel_with_cipher_end(const char *cipher)
uninit_crypto_options(&co);
}

static void
run_data_channel_with_cipher_end_and_long_pkt_counter(const char *cipher)
{
struct crypto_options co = init_crypto_options(cipher, "none");
co.flags |= CO_AEAD_TAG_AT_THE_END;
do_data_channel_round_trip(&co);
uninit_crypto_options(&co);
}

static void
run_data_channel_with_long_pkt_counter(const char *cipher)
{
struct crypto_options co = init_crypto_options(cipher, "none");
co.flags |= CO_64_BIT_PKT_ID;
do_data_channel_round_trip(&co);
uninit_crypto_options(&co);
}


static void
run_data_channel_with_cipher(const char *cipher, const char *auth)
{
Expand All @@ -288,25 +315,31 @@ run_data_channel_with_cipher(const char *cipher, const char *auth)
}


static void
run_aead_channel_tests(const char *cipher)
{
run_data_channel_with_cipher_end(cipher);
run_data_channel_with_cipher(cipher, "none");
run_data_channel_with_cipher_end_and_long_pkt_counter(cipher);
run_data_channel_with_long_pkt_counter(cipher);
}

static void
test_data_channel_roundtrip_aes_128_gcm(void **state)
{
run_data_channel_with_cipher_end("AES-128-GCM");
run_data_channel_with_cipher("AES-128-GCM", "none");
run_aead_channel_tests("AES-128-GCM");
}

static void
test_data_channel_roundtrip_aes_192_gcm(void **state)
{
run_data_channel_with_cipher_end("AES-192-GCM");
run_data_channel_with_cipher("AES-192-GCM", "none");
run_aead_channel_tests("AES-192-GCM");
}

static void
test_data_channel_roundtrip_aes_256_gcm(void **state)
{
run_data_channel_with_cipher_end("AES-256-GCM");
run_data_channel_with_cipher("AES-256-GCM", "none");
run_aead_channel_tests("AES-256-GCM");
}

static void
Expand Down Expand Up @@ -336,8 +369,7 @@ test_data_channel_roundtrip_chacha20_poly1305(void **state)
return;
}

run_data_channel_with_cipher_end("ChaCha20-Poly1305");
run_data_channel_with_cipher("ChaCha20-Poly1305", "none");
run_aead_channel_tests("ChaCha20-Poly1305");
}

static void
Expand Down

0 comments on commit 0879715

Please sign in to comment.