From 609e8131427686adca9b4ed2db44db4aaa920a01 Mon Sep 17 00:00:00 2001 From: Steffan Karger Date: Thu, 24 Apr 2014 00:31:08 +0200 Subject: Add support for elliptic curve diffie-hellmann key exchange (ECDH) This patch is based on Jan Just Keijser's patch from Feb 7, 2012. When OpenSSL 1.0.2+ or PolarSSL is used, lets the crypto library do the heavy lifting. For OpenSSL builds, if a user specifies a curve using --ecdh-curve, it first tries to override automatic selection using that curve. For older OpenSSL, tries the following things (in order of preference): * When supplied, use the ecdh curve specified by the user. * Try to extract the curve from the private key, use the same curve. * Fall back on secp384r1 curve. Note that although a curve lookup might succeed, OpenSSL 1.0.0 and older do *not* support TLSv1.1 or TLSv1.2, which means no that no EC-crypto can be used. Signed-off-by: Steffan Karger Acked-by: Arne Schwabe Message-Id: <53597BEA.6080408@karger.me> URL: http://article.gmane.org/gmane.network.openvpn.devel/8625 Signed-off-by: Gert Doering --- src/openvpn/init.c | 4 +- src/openvpn/options.c | 11 +++++ src/openvpn/options.h | 2 + src/openvpn/ssl.c | 4 ++ src/openvpn/ssl_backend.h | 15 ++++++ src/openvpn/ssl_openssl.c | 119 +++++++++++++++++++++++++++++++++++++++++++++ src/openvpn/ssl_polarssl.c | 26 ++++++++++ 7 files changed, 180 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/openvpn/init.c b/src/openvpn/init.c index c2907cd..467b98a 100644 --- a/src/openvpn/init.c +++ b/src/openvpn/init.c @@ -871,7 +871,7 @@ print_openssl_info (const struct options *options) #ifdef ENABLE_CRYPTO if (options->show_ciphers || options->show_digests || options->show_engines #ifdef ENABLE_SSL - || options->show_tls_ciphers + || options->show_tls_ciphers || options->show_curves #endif ) { @@ -884,6 +884,8 @@ print_openssl_info (const struct options *options) #ifdef ENABLE_SSL if (options->show_tls_ciphers) show_available_tls_ciphers (options->cipher_list); + if (options->show_curves) + show_available_curves(); #endif return true; } diff --git a/src/openvpn/options.c b/src/openvpn/options.c index 4af2974..40210e6 100644 --- a/src/openvpn/options.c +++ b/src/openvpn/options.c @@ -854,6 +854,7 @@ init_options (struct options *o, const bool init_gc) o->renegotiate_seconds = 3600; o->handshake_window = 60; o->transition_window = 3600; + o->ecdh_curve = NULL; #ifdef ENABLE_X509ALTUSERNAME o->x509_username_field = X509_USERNAME_FIELD_DEFAULT; #endif @@ -6516,6 +6517,16 @@ add_option (struct options *options, VERIFY_PERMISSION (OPT_P_GENERAL); options->show_tls_ciphers = true; } + else if (streq (p[0], "show-curves")) + { + VERIFY_PERMISSION (OPT_P_GENERAL); + options->show_curves = true; + } + else if (streq (p[0], "ecdh-curve") && p[1]) + { + VERIFY_PERMISSION (OPT_P_CRYPTO); + options->ecdh_curve= p[1]; + } else if (streq (p[0], "tls-server")) { VERIFY_PERMISSION (OPT_P_GENERAL); diff --git a/src/openvpn/options.h b/src/openvpn/options.h index ec1d091..092eac4 100644 --- a/src/openvpn/options.h +++ b/src/openvpn/options.h @@ -201,6 +201,7 @@ struct options bool show_engines; #ifdef ENABLE_SSL bool show_tls_ciphers; + bool show_curves; #endif bool genkey; #endif @@ -515,6 +516,7 @@ struct options const char *priv_key_file; const char *pkcs12_file; const char *cipher_list; + const char *ecdh_curve; const char *tls_verify; int verify_x509_type; const char *verify_x509_name; diff --git a/src/openvpn/ssl.c b/src/openvpn/ssl.c index b09e52b..9bcb2ac 100644 --- a/src/openvpn/ssl.c +++ b/src/openvpn/ssl.c @@ -555,6 +555,10 @@ init_ssl (const struct options *options, struct tls_root_ctx *new_ctx) tls_ctx_load_extra_certs(new_ctx, options->extra_certs_file, options->extra_certs_file_inline); } + /* Once keys and cert are loaded, load ECDH parameters */ + if (options->tls_server) + tls_ctx_load_ecdh_params(new_ctx, options->ecdh_curve); + /* Allowable ciphers */ tls_ctx_restrict_ciphers(new_ctx, options->cipher_list); diff --git a/src/openvpn/ssl_backend.h b/src/openvpn/ssl_backend.h index 57b03df..37a458c 100644 --- a/src/openvpn/ssl_backend.h +++ b/src/openvpn/ssl_backend.h @@ -185,6 +185,16 @@ void tls_ctx_restrict_ciphers(struct tls_root_ctx *ctx, const char *ciphers); void tls_ctx_load_dh_params(struct tls_root_ctx *ctx, const char *dh_file, const char *dh_file_inline); +/** + * Load Elliptic Curve Parameters, and load them into the library-specific + * TLS context. + * + * @param ctx TLS context to use + * @param curve_name The name of the elliptic curve to load. + */ +void tls_ctx_load_ecdh_params(struct tls_root_ctx *ctx, const char *curve_name + ); + /** * Load PKCS #12 file for key, cert and (optionally) CA certs, and add to * library-specific TLS context. @@ -460,6 +470,11 @@ void print_details (struct key_state_ssl * ks_ssl, const char *prefix); */ void show_available_tls_ciphers (const char *tls_ciphers); +/* + * Show the available elliptic curves in the crypto library + */ +void show_available_curves (void); + /* * The OpenSSL library has a notion of preference in TLS ciphers. Higher * preference == more secure. Return the highest preference cipher. diff --git a/src/openvpn/ssl_openssl.c b/src/openvpn/ssl_openssl.c index 1923230..1481850 100644 --- a/src/openvpn/ssl_openssl.c +++ b/src/openvpn/ssl_openssl.c @@ -56,6 +56,9 @@ #include #include #include +#ifndef OPENSSL_NO_EC +#include +#endif /* * Allocate space in SSL objects in which to store a struct tls_session @@ -329,6 +332,78 @@ tls_ctx_load_dh_params (struct tls_root_ctx *ctx, const char *dh_file, DH_free (dh); } +void +tls_ctx_load_ecdh_params (struct tls_root_ctx *ctx, const char *curve_name + ) +{ +#ifndef OPENSSL_NO_EC + int nid = NID_undef; + EC_KEY *ecdh = NULL; + const char *sname = NULL; + + /* Generate a new ECDH key for each SSL session (for non-ephemeral ECDH) */ + SSL_CTX_set_options(ctx->ctx, SSL_OP_SINGLE_ECDH_USE); +#if OPENSSL_VERSION_NUMBER >= 0x10002000L + /* OpenSSL 1.0.2 and newer can automatically handle ECDH parameter loading */ + if (NULL == curve_name) { + SSL_CTX_set_ecdh_auto(ctx->ctx, 1); + return; + } +#endif + /* For older OpenSSL, we'll have to do the parameter loading on our own */ + if (curve_name != NULL) + { + /* Use user supplied curve if given */ + msg (D_TLS_DEBUG, "Using user specified ECDH curve (%s)", curve_name); + nid = OBJ_sn2nid(curve_name); + } + else + { + /* Extract curve from key */ + EC_KEY *eckey = NULL; + const EC_GROUP *ecgrp = NULL; + EVP_PKEY *pkey = NULL; + + /* Little hack to get private key ref from SSL_CTX, yay OpenSSL... */ + SSL ssl; + ssl.cert = ctx->ctx->cert; + pkey = SSL_get_privatekey(&ssl); + + msg (D_TLS_DEBUG, "Extracting ECDH curve from private key"); + + if (pkey != NULL && (eckey = EVP_PKEY_get1_EC_KEY(pkey)) != NULL && + (ecgrp = EC_KEY_get0_group(eckey)) != NULL) + nid = EC_GROUP_get_curve_name(ecgrp); + } + + /* Translate NID back to name , just for kicks */ + sname = OBJ_nid2sn(nid); + if (sname == NULL) sname = "(Unknown)"; + + /* Create new EC key and set as ECDH key */ + if (NID_undef == nid || NULL == (ecdh = EC_KEY_new_by_curve_name(nid))) + { + /* Creating key failed, fall back on sane default */ + ecdh = EC_KEY_new_by_curve_name(NID_secp384r1); + const char *source = (NULL == curve_name) ? + "extract curve from certificate" : "use supplied curve"; + msg (D_TLS_DEBUG_LOW, + "Failed to %s (%s), using secp384r1 instead.", source, sname); + sname = OBJ_nid2sn(NID_secp384r1); + } + + if (!SSL_CTX_set_tmp_ecdh(ctx->ctx, ecdh)) + msg (M_SSLERR, "SSL_CTX_set_tmp_ecdh: cannot add curve"); + + msg (D_TLS_DEBUG_LOW, "ECDH curve %s added", sname); + + EC_KEY_free(ecdh); +#else + msg (M_DEBUG, "Your OpenSSL library was built without elliptic curve support." + " Skipping ECDH parameter loading."); +#endif /* OPENSSL_NO_EC */ +} + int tls_ctx_load_pkcs12(struct tls_root_ctx *ctx, const char *pkcs12_file, const char *pkcs12_file_inline, @@ -1299,6 +1374,50 @@ show_available_tls_ciphers (const char *cipher_list) SSL_CTX_free (tls_ctx.ctx); } +/* + * Show the Elliptic curves that are available for us to use + * in the OpenSSL library. + */ +void +show_available_curves() +{ +#ifndef OPENSSL_NO_EC + EC_builtin_curve *curves = NULL; + size_t crv_len = 0; + size_t n = 0; + + crv_len = EC_get_builtin_curves(NULL, 0); + + curves = OPENSSL_malloc((int)(sizeof(EC_builtin_curve) * crv_len)); + + if (curves == NULL) + msg (M_SSLERR, "Cannot create EC_builtin_curve object"); + else + { + if (EC_get_builtin_curves(curves, crv_len)) + { + printf ("Available Elliptic curves:\n"); + for (n = 0; n < crv_len; n++) + { + const char *sname; + sname = OBJ_nid2sn(curves[n].nid); + if (sname == NULL) sname = ""; + + printf("%s\n", sname); + } + } + else + { + msg (M_SSLERR, "Cannot get list of builtin curves"); + } + OPENSSL_free(curves); + } +#else + msg (M_WARN, "Your OpenSSL library was built without elliptic curve support. " + "No curves available."); +#endif +} + void get_highest_preference_tls_cipher (char *buf, int size) { diff --git a/src/openvpn/ssl_polarssl.c b/src/openvpn/ssl_polarssl.c index 8371893..5bd6d7d 100644 --- a/src/openvpn/ssl_polarssl.c +++ b/src/openvpn/ssl_polarssl.c @@ -228,6 +228,15 @@ else (counter_type) 8 * mpi_size(&ctx->dhm_ctx->P)); } +void +tls_ctx_load_ecdh_params (struct tls_root_ctx *ctx, const char *curve_name + ) +{ + if (NULL != curve_name) + msg(M_WARN, "WARNING: PolarSSL builds do not support specifying an ECDH " + "curve, using default curves."); +} + int tls_ctx_load_pkcs12(struct tls_root_ctx *ctx, const char *pkcs12_file, const char *pkcs12_file_inline, @@ -1083,6 +1092,23 @@ show_available_tls_ciphers (const char *cipher_list) tls_ctx_free(&tls_ctx); } +void +show_available_curves (void) +{ + const ecp_curve_info *pcurve = ecp_curve_list(); + + if (NULL == pcurve) + msg (M_FATAL, "Cannot retrieve curve list from PolarSSL"); + + /* Print curve list */ + printf ("Available Elliptic curves, listed in order of preference:\n\n"); + while (POLARSSL_ECP_DP_NONE != pcurve->grp_id) + { + printf("%s\n", pcurve->name); + pcurve++; + } +} + void get_highest_preference_tls_cipher (char *buf, int size) { -- cgit