From patchwork Tue Aug 11 22:55:39 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arne Schwabe X-Patchwork-Id: 1379 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director9.mail.ord1d.rsapps.net ([172.27.255.54]) by backend30.mail.ord1d.rsapps.net with LMTP id +IREB3G5M1/mIgAAIUCqbw for ; Wed, 12 Aug 2020 05:42:09 -0400 Received: from proxy11.mail.iad3a.rsapps.net ([172.27.255.54]) by director9.mail.ord1d.rsapps.net with LMTP id OIr7BXG5M198PQAAalYnBA (envelope-from ) for ; Wed, 12 Aug 2020 05:42:09 -0400 Received: from smtp17.gate.iad3a ([172.27.255.54]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy11.mail.iad3a.rsapps.net with LMTP id gEI+O3C5M1+hTgAAxCvdqw ; Wed, 12 Aug 2020 05:42:08 -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: smtp17.gate.iad3a.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: 1682816a-dc80-11ea-9f59-525400723ca9-1-1 Received: from [216.105.38.7] ([216.105.38.7:35660] helo=lists.sourceforge.net) by smtp17.gate.iad3a.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id E7/59-22810-079B33F5; Wed, 12 Aug 2020 05:42:08 -0400 Received: from [127.0.0.1] (helo=sfs-ml-4.v29.lw.sourceforge.com) by sfs-ml-4.v29.lw.sourceforge.com with esmtp (Exim 4.90_1) (envelope-from ) id 1k5nG1-0001bv-Ug; Wed, 12 Aug 2020 09:41:17 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-4.v29.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.90_1) (envelope-from ) id 1k5nFz-0001bP-Nf for openvpn-devel@lists.sourceforge.net; Wed, 12 Aug 2020 09:41:15 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=References:In-Reply-To:Message-Id:Date:Subject:To: From:Sender:Reply-To:Cc:MIME-Version:Content-Type:Content-Transfer-Encoding: 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=dOu/rOSpG1u+4KjXpOSKa9PmWlyUqLx/ca12nepEy/8=; b=HOExA7QErIDLRTwhp7n1bpIapU 1vjOgZ77pLajcdCIWXenfy6qwbdgb5T8n/lUNdFj+PI7Hytd8Ragjzrgyo2x4REYc/EfBfkVlU4ez 7BYtnXxf9uY69R1qymzY1rxGgH6VcvzyHm0LO7GCmLRNsC3MJsqdhkV6O2WB3D/e7bJA=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=References:In-Reply-To:Message-Id:Date:Subject:To:From:Sender:Reply-To:Cc :MIME-Version:Content-Type:Content-Transfer-Encoding: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=dOu/rOSpG1u+4KjXpOSKa9PmWlyUqLx/ca12nepEy/8=; b=SiETuAt6Aa2vfpj3iVxejV5GTs YX8akh/ikydADeFfkN5yWgivUihRKQlTGlwwd3vDJNjCw1kY0xAHA4S4bS2ebLSrtbXBF/mAhbPJ9 sm7Xz09/nbTS5Ey7CEq6cK+5uGa+wcerLEFRReFHFna2hQmAq2K2Ub9gIQ5z6r/iCdHo=; Received: from mail.blinkt.de ([192.26.174.232]) by sfi-mx-3.v28.lw.sourceforge.com with esmtps (TLSv1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.92.2) id 1k5nFy-007kGk-1v for openvpn-devel@lists.sourceforge.net; Wed, 12 Aug 2020 09:41:15 +0000 Received: from kamera.blinkt.de ([2001:638:502:390:20c:29ff:fec8:535c]) by mail.blinkt.de with smtp (Exim 4.94 (FreeBSD)) (envelope-from ) id 1k5nFi-000AQA-Ad for openvpn-devel@lists.sourceforge.net; Wed, 12 Aug 2020 11:40:58 +0200 Received: (nullmailer pid 19690 invoked by uid 10006); Wed, 12 Aug 2020 08:55:39 -0000 From: Arne Schwabe To: openvpn-devel@lists.sourceforge.net Date: Wed, 12 Aug 2020 10:55:39 +0200 Message-Id: <20200812085539.19620-3-arne@rfc2549.org> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20200812085539.19620-1-arne@rfc2549.org> References: <20200812085539.19620-1-arne@rfc2549.org> X-Spam-Report: Spam Filtering performed by mx.sourceforge.net. See http://spamassassin.org/tag/ for more details. 0.0 URIBL_BLOCKED ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [URIs: rfc2549.org] 0.0 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: 1k5nFy-007kGk-1v Subject: [Openvpn-devel] [PATCH 3/3] Implement generating data channel keys via EKM/RFC 5705 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: , MIME-Version: 1.0 Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox OpenVPN currently uses its own (based on TLS 1.0) key derivation mechansim to generate the 256 bytes key data in key2 struct that are then used used to generate encryption/hmac/iv vectors. While this mechanism is still secure, it is not state of the art. Instead of modernisating our own approach, this commit implements key derivation using the Keying Material Exporters API introduced by RFC 5705. We also use an opportunistic approach of negotiating the use of EKM (exported key material) through an IV_PROTO flag and prefer EKM to our own PRF if both client and server support it. The use of EKM is pushed to the client as part of NCP as key-derivation tls-ekm. We still exchange the random data (112 bytes from client to server and 64 byte from server to client) that for the OpenVPN PRF but do not use it. Removing that exchange would break the handshake and make a key-method 3 or similar necessary. Side note: this commit breaks the (not yet merged) WolfSSL support as it claims to support EKM in the OpenSSL compat API but always returns an error if you try to use it. Signed-off-by: Arne Schwabe --- doc/doxygen/doc_key_generation.h | 15 +++++++-- src/openvpn/crypto.h | 4 +++ src/openvpn/init.c | 1 + src/openvpn/multi.c | 4 +++ src/openvpn/options.c | 14 +++++++++ src/openvpn/options.h | 3 ++ src/openvpn/push.c | 5 ++- src/openvpn/ssl.c | 49 ++++++++++++++++++++++++++++-- src/openvpn/ssl.h | 2 ++ src/openvpn/ssl_backend.h | 8 +++-- src/openvpn/ssl_mbedtls.c | 52 ++++++++++++++++++++++---------- src/openvpn/ssl_mbedtls.h | 3 +- src/openvpn/ssl_openssl.c | 6 ++++ 13 files changed, 141 insertions(+), 25 deletions(-) diff --git a/doc/doxygen/doc_key_generation.h b/doc/doxygen/doc_key_generation.h index 4bb9c708..bbd6c0c5 100644 --- a/doc/doxygen/doc_key_generation.h +++ b/doc/doxygen/doc_key_generation.h @@ -58,6 +58,12 @@ * * @subsection key_generation_method_2 Key method 2 * + * There are two methods for generating key data when using key method 2 + * the first is OpenVPN's traditional approach that exchanges random + * data and uses a PRF and the other is using the RFC5705 keying material + * exporter to generate the key material. For both methods the random + * data is exchange but only used in the traditional method. + * * -# The client generates random material in the following amounts: * - Pre-master secret: 48 bytes * - Client's PRF seed for master secret: 32 bytes @@ -73,8 +79,13 @@ * server's random material. * * %Key method 2 %key expansion is performed by the \c - * generate_key_expansion() function. Please refer to its source code for - * details of the %key expansion process. + * generate_key_expansion_oepnvpn_prf() function. Please refer to its source + * code for details of the %key expansion process. + * + * When the client sends the IV_PROTO_TLS_KEY_EXPORT and the server replies + * with `key-derivation tls-ekm` RFC5705 key material exporter with the label + * EXPORTER-OpenVPN-datakeys is used for the key data. + * * * @subsection key_generation_random Source of random material * diff --git a/src/openvpn/crypto.h b/src/openvpn/crypto.h index 999f643e..ec935ca5 100644 --- a/src/openvpn/crypto.h +++ b/src/openvpn/crypto.h @@ -254,6 +254,10 @@ struct crypto_options #define CO_MUTE_REPLAY_WARNINGS (1<<2) /**< Bit-flag indicating not to display * replay warnings. */ +#define CO_USE_TLS_KEY_MATERIAL_EXPORT (1<<3) + /**< Bit-flag indicating that key derivation + * is done using TLS keying material export [RFC5705] + */ unsigned int flags; /**< Bit-flags determining behavior of * security operation functions. */ }; diff --git a/src/openvpn/init.c b/src/openvpn/init.c index dfa045b0..34a7313e 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -676,6 +676,7 @@ restore_ncp_options(struct context *c) c->options.ciphername = c->c1.ciphername; c->options.authname = c->c1.authname; c->options.keysize = c->c1.keysize; + c->options.data_channel_use_ekm = false; } void diff --git a/src/openvpn/multi.c b/src/openvpn/multi.c index 13738180..a5862020 100644 --- a/src/openvpn/multi.c +++ b/src/openvpn/multi.c @@ -1817,6 +1817,10 @@ multi_client_set_protocol_options(struct context *c) c->c2.push_request_received = true; } +#ifdef HAVE_EXPORT_KEYING_MATERIAL + o->data_channel_use_ekm = (proto & IV_PROTO_TLS_KEY_EXPORT); +#endif + /* Select cipher if client supports Negotiable Crypto Parameters */ if (!o->ncp_enabled) { diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 8bf82c57..90e78a7b 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -7947,6 +7947,20 @@ add_option(struct options *options, } options->ncp_ciphers = p[1]; } + else if (streq(p[0], "key-derivation") && p[1]) + { + VERIFY_PERMISSION(OPT_P_NCP) +#ifdef HAVE_EXPORT_KEYING_MATERIAL + if (streq(p[1], "tls-ekm")) + { + options->data_channel_use_ekm = true; + } + else +#endif + { + msg(msglevel, "Unknown key-derivation method %s", p[1]); + } + } else if (streq(p[0], "ncp-disable") && !p[1]) { VERIFY_PERMISSION(OPT_P_GENERAL|OPT_P_INSTANCE); diff --git a/src/openvpn/options.h b/src/openvpn/options.h index 877e9396..c730c6a7 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -648,6 +648,9 @@ struct options /* Useful when packets sent by openvpn itself are not subject * to the routing tables that would move packets into the tunnel. */ bool allow_recursive_routing; + + /* Use RFC 5705 key export */ + bool data_channel_use_ekm; }; #define streq(x, y) (!strcmp((x), (y))) diff --git a/src/openvpn/push.c b/src/openvpn/push.c index e0d2eeaf..17bba948 100644 --- a/src/openvpn/push.c +++ b/src/openvpn/push.c @@ -479,7 +479,10 @@ prepare_push_reply(struct context *c, struct gc_arena *gc, { push_option_fmt(gc, push_list, M_USAGE, "cipher %s", o->ciphername); } - + if (o->data_channel_use_ekm) + { + push_option_fmt(gc, push_list, M_USAGE, "key-derivation tls-ekm"); + } return true; } diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c index e44017c6..b78362f1 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -1784,6 +1784,28 @@ init_key_contexts(struct key_ctx_bi *key, } +static bool +generate_key_expansion_tls_export(struct tls_session *session, struct key2 *key2) +{ + struct key_state *ks = &session->key[KS_PRIMARY]; + struct gc_arena gc = gc_new(); + unsigned char *key2data; + + key2data = key_state_export_keying_material(&ks->ks_ssl, session, + EXPORT_KEY_DATA, + &gc); + if (!key2data) + { + return false; + } + memcpy(key2->keys, key2data, sizeof(key2->keys)); + secure_memzero(key2data, sizeof(key2->keys)); + key2->n = 2; + + gc_free(&gc); + return true; +} + static struct key2 generate_key_expansion_oepnvpn_prf(const struct tls_session *session) { @@ -1846,7 +1868,7 @@ generate_key_expansion_oepnvpn_prf(const struct tls_session *session) */ static bool generate_key_expansion(struct key_ctx_bi *key, - const struct tls_session *session) + struct tls_session *session) { bool ret = false; @@ -1859,7 +1881,20 @@ generate_key_expansion(struct key_ctx_bi *key, bool server = session->opt->server; - struct key2 key2 = generate_key_expansion_oepnvpn_prf(session); + struct key2 key2; + + if (session->opt->crypto_flags & CO_USE_TLS_KEY_MATERIAL_EXPORT) + { + if(!generate_key_expansion_tls_export(session, &key2)) + { + msg(D_TLS_ERRORS, "TLS Error: Keying material export failed"); + goto exit; + } + } + else + { + key2 = generate_key_expansion_oepnvpn_prf(session); + } key2_print(&key2, &session->opt->key_type, "Master Encrypt", "Master Decrypt"); @@ -1988,6 +2023,11 @@ tls_session_update_crypto_params(struct tls_session *session, session->opt->crypto_flags |= CO_PACKET_ID_LONG_FORM; } + if (options->data_channel_use_ekm) + { + session->opt->crypto_flags |= CO_USE_TLS_KEY_MATERIAL_EXPORT; + } + /* Update frame parameters: undo worst-case overhead, add actual overhead */ frame_remove_from_extra_frame(frame, crypto_max_overhead()); crypto_adjust_frame_parameters(frame, &session->opt->key_type, @@ -2244,10 +2284,13 @@ push_peer_info(struct buffer *buf, struct tls_session *session) * push request, also signal that the client wants * to get push-reply messages without without requiring a round * trip for a push request message*/ - if(session->opt->pull) + if (session->opt->pull) { iv_proto |= IV_PROTO_REQUEST_PUSH; } +#ifdef HAVE_EXPORT_KEYING_MATERIAL + iv_proto |= IV_PROTO_TLS_KEY_EXPORT; +#endif buf_printf(&out, "IV_PROTO=%d\n", iv_proto); diff --git a/src/openvpn/ssl.h b/src/openvpn/ssl.h index 005628f6..f00f8abd 100644 --- a/src/openvpn/ssl.h +++ b/src/openvpn/ssl.h @@ -116,6 +116,8 @@ * to wait for a push-request to send a push-reply */ #define IV_PROTO_REQUEST_PUSH (1<<2) +/** Supports key derivation via TLS key material exporter [RFC5705] */ +#define IV_PROTO_TLS_KEY_EXPORT (1<<3) /* Default field in X509 to be username */ #define X509_USERNAME_FIELD_DEFAULT "CN" diff --git a/src/openvpn/ssl_backend.h b/src/openvpn/ssl_backend.h index 8faaefd5..e08f6e1e 100644 --- a/src/openvpn/ssl_backend.h +++ b/src/openvpn/ssl_backend.h @@ -390,11 +390,15 @@ void backend_tls_ctx_reload_crl(struct tls_root_ctx *ssl_ctx, const char *crl_file, bool crl_inline); -/* defines the different RFC5705 that are used in OpenVPN */ +/* defines the different RFC5705 keys that are used in OpenVPN */ enum export_key_identifier { - EXPORT_KEY_USER + EXPORT_KEY_USER, + EXPORT_KEY_DATA }; +#define EXPORT_KEY_DATA_LABEL "EXPORTER-OpenVPN-datakeys" +#define EXPORT_KEY_DATA_EKM_SIZE (2 * (MAX_CIPHER_KEY_LENGTH + MAX_HMAC_KEY_LENGTH)) + /** * 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_mbedtls.c b/src/openvpn/ssl_mbedtls.c index 8ae6ec7b..3721163f 100644 --- a/src/openvpn/ssl_mbedtls.c +++ b/src/openvpn/ssl_mbedtls.c @@ -208,22 +208,41 @@ mbedtls_ssl_export_keys_cb(void *p_expkey, const unsigned char *ms, struct key_state_ssl *ks_ssl = &session->key[KS_PRIMARY].ks_ssl; unsigned char client_server_random[64]; - ks_ssl->exported_key_material = gc_malloc(session->opt->ekm_size, - true, NULL); - memcpy(client_server_random, client_random, 32); memcpy(client_server_random + 32, server_random, 32); const size_t ms_len = sizeof(ks_ssl->ctx->session->master); - int ret = mbedtls_ssl_tls_prf(tls_prf_type, ms, ms_len, + + int ret; + /* Generate user exported key if requested */ + if (session->opt->ekm_size) + { + + ks_ssl->exported_key_material_user = gc_malloc(session->opt->ekm_size, + true, NULL); + ret = mbedtls_ssl_tls_prf(tls_prf_type, ms, ms_len, session->opt->ekm_label, client_server_random, - sizeof(client_server_random), ks_ssl->exported_key_material, + sizeof(client_server_random), + ks_ssl->exported_key_material_user, session->opt->ekm_size); - if (!mbed_ok(ret)) - { - secure_memzero(ks_ssl->exported_key_material, session->opt->ekm_size); + if (!mbed_ok(ret)) + { + secure_memzero(ks_ssl->exported_key_material_user, + session->opt->ekm_size); + ks_ssl->exported_key_material_user = NULL; + } } + /* We always generate the data channel key here even if we are not using + * it */ + + ks_ssl->exported_key_material_data = gc_malloc(EXPORT_KEY_DATA_EKM_SIZE, + true, NULL); + ret = mbedtls_ssl_tls_prf(tls_prf_type, ms, ms_len, + EXPORT_KEY_DATA_LABEL, client_server_random, + sizeof(client_server_random), + ks_ssl->exported_key_material_data, + EXPORT_KEY_DATA_EKM_SIZE); secure_memzero(client_server_random, sizeof(client_server_random)); @@ -239,9 +258,12 @@ key_state_export_keying_material(struct key_state_ssl *ssl, { if (key_id == EXPORT_KEY_USER) { - return ssl->exported_key_material; + return ssl->exported_key_material_user; + } + else if (key_id == EXPORT_KEY_DATA) + { + return ssl->exported_key_material_data; } - return NULL; } @@ -1149,11 +1171,8 @@ key_state_ssl_init(struct key_state_ssl *ks_ssl, #ifdef HAVE_EXPORT_KEYING_MATERIAL /* Initialize keying material exporter */ - if (session->opt->ekm_size) - { - mbedtls_ssl_conf_export_keys_ext_cb(ks_ssl->ssl_config, - mbedtls_ssl_export_keys_cb, session); - } + mbedtls_ssl_conf_export_keys_ext_cb(ks_ssl->ssl_config, + mbedtls_ssl_export_keys_cb, session); #endif /* Initialise SSL context */ @@ -1172,7 +1191,8 @@ key_state_ssl_free(struct key_state_ssl *ks_ssl) { if (ks_ssl) { - free(ks_ssl->exported_key_material); + free(ks_ssl->exported_key_material_user); + free(ks_ssl->exported_key_material_data); if (ks_ssl->ctx) { diff --git a/src/openvpn/ssl_mbedtls.h b/src/openvpn/ssl_mbedtls.h index 0525134f..d5652350 100644 --- a/src/openvpn/ssl_mbedtls.h +++ b/src/openvpn/ssl_mbedtls.h @@ -115,7 +115,8 @@ struct key_state_ssl { bio_ctx *bio_ctx; /** Keying material exporter cache (RFC 5705). */ - uint8_t *exported_key_material; + uint8_t *exported_key_material_user; + uint8_t *exported_key_material_data; }; diff --git a/src/openvpn/ssl_openssl.c b/src/openvpn/ssl_openssl.c index 1aad4f89..e807f581 100644 --- a/src/openvpn/ssl_openssl.c +++ b/src/openvpn/ssl_openssl.c @@ -174,6 +174,12 @@ key_state_export_keying_material(struct key_state_ssl *ssl, label_size = session->opt->ekm_label_size; ekm_size = session->opt->ekm_size; } + else if (key_id == EXPORT_KEY_DATA) + { + label = EXPORT_KEY_DATA_LABEL; + label_size = strlen(EXPORT_KEY_DATA_LABEL); + ekm_size = EXPORT_KEY_DATA_EKM_SIZE; + } else { ASSERT(0);