diff --git a/CMakeLists.txt b/CMakeLists.txt index cbc93cdda..6daeaea8e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 @@ -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 @@ -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 @@ -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 @@ -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 ) @@ -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 diff --git a/src/openvpn/crypto.c b/src/openvpn/crypto.c index 996830cd8..f239282a6 100644 --- a/src/openvpn/crypto.c +++ b/src/openvpn/crypto.c @@ -32,6 +32,8 @@ #include #include "crypto.h" +#include "crypto_epoch.h" +#include "packet_id.h" #include "error.h" #include "integer.h" #include "platform.h" @@ -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; @@ -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 */ @@ -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); @@ -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); @@ -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); @@ -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); @@ -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++) { @@ -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; @@ -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)) { @@ -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; } @@ -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; } diff --git a/src/openvpn/crypto.h b/src/openvpn/crypto.h index 933bc2fdf..369d19f5f 100644 --- a/src/openvpn/crypto.h +++ b/src/openvpn/crypto.h @@ -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); diff --git a/src/openvpn/crypto_backend.h b/src/openvpn/crypto_backend.h index c454c64d8..de74def10 100644 --- a/src/openvpn/crypto_backend.h +++ b/src/openvpn/crypto_backend.h @@ -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) */ diff --git a/src/openvpn/dco.h b/src/openvpn/dco.h index 035474fca..c3e05a830 100644 --- a/src/openvpn/dco.h +++ b/src/openvpn/dco.h @@ -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; @@ -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 */ diff --git a/src/openvpn/init.c b/src/openvpn/init.c index 2c831fef4..387d8951b 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -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 @@ -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? */ diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c index 45b3cfa26..8a6887a41 100644 --- a/src/openvpn/multi.c +++ b/src/openvpn/multi.c @@ -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; diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c index 9eaf3ac45..d2d862ffb 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -54,6 +54,7 @@ #include "route.h" #include "tls_crypt.h" +#include "crypto_epoch.h" #include "ssl.h" #include "ssl_verify.h" #include "ssl_backend.h" @@ -912,6 +913,7 @@ key_state_free(struct key_state *ks, bool clear) key_state_ssl_free(&ks->ks_ssl); free_key_ctx_bi(&ks->crypto_options.key_ctx_bi); + free_epoch_key_ctx(&ks->crypto_options); free_buf(&ks->plaintext_read_buf); free_buf(&ks->plaintext_write_buf); free_buf(&ks->ack_write_buf); @@ -1358,6 +1360,48 @@ openvpn_PRF(const uint8_t *secret, return ret; } +static void +init_epoch_keys(struct key_state *ks, + struct tls_multi *multi, + const struct key_type *key_type, + bool server, + struct key2 *key2) +{ + /* For now we hardcode this to be 16 for the software based data channel + * DCO based implementations/HW implementation might adjust this number + * based on their expected speed */ + const int future_key_count = 16; + + int key_direction = server ? KEY_DIRECTION_INVERSE : KEY_DIRECTION_NORMAL; + struct key_direction_state kds; + key_direction_state_init(&kds, key_direction); + + struct crypto_options *co = &ks->crypto_options; + + /* For the epoch key we use the first 32 bytes of key2 cipher keys + * for the initial secret */ + struct epoch_key e1_send = { 0 }; + e1_send.epoch = 1; + memcpy(&e1_send.epoch_key, key2->keys[kds.out_key].cipher, sizeof(e1_send.epoch_key)); + + struct epoch_key e1_recv = { 0 }; + e1_recv.epoch = 1; + memcpy(&e1_recv.epoch_key, key2->keys[kds.in_key].cipher, sizeof(e1_recv.epoch_key)); + + /* DCO implementations have two choices at this point. + * + * a) (more likely) they probably to pass E1 directly to kernel + * space at this point and do all the other key derivation in kernel + * + * b) They let userspace do the key derivation and pass all the individual + * keys to the DCO layer. + * */ + epoch_init_key_ctx(co, key_type, &e1_send, &e1_recv, future_key_count); + + secure_memzero(&e1_send, sizeof(e1_send)); + secure_memzero(&e1_recv, sizeof(e1_recv)); +} + static void init_key_contexts(struct key_state *ks, struct tls_multi *multi, @@ -1391,6 +1435,16 @@ init_key_contexts(struct key_state *ks, CLEAR(key->decrypt); key->initialized = true; } + else if (multi->opt.crypto_flags & CO_EPOCH_DATA_KEY_FORMAT) + { + if (!cipher_kt_mode_aead(key_type->cipher)) + { + msg(M_FATAL, "AEAD cipher (currently %s) " + "required for epoch data format.", + cipher_kt_name(key_type->cipher)); + } + init_epoch_keys(ks, multi, key_type, server, key2); + } else { init_key_ctx_bi(key, key2, key_direction, key_type, "Data Channel"); @@ -1966,6 +2020,11 @@ push_peer_info(struct buffer *buf, struct tls_session *session) iv_proto |= IV_PROTO_NCP_P2P; } + if (session->opt->data_epoch_supported) + { + iv_proto |= IV_PROTO_DATA_EPOCH; + } + buf_printf(&out, "IV_CIPHERS=%s\n", session->opt->config_ncp_ciphers); #ifdef HAVE_EXPORT_KEYING_MATERIAL @@ -2975,13 +3034,29 @@ should_trigger_renegotiation(const struct tls_session *session, const struct key return true; } + if (ks->crypto_options.flags & CO_EPOCH_DATA_KEY_FORMAT) + { + /* We only need to check the send key as we always keep send + * key epoch >= recv key epoch in \c epoch_replace_update_recv_key */ + if (ks->crypto_options.epoch_key_send.epoch >= 0xF000) + { + return true; + } + else + { + return false; + } + } + + /* Packet id approach the limit of the packet id */ if (packet_id_close_to_wrapping(&ks->crypto_options.packet_id.send)) { return true; } - /* Check the AEAD usage limit of cleartext blocks + packets */ + /* Check the AEAD usage limit of cleartext blocks + packets. + * When epoch are in use the crypto layer will handle this internally */ const struct key_ctx_bi *key_ctx_bi = &ks->crypto_options.key_ctx_bi; const int64_t usage_limit = session->opt->aead_usage_limit; diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h index 511127dfb..6a4b39d39 100644 --- a/src/openvpn/ssl_common.h +++ b/src/openvpn/ssl_common.h @@ -314,7 +314,6 @@ struct tls_options /* from command line */ bool single_session; - bool disable_occ; int mode; bool pull; /** @@ -367,6 +366,12 @@ struct tls_options const char *config_ciphername; const char *config_ncp_ciphers; + /** whether our underlying data channel supports new data channel + * features (epoch keys with AEAD tag at the end). This is always true + * for the internal implementation but can be false for DCO + * implementations */ + bool data_epoch_supported; + bool tls_crypt_v2; const char *tls_crypt_v2_verify_script; @@ -496,8 +501,6 @@ struct tls_session */ int key_id; - int limit_next; /* used for traffic shaping on the control channel */ - int verify_maxlevel; char *common_name; diff --git a/src/openvpn/ssl_ncp.c b/src/openvpn/ssl_ncp.c index 968858ed6..00be96d50 100644 --- a/src/openvpn/ssl_ncp.c +++ b/src/openvpn/ssl_ncp.c @@ -430,6 +430,11 @@ 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_epoch_supported && (iv_proto_peer & IV_PROTO_DATA_EPOCH)) + { + session->opt->crypto_flags |= CO_EPOCH_DATA_KEY_FORMAT; + } + #if defined(HAVE_EXPORT_KEYING_MATERIAL) if (iv_proto_peer & IV_PROTO_TLS_KEY_EXPORT) { @@ -499,9 +504,12 @@ p2p_mode_ncp(struct tls_multi *multi, struct tls_session *session) } msg(D_TLS_DEBUG_LOW, "P2P mode NCP negotiation result: " - "TLS_export=%d, DATA_v2=%d, peer-id %d, cipher=%s", + "TLS_export=%d, DATA_v2=%d, peer-id %d, epoch=%d, cipher=%s", (bool)(session->opt->crypto_flags & CO_USE_TLS_KEY_MATERIAL_EXPORT), - multi->use_peer_id, multi->peer_id, common_cipher); + multi->use_peer_id, + multi->peer_id, + (bool)(session->opt->crypto_flags & CO_EPOCH_DATA_KEY_FORMAT), + common_cipher); gc_free(&gc); } diff --git a/src/openvpn/tls_crypt.c b/src/openvpn/tls_crypt.c index 76f06bc1d..7c5eb0153 100644 --- a/src/openvpn/tls_crypt.c +++ b/src/openvpn/tls_crypt.c @@ -301,7 +301,7 @@ tls_crypt_unwrap(const struct buffer *src, struct buffer *dst, struct buffer tmp = *src; ASSERT(buf_advance(&tmp, TLS_CRYPT_OFF_PID)); ASSERT(packet_id_read(&pin, &tmp, true)); - if (!crypto_check_replay(opt, &pin, error_prefix, &gc)) + if (!crypto_check_replay(opt, &pin, 0, error_prefix, &gc)) { CRYPT_ERROR("packet replay"); } diff --git a/tests/Makefile.am b/tests/Makefile.am index f26b3b82d..3246e34b1 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -52,6 +52,7 @@ ntlm_support_SOURCES = ntlm_support.c \ unit_tests/openvpn/mock_msg.c unit_tests/openvpn/mock_msg.h \ $(top_srcdir)/src/openvpn/buffer.c \ $(top_srcdir)/src/openvpn/crypto.c \ + $(top_srcdir)/src/openvpn/crypto_epoch.c \ $(top_srcdir)/src/openvpn/crypto_openssl.c \ $(top_srcdir)/src/openvpn/crypto_mbedtls.c \ $(top_srcdir)/src/openvpn/otime.c \ diff --git a/tests/unit_tests/openvpn/Makefile.am b/tests/unit_tests/openvpn/Makefile.am index 307f9ed73..471389ba0 100644 --- a/tests/unit_tests/openvpn/Makefile.am +++ b/tests/unit_tests/openvpn/Makefile.am @@ -86,6 +86,7 @@ ssl_testdriver_SOURCES = test_ssl.c mock_msg.c mock_msg.h \ $(top_srcdir)/src/compat/compat-strsep.c \ $(top_srcdir)/src/openvpn/crypto.c \ $(top_srcdir)/src/openvpn/cryptoapi.c \ + $(top_srcdir)/src/openvpn/crypto_epoch.c \ $(top_srcdir)/src/openvpn/crypto_mbedtls.c \ $(top_srcdir)/src/openvpn/crypto_openssl.c \ $(top_srcdir)/src/openvpn/env_set.c \ @@ -132,6 +133,7 @@ pkt_testdriver_SOURCES = test_pkt.c mock_msg.c mock_msg.h mock_win32_execve.c \ $(top_srcdir)/src/openvpn/base64.c \ $(top_srcdir)/src/openvpn/buffer.c \ $(top_srcdir)/src/openvpn/crypto.c \ + $(top_srcdir)/src/openvpn/crypto_epoch.c \ $(top_srcdir)/src/openvpn/crypto_mbedtls.c \ $(top_srcdir)/src/openvpn/crypto_openssl.c \ $(top_srcdir)/src/openvpn/env_set.c \ @@ -160,6 +162,7 @@ tls_crypt_testdriver_SOURCES = test_tls_crypt.c mock_msg.c mock_msg.h \ $(top_srcdir)/src/openvpn/base64.c \ $(top_srcdir)/src/openvpn/buffer.c \ $(top_srcdir)/src/openvpn/crypto.c \ + $(top_srcdir)/src/openvpn/crypto_epoch.c \ $(top_srcdir)/src/openvpn/crypto_mbedtls.c \ $(top_srcdir)/src/openvpn/crypto_openssl.c \ $(top_srcdir)/src/openvpn/env_set.c \ @@ -179,6 +182,7 @@ networking_testdriver_SOURCES = test_networking.c mock_msg.c \ $(top_srcdir)/src/openvpn/networking_sitnl.c \ $(top_srcdir)/src/openvpn/buffer.c \ $(top_srcdir)/src/openvpn/crypto.c \ + $(top_srcdir)/src/openvpn/crypto_epoch.c \ $(top_srcdir)/src/openvpn/crypto_mbedtls.c \ $(top_srcdir)/src/openvpn/crypto_openssl.c \ $(top_srcdir)/src/openvpn/otime.c \ @@ -250,6 +254,7 @@ auth_token_testdriver_LDFLAGS = @TEST_LDFLAGS@ \ auth_token_testdriver_SOURCES = test_auth_token.c mock_msg.c \ $(top_srcdir)/src/openvpn/buffer.c \ $(top_srcdir)/src/openvpn/crypto.c \ + $(top_srcdir)/src/openvpn/crypto_epoch.c \ $(top_srcdir)/src/openvpn/crypto_mbedtls.c \ $(top_srcdir)/src/openvpn/crypto_openssl.c \ $(top_srcdir)/src/openvpn/otime.c \ @@ -285,6 +290,7 @@ ncp_testdriver_LDFLAGS = @TEST_LDFLAGS@ \ ncp_testdriver_SOURCES = test_ncp.c mock_msg.c \ $(top_srcdir)/src/openvpn/buffer.c \ $(top_srcdir)/src/openvpn/crypto.c \ + $(top_srcdir)/src/openvpn/crypto_epoch.c \ $(top_srcdir)/src/openvpn/crypto_mbedtls.c \ $(top_srcdir)/src/openvpn/crypto_openssl.c \ $(top_srcdir)/src/openvpn/otime.c \ diff --git a/tests/unit_tests/openvpn/test_ssl.c b/tests/unit_tests/openvpn/test_ssl.c index 845ca56b4..93b309458 100644 --- a/tests/unit_tests/openvpn/test_ssl.c +++ b/tests/unit_tests/openvpn/test_ssl.c @@ -35,6 +35,7 @@ #include #include "crypto.h" +#include "crypto_epoch.h" #include "options.h" #include "ssl_backend.h" #include "options_util.h" @@ -370,22 +371,46 @@ do_data_channel_round_trip(struct crypto_options *co) struct crypto_options -init_crypto_options(const char *cipher, const char *auth) +init_crypto_options(const char *cipher, const char *auth, bool epoch, + struct key2 *statickey) { struct key2 key2 = { .n = 2}; - ASSERT(rand_bytes(key2.keys[0].cipher, sizeof(key2.keys[0].cipher))); - ASSERT(rand_bytes(key2.keys[0].hmac, sizeof(key2.keys[0].hmac))); - ASSERT(rand_bytes(key2.keys[1].cipher, sizeof(key2.keys[1].cipher))); - ASSERT(rand_bytes(key2.keys[1].hmac, sizeof(key2.keys)[1].hmac)); + if (statickey) + { + /* Use chosen static key instead of random key when defined */ + key2 = *statickey; + } + else + { + ASSERT(rand_bytes(key2.keys[0].cipher, sizeof(key2.keys[0].cipher))); + ASSERT(rand_bytes(key2.keys[0].hmac, sizeof(key2.keys[0].hmac))); + ASSERT(rand_bytes(key2.keys[1].cipher, sizeof(key2.keys[1].cipher))); + ASSERT(rand_bytes(key2.keys[1].hmac, sizeof(key2.keys)[1].hmac)); + } struct crypto_options co = { 0 }; struct key_type kt = create_kt(cipher, auth, "ssl-test"); - init_key_ctx_bi(&co.key_ctx_bi, &key2, 0, &kt, "unit-test-ssl"); - packet_id_init(&co.packet_id, 5, 5, "UNITTEST", 0); - + if (epoch) + { + struct epoch_key e1 = { .epoch = 1, .epoch_key = { 0 }}; + memcpy(e1.epoch_key, key2.keys[0].cipher, sizeof(e1.epoch_key)); + co.flags |= CO_EPOCH_DATA_KEY_FORMAT; + epoch_init_key_ctx(&co, &kt, &e1, &e1, 5); + + /* Do a little of dancing for the epoch_send_key_iterate to test + * that this works too */ + epoch_iterate_send_key(&co); + epoch_iterate_send_key(&co); + epoch_iterate_send_key(&co); + } + else + { + init_key_ctx_bi(&co.key_ctx_bi, &key2, KEY_DIRECTION_BIDIRECTIONAL, &kt, "unit-test-ssl"); + } + packet_id_init(&co.packet_id, 5, 5, "UNITTEST", 0); return co; } @@ -394,17 +419,16 @@ uninit_crypto_options(struct crypto_options *co) { packet_id_free(&co->packet_id); free_key_ctx_bi(&co->key_ctx_bi); - + free_epoch_key_ctx(co); } /* This adds a few more methods than strictly necessary but this allows * us to see which exact test was run from the backtrace of the test * when it fails */ static void -run_data_channel_with_cipher_end(const char *cipher) +run_data_channel_with_cipher_epoch(const char *cipher) { - struct crypto_options co = init_crypto_options(cipher, "none"); - co.flags |= CO_EPOCH_DATA_KEY_FORMAT; + struct crypto_options co = init_crypto_options(cipher, "none", true, NULL); do_data_channel_round_trip(&co); uninit_crypto_options(&co); } @@ -412,7 +436,7 @@ run_data_channel_with_cipher_end(const char *cipher) static void run_data_channel_with_cipher(const char *cipher, const char *auth) { - struct crypto_options co = init_crypto_options(cipher, auth); + struct crypto_options co = init_crypto_options(cipher, auth, false, NULL); do_data_channel_round_trip(&co); uninit_crypto_options(&co); } @@ -421,21 +445,21 @@ run_data_channel_with_cipher(const char *cipher, const char *auth) 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_epoch("AES-128-GCM"); run_data_channel_with_cipher("AES-128-GCM", "none"); } 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_epoch("AES-192-GCM"); run_data_channel_with_cipher("AES-192-GCM", "none"); } 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_epoch("AES-256-GCM"); run_data_channel_with_cipher("AES-256-GCM", "none"); } @@ -466,7 +490,7 @@ test_data_channel_roundtrip_chacha20_poly1305(void **state) return; } - run_data_channel_with_cipher_end("ChaCha20-Poly1305"); + run_data_channel_with_cipher_epoch("ChaCha20-Poly1305"); run_data_channel_with_cipher("ChaCha20-Poly1305", "none"); } @@ -482,6 +506,153 @@ test_data_channel_roundtrip_bf_cbc(void **state) } +static struct key2 +create_key(void) +{ + struct key2 key2 = {.n = 2}; + + const uint8_t key[] = + {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', '0', '1', '2', '3', '4', '5', '6', '7', 'A', 'B', 'C', 'D', 'E', 'F', + 'G', 'H', 'j', 'k', 'u', 'c', 'h', 'e', 'n', 'l'}; + + static_assert(sizeof(key) == 32, "Size of key should be 32 bytes"); + + /* copy the key a few times to ensure to have the size we need for + * Statickey but XOR it to not repeat it */ + uint8_t keydata[sizeof(key2.keys)]; + + for (int i = 0; i < sizeof(key2.keys); i++) + { + keydata[i] = (uint8_t) (key[i % sizeof(key)] ^ i); + } + + ASSERT(memcpy(key2.keys[0].cipher, keydata, sizeof(key2.keys[0].cipher))); + ASSERT(memcpy(key2.keys[0].hmac, keydata + 64, sizeof(key2.keys[0].hmac))); + ASSERT(memcpy(key2.keys[1].cipher, keydata + 128, sizeof(key2.keys[1].cipher))); + ASSERT(memcpy(key2.keys[1].hmac, keydata + 192, sizeof(key2.keys)[1].hmac)); + + return key2; +} + +static void +test_data_channel_known_vectors_run(bool epoch) +{ + struct key2 key2 = create_key(); + + struct crypto_options co = init_crypto_options("AES-256-GCM", "none", epoch, + &key2); + + struct gc_arena gc = gc_new(); + + /* initialise frame for the test */ + struct frame frame; + init_frame_parameters(&frame); + + struct buffer src = alloc_buf_gc(frame.buf.payload_size, &gc); + struct buffer work = alloc_buf_gc(BUF_SIZE(&frame), &gc); + struct buffer encrypt_workspace = alloc_buf_gc(BUF_SIZE(&frame), &gc); + struct buffer decrypt_workspace = alloc_buf_gc(BUF_SIZE(&frame), &gc); + struct buffer buf = clear_buf(); + void *buf_p; + + /* init work */ + ASSERT(buf_init(&work, frame.buf.headroom)); + + now = 0; + + /* + * Load src with known data. + */ + ASSERT(buf_init(&src, 0)); + const char *plaintext = "The quick little fox jumps over the bureaucratic hurdles"; + + ASSERT(buf_write(&src, plaintext, strlen(plaintext))); + + /* copy source to input buf */ + buf = work; + buf_p = buf_write_alloc(&buf, BLEN(&src)); + ASSERT(buf_p); + memcpy(buf_p, BPTR(&src), BLEN(&src)); + + /* initialize work buffer with buf.headroom bytes of prepend capacity */ + ASSERT(buf_init(&encrypt_workspace, frame.buf.headroom)); + + /* add packet opcode and peer id */ + buf_write_u8(&encrypt_workspace, 7); + buf_write_u8(&encrypt_workspace, 0); + buf_write_u8(&encrypt_workspace, 0); + buf_write_u8(&encrypt_workspace, 23); + + /* encrypt */ + openvpn_encrypt(&buf, encrypt_workspace, &co); + + /* separate buffer in authenticated data and encrypted data */ + uint8_t *ad_start = BPTR(&buf); + buf_advance(&buf, 4); + + if (epoch) + { + uint8_t packetid1[8] = {0, 0x04, 0, 0, 0, 0, 0, 1}; + assert_memory_equal(BPTR(&buf), packetid1, 8); + } + else + { + uint8_t packetid1[4] = {0, 0, 0, 1}; + assert_memory_equal(BPTR(&buf), packetid1, 4); + } + + if (epoch) + { + uint8_t *tag_location = BEND(&buf) - OPENVPN_AEAD_TAG_LENGTH; + const uint8_t exp_tag_long[16] = + {0x86, 0x20, 0xfe, 0xc6, 0x65, 0xa6, 0xab, 0x2a, + 0x34, 0xa6, 0xb8, 0xd4, 0xb4, 0xa9, 0x00, 0x9e}; + assert_memory_equal(tag_location, exp_tag_long, OPENVPN_AEAD_TAG_LENGTH); + } + else + { + uint8_t *tag_location = BPTR(&buf) + 4; + const uint8_t exp_tag_short[16] = + {0x1f, 0xdd, 0x90, 0x8f, 0x0e, 0x9d, 0xc2, 0x5e, 0x79, 0xd8, 0x32, 0x02, 0x0d, 0x58, 0xe7, 0x3f}; + assert_memory_equal(tag_location, exp_tag_short, OPENVPN_AEAD_TAG_LENGTH); + } + + /* Check some bytes at the beginning of the encrypted part */ + if (epoch) + { + const uint8_t bytesat14[6] = {0x62, 0xa9, 0xe3, 0x7a, 0xa7, 0xf0}; + assert_memory_equal(BPTR(&buf) + 14, bytesat14, sizeof(bytesat14)); + } + else + { + const uint8_t bytesat30[6] = {0xa8, 0x2e, 0x6b, 0x17, 0x06, 0xd9}; + assert_memory_equal(BPTR(&buf) + 30, bytesat30, sizeof(bytesat30)); + } + + /* decrypt */ + openvpn_decrypt(&buf, decrypt_workspace, &co, &frame, ad_start); + + /* compare */ + assert_int_equal(buf.len, strlen(plaintext)); + assert_memory_equal(BPTR(&buf), plaintext, strlen(plaintext)); + + uninit_crypto_options(&co); + gc_free(&gc); +} + +static void +test_data_channel_known_vectors_epoch(void **state) +{ + test_data_channel_known_vectors_run(true); +} + +static void +test_data_channel_known_vectors_shortpktid(void **state) +{ + test_data_channel_known_vectors_run(false); +} + + int main(void) { @@ -499,6 +670,8 @@ main(void) cmocka_unit_test(test_data_channel_roundtrip_aes_192_cbc), cmocka_unit_test(test_data_channel_roundtrip_aes_256_cbc), cmocka_unit_test(test_data_channel_roundtrip_bf_cbc), + cmocka_unit_test(test_data_channel_known_vectors_epoch), + cmocka_unit_test(test_data_channel_known_vectors_shortpktid) }; #if defined(ENABLE_CRYPTO_OPENSSL)