* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2014 */
+/* Copyright (c) University of Cambridge 1995 - 2015 */
/* See the file NOTICE for conditions of use and distribution. */
/* Portions Copyright (c) The OpenSSL Project 1999 */
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/rand.h>
+#ifndef OPENSSL_NO_ECDH
+# include <openssl/ec.h>
+#endif
#ifndef DISABLE_OCSP
# include <openssl/ocsp.h>
#endif
#if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT)
# define EXIM_HAVE_OPENSSL_TLSEXT
#endif
-#if OPENSSL_VERSION_NUMBER >= 0x010100000L
-# define EXIM_HAVE_OPENSSL_CHECKHOST
-#endif
-#if OPENSSL_VERSION_NUMBER >= 0x010000000L \
+
+/*
+ * X509_check_host provides sane certificate hostname checking, but was added
+ * to OpenSSL late, after other projects forked off the code-base. So in
+ * addition to guarding against the base version number, beware that LibreSSL
+ * does not (at this time) support this function.
+ *
+ * If LibreSSL gains a different API, perhaps via libtls, then we'll probably
+ * opt to disentangle and ask a LibreSSL user to provide glue for a third
+ * crypto provider for libtls instead of continuing to tie the OpenSSL glue
+ * into even twistier knots. If LibreSSL gains the same API, we can just
+ * change this guard and punt the issue for a while longer.
+ */
+#ifndef LIBRESSL_VERSION_NUMBER
+# if OPENSSL_VERSION_NUMBER >= 0x010100000L
+# define EXIM_HAVE_OPENSSL_CHECKHOST
+# endif
+# if OPENSSL_VERSION_NUMBER >= 0x010000000L \
&& (OPENSSL_VERSION_NUMBER & 0x0000ff000L) >= 0x000002000L
-# define EXIM_HAVE_OPENSSL_CHECKHOST
+# define EXIM_HAVE_OPENSSL_CHECKHOST
+# endif
+
+# if !defined(OPENSSL_NO_ECDH)
+# if OPENSSL_VERSION_NUMBER >= 0x0090800fL
+# define EXIM_HAVE_ECDH
+# endif
+# if OPENSSL_VERSION_NUMBER >= 0x10002000L
+# define EXIM_HAVE_OPENSSL_ECDH_AUTO
+# define EXIM_HAVE_OPENSSL_EC_NIST2NID
+# endif
+# endif
#endif
#if !defined(EXIM_HAVE_OPENSSL_TLSEXT) && !defined(DISABLE_OCSP)
{ /* client, wanting stapling */
/* Add the server cert's signing chain as the one
for the verification of the OCSP stapled information. */
-
+
if (!X509_STORE_add_cert(client_static_cbinfo->u_ocsp.client.verify_store,
cert))
ERR_clear_error();
/* If dhparam is set, expand it, and load up the parameters for DH encryption.
Arguments:
+ sctx The current SSL CTX (inbound or outbound)
dhparam DH parameter file or fixed parameter identity string
host connected host, if client; NULL if server
+/*************************************************
+* Initialize for ECDH *
+*************************************************/
+
+/* Load parameters for ECDH encryption.
+
+For now, we stick to NIST P-256 because: it's simple and easy to configure;
+it avoids any patent issues that might bite redistributors; despite events in
+the news and concerns over curve choices, we're not cryptographers, we're not
+pretending to be, and this is "good enough" to be better than no support,
+protecting against most adversaries. Given another year or two, there might
+be sufficient clarity about a "right" way forward to let us make an informed
+decision, instead of a knee-jerk reaction.
+
+Longer-term, we should look at supporting both various named curves and
+external files generated with "openssl ecparam", much as we do for init_dh().
+We should also support "none" as a value, to explicitly avoid initialisation.
+
+Patches welcome.
+
+Arguments:
+ sctx The current SSL CTX (inbound or outbound)
+ host connected host, if client; NULL if server
+
+Returns: TRUE if OK (nothing to set up, or setup worked)
+*/
+
+static BOOL
+init_ecdh(SSL_CTX * sctx, host_item * host)
+{
+#ifdef OPENSSL_NO_ECDH
+return TRUE;
+#else
+
+EC_KEY * ecdh;
+uschar * exp_curve;
+int nid;
+BOOL rv;
+
+if (host) /* No ECDH setup for clients, only for servers */
+ return TRUE;
+
+# ifndef EXIM_HAVE_ECDH
+DEBUG(D_tls)
+ debug_printf("No OpenSSL API to define ECDH parameters, skipping\n");
+return TRUE;
+# else
+
+if (!expand_check(tls_eccurve, US"tls_eccurve", &exp_curve))
+ return FALSE;
+if (!exp_curve || !*exp_curve)
+ return TRUE;
+
+# ifdef EXIM_HAVE_OPENSSL_ECDH_AUTO
+/* check if new enough library to support auto ECDH temp key parameter selection */
+if (Ustrcmp(exp_curve, "auto") == 0)
+ {
+ DEBUG(D_tls) debug_printf(
+ "ECDH temp key parameter settings: OpenSSL 1.2+ autoselection\n");
+ SSL_CTX_set_ecdh_auto(sctx, 1);
+ return TRUE;
+ }
+# endif
+
+DEBUG(D_tls) debug_printf("ECDH: curve '%s'\n", exp_curve);
+if ( (nid = OBJ_sn2nid (CCS exp_curve)) == NID_undef
+# ifdef EXIM_HAVE_OPENSSL_EC_NIST2NID
+ && (nid = EC_curve_nist2nid(CCS exp_curve)) == NID_undef
+# endif
+ )
+ {
+ tls_error(string_sprintf("Unknown curve name tls_eccurve '%s'",
+ exp_curve),
+ host, NULL);
+ return FALSE;
+ }
+
+if (!(ecdh = EC_KEY_new_by_curve_name(nid)))
+ {
+ tls_error(US"Unable to create ec curve", host, NULL);
+ return FALSE;
+ }
+
+/* The "tmp" in the name here refers to setting a temporary key
+not to the stability of the interface. */
+
+if ((rv = SSL_CTX_set_tmp_ecdh(sctx, ecdh) == 0))
+ tls_error(string_sprintf("Error enabling '%s' curve", exp_curve), host, NULL);
+else
+ DEBUG(D_tls) debug_printf("ECDH: enabled '%s' curve\n", exp_curve);
+
+EC_KEY_free(ecdh);
+return !rv;
+
+# endif /*EXIM_HAVE_ECDH*/
+#endif /*OPENSSL_NO_ECDH*/
+}
+
+
+
+
#ifndef DISABLE_OCSP
/*************************************************
* Load OCSP information into state *
SSL_CTX_set_timeout(server_sni, SSL_CTX_get_timeout(server_ctx));
SSL_CTX_set_tlsext_servername_callback(server_sni, tls_servername_cb);
SSL_CTX_set_tlsext_servername_arg(server_sni, cbinfo);
+
+if ( !init_dh(server_sni, cbinfo->dhparam, NULL)
+ || !init_ecdh(server_sni, NULL)
+ )
+ return SSL_TLSEXT_ERR_NOACK;
+
if (cbinfo->server_cipher_list)
SSL_CTX_set_cipher_list(server_sni, CS cbinfo->server_cipher_list);
#ifndef DISABLE_OCSP
/* do this after setup_certs, because this can require the certs for verifying
OCSP information. */
-rc = tls_expand_session_files(server_sni, cbinfo);
-if (rc != OK) return SSL_TLSEXT_ERR_NOACK;
-
-if (!init_dh(server_sni, cbinfo->dhparam, NULL))
+if ((rc = tls_expand_session_files(server_sni, cbinfo)) != OK)
return SSL_TLSEXT_ERR_NOACK;
DEBUG(D_tls) debug_printf("Switching SSL context.\n");
int response_der_len;
DEBUG(D_tls)
- debug_printf("Received TLS status request (OCSP stapling); %s response.",
+ debug_printf("Received TLS status request (OCSP stapling); %s response\n",
cbinfo->u_ocsp.server.response ? "have" : "lack");
tls_in.ocsp = OCSP_NOT_RESP;
if(!p)
{
/* Expect this when we requested ocsp but got none */
- if ( cbinfo->u_ocsp.client.verify_required
- && log_extra_selector & LX_tls_cipher)
+ if (cbinfo->u_ocsp.client.verify_required && LOGGING(tls_cipher))
log_write(0, LOG_MAIN, "Received TLS status callback, null content");
else
DEBUG(D_tls) debug_printf(" null\n");
if(!(rsp = d2i_OCSP_RESPONSE(NULL, &p, len)))
{
tls_out.ocsp = OCSP_FAILED;
- if (log_extra_selector & LX_tls_cipher)
+ if (LOGGING(tls_cipher))
log_write(0, LOG_MAIN, "Received TLS cert status response, parse error");
else
DEBUG(D_tls) debug_printf(" parse error\n");
if(!(bs = OCSP_response_get1_basic(rsp)))
{
tls_out.ocsp = OCSP_FAILED;
- if (log_extra_selector & LX_tls_cipher)
+ if (LOGGING(tls_cipher))
log_write(0, LOG_MAIN, "Received TLS cert status response, error parsing response");
else
DEBUG(D_tls) debug_printf(" error parsing response\n");
cbinfo->u_ocsp.client.verify_store, 0)) <= 0)
{
tls_out.ocsp = OCSP_FAILED;
- if (log_extra_selector & LX_tls_cipher)
+ if (LOGGING(tls_cipher))
log_write(0, LOG_MAIN, "Received TLS cert status response, itself unverifiable");
BIO_printf(bp, "OCSP response verify failure\n");
ERR_print_errors(bp);
DEBUG(D_tls) debug_printf("no SSL CTX options to set\n");
/* Initialize with DH parameters if supplied */
+/* Initialize ECDH temp key parameter selection */
-if (!init_dh(*ctxp, dhparam, host)) return DEFER;
+if ( !init_dh(*ctxp, dhparam, host)
+ || !init_ecdh(*ctxp, host)
+ )
+ return DEFER;
/* Set up certificate and key (and perhaps OCSP info) */
)
{
int rc;
-/* stick to the old behaviour for compatibility if tls_verify_certificates is
+/* stick to the old behaviour for compatibility if tls_verify_certificates is
set but both tls_verify_hosts and tls_try_verify_hosts is not set. Check only
the specified host patterns if one of them is defined */
if (verify_check_given_host(&ob->tls_verify_cert_hostnames, host) == OK)
{
cbinfo->verify_cert_hostnames =
-#ifdef EXPERIMENTAL_INTERNATIONAL
+#ifdef SUPPORT_I18N
string_domain_utf8_to_alabel(host->name, NULL);
#else
host->name;