Skip to content

Commit

Permalink
Add support for elliptic curve diffie-hellmann key exchange (ECDH)
Browse files Browse the repository at this point in the history
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 <steffan@karger.me>
Acked-by: Arne Schwabe <arne@rfc2549.org>
Message-Id: <53597BEA.6080408@karger.me>
URL: http://article.gmane.org/gmane.network.openvpn.devel/8625
Signed-off-by: Gert Doering <gert@greenie.muc.de>
  • Loading branch information
syzzer authored and cron2 committed Apr 25, 2014
1 parent 1e3a178 commit 609e813
Show file tree
Hide file tree
Showing 9 changed files with 229 additions and 1 deletion.
35 changes: 35 additions & 0 deletions README.ec
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
Since 2.4.0, OpenVPN has official support for elliptic curve crypto. Elliptic
curves are an alternative to RSA for asymmetric encryption.

Elliptic curve crypto ('ECC') can be used for the ('TLS') control channel only
in OpenVPN; the data channel (encrypting the actual network traffic) uses
symmetric encryption. ECC can be used in TLS for authentication (ECDSA) and key
exchange (ECDH).

Key exchange (ECDH)
-------------------
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.

An administrator can force an OpenVPN/OpenSSL server to use a specific curve
using the --ecdh-curve <curvename> option with one of the curves listed as
available by the --show-curves 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;
in that case connecting will fail with a 'no shared cipher' TLS error.

Authentication (ECDSA)
----------------------
Since OpenVPN 2.4.0, using ECDSA certificates works 'out of the box'. Which
specific curves and cipher suites are available depends on your version and
configuration of the crypto library. The crypto library will automatically
select a cipher suite for the TLS control channel.

Support for generating an ECDSA certificate chain is available in EasyRSA (in
spite of it's name) since EasyRSA 3.0. The parameters you're looking for are
'--use-algo=ec' and '--curve=<curve_name>'. See the EasyRSA documentation for
more details on generating ECDSA certificates.
14 changes: 14 additions & 0 deletions doc/openvpn.8
Original file line number Diff line number Diff line change
Expand Up @@ -4246,6 +4246,13 @@ included with the OpenVPN distribution. Diffie Hellman parameters
may be considered public.
.\"*********************************************************
.TP
.B \-\-ecdh-curve name
Specify the curve to use for elliptic curve Diffie Hellman. Available
curves can be listed with
.B \-\-show-curves
. The specified curve will only be used for ECDH TLS-ciphers.
.\"*********************************************************
.TP
.B \-\-cert file
Local peer's signed certificate in .pem format \-\- must be signed
by a certificate authority whose certificate is in
Expand Down Expand Up @@ -5027,6 +5034,13 @@ lowest.
Show currently available hardware-based crypto acceleration
engines supported by the OpenSSL library.
.\"*********************************************************
.TP
.B \-\-show-curves
(Standalone)
Show all available elliptic curves to use with the
.B \-\-ecdh-curve
option.
.\"*********************************************************
.SS Generate a random key:
Used only for non-TLS static key encryption mode.
.\"*********************************************************
Expand Down
4 changes: 3 additions & 1 deletion src/openvpn/init.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
{
Expand All @@ -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;
}
Expand Down
11 changes: 11 additions & 0 deletions src/openvpn/options.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions src/openvpn/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ struct options
bool show_engines;
#ifdef ENABLE_SSL
bool show_tls_ciphers;
bool show_curves;
#endif
bool genkey;
#endif
Expand Down Expand Up @@ -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;
Expand Down
4 changes: 4 additions & 0 deletions src/openvpn/ssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
15 changes: 15 additions & 0 deletions src/openvpn/ssl_backend.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
119 changes: 119 additions & 0 deletions src/openvpn/ssl_openssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@
#include <openssl/pkcs12.h>
#include <openssl/x509.h>
#include <openssl/crypto.h>
#ifndef OPENSSL_NO_EC
#include <openssl/ec.h>
#endif

/*
* Allocate space in SSL objects in which to store a struct tls_session
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
{
Expand Down
26 changes: 26 additions & 0 deletions src/openvpn/ssl_polarssl.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
{
Expand Down

0 comments on commit 609e813

Please sign in to comment.