From patchwork Mon Jun 22 04:02:36 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Arne Schwabe X-Patchwork-Id: 1159 Return-Path: Delivered-To: patchwork@openvpn.net Delivered-To: patchwork@openvpn.net Received: from director12.mail.ord1d.rsapps.net ([172.27.255.53]) by backend30.mail.ord1d.rsapps.net with LMTP id KMrfLT668F5mIAAAIUCqbw for ; Mon, 22 Jun 2020 10:03:42 -0400 Received: from proxy18.mail.iad3a.rsapps.net ([172.27.255.53]) by director12.mail.ord1d.rsapps.net with LMTP id GAycKz668F6rXwAAIasKDg ; Mon, 22 Jun 2020 10:03:42 -0400 Received: from smtp14.gate.iad3a ([172.27.255.53]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) by proxy18.mail.iad3a.rsapps.net with LMTP id QFSfJj668F6VOgAAon3hFg ; Mon, 22 Jun 2020 10:03:42 -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: smtp14.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: 2d694e70-b491-11ea-a2f2-5254005d41e3-1-1 Received: from [216.105.38.7] ([216.105.38.7:40542] helo=lists.sourceforge.net) by smtp14.gate.iad3a.rsapps.net (envelope-from ) (ecelerity 4.2.38.62370 r(:)) with ESMTPS (cipher=DHE-RSA-AES256-GCM-SHA384) id 78/67-20713-D3AB0FE5; Mon, 22 Jun 2020 10:03:42 -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 1jnN2F-0004Ne-4U; Mon, 22 Jun 2020 14:02:55 +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 1jnN2D-0004NV-Ft for openvpn-devel@lists.sourceforge.net; Mon, 22 Jun 2020 14:02:53 +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=e9L2ivKo87D7fSqJnvpOps/UUkgKXRs5ys9zF2ANIGQ=; b=UU7zwWN4B2dwQ1I3WX9TFuLtAV jmRqPxrphbK5BIK6QdfH59CSFC5u5w+Bcyp8ecZldG9ymX1uZgcxAGWENw54J2h7/mXUhhpD4sVN7 UUji1/8MFCfP1/sFUNtbh3Ab+AQQYGenp46e3oEtWUPImoFFq0S0qZPvcmFj9aDhS1FA=; 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=e9L2ivKo87D7fSqJnvpOps/UUkgKXRs5ys9zF2ANIGQ=; b=e3/jR/1Kg5YRYSelItVenEpZdn bJcqV/VlReR9gBsG+lB7tK/FIvXIuoBTa4ctfsLel0a/Dc1Z3a5ibPfbJZkwEuriMMygQYVYhgbxl CDHkAYzSTtlgDZ8bsnq/ZSI5Evm5Tmr+c8oLAe793l6SEggkBMXePmzWPWWcIjxeqZxU=; 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 1jnN2A-00BzJ1-Bw for openvpn-devel@lists.sourceforge.net; Mon, 22 Jun 2020 14:02:53 +0000 Received: from kamera.blinkt.de ([2001:638:502:390:20c:29ff:fec8:535c]) by mail.blinkt.de with smtp (Exim 4.92.3 (FreeBSD)) (envelope-from ) id 1jnN1w-0000mX-V2 for openvpn-devel@lists.sourceforge.net; Mon, 22 Jun 2020 16:02:36 +0200 Received: (nullmailer pid 15283 invoked by uid 10006); Mon, 22 Jun 2020 14:02:36 -0000 From: Arne Schwabe To: openvpn-devel@lists.sourceforge.net Date: Mon, 22 Jun 2020 16:02:36 +0200 Message-Id: <20200622140236.15238-1-arne@rfc2549.org> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20200604221628.31562-1-arne@rfc2549.org> References: <20200604221628.31562-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: 1jnN2A-00BzJ1-Bw Subject: [Openvpn-devel] [PATCH v4 3/3] Implement tls-groups option to specify eliptic curves/groups 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 By default OpenSSL 1.1+ only allows signatures and ecdh/ecdhx from the default list of X25519:secp256r1:X448:secp521r1:secp384r1. In TLS1.3 key exchange is independent from the signature/key of the certificates, so allowing all groups per default is not a sensible choice anymore and instead a shorter list is reasonable. However, when using certificates with exotic curves that are not on the group list, the signatures of these certificates will no longer be accepted. The tls-groups option allows to modify the group list to account for these corner cases. Patch V2: Uses local gc_arena instead of malloc/free, reword commit message. Fix other typos/clarify messages Patch V3: Style fixes, adjust code to changes from mbed tls session fix Patch V4: Resend V3 as V4 as the invalid V3 confused patchwork and Gert. Signed-off-by: Arne Schwabe --- README.ec | 7 ++--- doc/openvpn.8 | 33 ++++++++++++++++++++++- src/openvpn/options.c | 10 ++++++- src/openvpn/options.h | 1 + src/openvpn/ssl.c | 6 +++++ src/openvpn/ssl_backend.h | 10 +++++++ src/openvpn/ssl_mbedtls.c | 46 ++++++++++++++++++++++++++++++++ src/openvpn/ssl_mbedtls.h | 1 + src/openvpn/ssl_openssl.c | 55 ++++++++++++++++++++++++++++++++++++++- 9 files changed, 163 insertions(+), 6 deletions(-) diff --git a/README.ec b/README.ec index 32938017..61f23b2e 100644 --- a/README.ec +++ b/README.ec @@ -12,14 +12,15 @@ OpenVPN 2.4.0 and newer automatically initialize ECDH parameters. When ECDSA is used for authentication, the curve used for the server certificate will be used for ECDH too. When autodetection fails (e.g. when using RSA certificates) OpenVPN lets the crypto library decide if possible, or falls back to the -secp384r1 curve. +secp384r1 curve. The list of groups/curves that the crypto library will choose +from can be set with the --tls-groups option. An administrator can force an OpenVPN/OpenSSL server to use a specific curve using the --ecdh-curve option with one of the curves listed as -available by the --show-curves option. Clients will use the same curve as +available by the --show-groups option. Clients will use the same curve as selected by the server. -Note that not all curves listed by --show-curves are available for use with TLS; +Note that not all curves listed by --show-groups are available for use with TLS; in that case connecting will fail with a 'no shared cipher' TLS error. Authentication (ECDSA) diff --git a/doc/openvpn.8 b/doc/openvpn.8 index dcc72abe..f35a4f64 100644 --- a/doc/openvpn.8 +++ b/doc/openvpn.8 @@ -5107,6 +5107,8 @@ Use to see a list of TLS ciphers supported by your crypto library. Warning! +.B \-\-tls\-groups +, .B \-\-tls\-cipher and .B \-\-tls\-ciphersuites @@ -5122,6 +5124,33 @@ OpenSSL. The default for \-\-tls\-ciphersuites is to use the crypto library's default. .\"********************************************************* .TP +.B \-\-tls\-groups l +A list +.B l +of allowable groups/curves in order of preference. + +Set the allowed elictipic curves/groups for the TLS session. +These groups are allowed to be used in signatures and key exchange. + +mbed TLS currently allows all known curves per default. + +OpenSSL 1.1+ restricts the list per default to +"X25519:secp256r1:X448:secp521r1:secp384r1". + +If you use certificates that use non-standard curves, you +might need to add them here. If you do not force the ecdh curve +by using +.B \-\-ecdh\-curve +, the groups for ecdh will also be picked from this list. + +OpenVPN maps the curve name secp256r1 to prime256v1 to allow +specifying the same tls-groups option for mbed TLS and OpenSSL. + +Warning: this option not only affects eliptic curve certificates +but also the key exchange in TLS 1.3 and using this option improperly +will disable TLS 1.3. +.\"********************************************************* +.TP .B \-\-tls\-cert\-profile profile Set the allowed cryptographic algorithms for certificates according to .B profile\fN. @@ -5887,8 +5916,10 @@ engines supported by the OpenSSL library. .TP .B \-\-show\-curves (Standalone) -Show all available elliptic curves to use with the +Show all available elliptic groups/curves to use with the .B \-\-ecdh\-curve +and +.B \-\-tls\-groups option. .\"********************************************************* .SS Generating key material: diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 16f9da6a..0c1d590c 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -7947,7 +7947,7 @@ add_option(struct options *options, VERIFY_PERMISSION(OPT_P_GENERAL); options->show_tls_ciphers = true; } - else if (streq(p[0], "show-curves") && !p[1]) + else if ((streq(p[0], "show-curves") || streq(p[0], "show-groups")) && !p[1]) { VERIFY_PERMISSION(OPT_P_GENERAL); options->show_curves = true; @@ -7955,6 +7955,9 @@ add_option(struct options *options, else if (streq(p[0], "ecdh-curve") && p[1] && !p[2]) { VERIFY_PERMISSION(OPT_P_GENERAL); + msg(M_WARN, "Consider setting groups/curves preference with " + "tls-groups instead of forcing a specific curve with " + "ecdh-curve."); options->ecdh_curve = p[1]; } else if (streq(p[0], "tls-server") && !p[1]) @@ -8125,6 +8128,11 @@ add_option(struct options *options, VERIFY_PERMISSION(OPT_P_GENERAL); options->cipher_list_tls13 = p[1]; } + else if (streq(p[0], "tls-groups") && p[1] && !p[2]) + { + VERIFY_PERMISSION(OPT_P_GENERAL); + options->tls_groups = p[1]; + } else if (streq(p[0], "crl-verify") && p[1] && ((p[2] && streq(p[2], "dir")) || !p[2])) { diff --git a/src/openvpn/options.h b/src/openvpn/options.h index 375a4fc9..a6a5ba1b 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -541,6 +541,7 @@ struct options bool pkcs12_file_inline; const char *cipher_list; const char *cipher_list_tls13; + const char *tls_groups; const char *tls_cert_profile; const char *ecdh_curve; const char *tls_verify; diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c index 68d2b5c1..7244c43d 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -630,6 +630,12 @@ init_ssl(const struct options *options, struct tls_root_ctx *new_ctx) tls_ctx_restrict_ciphers(new_ctx, options->cipher_list); tls_ctx_restrict_ciphers_tls13(new_ctx, options->cipher_list_tls13); + /* Set the allow groups/curves for TLS if we want to override them */ + if (options->tls_groups) + { + tls_ctx_set_tls_groups(new_ctx, options->tls_groups); + } + if (!tls_ctx_set_options(new_ctx, options->ssl_flags)) { goto err; diff --git a/src/openvpn/ssl_backend.h b/src/openvpn/ssl_backend.h index a1770bd4..75692797 100644 --- a/src/openvpn/ssl_backend.h +++ b/src/openvpn/ssl_backend.h @@ -198,6 +198,16 @@ void tls_ctx_restrict_ciphers_tls13(struct tls_root_ctx *ctx, const char *cipher */ void tls_ctx_set_cert_profile(struct tls_root_ctx *ctx, const char *profile); +/** + * Set the allowed (eliptic curve) group allowed for signatures and + * key exchange. + * + * @param ctx TLS context to restrict, must be valid. + * @param groups List of groups that will be allowed, in priority, + * separated by : + */ +void tls_ctx_set_tls_groups(struct tls_root_ctx *ctx, const char *groups); + /** * Check our certificate notBefore and notAfter fields, and warn if the cert is * either not yet valid or has expired. Note that this is a non-fatal error, diff --git a/src/openvpn/ssl_mbedtls.c b/src/openvpn/ssl_mbedtls.c index f518f593..d74318f9 100644 --- a/src/openvpn/ssl_mbedtls.c +++ b/src/openvpn/ssl_mbedtls.c @@ -176,6 +176,11 @@ tls_ctx_free(struct tls_root_ctx *ctx) free(ctx->allowed_ciphers); } + if (ctx->groups) + { + free(ctx->groups); + } + CLEAR(*ctx); ctx->initialised = false; @@ -343,6 +348,42 @@ tls_ctx_set_cert_profile(struct tls_root_ctx *ctx, const char *profile) } } +void +tls_ctx_set_tls_groups(struct tls_root_ctx *ctx, const char *groups) +{ + ASSERT(ctx); + struct gc_arena gc = gc_new(); + + /* Get number of groups and allocate an array in ctx */ + int groups_count = get_num_elements(groups, ':'); + ALLOC_ARRAY_CLEAR(ctx->groups, mbedtls_ecp_group_id, groups_count + 1) + + /* Parse allowed ciphers, getting IDs */ + int i = 0; + char *tmp_groups = string_alloc(groups, &gc); + + const char *token; + while ((token = strsep(&tmp_groups, ":"))) + { + const mbedtls_ecp_curve_info *ci = + mbedtls_ecp_curve_info_from_name(token); + if (!ci) + { + msg(M_WARN, "Warning unknown curve/group specified: %s", token); + } + else + { + ctx->groups[i] = ci->grp_id; + i++; + } + token = strsep(&tmp_groups, ":"); + } + ctx->groups[i] = MBEDTLS_ECP_DP_NONE; + + gc_free(&gc); +} + + void tls_ctx_check_cert_time(const struct tls_root_ctx *ctx) { @@ -1043,6 +1084,11 @@ key_state_ssl_init(struct key_state_ssl *ks_ssl, mbedtls_ssl_conf_ciphersuites(ks_ssl->ssl_config, ssl_ctx->allowed_ciphers); } + if (ssl_ctx->groups) + { + mbedtls_ssl_conf_curves(ks_ssl->ssl_config, ssl_ctx->groups); + } + /* Disable record splitting (for now). OpenVPN assumes records are sent * unfragmented, and changing that will require thorough review and * testing. Since OpenVPN is not susceptible to BEAST, we can just diff --git a/src/openvpn/ssl_mbedtls.h b/src/openvpn/ssl_mbedtls.h index 92381f1a..0525134f 100644 --- a/src/openvpn/ssl_mbedtls.h +++ b/src/openvpn/ssl_mbedtls.h @@ -105,6 +105,7 @@ struct tls_root_ctx { #endif struct external_context external_key; /**< External key context */ int *allowed_ciphers; /**< List of allowed ciphers for this connection */ + mbedtls_ecp_group_id *groups; /**< List of allowed groups for this connection */ mbedtls_x509_crt_profile cert_profile; /**< Allowed certificate types */ }; diff --git a/src/openvpn/ssl_openssl.c b/src/openvpn/ssl_openssl.c index a489053b..da7d252a 100644 --- a/src/openvpn/ssl_openssl.c +++ b/src/openvpn/ssl_openssl.c @@ -565,6 +565,57 @@ tls_ctx_set_cert_profile(struct tls_root_ctx *ctx, const char *profile) #endif /* ifdef HAVE_SSL_CTX_SET_SECURITY_LEVEL */ } +void +tls_ctx_set_tls_groups(struct tls_root_ctx *ctx, const char *groups) +{ + ASSERT(ctx); + struct gc_arena gc = gc_new(); + /* This method could be as easy as + * SSL_CTX_set1_groups_list(ctx->ctx, groups) + * but OpenSSL does not like the name secp256r1 for prime256v1 + * This is one of the important curves. + * To support the same name for OpenSSL and mbedTLS, we do + * this dance. + */ + + int groups_count = get_num_elements(groups, ':'); + + int *glist; + /* Allocate an array for them */ + ALLOC_ARRAY_CLEAR_GC(glist, int, groups_count, &gc); + + /* Parse allowed ciphers, getting IDs */ + int glistlen = 0; + char *tmp_groups = string_alloc(groups, &gc); + + const char *token; + while ((token = strsep(&tmp_groups, ":"))) + { + if (streq(token, "secp256r1")) + { + token = "prime256v1"; + } + int nid = OBJ_sn2nid(token); + + if (nid == 0) + { + msg(M_WARN, "Warning unknown curve/group specified: %s", token); + } + else + { + glist[glistlen] = nid; + glistlen++; + } + } + + if (!SSL_CTX_set1_groups(ctx->ctx, glist, glistlen)) + { + crypto_msg(M_FATAL, "Failed to set allowed TLS group list: %s", + groups); + } + gc_free(&gc); +} + void tls_ctx_check_cert_time(const struct tls_root_ctx *ctx) { @@ -2191,6 +2242,8 @@ show_available_tls_ciphers_list(const char *cipher_list, void show_available_curves(void) { + printf("Consider using openssl 'ecparam -list_curves' as\n" + "alternative to running this command.\n"); #ifndef OPENSSL_NO_EC EC_builtin_curve *curves = NULL; size_t crv_len = 0; @@ -2200,7 +2253,7 @@ show_available_curves(void) ALLOC_ARRAY(curves, EC_builtin_curve, crv_len); if (EC_get_builtin_curves(curves, crv_len)) { - printf("Available Elliptic curves:\n"); + printf("\nAvailable Elliptic curves/groups:\n"); for (n = 0; n < crv_len; n++) { const char *sname;