Currently we have only one slot for renegotiation of the session/keys If a replayed/faked packet is inserted by a malicous attacker, the legimate peer cannot renegotiate anymore.
This commit introduces dynamic tls-crypt. When both peer support this feature, both peer create a dynamic tls-crypt key using TLS EKM (export key material) and will enforce using that key and tls-crypt for all renegotiations. Since one of tls-crypt/tls-crypt-v2 purpose is to provide poor man's post quantum crypto guarantees, we have to ensure that the dynamic key tls-crypt key that replace the original tls-crypt key is as strong as the orginal key to avoid problems if there is a weak RNG or TLS EKM produces weak keys. We ensure this but XORing the original key with the key from TLS EKM. If tls-crypt/tls-cryptv2 is not active, we use just the key generated by TLS EKM. OpenVPN 2.x reserves the TM_ACTIVE session for renegotians. When a SOFT_RESET_V1 packet is received, the active TLS session is moved from KS_PRIMARY to KS_SECONDARY. If the SOFT_RESET_V1 came from a replay or a was faked (no tls-auth/tls-crypt), the session is blocked until the TLS renegotiation attempt times out, blocking the legimitate client. Using a dynamic tls-crypt key here block any SOFT_RESET_V1 as replay and fake packets will not have a matching authentication/encryption are discarded. HARD_RESET packets that are from a reconnecting peer are instead put in the TM_UNTRUSTED/KS_PRIMARY slot until they are sufficiently verified, so the dyanmic tls-crypt key is not used here. Replay/fake packets also do not block the legimiate client. The issue was initially reported by Fabio Streun <fabio.str...@inf.ethz.ch> Patch v2: fix spellings of reneg and renegotiations. Signed-off-by: Arne Schwabe <a...@rfc2549.org> --- src/openvpn/auth_token.h | 2 +- src/openvpn/crypto.c | 7 +- src/openvpn/crypto.h | 16 +++- src/openvpn/init.c | 8 +- src/openvpn/multi.c | 4 + src/openvpn/openvpn.h | 2 + src/openvpn/options.c | 4 + src/openvpn/push.c | 5 ++ src/openvpn/ssl.c | 25 ++++-- src/openvpn/ssl.h | 5 ++ src/openvpn/ssl_backend.h | 1 + src/openvpn/ssl_common.h | 6 ++ src/openvpn/ssl_ncp.c | 4 + src/openvpn/ssl_pkt.c | 2 +- src/openvpn/ssl_pkt.h | 21 +++++ src/openvpn/tls_crypt.c | 93 ++++++++++++++++++++--- src/openvpn/tls_crypt.h | 19 ++++- tests/unit_tests/openvpn/test_pkt.c | 17 ++++- tests/unit_tests/openvpn/test_tls_crypt.c | 85 +++++++++++++++++++++ 19 files changed, 297 insertions(+), 29 deletions(-) diff --git a/src/openvpn/auth_token.h b/src/openvpn/auth_token.h index 5e23d8c44..0a847ce56 100644 --- a/src/openvpn/auth_token.h +++ b/src/openvpn/auth_token.h @@ -43,7 +43,7 @@ * * The second timestamp is the time the token was renewed/regenerated and is used * to determine if this token has been renewed in the acceptable time range - * (2 * renogiation timeout) + * (2 * renegoiation timeout) * * The session id is a random string of 12 byte (or 16 in base64) that is not * used by OpenVPN itself but kept intact so that external logging/management diff --git a/src/openvpn/crypto.c b/src/openvpn/crypto.c index 9e10f64ee..4a8f514cd 100644 --- a/src/openvpn/crypto.c +++ b/src/openvpn/crypto.c @@ -1121,7 +1121,8 @@ void crypto_read_openvpn_key(const struct key_type *key_type, struct key_ctx_bi *ctx, const char *key_file, bool key_inline, const int key_direction, - const char *key_name, const char *opt_name) + const char *key_name, const char *opt_name, + struct key2 *keydata) { struct key2 key2; struct key_direction_state kds; @@ -1149,6 +1150,10 @@ crypto_read_openvpn_key(const struct key_type *key_type, /* initialize key in both directions */ init_key_ctx_bi(ctx, &key2, key_direction, key_type, key_name); + if (keydata) + { + *keydata = key2; + } secure_memzero(&key2, sizeof(key2)); } diff --git a/src/openvpn/crypto.h b/src/openvpn/crypto.h index 5ea889081..e7177ea43 100644 --- a/src/openvpn/crypto.h +++ b/src/openvpn/crypto.h @@ -234,7 +234,14 @@ struct crypto_options * both sending and receiving * directions. */ struct packet_id packet_id; /**< Current packet ID state for both - * sending and receiving directions. */ + * sending and receiving directions. + * + * This contains the packet id that is + * used for replay protection. + * + * The packet id also used as the IV + * for AEAD/OFB/CFG ciphers. + * */ struct packet_id_persist *pid_persist; /**< Persistent packet ID state for * keeping state between successive @@ -268,6 +275,10 @@ struct crypto_options /**< Bit-flag indicating that explicit exit notifies should be * sent via the control channel instead of using an OCC message */ +#define CO_USE_SECURE_RENEGOTIATION (1<<7) + /**< Bit-flag indicating that renegotiations are using tls-crypt + * with a TLS-EKM derived key. + */ unsigned int flags; /**< Bit-flags determining behavior of * security operation functions. */ @@ -530,7 +541,8 @@ void key2_print(const struct key2 *k, void crypto_read_openvpn_key(const struct key_type *key_type, struct key_ctx_bi *ctx, const char *key_file, bool key_inline, const int key_direction, - const char *key_name, const char *opt_name); + const char *key_name, const char *opt_name, + struct key2 *keydata); /* * Inline functions diff --git a/src/openvpn/init.c b/src/openvpn/init.c index ed2ccb815..3e24396f5 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -2783,7 +2783,7 @@ do_init_crypto_static(struct context *c, const unsigned int flags) options->shared_secret_file, options->shared_secret_file_inline, options->key_direction, "Static Key Encryption", - "secret"); + "secret", NULL); } else { @@ -2823,13 +2823,15 @@ do_init_tls_wrap_key(struct context *c) options->ce.tls_auth_file, options->ce.tls_auth_file_inline, options->ce.key_direction, - "Control Channel Authentication", "tls-auth"); + "Control Channel Authentication", "tls-auth", + NULL); } /* TLS handshake encryption+authentication (--tls-crypt) */ if (options->ce.tls_crypt_file) { tls_crypt_init_key(&c->c1.ks.tls_wrap_key, + &c->c1.ks.original_tlscrypt_keydata, options->ce.tls_crypt_file, options->ce.tls_crypt_file_inline, options->tls_server); @@ -2847,6 +2849,7 @@ do_init_tls_wrap_key(struct context *c) else { tls_crypt_v2_init_client_key(&c->c1.ks.tls_wrap_key, + &c->c1.ks.original_tlscrypt_keydata, &c->c1.ks.tls_crypt_v2_wkc, options->ce.tls_crypt_v2_file, options->ce.tls_crypt_v2_file_inline); @@ -3149,6 +3152,7 @@ do_init_crypto_tls(struct context *c, const unsigned int flags) to.tls_wrap.opt.key_ctx_bi = c->c1.ks.tls_wrap_key; to.tls_wrap.opt.pid_persist = &c->c1.pid_persist; to.tls_wrap.opt.flags |= CO_PACKET_ID_LONG_FORM; + to.tls_wrap.original_tlscrypt_keydata = c->c1.ks.original_tlscrypt_keydata; if (options->ce.tls_crypt_v2_file) { diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c index b9b087e01..afd7ec3ee 100644 --- a/src/openvpn/multi.c +++ b/src/openvpn/multi.c @@ -1803,6 +1803,10 @@ multi_client_set_protocol_options(struct context *c) { o->imported_protocol_flags |= CO_USE_TLS_KEY_MATERIAL_EXPORT; } + if (proto & IV_PROTO_SECURE_RENEG) + { + o->imported_protocol_flags |= CO_USE_SECURE_RENEGOTIATION; + } #endif if (proto & IV_PROTO_CC_EXIT_NOTIFY) diff --git a/src/openvpn/openvpn.h b/src/openvpn/openvpn.h index c543cbf60..9df613179 100644 --- a/src/openvpn/openvpn.h +++ b/src/openvpn/openvpn.h @@ -65,6 +65,8 @@ struct key_schedule /* optional TLS control channel wrapping */ struct key_type tls_auth_key_type; struct key_ctx_bi tls_wrap_key; + /** original tls-crypt preserved to xored into the tls_crypt renegotiation key */ + struct key2 original_tlscrypt_keydata; struct key_ctx tls_crypt_v2_server_key; struct buffer tls_crypt_v2_wkc; /**< Wrapped client key */ struct key_ctx auth_token_key; diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 60fec147f..b395316ef 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -8554,6 +8554,10 @@ add_option(struct options *options, { options->imported_protocol_flags |= CO_USE_TLS_KEY_MATERIAL_EXPORT; } + else if (streq(p[j], "secure-reneg")) + { + options->imported_protocol_flags |= CO_USE_SECURE_RENEGOTIATION; + } #endif else { diff --git a/src/openvpn/push.c b/src/openvpn/push.c index 1bad00605..b2e46f1ca 100644 --- a/src/openvpn/push.c +++ b/src/openvpn/push.c @@ -668,6 +668,11 @@ prepare_push_reply(struct context *c, struct gc_arena *gc, push_option_fmt(gc, push_list, M_USAGE, "key-derivation tls-ekm"); } + if (o->imported_protocol_flags & CO_USE_SECURE_RENEGOTIATION) + { + buf_printf(&proto_flags, " secure-reneg"); + } + if (buf_len(&proto_flags) > 0) { push_option_fmt(gc, push_list, M_USAGE, "protocol-flags%s", buf_str(&proto_flags)); diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c index 66aa9da3d..c6538b92e 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -1153,6 +1153,7 @@ static void tls_session_free(struct tls_session *session, bool clear) { tls_wrap_free(&session->tls_wrap); + tls_wrap_free(&session->tls_wrap_reneg); for (size_t i = 0; i < KS_SIZE; ++i) { @@ -1717,6 +1718,17 @@ tls_session_update_crypto_params_do_work(struct tls_multi *multi, frame_print(frame_fragment, D_MTU_INFO, "Fragmentation MTU parms"); } + if (session->key[KS_PRIMARY].key_id == 0 + && session->opt->crypto_flags & CO_USE_SECURE_RENEGOTIATION) + { + /* If the secure renegotiation has been negotiated, and we are on the + * first session (key_id = 0), generate a tls-crypt key for following + * renegotiations */ + if (!tls_session_generate_secure_renegotiation_key(multi, session)) + { + return false; + } + } return tls_session_generate_data_channel_keys(multi, session); } @@ -2021,6 +2033,7 @@ push_peer_info(struct buffer *buf, struct tls_session *session) #ifdef HAVE_EXPORT_KEYING_MATERIAL iv_proto |= IV_PROTO_TLS_KEY_EXPORT; + iv_proto |= IV_PROTO_SECURE_RENEG; #endif buf_printf(&out, "IV_PROTO=%d\n", iv_proto); @@ -3532,7 +3545,7 @@ tls_pre_decrypt(struct tls_multi *multi, /* * If --single-session, don't allow any hard-reset connection request - * unless it the first packet of the session. + * unless it is the first packet of the session. */ if (multi->opt.single_session) { @@ -3542,7 +3555,7 @@ tls_pre_decrypt(struct tls_multi *multi, goto error; } - if (!read_control_auth(buf, &session->tls_wrap, from, + if (!read_control_auth(buf, tls_session_get_tls_wrap(session, key_id), from, session->opt)) { goto error; @@ -3595,8 +3608,8 @@ tls_pre_decrypt(struct tls_multi *multi, */ if (op == P_CONTROL_SOFT_RESET_V1 && TLS_AUTHENTICATED(multi, ks)) { - if (!read_control_auth(buf, &session->tls_wrap, from, - session->opt)) + if (!read_control_auth(buf, tls_session_get_tls_wrap(session, key_id), + from, session->opt)) { goto error; } @@ -3617,8 +3630,8 @@ tls_pre_decrypt(struct tls_multi *multi, do_burst = true; } - if (!read_control_auth(buf, &session->tls_wrap, from, - session->opt)) + if (!read_control_auth(buf, tls_session_get_tls_wrap(session, key_id), + from, session->opt)) { goto error; } diff --git a/src/openvpn/ssl.h b/src/openvpn/ssl.h index 9ae6ae8fc..370467deb 100644 --- a/src/openvpn/ssl.h +++ b/src/openvpn/ssl.h @@ -43,6 +43,7 @@ #include "ssl_common.h" #include "ssl_backend.h" #include "ssl_pkt.h" +#include "tls_crypt.h" /* Used in the TLS PRF function */ #define KEY_EXPANSION_ID "OpenVPN" @@ -103,6 +104,9 @@ /** Support for AUTH_FAIL,TEMP messages */ #define IV_PROTO_AUTH_FAIL_TEMP (1<<8) +/** Support to secure renegoiations with TLS-EKM dervied tls-crypt key */ +#define IV_PROTO_SECURE_RENEG (1<<9) + /* Default field in X509 to be username */ #define X509_USERNAME_FIELD_DEFAULT "CN" @@ -476,6 +480,7 @@ tls_wrap_free(struct tls_wrap_ctx *tls_wrap) free_buf(&tls_wrap->tls_crypt_v2_metadata); free_buf(&tls_wrap->work); + secure_memzero(&tls_wrap->original_tlscrypt_keydata, sizeof(tls_wrap->original_tlscrypt_keydata)); } static inline bool diff --git a/src/openvpn/ssl_backend.h b/src/openvpn/ssl_backend.h index 1bd336999..82fb7f94b 100644 --- a/src/openvpn/ssl_backend.h +++ b/src/openvpn/ssl_backend.h @@ -391,6 +391,7 @@ void backend_tls_ctx_reload_crl(struct tls_root_ctx *ssl_ctx, #define EXPORT_KEY_DATA_LABEL "EXPORTER-OpenVPN-datakeys" #define EXPORT_P2P_PEERID_LABEL "EXPORTER-OpenVPN-p2p-peerid" +#define EXPORT_SECURE_RENEG_LABEL "EXPORTER-OpenVPN-secure-renegotiation" /** * Keying Material Exporters [RFC 5705] allows additional keying material to be * derived from existing TLS channel. This exported keying material can then be diff --git a/src/openvpn/ssl_common.h b/src/openvpn/ssl_common.h index 6135556a9..366d83327 100644 --- a/src/openvpn/ssl_common.h +++ b/src/openvpn/ssl_common.h @@ -274,6 +274,8 @@ struct tls_wrap_ctx struct buffer tls_crypt_v2_metadata; /**< Received from client */ bool cleanup_key_ctx; /**< opt.key_ctx_bi is owned by * this context */ + struct key2 original_tlscrypt_keydata; + /**< original key data to be xored in to the key for secure renegotiation */ }; /* @@ -467,6 +469,10 @@ struct tls_session /* authenticate control packets */ struct tls_wrap_ctx tls_wrap; + /* Specific tls-crypt for renegotiations, if this is valid, + * tls_wrap_reneg.mode is TLS_WRAP_CRYPT, otherwise ignore it */ + struct tls_wrap_ctx tls_wrap_reneg; + int initial_opcode; /* our initial P_ opcode */ struct session_id session_id; /* our random session ID */ diff --git a/src/openvpn/ssl_ncp.c b/src/openvpn/ssl_ncp.c index b6884af96..8de35323c 100644 --- a/src/openvpn/ssl_ncp.c +++ b/src/openvpn/ssl_ncp.c @@ -461,6 +461,10 @@ p2p_ncp_set_options(struct tls_multi *multi, struct tls_session *session) } } + if (iv_proto_peer & IV_PROTO_SECURE_RENEG) + { + session->opt->crypto_flags |= CO_USE_SECURE_RENEGOTIATION; + } #endif /* if defined(HAVE_EXPORT_KEYING_MATERIAL) */ } diff --git a/src/openvpn/ssl_pkt.c b/src/openvpn/ssl_pkt.c index 0083fc470..b58f30d07 100644 --- a/src/openvpn/ssl_pkt.c +++ b/src/openvpn/ssl_pkt.c @@ -183,7 +183,7 @@ write_control_auth(struct tls_session *session, msg(D_TLS_DEBUG, "%s(): %s", __func__, packet_opcode_name(opcode)); - tls_wrap_control(&session->tls_wrap, header, buf, &session->session_id); + tls_wrap_control(tls_session_get_tls_wrap(session, ks->key_id), header, buf, &session->session_id); *to_link_addr = &ks->remote_addr; } diff --git a/src/openvpn/ssl_pkt.h b/src/openvpn/ssl_pkt.h index 45e0a81f5..7d13690dd 100644 --- a/src/openvpn/ssl_pkt.h +++ b/src/openvpn/ssl_pkt.h @@ -273,6 +273,27 @@ packet_opcode_name(int op) } } +/** + * Determines if the current session should use the renegotiation tls wrap + * struct instead the normal one and returns it + * + * @param session + * @param key_id key_id of the received/or to be send packet + * @return + */ +static inline struct tls_wrap_ctx * +tls_session_get_tls_wrap(struct tls_session *session, int key_id) +{ + if (key_id > 0 && session->tls_wrap_reneg.mode == TLS_WRAP_CRYPT) + { + return &session->tls_wrap_reneg; + } + else + { + return &session->tls_wrap; + } +} + /* initial packet id (instead of 0) that indicates that the peer supports * early protocol negotiation. This will make the packet id turn a bit faster * but the network time part of the packet id takes care of that. And diff --git a/src/openvpn/tls_crypt.c b/src/openvpn/tls_crypt.c index 2fc791119..e95de63b0 100644 --- a/src/openvpn/tls_crypt.c +++ b/src/openvpn/tls_crypt.c @@ -60,8 +60,8 @@ tls_crypt_buf_overhead(void) } void -tls_crypt_init_key(struct key_ctx_bi *key, const char *key_file, - bool key_inline, bool tls_server) +tls_crypt_init_key(struct key_ctx_bi *key, struct key2 *keydata, + const char *key_file, bool key_inline, bool tls_server) { const int key_direction = tls_server ? KEY_DIRECTION_NORMAL : KEY_DIRECTION_INVERSE; @@ -71,9 +71,77 @@ tls_crypt_init_key(struct key_ctx_bi *key, const char *key_file, msg(M_FATAL, "ERROR: --tls-crypt not supported"); } crypto_read_openvpn_key(&kt, key, key_file, key_inline, key_direction, - "Control Channel Encryption", "tls-crypt"); + "Control Channel Encryption", "tls-crypt", keydata); } +/** + * Will produce key = key XOR other + */ +static void +xor_key2(struct key2 *key, const struct key2 *other) +{ + ASSERT(key->n == 2 && other->n == 2); + for (int k = 0; k < 2; k++) + { + for (int j = 0; j < MAX_CIPHER_KEY_LENGTH; j++) + { + key->keys[k].cipher[j] = key->keys[k].cipher[j] ^ other->keys[k].cipher[j]; + } + + for (int j = 0; j < MAX_HMAC_KEY_LENGTH; j++) + { + key->keys[k].hmac[j] = key->keys[k].hmac[j] ^ other->keys[k].hmac[j]; + } + + } +} + +bool +tls_session_generate_secure_renegotiation_key(struct tls_multi *multi, + struct tls_session *session) +{ + session->tls_wrap_reneg.opt = session->tls_wrap.opt; + session->tls_wrap_reneg.mode = TLS_WRAP_CRYPT; + session->tls_wrap_reneg.cleanup_key_ctx = true; + session->tls_wrap_reneg.work = alloc_buf(BUF_SIZE(&session->opt->frame)); + session->tls_wrap_reneg.opt.pid_persist = NULL; + + packet_id_init(&session->tls_wrap_reneg.opt.packet_id, + session->opt->replay_window, + session->opt->replay_time, + "TLS_WRAP_RENEG", session->key_id); + + + struct key2 rengokeys; + if (!key_state_export_keying_material(session, EXPORT_SECURE_RENEG_LABEL, + strlen(EXPORT_SECURE_RENEG_LABEL), + rengokeys.keys, sizeof(rengokeys.keys))) + { + return false; + } + rengokeys.n = 2; + + if (session->tls_wrap.mode == TLS_WRAP_CRYPT) + { + xor_key2(&rengokeys, &session->tls_wrap.original_tlscrypt_keydata); + } + + const int key_direction = session->opt->server ? + KEY_DIRECTION_NORMAL : KEY_DIRECTION_INVERSE; + + struct key_direction_state kds; + key_direction_state_init(&kds, key_direction); + + struct key_type kt = tls_crypt_kt(); + + init_key_ctx_bi(&session->tls_wrap_reneg.opt.key_ctx_bi, &rengokeys, key_direction, + &kt, "secure renegotiation"); + secure_memzero(&rengokeys, sizeof(rengokeys)); + + return true; +} + + bool tls_crypt_wrap(const struct buffer *src, struct buffer *dst, struct crypto_options *opt) @@ -266,8 +334,9 @@ tls_crypt_v2_load_client_key(struct key_ctx_bi *key, const struct key2 *key2, } void -tls_crypt_v2_init_client_key(struct key_ctx_bi *key, struct buffer *wkc_buf, - const char *key_file, bool key_inline) +tls_crypt_v2_init_client_key(struct key_ctx_bi *key, struct key2 *original_key, + struct buffer *wkc_buf, const char *key_file, + bool key_inline) { struct buffer client_key = alloc_buf(TLS_CRYPT_V2_CLIENT_KEY_LEN + TLS_CRYPT_V2_MAX_WKC_LEN); @@ -285,7 +354,7 @@ tls_crypt_v2_init_client_key(struct key_ctx_bi *key, struct buffer *wkc_buf, } tls_crypt_v2_load_client_key(key, &key2, false); - secure_memzero(&key2, sizeof(key2)); + *original_key = key2; *wkc_buf = client_key; } @@ -570,15 +639,14 @@ tls_crypt_v2_extract_client_key(struct buffer *buf, return false; } - struct key2 client_key = { 0 }; ctx->tls_crypt_v2_metadata = alloc_buf(TLS_CRYPT_V2_MAX_METADATA_LEN); - if (!tls_crypt_v2_unwrap_client_key(&client_key, + if (!tls_crypt_v2_unwrap_client_key(&ctx->original_tlscrypt_keydata, &ctx->tls_crypt_v2_metadata, wrapped_client_key, &ctx->tls_crypt_v2_server_key)) { msg(D_TLS_ERRORS, "Can not unwrap tls-crypt-v2 client key"); - secure_memzero(&client_key, sizeof(client_key)); + secure_memzero(&ctx->original_tlscrypt_keydata, sizeof(ctx->original_tlscrypt_keydata)); return false; } @@ -587,8 +655,8 @@ tls_crypt_v2_extract_client_key(struct buffer *buf, ctx->cleanup_key_ctx = true; ctx->opt.flags |= CO_PACKET_ID_LONG_FORM; memset(&ctx->opt.key_ctx_bi, 0, sizeof(ctx->opt.key_ctx_bi)); - tls_crypt_v2_load_client_key(&ctx->opt.key_ctx_bi, &client_key, true); - secure_memzero(&client_key, sizeof(client_key)); + tls_crypt_v2_load_client_key(&ctx->opt.key_ctx_bi, + &ctx->original_tlscrypt_keydata, true); /* Remove client key from buffer so tls-crypt code can unwrap message */ ASSERT(buf_inc_len(buf, -(BLEN(&wrapped_client_key)))); @@ -688,8 +756,9 @@ tls_crypt_v2_write_client_key_file(const char *filename, /* Sanity check: load client key (as "client") */ struct key_ctx_bi test_client_key; struct buffer test_wrapped_client_key; + struct key2 keydata; msg(D_GENKEY, "Testing client-side key loading..."); - tls_crypt_v2_init_client_key(&test_client_key, &test_wrapped_client_key, + tls_crypt_v2_init_client_key(&test_client_key, &keydata, &test_wrapped_client_key, client_file, client_inline); free_key_ctx_bi(&test_client_key); diff --git a/src/openvpn/tls_crypt.h b/src/openvpn/tls_crypt.h index 928ff5475..21b012ce4 100644 --- a/src/openvpn/tls_crypt.h +++ b/src/openvpn/tls_crypt.h @@ -110,12 +110,24 @@ * @param key The key context to initialize * @param key_file The file to read the key from or the key itself if * key_inline is true. + * @param keydata The keydata used to create key will be written here. * @param key_inline True if key_file contains an inline key, False * otherwise. * @param tls_server Must be set to true is this is a TLS server instance. */ -void tls_crypt_init_key(struct key_ctx_bi *key, const char *key_file, - bool key_inline, bool tls_server); +void tls_crypt_init_key(struct key_ctx_bi *key, struct key2 *keydata, + const char *key_file, bool key_inline, bool tls_server); + +/** + * Generates a TLS Crypt to be used in the secure renegotiation using the + * TLS EKM exporter function. + * @param multi multi session struct + * @param session session that will be used for the TLS EKM exporter + * @return true iff generating the key was successful + */ +bool +tls_session_generate_secure_renegotiation_key(struct tls_multi *multi, + struct tls_session *session); /** * Returns the maximum overhead (in bytes) added to the destination buffer by @@ -171,6 +183,8 @@ void tls_crypt_v2_init_server_key(struct key_ctx *key_ctx, bool encrypt, * * @param key Key structure to be initialized with the client * key. + * @param original_key contains the key data, that has been used to + * initialise the key parameter * @param wrapped_key_buf Returns buffer containing the wrapped key that will * be sent to the server when connecting. Caller must * free this buffer when no longer needed. @@ -180,6 +194,7 @@ void tls_crypt_v2_init_server_key(struct key_ctx *key_ctx, bool encrypt, * otherwise. */ void tls_crypt_v2_init_client_key(struct key_ctx_bi *key, + struct key2 *original_key, struct buffer *wrapped_key_buf, const char *key_file, bool key_inline); diff --git a/tests/unit_tests/openvpn/test_pkt.c b/tests/unit_tests/openvpn/test_pkt.c index 2d771e301..d05f981f7 100644 --- a/tests/unit_tests/openvpn/test_pkt.c +++ b/tests/unit_tests/openvpn/test_pkt.c @@ -55,6 +55,16 @@ parse_line(const char *line, char **p, const int n, const char *file, return 0; } +/* Define this function here as dummy since including the ssl_*.c files + * leads to having to include even more unrelated code */ +bool +key_state_export_keying_material(struct tls_session *session, + const char *label, size_t label_size, + void *ekm, size_t ekm_size) +{ + ASSERT(0); +} + const char * print_link_socket_actual(const struct link_socket_actual *act, struct gc_arena *gc) { @@ -183,7 +193,8 @@ init_tas_auth(int key_direction) crypto_read_openvpn_key(&tls_crypt_kt, &tas.tls_wrap.opt.key_ctx_bi, static_key, true, key_direction, - "Control Channel Authentication", "tls-auth"); + "Control Channel Authentication", "tls-auth", + NULL); return tas; } @@ -195,7 +206,9 @@ init_tas_crypt(bool server) tas.tls_wrap.mode = TLS_WRAP_CRYPT; tas.tls_wrap.opt.flags |= (CO_IGNORE_PACKET_ID|CO_PACKET_ID_LONG_FORM); - tls_crypt_init_key(&tas.tls_wrap.opt.key_ctx_bi, static_key, true, server); + tls_crypt_init_key(&tas.tls_wrap.opt.key_ctx_bi, + &tas.tls_wrap.original_tlscrypt_keydata, static_key, + true, server); return tas; } diff --git a/tests/unit_tests/openvpn/test_tls_crypt.c b/tests/unit_tests/openvpn/test_tls_crypt.c index 82bb0a266..e5b189696 100644 --- a/tests/unit_tests/openvpn/test_tls_crypt.c +++ b/tests/unit_tests/openvpn/test_tls_crypt.c @@ -40,6 +40,18 @@ #include "mock_msg.h" +/* Define this function here as dummy since including the ssl_*.c files + * leads to having to include even more unrelated code */ +bool +key_state_export_keying_material(struct tls_session *session, + const char *label, size_t label_size, + void *ekm, size_t ekm_size) +{ + memset(ekm, 0xba, ekm_size); + return true; +} + + #define TESTBUF_SIZE 128 /* Defines for use in the tests and the mock parse_line() */ @@ -141,6 +153,7 @@ struct test_tls_crypt_context { struct buffer unwrapped; }; + static int test_tls_crypt_setup(void **state) { @@ -218,6 +231,75 @@ tls_crypt_loopback(void **state) BLEN(&ctx->source)); } + +/** + * Test generating secure renegotiation key + */ +static void +test_tls_crypt_secure_reneg_key(void **state) +{ + struct test_tls_crypt_context *ctx = + (struct test_tls_crypt_context *)*state; + + struct gc_arena gc = gc_new(); + + struct tls_multi multi = { 0 }; + struct tls_session session = { 0 }; + + struct tls_options tls_opt = { 0 }; + tls_opt.replay_window = 32; + tls_opt.replay_time = 60; + tls_opt.frame.buf.payload_size = 512; + session.opt = &tls_opt; + + tls_session_generate_secure_renegotiation_key(&multi, &session); + + struct tls_wrap_ctx *rctx = &session.tls_wrap_reneg; + + tls_crypt_wrap(&ctx->source, &rctx->work, &rctx->opt); + assert_int_equal(buf_len(&ctx->source) + 40, buf_len(&rctx->work)); + + uint8_t expected_ciphertext[] = { + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0xe3, 0x19, 0x27, 0x7f, 0x1c, 0x8d, 0x6e, 0x6a, + 0x77, 0x96, 0xa8, 0x55, 0x33, 0x7b, 0x9c, 0xfb, 0x56, 0xe1, 0xf1, 0x3a, 0x87, 0x0e, 0x66, 0x47, + 0xdf, 0xa1, 0x95, 0xc9, 0x2c, 0x17, 0xa0, 0x15, 0xba, 0x49, 0x67, 0xa1, 0x1d, 0x55, 0xea, 0x1a, + 0x06, 0xa7 + }; + assert_memory_equal(BPTR(&rctx->work), expected_ciphertext, buf_len(&rctx->work)); + tls_wrap_free(&session.tls_wrap_reneg); + + /* Use previous tls-crypt key as 0x00, with xor we should have the same key + * and expect the same result */ + session.tls_wrap.mode = TLS_WRAP_CRYPT; + memset(&session.tls_wrap.original_tlscrypt_keydata.keys, 0x00, sizeof(session.tls_wrap.original_tlscrypt_keydata.keys)); + session.tls_wrap.original_tlscrypt_keydata.n = 2; + + tls_session_generate_secure_renegotiation_key(&multi, &session); + tls_crypt_wrap(&ctx->source, &rctx->work, &rctx->opt); + assert_int_equal(buf_len(&ctx->source) + 40, buf_len(&rctx->work)); + + assert_memory_equal(BPTR(&rctx->work), expected_ciphertext, buf_len(&rctx->work)); + tls_wrap_free(&session.tls_wrap_reneg); + + /* XOR should not force a different key */ + memset(&session.tls_wrap.original_tlscrypt_keydata.keys, 0x42, sizeof(session.tls_wrap.original_tlscrypt_keydata.keys)); + tls_session_generate_secure_renegotiation_key(&multi, &session); + + tls_crypt_wrap(&ctx->source, &rctx->work, &rctx->opt); + assert_int_equal(buf_len(&ctx->source) + 40, buf_len(&rctx->work)); + + /* packet id at the start should be equal */ + assert_memory_equal(BPTR(&rctx->work), expected_ciphertext, 8); + + /* Skip packet id */ + buf_advance(&rctx->work, 8); + assert_memory_not_equal(BPTR(&rctx->work), expected_ciphertext, buf_len(&rctx->work)); + tls_wrap_free(&session.tls_wrap_reneg); + + + gc_free(&gc); +} + /** * Check that zero-byte messages are successfully wrapped-and-unwrapped. */ @@ -632,6 +714,9 @@ main(void) cmocka_unit_test_setup_teardown(tls_crypt_v2_wrap_unwrap_dst_too_small, test_tls_crypt_v2_setup, test_tls_crypt_v2_teardown), + cmocka_unit_test_setup_teardown(test_tls_crypt_secure_reneg_key, + test_tls_crypt_setup, + test_tls_crypt_teardown), cmocka_unit_test(test_tls_crypt_v2_write_server_key_file), cmocka_unit_test(test_tls_crypt_v2_write_client_key_file), cmocka_unit_test(test_tls_crypt_v2_write_client_key_file_metadata), -- 2.37.0 (Apple Git-136) _______________________________________________ Openvpn-devel mailing list Openvpn-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/openvpn-devel