diff --git a/src/openvpn/crypto.c b/src/openvpn/crypto.c index 674971340..1ebeba40e 100644 --- a/src/openvpn/crypto.c +++ b/src/openvpn/crypto.c @@ -37,6 +37,7 @@ #include "platform.h" #include "memdbg.h" +#include /* * Encryption and Compression Routines. @@ -138,6 +139,11 @@ openvpn_encrypt_aead(struct buffer *buf, struct buffer work, ASSERT(cipher_ctx_update(ctx->cipher, BEND(&work), &outlen, BPTR(buf), BLEN(buf))); ASSERT(buf_inc_len(&work, outlen)); + /* update number of plaintext blocks encrypted. Use the x + (n-1)/n trick + * to round up the result to the number of blocked used */ + const int blocksize = AEAD_LIMIT_BLOCKSIZE; + opt->key_ctx_bi.encrypt.plaintext_blocks += (outlen + (blocksize - 1))/blocksize; + /* Flush the encryption buffer */ ASSERT(cipher_ctx_final(ctx->cipher, BEND(&work), &outlen)); ASSERT(buf_inc_len(&work, outlen)); @@ -325,6 +331,38 @@ openvpn_encrypt(struct buffer *buf, struct buffer work, } } +int64_t +cipher_get_aead_limits(const char *ciphername) +{ + if (!cipher_kt_mode_aead(ciphername)) + { + return -1; + } + + if (cipher_kt_name(ciphername) == cipher_kt_name("CHACHA20-POLY1305")) + { + return -1; + } + + /* Assume all other ciphers require the limit */ + + /* We focus here on the equation + * + * q + s <= p^(1/2) * 2^(129/2) - 1 + * q <= (p^(1/2) * 2^(129/2) - 1) / (L + 1) + * + * as is the one that is limiting us. + * + * With p = 2^-57 this becomes + * + * q + s <= (p^36 - 1) + * + */ + int64_t rs = (1ull << 36) - 1; + + return rs; +} + bool crypto_check_replay(struct crypto_options *opt, const struct packet_id_net *pin, const char *error_prefix, @@ -471,6 +509,11 @@ openvpn_decrypt_aead(struct buffer *buf, struct buffer work, CRYPT_ERROR("cipher update failed"); } + /* update number of plaintext blocks decrypted. Use the x + (n-1)/n trick + * to round up the result to the number of blocked used. */ + const int blocksize = AEAD_LIMIT_BLOCKSIZE; + opt->key_ctx_bi.decrypt.plaintext_blocks += (outlen + (blocksize - 1))/blocksize; + ASSERT(buf_inc_len(&work, outlen)); if (!cipher_ctx_final_check_tag(ctx->cipher, BPTR(&work) + outlen, &outlen, tag_ptr, tag_size)) diff --git a/src/openvpn/crypto.h b/src/openvpn/crypto.h index 457fd0e60..d96061691 100644 --- a/src/openvpn/crypto.h +++ b/src/openvpn/crypto.h @@ -170,6 +170,9 @@ struct key_ctx * and the lower 32 bit of the IV are the packet id */ uint8_t implicit_iv[OPENVPN_MAX_IV_LENGTH]; + /** Counters the number of plaintext encrypted using this cipher + * for AEAD blocks */ + uint64_t plaintext_blocks; }; #define KEY_DIRECTION_BIDIRECTIONAL 0 /* same keys for both directions */ @@ -601,6 +604,24 @@ create_kt(const char *cipher, const char *md, const char *optname) return kt; } +/** + * Check if the cipher is an AEAD cipher and needs to be limited to a certain + * number of number of block + packets. Return -1 if ciphername is not an AEAD + * cipher or no limit (e.g. Chacha20-Poly1305) is needed. + * + * For reference see the OpenVPN RFC draft and + * https://www.ietf.org/archive/id/draft-irtf-cfrg-aead-limits-08.html + */ +int64_t +cipher_get_aead_limits(const char *ciphername); + +/** + * Blocksize used for the AEAD limit caluclation + * + * Since cipher_ctx_block_size() is reliable and will return 1 in many + * cases use a hardcoded blocksize instead */ +#define AEAD_LIMIT_BLOCKSIZE 16 + /** * Checks if the current TLS library supports the TLS 1.0 PRF with MD5+SHA1 * that OpenVPN uses when TLS Keying Material Export is not available. @@ -650,4 +671,5 @@ ovpn_expand_label(uint8_t *secret, size_t secret_len, uint8_t *context, size_t context_len, uint8_t *out, size_t out_len); + #endif /* CRYPTO_H */ diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c index 39efcaf02..1c46b8cd4 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -131,6 +131,26 @@ tls_limit_reneg_bytes(const char *ciphername, int64_t *reneg_bytes) } } +static int64_t +tls_get_limit_aead(const char *ciphername) +{ + int64_t limit = cipher_get_aead_limits(ciphername); + + if (limit == -1) + { + return 0; + } + + /* set limit to 7/8 of the limit so the renogiation has can succeeds before + * we go over the limit */ + limit = limit * 7/8; + + msg(D_SHOW_KEYS, "Note: AEAD cipher %s will be limited to a sum of %" + PRIi64 " for block and packets before renegotiation", + ciphername, limit); + return limit; +} + void tls_init_control_channel_frame_parameters(struct frame *frame, int tls_mtu) { @@ -1577,6 +1597,8 @@ tls_session_generate_data_channel_keys(struct tls_multi *multi, tls_limit_reneg_bytes(session->opt->key_type.cipher, &session->opt->renegotiate_bytes); + session->opt->aead_usage_limit = tls_get_limit_aead(session->opt->key_type.cipher); + /* set the state of the keys for the session to generated */ ks->state = S_GENERATED_KEYS; @@ -2937,6 +2959,22 @@ tls_process_state(struct tls_multi *multi, return true; } +/** + * Checks if the limit is reached + * + * This method abstracts the calculation to make the calling function easier + * to read. + */ +static inline bool +aead_usage_limit_reached(const int64_t limit, const struct key_ctx *key_ctx, + int64_t higest_pid) +{ + /* This is the q + s <= p^(1/2) * 2^(129/2) - 1 calculation where + * q is the number of protected messages (highest_pid) + * s Total plaintext length in all messages (in blocks) */ + return (limit > 0 && key_ctx->plaintext_blocks + higest_pid > limit); +} + /** * Determines if a renegotiation should be triggerred based on the various * factors that can trigger one @@ -2971,6 +3009,18 @@ should_trigger_renegotiation(const struct tls_session *session, const struct key return true; } + /* Check the AEAD usage limit of cleartext blocks + packets */ + const struct key_ctx_bi *key_ctx_bi = &ks->crypto_options.key_ctx_bi; + const int64_t usage_limit = session->opt->aead_usage_limit; + + if (aead_usage_limit_reached(usage_limit, &key_ctx_bi->encrypt, + ks->crypto_options.packet_id.send.id) + || aead_usage_limit_reached(usage_limit, &key_ctx_bi->decrypt, + ks->crypto_options.packet_id.rec.id)) + { + return true; + } + return false; } /* @@ -3001,12 +3051,19 @@ tls_process(struct tls_multi *multi, /* Should we trigger a soft reset? -- new key, keeps old key for a while */ if (ks->state >= S_GENERATED_KEYS && should_trigger_renegotiation(session, ks)) - { + { msg(D_TLS_DEBUG_LOW, "TLS: soft reset sec=%d/%d bytes=" counter_format - "/%lld pkts=" counter_format "/%lld", + "/%lld pkts=" counter_format "/%lld" + " aead_limit_send=%" PRIu64 "/%" PRIu64 + " aead_limit_recv=%" PRIu64 "/%" PRIu64, (int) (now - ks->established), session->opt->renegotiate_seconds, ks->n_bytes, session->opt->renegotiate_bytes, - ks->n_packets, session->opt->renegotiate_packets); + ks->n_packets, session->opt->renegotiate_packets, + ks->crypto_options.key_ctx_bi.encrypt.plaintext_blocks + ks->n_packets, + session->opt->aead_usage_limit, + ks->crypto_options.key_ctx_bi.decrypt.plaintext_blocks + ks->n_packets, + session->opt->aead_usage_limit + ); key_state_soft_reset(session); } diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h index 5840e2d73..ce1cd9e4a 100644 --- a/src/openvpn/ssl_common.h +++ b/src/openvpn/ssl_common.h @@ -333,6 +333,9 @@ struct tls_options interval_t packet_timeout; int64_t renegotiate_bytes; int64_t renegotiate_packets; + /** This limit for AEAD cipher, this is the sum of packets + blocks + * that are allowed to be used */ + int64_t aead_usage_limit; interval_t renegotiate_seconds; /* cert verification parms */ diff --git a/tests/unit_tests/openvpn/test_crypto.c b/tests/unit_tests/openvpn/test_crypto.c index c57e72f6d..fc563d34c 100644 --- a/tests/unit_tests/openvpn/test_crypto.c +++ b/tests/unit_tests/openvpn/test_crypto.c @@ -33,6 +33,7 @@ #include #include #include +#include #include "crypto.h" #include "options.h" @@ -448,6 +449,26 @@ test_mssfix_mtu_calculation(void **state) gc_free(&gc); } +void +crypto_test_aead_limits(void **state) +{ + /* if ChaCha20-Poly1305 is not supported by the crypto library or in the + * current mode (FIPS), this will still return -1 */ + assert_int_equal(cipher_get_aead_limits("CHACHA20-POLY1305"), -1); + + int64_t aeslimit = cipher_get_aead_limits("AES-128-GCM"); + + assert_double_equal(aeslimit, pow(2, 36) -1, 0.1); + + /* Check if this matches our exception for 1600 size packets */ + double L = 101; + assert_double_equal(log2(aeslimit/L), 29.34, 0.01); + + /* and for 9000 */ + L = 563; + assert_double_equal(log2(aeslimit/ L), 26.86, 0.01); +} + void crypto_test_hkdf_expand_testa1(void **state) { @@ -555,6 +576,7 @@ main(void) cmocka_unit_test(crypto_test_hmac), cmocka_unit_test(test_occ_mtu_calculation), cmocka_unit_test(test_mssfix_mtu_calculation), + cmocka_unit_test(crypto_test_aead_limits), cmocka_unit_test(crypto_test_hkdf_expand_testa1), cmocka_unit_test(crypto_test_hkdf_expand_testa2), cmocka_unit_test(crypto_test_hkdf_expand_testa3)