From patchwork Fri Sep 9 09:59:02 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arne Schwabe X-Patchwork-Id: 2747 X-Patchwork-Delegate: heiko@ist.eigentlich.net Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director7.mail.ord1d.rsapps.net ([172.31.255.6]) by backend30.mail.ord1d.rsapps.net with LMTP id ExjxOD2bG2PXSQAAIUCqbw (envelope-from ) for ; Fri, 09 Sep 2022 15:59:57 -0400 Received: from proxy13.mail.iad3b.rsapps.net ([172.31.255.6]) by director7.mail.ord1d.rsapps.net with LMTP id 4PxYOD2bG2MPagAAovjBpQ (envelope-from ) for ; Fri, 09 Sep 2022 15:59:57 -0400 Received: from smtp33.gate.iad3b ([172.31.255.6]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy13.mail.iad3b.rsapps.net with LMTPS id 2LdrMT2bG2MEQwAAvUvv+w (envelope-from ) for ; Fri, 09 Sep 2022 15:59:57 -0400 X-Spam-Threshold: 95 X-Spam-Score: 0 X-Spam-Flag: NO X-Virus-Scanned: OK X-Orig-To: openvpnslackdevel@openvpn.net X-Originating-Ip: [216.105.38.7] Authentication-Results: smtp33.gate.iad3b.rsapps.net; iprev=pass policy.iprev="216.105.38.7"; spf=pass smtp.mailfrom="openvpn-devel-bounces@lists.sourceforge.net" smtp.helo="lists.sourceforge.net"; dkim=fail (signature verification failed) header.d=sourceforge.net; dkim=fail (signature verification failed) header.d=sf.net; dmarc=none (p=nil; dis=none) header.from=rfc2549.org X-Suspicious-Flag: YES X-Classification-ID: fa44a6f4-3079-11ed-a05b-525400fb5834-1-1 Received: from [216.105.38.7] ([216.105.38.7:52226] helo=lists.sourceforge.net) by smtp33.gate.iad3b.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 1A/E6-03466-D3B9B136; Fri, 09 Sep 2022 15:59:57 -0400 Received: from [127.0.0.1] (helo=sfs-ml-1.v29.lw.sourceforge.com) by sfs-ml-1.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1oWk9l-0003HS-CN; Fri, 09 Sep 2022 19:59:17 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-1.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1oWk9j-0003HL-KS for openvpn-devel@lists.sourceforge.net; Fri, 09 Sep 2022 19:59:15 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-Id:Date:Subject:To:From:Sender:Reply-To:Cc:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=JXnjHwX9oeAvwnFp5kAw0XYKgBOr5553sKVHfDgx+V8=; b=dowGLSoUkC0B/9nQcgSo2DRydX v6FhHBC+00uB6tHOjVXmZLA0I3gglH0aAHIrEUT8M6gi0LLmbek2qaESK6LfclxcRvlMv16u9zxhY F/5mUy2gB9vC0qsWx+U9GMsK0+3Jf/egr1kUqh2Vb8oHaC3cNl1wA1YKgQ5dnWyGls7k=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-Id: Date:Subject:To:From:Sender:Reply-To:Cc:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=JXnjHwX9oeAvwnFp5kAw0XYKgBOr5553sKVHfDgx+V8=; b=lz+yCHDrDHaCcLT9Gw6CJjdjOq DssOnCZlpbZINZCLTZ6PtuVAFLPcZ4eoafPyglGcDRS+nxLocG+7eojDjpQ5PwE72WQQQfZXzO6Jc ZMBZwNmFCzyUEdezsDhYsz7fklqTPZYvnafU+OwAXQLfI79EI6IZYsHWkwJ1RwBFAZXE=; Received: from mail.blinkt.de ([192.26.174.232]) by sfi-mx-1.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1oWk9h-003UpM-2C for openvpn-devel@lists.sourceforge.net; Fri, 09 Sep 2022 19:59:15 +0000 Received: from kamera.blinkt.de ([2001:638:502:390:20c:29ff:fec8:535c]) by mail.blinkt.de with smtp (Exim 4.95 (FreeBSD)) (envelope-from ) id 1oWk9W-000IjR-Rq for openvpn-devel@lists.sourceforge.net; Fri, 09 Sep 2022 21:59:02 +0200 Received: (nullmailer pid 2011850 invoked by uid 10006); Fri, 09 Sep 2022 19:59:02 -0000 From: Arne Schwabe To: openvpn-devel@lists.sourceforge.net Date: Fri, 9 Sep 2022 21:59:02 +0200 Message-Id: <20220909195902.2011798-3-arne@rfc2549.org> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20220909195902.2011798-1-arne@rfc2549.org> References: <20220909195902.2011798-1-arne@rfc2549.org> MIME-Version: 1.0 X-Spam-Report: Spam detection software, running on the system "util-spamd-1.v13.lw.sourceforge.com", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: 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-cryp [...] Content analysis details: (0.3 points, 6.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- 0.2 HEADER_FROM_DIFFERENT_DOMAINS From and EnvelopeFrom 2nd level mail domains are different 0.0 SPF_NONE SPF: sender does not publish an SPF Record 0.0 SPF_HELO_NONE SPF: HELO does not publish an SPF Record X-Headers-End: 1oWk9h-003UpM-2C Subject: [Openvpn-devel] [PATCH 3/3] Introduce dynamic tls-crypt for secure soft_reset/session renegotiation X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox 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 Signed-off-by: Arne Schwabe --- 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 | 4 + src/openvpn/ssl.c | 30 ++++++-- 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 +++++++++++++++++++++ 18 files changed, 299 insertions(+), 29 deletions(-) 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 7ff7d545a..075c8d5a5 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); @@ -3147,6 +3150,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 1d23c8237..b972c2ff4 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_RENOG) + { + 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 00cd652fa..41391f9ce 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 renog 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 13b254186..cce48b5ff 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -8553,6 +8553,10 @@ add_option(struct options *options, { options->imported_protocol_flags |= CO_USE_TLS_KEY_MATERIAL_EXPORT; } + else if (streq(p[j], "secure-renog")) + { + options->imported_protocol_flags |= CO_USE_SECURE_RENEGOTIATION; + } #endif else { diff --git a/src/openvpn/push.c b/src/openvpn/push.c index 989316130..9349c931a 100644 --- a/src/openvpn/push.c +++ b/src/openvpn/push.c @@ -665,6 +665,10 @@ 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-renog"); + } if (buf_len(&proto_flags) > 0) { diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c index 36a236fe3..19bea7b24 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -362,7 +362,7 @@ calc_control_channel_frame_overhead(const struct tls_session *session) /* Message packet id */ overhead += sizeof(packet_id_type); - if (session->tls_wrap.mode == TLS_WRAP_CRYPT) + if (session->tls_wrap.mode == TLS_WRAP_CRYPT || session->tls_wrap_renog.mode == TLS_WRAP_CRYPT) { overhead += tls_crypt_buf_overhead(); } @@ -1146,6 +1146,7 @@ tls_session_init(struct tls_multi *multi, struct tls_session *session) session->tls_wrap = session->opt->tls_wrap; session->tls_wrap.work = alloc_buf(BUF_SIZE(&session->opt->frame)); + /* initialize packet ID replay window for --tls-auth */ packet_id_init(&session->tls_wrap.opt.packet_id, session->opt->replay_window, @@ -1191,6 +1192,7 @@ static void tls_session_free(struct tls_session *session, bool clear) { tls_wrap_free(&session->tls_wrap); + tls_wrap_free(&session->tls_wrap_renog); for (size_t i = 0; i < KS_SIZE; ++i) { @@ -1708,6 +1710,7 @@ cleanup: return ret; } + bool tls_session_update_crypto_params_do_work(struct tls_multi *multi, struct tls_session *session, @@ -1755,6 +1758,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_renegotition_key(multi, session)) + { + return false; + } + } return tls_session_generate_data_channel_keys(multi, session); } @@ -2062,6 +2076,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_RENOG; #endif buf_printf(&out, "IV_PROTO=%d\n", iv_proto); @@ -3225,6 +3240,7 @@ tls_multi_process(struct tls_multi *multi, } else { + /* TODO: secure negotiation fix? */ reset_session(multi, session); } } @@ -3674,7 +3690,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) { @@ -3684,7 +3700,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; @@ -3739,8 +3755,8 @@ tls_pre_decrypt(struct tls_multi *multi, */ if (op == P_CONTROL_SOFT_RESET_V1 && ks->state >= S_GENERATED_KEYS) { - 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; } @@ -3761,8 +3777,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 422e957ae..33e45a2da 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 renogiations with TLS-EKM dervied tls-crypt key */ +#define IV_PROTO_SECURE_RENOG (1<<9) + /* Default field in X509 to be username */ #define X509_USERNAME_FIELD_DEFAULT "CN" @@ -472,6 +476,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 215425d41..7f72dd486 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_RENOG_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 9dcd447cb..6f7d7cae0 100644 --- a/src/openvpn/ssl_common.h +++ b/src/openvpn/ssl_common.h @@ -275,6 +275,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 */ }; /* @@ -466,6 +468,10 @@ struct tls_session /* authenticate control packets */ struct tls_wrap_ctx tls_wrap; + /* Specific tls-crypt for renegotiations, if this is valid, + * tls_wrap_renog.mode is TLS_WRAP_CRYPT, otherwise ignore it */ + struct tls_wrap_ctx tls_wrap_renog; + 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 fe8491925..013021d6d 100644 --- a/src/openvpn/ssl_ncp.c +++ b/src/openvpn/ssl_ncp.c @@ -453,6 +453,10 @@ p2p_ncp_set_options(struct tls_multi *multi, struct tls_session *session) } } + if (iv_proto_peer & IV_PROTO_SECURE_RENOG) + { + 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 9b180cbf4..656a14923 100644 --- a/src/openvpn/ssl_pkt.c +++ b/src/openvpn/ssl_pkt.c @@ -193,7 +193,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 9bb3ca958..3cef3ae15 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_renog.mode == TLS_WRAP_CRYPT) + { + return &session->tls_wrap_renog; + } + 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..cf3e68e5f 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 dest = dest XOR data + */ +static void +xor_key2_key(struct key2 *dest, const struct key2 *data) +{ + ASSERT(dest->n == 2 && data->n == 2); + for (int k = 0; k < 2; k++) + { + for (int j = 0; j < MAX_CIPHER_KEY_LENGTH; j++) + { + dest->keys[k].cipher[j] = dest->keys[k].cipher[j] ^ data->keys[k].cipher[j]; + } + + for (int j = 0; j < MAX_HMAC_KEY_LENGTH; j++) + { + dest->keys[k].hmac[j] = dest->keys[k].hmac[j] ^ data->keys[k].hmac[j]; + } + + } +} + +bool +tls_session_generate_secure_renegotition_key(struct tls_multi *multi, + struct tls_session *session) +{ + session->tls_wrap_renog.opt = session->tls_wrap.opt; + session->tls_wrap_renog.mode = TLS_WRAP_CRYPT; + session->tls_wrap_renog.cleanup_key_ctx = true; + session->tls_wrap_renog.work = alloc_buf(BUF_SIZE(&session->opt->frame)); + session->tls_wrap_renog.opt.pid_persist = NULL; + + packet_id_init(&session->tls_wrap_renog.opt.packet_id, + session->opt->replay_window, + session->opt->replay_time, + "TLS_WRAP_RENOG", session->key_id); + + + struct key2 rengokeys; + if (!key_state_export_keying_material(session, EXPORT_SECURE_RENOG_LABEL, + strlen(EXPORT_SECURE_RENOG_LABEL), + rengokeys.keys, sizeof(rengokeys.keys))) + { + return false; + } + rengokeys.n = 2; + + if (session->tls_wrap.mode == TLS_WRAP_CRYPT) + { + xor_key2_key(&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_renog.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..cf30768ad 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_renegotition_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..1a46478d7 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_renog_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_renegotition_key(&multi, &session); + + struct tls_wrap_ctx *rctx = &session.tls_wrap_renog; + + 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_renog); + + /* 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_renegotition_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_renog); + + /* 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_renegotition_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_renog); + + + 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_renog_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),