* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2016 */
+/* Copyright (c) University of Cambridge 1995 - 2019 */
/* See the file NOTICE for conditions of use and distribution. */
/* Portions Copyright (c) The OpenSSL Project 1999 */
#ifndef DISABLE_OCSP
# include <openssl/ocsp.h>
#endif
-#ifdef EXPERIMENTAL_DANE
-# include <danessl.h>
+#ifdef SUPPORT_DANE
+# include "danessl.h"
#endif
# define EXIM_HAVE_SHA256
#endif
-/*
- * 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.
- */
+/* 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
+# define EXIM_HAVE_OPENSSL_DH_BITS
+# define EXIM_HAVE_OPENSSL_TLS_METHOD
+# define EXIM_HAVE_OPENSSL_KEYLOG
+# define EXIM_HAVE_OPENSSL_CIPHER_GET_ID
+# define EXIM_HAVE_SESSION_TICKET
+# define EXIM_HAVE_OPESSL_TRACE
+# else
+# define EXIM_NEED_OPENSSL_INIT
# endif
# if OPENSSL_VERSION_NUMBER >= 0x010000000L \
&& (OPENSSL_VERSION_NUMBER & 0x0000ff000L) >= 0x000002000L
# endif
#endif
+#ifndef LIBRESSL_VERSION_NUMBER
+# if OPENSSL_VERSION_NUMBER >= 0x010101000L
+# define OPENSSL_HAVE_KEYLOG_CB
+# define OPENSSL_HAVE_NUM_TICKETS
+# define EXIM_HAVE_OPENSSL_CIPHER_STD_NAME
+# endif
+#endif
+
#if !defined(EXIM_HAVE_OPENSSL_TLSEXT) && !defined(DISABLE_OCSP)
# warning "OpenSSL library version too old; define DISABLE_OCSP in Makefile"
# define DISABLE_OCSP
#endif
+#ifdef EXPERIMENTAL_TLS_RESUME
+# if OPENSSL_VERSION_NUMBER < 0x0101010L
+# error OpenSSL version too old for session-resumption
+# endif
+#endif
+
+#ifdef EXIM_HAVE_OPENSSL_CHECKHOST
+# include <openssl/x509v3.h>
+#endif
+
+#ifndef EXIM_HAVE_OPENSSL_CIPHER_STD_NAME
+# ifndef EXIM_HAVE_OPENSSL_CIPHER_GET_ID
+# define SSL_CIPHER_get_id(c) (c->id)
+# endif
+# ifndef MACRO_PREDEF
+# include "tls-cipher-stdname.c"
+# endif
+#endif
+
+/*************************************************
+* OpenSSL option parse *
+*************************************************/
+
+typedef struct exim_openssl_option {
+ uschar *name;
+ long value;
+} exim_openssl_option;
+/* We could use a macro to expand, but we need the ifdef and not all the
+options document which version they were introduced in. Policylet: include
+all options unless explicitly for DTLS, let the administrator choose which
+to apply.
+
+This list is current as of:
+ ==> 1.0.1b <==
+Plus SSL_OP_SAFARI_ECDHE_ECDSA_BUG from 2013-June patch/discussion on openssl-dev
+Plus SSL_OP_NO_TLSv1_3 for 1.1.2-dev
+*/
+static exim_openssl_option exim_openssl_options[] = {
+/* KEEP SORTED ALPHABETICALLY! */
+#ifdef SSL_OP_ALL
+ { US"all", (long) SSL_OP_ALL },
+#endif
+#ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION
+ { US"allow_unsafe_legacy_renegotiation", SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION },
+#endif
+#ifdef SSL_OP_CIPHER_SERVER_PREFERENCE
+ { US"cipher_server_preference", SSL_OP_CIPHER_SERVER_PREFERENCE },
+#endif
+#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
+ { US"dont_insert_empty_fragments", SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS },
+#endif
+#ifdef SSL_OP_EPHEMERAL_RSA
+ { US"ephemeral_rsa", SSL_OP_EPHEMERAL_RSA },
+#endif
+#ifdef SSL_OP_LEGACY_SERVER_CONNECT
+ { US"legacy_server_connect", SSL_OP_LEGACY_SERVER_CONNECT },
+#endif
+#ifdef SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER
+ { US"microsoft_big_sslv3_buffer", SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER },
+#endif
+#ifdef SSL_OP_MICROSOFT_SESS_ID_BUG
+ { US"microsoft_sess_id_bug", SSL_OP_MICROSOFT_SESS_ID_BUG },
+#endif
+#ifdef SSL_OP_MSIE_SSLV2_RSA_PADDING
+ { US"msie_sslv2_rsa_padding", SSL_OP_MSIE_SSLV2_RSA_PADDING },
+#endif
+#ifdef SSL_OP_NETSCAPE_CHALLENGE_BUG
+ { US"netscape_challenge_bug", SSL_OP_NETSCAPE_CHALLENGE_BUG },
+#endif
+#ifdef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG
+ { US"netscape_reuse_cipher_change_bug", SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG },
+#endif
+#ifdef SSL_OP_NO_COMPRESSION
+ { US"no_compression", SSL_OP_NO_COMPRESSION },
+#endif
+#ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
+ { US"no_session_resumption_on_renegotiation", SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION },
+#endif
+#ifdef SSL_OP_NO_SSLv2
+ { US"no_sslv2", SSL_OP_NO_SSLv2 },
+#endif
+#ifdef SSL_OP_NO_SSLv3
+ { US"no_sslv3", SSL_OP_NO_SSLv3 },
+#endif
+#ifdef SSL_OP_NO_TICKET
+ { US"no_ticket", SSL_OP_NO_TICKET },
+#endif
+#ifdef SSL_OP_NO_TLSv1
+ { US"no_tlsv1", SSL_OP_NO_TLSv1 },
+#endif
+#ifdef SSL_OP_NO_TLSv1_1
+#if SSL_OP_NO_TLSv1_1 == 0x00000400L
+ /* Error in chosen value in 1.0.1a; see first item in CHANGES for 1.0.1b */
+#warning OpenSSL 1.0.1a uses a bad value for SSL_OP_NO_TLSv1_1, ignoring
+#else
+ { US"no_tlsv1_1", SSL_OP_NO_TLSv1_1 },
+#endif
+#endif
+#ifdef SSL_OP_NO_TLSv1_2
+ { US"no_tlsv1_2", SSL_OP_NO_TLSv1_2 },
+#endif
+#ifdef SSL_OP_NO_TLSv1_3
+ { US"no_tlsv1_3", SSL_OP_NO_TLSv1_3 },
+#endif
+#ifdef SSL_OP_SAFARI_ECDHE_ECDSA_BUG
+ { US"safari_ecdhe_ecdsa_bug", SSL_OP_SAFARI_ECDHE_ECDSA_BUG },
+#endif
+#ifdef SSL_OP_SINGLE_DH_USE
+ { US"single_dh_use", SSL_OP_SINGLE_DH_USE },
+#endif
+#ifdef SSL_OP_SINGLE_ECDH_USE
+ { US"single_ecdh_use", SSL_OP_SINGLE_ECDH_USE },
+#endif
+#ifdef SSL_OP_SSLEAY_080_CLIENT_DH_BUG
+ { US"ssleay_080_client_dh_bug", SSL_OP_SSLEAY_080_CLIENT_DH_BUG },
+#endif
+#ifdef SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG
+ { US"sslref2_reuse_cert_type_bug", SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG },
+#endif
+#ifdef SSL_OP_TLS_BLOCK_PADDING_BUG
+ { US"tls_block_padding_bug", SSL_OP_TLS_BLOCK_PADDING_BUG },
+#endif
+#ifdef SSL_OP_TLS_D5_BUG
+ { US"tls_d5_bug", SSL_OP_TLS_D5_BUG },
+#endif
+#ifdef SSL_OP_TLS_ROLLBACK_BUG
+ { US"tls_rollback_bug", SSL_OP_TLS_ROLLBACK_BUG },
+#endif
+};
+
+#ifndef MACRO_PREDEF
+static int exim_openssl_options_size = nelem(exim_openssl_options);
+#endif
+
+#ifdef MACRO_PREDEF
+void
+options_tls(void)
+{
+uschar buf[64];
+
+for (struct exim_openssl_option * o = exim_openssl_options;
+ o < exim_openssl_options + nelem(exim_openssl_options); o++)
+ {
+ /* Trailing X is workaround for problem with _OPT_OPENSSL_NO_TLSV1
+ being a ".ifdef _OPT_OPENSSL_NO_TLSV1_3" match */
+
+ spf(buf, sizeof(buf), US"_OPT_OPENSSL_%T_X", o->name);
+ builtin_macro_create(buf);
+ }
+
+# ifdef EXPERIMENTAL_TLS_RESUME
+builtin_macro_create_var(US"_RESUME_DECODE", RESUME_DECODE_STRING );
+# endif
+}
+#else
+
+/******************************************************************************/
+
/* Structure for collecting random data for seeding. */
typedef struct randstuff {
Simple case: client, `client_ctx`
As a client, we can be doing a callout or cut-through delivery while receiving
a message. So we have a client context, which should have options initialised
- from the SMTP Transport.
+ from the SMTP Transport. We may also concurrently want to make TLS connections
+ to utility daemons, so client-contexts are allocated and passed around in call
+ args rather than using a gobal.
Server:
There are two cases: with and without ServerNameIndication from the client.
configuration.
*/
-static SSL_CTX *client_ctx = NULL;
+typedef struct {
+ SSL_CTX * ctx;
+ SSL * ssl;
+ gstring * corked;
+} exim_openssl_client_tls_ctx;
+
static SSL_CTX *server_ctx = NULL;
-static SSL *client_ssl = NULL;
static SSL *server_ssl = NULL;
#ifdef EXIM_HAVE_OPENSSL_TLSEXT
static char ssl_errstring[256];
-static int ssl_session_timeout = 200;
+static int ssl_session_timeout = 7200; /* Two hours */
static BOOL client_verify_optional = FALSE;
static BOOL server_verify_optional = FALSE;
typedef struct tls_ext_ctx_cb {
+ tls_support * tlsp;
uschar *certificate;
uschar *privatekey;
-#ifndef DISABLE_OCSP
BOOL is_server;
+#ifndef DISABLE_OCSP
STACK_OF(X509) *verify_stack; /* chain for verifying the proof */
union {
struct {
/* should figure out a cleanup of API to handle state preserved per
implementation, for various reasons, which can be void * in the APIs.
For now, we hack around it. */
-tls_ext_ctx_cb *client_static_cbinfo = NULL;
+tls_ext_ctx_cb *client_static_cbinfo = NULL; /*XXX should not use static; multiple concurrent clients! */
tls_ext_ctx_cb *server_static_cbinfo = NULL;
static int
setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional,
- int (*cert_vfy_cb)(int, X509_STORE_CTX *) );
+ int (*cert_vfy_cb)(int, X509_STORE_CTX *), uschar ** errstr );
/* Callbacks */
#ifdef EXIM_HAVE_OPENSSL_TLSEXT
#endif
+
+/* Daemon-called, before every connection, key create/rotate */
+#ifdef EXPERIMENTAL_TLS_RESUME
+static void tk_init(void);
+static int tls_exdata_idx = -1;
+#endif
+
+void
+tls_daemon_init(void)
+{
+#ifdef EXPERIMENTAL_TLS_RESUME
+tk_init();
+#endif
+return;
+}
+
+
/*************************************************
* Handle TLS error *
*************************************************/
host NULL if setting up a server;
the connected host if setting up a client
msg error message or NULL if we should ask OpenSSL
+ errstr pointer to output error message
Returns: OK/DEFER/FAIL
*/
static int
-tls_error(uschar * prefix, const host_item * host, uschar * msg)
+tls_error(uschar * prefix, const host_item * host, uschar * msg, uschar ** errstr)
{
if (!msg)
{
- ERR_error_string(ERR_get_error(), ssl_errstring);
- msg = (uschar *)ssl_errstring;
+ ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
+ msg = US ssl_errstring;
}
-if (host)
- {
- log_write(0, LOG_MAIN, "H=%s [%s] TLS error on connection (%s): %s",
- host->name, host->address, prefix, msg);
- return FAIL;
- }
-else
- {
- uschar *conn_info = smtp_get_connection_info();
- if (Ustrncmp(conn_info, US"SMTP ", 5) == 0)
- conn_info += 5;
- /* I'd like to get separated H= here, but too hard for now */
- log_write(0, LOG_MAIN, "TLS error on %s (%s): %s",
- conn_info, prefix, msg);
- return DEFER;
- }
+msg = string_sprintf("(%s): %s", prefix, msg);
+DEBUG(D_tls) debug_printf("TLS error '%s'\n", msg);
+if (errstr) *errstr = msg;
+return host ? FAIL : DEFER;
}
-#ifdef EXIM_HAVE_EPHEM_RSA_KEX
/*************************************************
* Callback to generate RSA key *
*************************************************/
/*
Arguments:
- s SSL connection
+ s SSL connection (not used)
export not used
keylength keylength
#endif
{
- ERR_error_string(ERR_get_error(), ssl_errstring);
+ ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
log_write(0, LOG_MAIN|LOG_PANIC, "TLS error (RSA_generate_key): %s",
ssl_errstring);
return NULL;
}
return rsa_key;
}
-#endif
x509_store_dump_cert_s_names(X509_STORE * store)
{
STACK_OF(X509_OBJECT) * roots= store->objs;
-int i;
static uschar name[256];
-for(i= 0; i<sk_X509_OBJECT_num(roots); i++)
+for (int i= 0; i < sk_X509_OBJECT_num(roots); i++)
{
X509_OBJECT * tmp_obj= sk_X509_OBJECT_value(roots, i);
if(tmp_obj->type == X509_LU_X509)
{
- X509 * current_cert= tmp_obj->data.x509;
- X509_NAME_oneline(X509_get_subject_name(current_cert), CS name, sizeof(name));
- name[sizeof(name)-1] = '\0';
- debug_printf(" %s\n", name);
+ X509_NAME * sn = X509_get_subject_name(tmp_obj->data.x509);
+ if (X509_NAME_oneline(sn, CS name, sizeof(name)))
+ {
+ name[sizeof(name)-1] = '\0';
+ debug_printf(" %s\n", name);
+ }
}
}
}
}
DEBUG(D_tls) debug_printf("Event-action verify failure overridden "
"(host in tls_try_verify_hosts)\n");
+ tlsp->verify_override = TRUE;
}
X509_free(tlsp->peercert);
tlsp->peercert = old_cert;
*/
static int
-verify_callback(int preverify_ok, X509_STORE_CTX *x509ctx,
- tls_support *tlsp, BOOL *calledp, BOOL *optionalp)
+verify_callback(int preverify_ok, X509_STORE_CTX * x509ctx,
+ tls_support * tlsp, BOOL * calledp, BOOL * optionalp)
{
X509 * cert = X509_STORE_CTX_get_current_cert(x509ctx);
int depth = X509_STORE_CTX_get_error_depth(x509ctx);
uschar dn[256];
-X509_NAME_oneline(X509_get_subject_name(cert), CS dn, sizeof(dn));
+if (!X509_NAME_oneline(X509_get_subject_name(cert), CS dn, sizeof(dn)))
+ {
+ DEBUG(D_tls) debug_printf("X509_NAME_oneline() error\n");
+ log_write(0, LOG_MAIN, "[%s] SSL verify error: internal error",
+ tlsp == &tls_out ? deliver_host_address : sender_host_address);
+ return 0;
+ }
dn[sizeof(dn)-1] = '\0';
+tlsp->verify_override = FALSE;
if (preverify_ok == 0)
{
- log_write(0, LOG_MAIN, "[%s] SSL verify error: depth=%d error=%s cert=%s",
- tlsp == &tls_out ? deliver_host_address : sender_host_address,
- depth,
- X509_verify_cert_error_string(X509_STORE_CTX_get_error(x509ctx)),
- dn);
+ uschar * extra = verify_mode ? string_sprintf(" (during %c-verify for [%s])",
+ *verify_mode, sender_host_address)
+ : US"";
+ log_write(0, LOG_MAIN, "[%s] SSL verify error%s: depth=%d error=%s cert=%s",
+ tlsp == &tls_out ? deliver_host_address : sender_host_address,
+ extra, depth,
+ X509_verify_cert_error_string(X509_STORE_CTX_get_error(x509ctx)), dn);
*calledp = TRUE;
if (!*optionalp)
{
}
DEBUG(D_tls) debug_printf("SSL verify failure overridden (host in "
"tls_try_verify_hosts)\n");
+ tlsp->verify_override = TRUE;
}
else if (depth != 0)
if ( tlsp == &tls_out
&& ((verify_cert_hostnames = client_static_cbinfo->verify_cert_hostnames)))
- /* client, wanting hostname check */
+ /* client, wanting hostname check */
{
#ifdef EXIM_HAVE_OPENSSL_CHECKHOST
if (rc < 0)
{
log_write(0, LOG_MAIN, "[%s] SSL verify error: internal error",
- deliver_host_address);
+ tlsp == &tls_out ? deliver_host_address : sender_host_address);
name = NULL;
}
break;
if (!tls_is_name_for_cert(verify_cert_hostnames, cert))
#endif
{
+ uschar * extra = verify_mode
+ ? string_sprintf(" (during %c-verify for [%s])",
+ *verify_mode, sender_host_address)
+ : US"";
log_write(0, LOG_MAIN,
- "[%s] SSL verify error: certificate name mismatch: "
- "DN=\"%s\" H=\"%s\"",
- deliver_host_address, dn, verify_cert_hostnames);
+ "[%s] SSL verify error%s: certificate name mismatch: DN=\"%s\" H=\"%s\"",
+ tlsp == &tls_out ? deliver_host_address : sender_host_address,
+ extra, dn, verify_cert_hostnames);
*calledp = TRUE;
if (!*optionalp)
{
tlsp->peercert = X509_dup(cert); /* record failing cert */
return 0; /* reject */
}
- DEBUG(D_tls) debug_printf("SSL verify failure overridden (host in "
+ DEBUG(D_tls) debug_printf("SSL verify name failure overridden (host in "
"tls_try_verify_hosts)\n");
+ tlsp->verify_override = TRUE;
}
}
DEBUG(D_tls) debug_printf("SSL%s verify ok: depth=0 SN=%s\n",
*calledp ? "" : " authenticated", dn);
- if (!*calledp) tlsp->certificate_verified = TRUE;
*calledp = TRUE;
}
}
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
/* This gets called *by* the dane library verify callback, which interposes
itself.
{
X509 * cert = X509_STORE_CTX_get_current_cert(x509ctx);
uschar dn[256];
-#ifndef DISABLE_EVENT
int depth = X509_STORE_CTX_get_error_depth(x509ctx);
+#ifndef DISABLE_EVENT
BOOL dummy_called, optional = FALSE;
#endif
-X509_NAME_oneline(X509_get_subject_name(cert), CS dn, sizeof(dn));
+if (!X509_NAME_oneline(X509_get_subject_name(cert), CS dn, sizeof(dn)))
+ {
+ DEBUG(D_tls) debug_printf("X509_NAME_oneline() error\n");
+ log_write(0, LOG_MAIN, "[%s] SSL verify error: internal error",
+ deliver_host_address);
+ return 0;
+ }
dn[sizeof(dn)-1] = '\0';
DEBUG(D_tls) debug_printf("verify_callback_client_dane: %s depth %d %s\n",
#endif
if (preverify_ok == 1)
- tls_out.dane_verified =
- tls_out.certificate_verified = TRUE;
+ {
+ tls_out.dane_verified = TRUE;
+#ifndef DISABLE_OCSP
+ if (client_static_cbinfo->u_ocsp.client.verify_store)
+ { /* 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();
+ sk_X509_push(client_static_cbinfo->verify_stack, cert);
+ }
+#endif
+ }
else
{
int err = X509_STORE_CTX_get_error(x509ctx);
return preverify_ok;
}
-#endif /*EXPERIMENTAL_DANE*/
+#endif /*SUPPORT_DANE*/
/*************************************************
static void
info_callback(SSL *s, int where, int ret)
{
-where = where;
-ret = ret;
-DEBUG(D_tls) debug_printf("SSL info: %s\n", SSL_state_string_long(s));
+DEBUG(D_tls)
+ {
+ const uschar * str;
+
+ if (where & SSL_ST_CONNECT)
+ str = US"SSL_connect";
+ else if (where & SSL_ST_ACCEPT)
+ str = US"SSL_accept";
+ else
+ str = US"SSL info (undefined)";
+
+ if (where & SSL_CB_LOOP)
+ debug_printf("%s: %s\n", str, SSL_state_string_long(s));
+ else if (where & SSL_CB_ALERT)
+ debug_printf("SSL3 alert %s:%s:%s\n",
+ str = where & SSL_CB_READ ? US"read" : US"write",
+ SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret));
+ else if (where & SSL_CB_EXIT)
+ if (ret == 0)
+ debug_printf("%s: failed in %s\n", str, SSL_state_string_long(s));
+ else if (ret < 0)
+ debug_printf("%s: error in %s\n", str, SSL_state_string_long(s));
+ else if (where & SSL_CB_HANDSHAKE_START)
+ debug_printf("%s: hshake start: %s\n", str, SSL_state_string_long(s));
+ else if (where & SSL_CB_HANDSHAKE_DONE)
+ debug_printf("%s: hshake done: %s\n", str, SSL_state_string_long(s));
+ }
+}
+
+#ifdef OPENSSL_HAVE_KEYLOG_CB
+static void
+keylog_callback(const SSL *ssl, const char *line)
+{
+DEBUG(D_tls) debug_printf("%.200s\n", line);
+}
+#endif
+
+
+#ifdef EXPERIMENTAL_TLS_RESUME
+/* Manage the keysets used for encrypting the session tickets, on the server. */
+
+typedef struct { /* Session ticket encryption key */
+ uschar name[16];
+
+ const EVP_CIPHER * aes_cipher;
+ uschar aes_key[32]; /* size needed depends on cipher. aes_128 implies 128/8 = 16? */
+ const EVP_MD * hmac_hash;
+ uschar hmac_key[16];
+ time_t renew;
+ time_t expire;
+} exim_stek;
+
+static exim_stek exim_tk; /* current key */
+static exim_stek exim_tk_old; /* previous key */
+
+static void
+tk_init(void)
+{
+time_t t = time(NULL);
+
+if (exim_tk.name[0])
+ {
+ if (exim_tk.renew >= t) return;
+ exim_tk_old = exim_tk;
+ }
+
+if (f.running_in_test_harness) ssl_session_timeout = 6;
+
+DEBUG(D_tls) debug_printf("OpenSSL: %s STEK\n", exim_tk.name[0] ? "rotating" : "creating");
+if (RAND_bytes(exim_tk.aes_key, sizeof(exim_tk.aes_key)) <= 0) return;
+if (RAND_bytes(exim_tk.hmac_key, sizeof(exim_tk.hmac_key)) <= 0) return;
+if (RAND_bytes(exim_tk.name+1, sizeof(exim_tk.name)-1) <= 0) return;
+
+exim_tk.name[0] = 'E';
+exim_tk.aes_cipher = EVP_aes_256_cbc();
+exim_tk.hmac_hash = EVP_sha256();
+exim_tk.expire = t + ssl_session_timeout;
+exim_tk.renew = t + ssl_session_timeout/2;
+}
+
+static exim_stek *
+tk_current(void)
+{
+if (!exim_tk.name[0]) return NULL;
+return &exim_tk;
+}
+
+static exim_stek *
+tk_find(const uschar * name)
+{
+return memcmp(name, exim_tk.name, sizeof(exim_tk.name)) == 0 ? &exim_tk
+ : memcmp(name, exim_tk_old.name, sizeof(exim_tk_old.name)) == 0 ? &exim_tk_old
+ : NULL;
+}
+
+/* Callback for session tickets, on server */
+static int
+ticket_key_callback(SSL * ssl, uschar key_name[16],
+ uschar * iv, EVP_CIPHER_CTX * ctx, HMAC_CTX * hctx, int enc)
+{
+tls_support * tlsp = server_static_cbinfo->tlsp;
+exim_stek * key;
+
+if (enc)
+ {
+ DEBUG(D_tls) debug_printf("ticket_key_callback: create new session\n");
+ tlsp->resumption |= RESUME_CLIENT_REQUESTED;
+
+ if (RAND_bytes(iv, EVP_MAX_IV_LENGTH) <= 0)
+ return -1; /* insufficient random */
+
+ if (!(key = tk_current())) /* current key doesn't exist or isn't valid */
+ return 0; /* key couldn't be created */
+ memcpy(key_name, key->name, 16);
+ DEBUG(D_tls) debug_printf("STEK expire %ld\n", key->expire - time(NULL));
+
+ /*XXX will want these dependent on the ssl session strength */
+ HMAC_Init_ex(hctx, key->hmac_key, sizeof(key->hmac_key),
+ key->hmac_hash, NULL);
+ EVP_EncryptInit_ex(ctx, key->aes_cipher, NULL, key->aes_key, iv);
+
+ DEBUG(D_tls) debug_printf("ticket created\n");
+ return 1;
+ }
+else
+ {
+ time_t now = time(NULL);
+
+ DEBUG(D_tls) debug_printf("ticket_key_callback: retrieve session\n");
+ tlsp->resumption |= RESUME_CLIENT_SUGGESTED;
+
+ if (!(key = tk_find(key_name)) || key->expire < now)
+ {
+ DEBUG(D_tls)
+ {
+ debug_printf("ticket not usable (%s)\n", key ? "expired" : "not found");
+ if (key) debug_printf("STEK expire %ld\n", key->expire - now);
+ }
+ return 0;
+ }
+
+ HMAC_Init_ex(hctx, key->hmac_key, sizeof(key->hmac_key),
+ key->hmac_hash, NULL);
+ EVP_DecryptInit_ex(ctx, key->aes_cipher, NULL, key->aes_key, iv);
+
+ DEBUG(D_tls) debug_printf("ticket usable, STEK expire %ld\n", key->expire - now);
+
+ /* The ticket lifetime and renewal are the same as the STEK lifetime and
+ renewal, which is overenthusiastic. A factor of, say, 3x longer STEK would
+ be better. To do that we'd have to encode ticket lifetime in the name as
+ we don't yet see the restored session. Could check posthandshake for TLS1.3
+ and trigger a new ticket then, but cannot do that for TLS1.2 */
+ return key->renew < now ? 2 : 1;
+ }
}
+#endif
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
+ errstr error string pointer
Returns: TRUE if OK (nothing to set up, or setup worked)
*/
static BOOL
-init_dh(SSL_CTX *sctx, uschar *dhparam, const host_item *host)
+init_dh(SSL_CTX *sctx, uschar *dhparam, const host_item *host, uschar ** errstr)
{
BIO *bio;
DH *dh;
uschar *dhexpanded;
const char *pem;
+int dh_bitsize;
-if (!expand_check(dhparam, US"tls_dhparam", &dhexpanded))
+if (!expand_check(dhparam, US"tls_dhparam", &dhexpanded, errstr))
return FALSE;
if (!dhexpanded || !*dhexpanded)
if (!(bio = BIO_new_file(CS dhexpanded, "r")))
{
tls_error(string_sprintf("could not read dhparams file %s", dhexpanded),
- host, US strerror(errno));
+ host, US strerror(errno), errstr);
return FALSE;
}
}
if (!(pem = std_dh_prime_named(dhexpanded)))
{
tls_error(string_sprintf("Unknown standard DH prime \"%s\"", dhexpanded),
- host, US strerror(errno));
+ host, US strerror(errno), errstr);
return FALSE;
}
bio = BIO_new_mem_buf(CS pem, -1);
{
BIO_free(bio);
tls_error(string_sprintf("Could not read tls_dhparams \"%s\"", dhexpanded),
- host, NULL);
+ host, NULL, errstr);
return FALSE;
}
+/* note: our default limit of 2236 is not a multiple of 8; the limit comes from
+ * an NSS limit, and the GnuTLS APIs handle bit-sizes fine, so we went with
+ * 2236. But older OpenSSL can only report in bytes (octets), not bits.
+ * If someone wants to dance at the edge, then they can raise the limit or use
+ * current libraries. */
+#ifdef EXIM_HAVE_OPENSSL_DH_BITS
+/* Added in commit 26c79d5641d; `git describe --contains` says OpenSSL_1_1_0-pre1~1022
+ * This predates OpenSSL_1_1_0 (before a, b, ...) so is in all 1.1.0 */
+dh_bitsize = DH_bits(dh);
+#else
+dh_bitsize = 8 * DH_size(dh);
+#endif
+
/* Even if it is larger, we silently return success rather than cause things
* to fail out, so that a too-large DH will not knock out all TLS; it's a
* debatable choice. */
-if ((8*DH_size(dh)) > tls_dh_max_bits)
+if (dh_bitsize > tls_dh_max_bits)
{
DEBUG(D_tls)
- debug_printf("dhparams file %d bits, is > tls_dh_max_bits limit of %d",
- 8*DH_size(dh), tls_dh_max_bits);
+ debug_printf("dhparams file %d bits, is > tls_dh_max_bits limit of %d\n",
+ dh_bitsize, tls_dh_max_bits);
}
else
{
SSL_CTX_set_tmp_dh(sctx, dh);
DEBUG(D_tls)
debug_printf("Diffie-Hellman initialized from %s with %d-bit prime\n",
- dhexpanded ? dhexpanded : US"default", 8*DH_size(dh));
+ dhexpanded ? dhexpanded : US"default", dh_bitsize);
}
DH_free(dh);
Arguments:
sctx The current SSL CTX (inbound or outbound)
host connected host, if client; NULL if server
+ errstr error string pointer
Returns: TRUE if OK (nothing to set up, or setup worked)
*/
static BOOL
-init_ecdh(SSL_CTX * sctx, host_item * host)
+init_ecdh(SSL_CTX * sctx, host_item * host, uschar ** errstr)
{
#ifdef OPENSSL_NO_ECDH
return TRUE;
return TRUE;
# else
-if (!expand_check(tls_eccurve, US"tls_eccurve", &exp_curve))
+if (!expand_check(tls_eccurve, US"tls_eccurve", &exp_curve, errstr))
return FALSE;
if (!exp_curve || !*exp_curve)
return TRUE;
#if OPENSSL_VERSION_NUMBER < 0x10002000L
DEBUG(D_tls) debug_printf(
"ECDH OpenSSL < 1.0.2: temp key parameter settings: overriding \"auto\" with \"prime256v1\"\n");
- exp_curve = "prime256v1";
+ exp_curve = US"prime256v1";
#else
# if defined SSL_CTRL_SET_ECDH_AUTO
DEBUG(D_tls) debug_printf(
# endif
)
{
- tls_error(string_sprintf("Unknown curve name tls_eccurve '%s'",
- exp_curve),
- host, NULL);
+ tls_error(string_sprintf("Unknown curve name tls_eccurve '%s'", exp_curve),
+ host, NULL, errstr);
return FALSE;
}
if (!(ecdh = EC_KEY_new_by_curve_name(nid)))
{
- tls_error(US"Unable to create ec curve", host, NULL);
+ tls_error(US"Unable to create ec curve", host, NULL, errstr);
return FALSE;
}
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);
+ tls_error(string_sprintf("Error enabling '%s' curve", exp_curve), host, NULL, errstr);
else
DEBUG(D_tls) debug_printf("ECDH: enabled '%s' curve\n", exp_curve);
SNI handling.
Separately we might try to replace using OCSP_basic_verify() - which seems to not
-be a public interface into the OpenSSL library (there's no manual entry) -
+be a public interface into the OpenSSL library (there's no manual entry) -
But what with? We also use OCSP_basic_verify in the client stapling callback.
And there we NEED it; we must verify that status... unless the
library does it for us anyway? */
{
DEBUG(D_tls)
{
- ERR_error_string(ERR_get_error(), ssl_errstring);
+ ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
debug_printf("OCSP response verify failure: %s\n", US ssl_errstring);
}
goto bad;
}
supply_response:
- cbinfo->u_ocsp.server.response = resp;
+ cbinfo->u_ocsp.server.response = resp; /*XXX stack?*/
return;
bad:
- if (running_in_test_harness)
+ if (f.running_in_test_harness)
{
extern char ** environ;
- uschar ** p;
- if (environ) for (p = USS environ; *p != NULL; p++)
+ if (environ) for (uschar ** p = USS environ; *p; p++)
if (Ustrncmp(*p, "EXIM_TESTHARNESS_DISABLE_OCSPVALIDITYCHECK", 42) == 0)
{
DEBUG(D_tls) debug_printf("Supplying known bad OCSP response\n");
/* Create and install a selfsigned certificate, for use in server mode */
static int
-tls_install_selfsign(SSL_CTX * sctx)
+tls_install_selfsign(SSL_CTX * sctx, uschar ** errstr)
{
X509 * x509 = NULL;
EVP_PKEY * pkey;
goto err;
where = US"generating pkey";
- /* deprecated, use RSA_generate_key_ex() */
-if (!(rsa = RSA_generate_key(1024, RSA_F4, NULL, NULL)))
+if (!(rsa = rsa_callback(NULL, 0, 2048)))
goto err;
where = US"assigning pkey";
goto err;
X509_set_version(x509, 2); /* N+1 - version 3 */
-ASN1_INTEGER_set(X509_get_serialNumber(x509), 0);
+ASN1_INTEGER_set(X509_get_serialNumber(x509), 1);
X509_gmtime_adj(X509_get_notBefore(x509), 0);
X509_gmtime_adj(X509_get_notAfter(x509), (long)60 * 60); /* 1 hour */
X509_set_pubkey(x509, pkey);
return OK;
err:
- (void) tls_error(where, NULL, NULL);
+ (void) tls_error(where, NULL, NULL, errstr);
if (x509) X509_free(x509);
if (pkey) EVP_PKEY_free(pkey);
return DEFER;
+static int
+tls_add_certfile(SSL_CTX * sctx, tls_ext_ctx_cb * cbinfo, uschar * file,
+ uschar ** errstr)
+{
+DEBUG(D_tls) debug_printf("tls_certificate file %s\n", file);
+if (!SSL_CTX_use_certificate_chain_file(sctx, CS file))
+ return tls_error(string_sprintf(
+ "SSL_CTX_use_certificate_chain_file file=%s", file),
+ cbinfo->host, NULL, errstr);
+return 0;
+}
+
+static int
+tls_add_pkeyfile(SSL_CTX * sctx, tls_ext_ctx_cb * cbinfo, uschar * file,
+ uschar ** errstr)
+{
+DEBUG(D_tls) debug_printf("tls_privatekey file %s\n", file);
+if (!SSL_CTX_use_PrivateKey_file(sctx, CS file, SSL_FILETYPE_PEM))
+ return tls_error(string_sprintf(
+ "SSL_CTX_use_PrivateKey_file file=%s", file), cbinfo->host, NULL, errstr);
+return 0;
+}
+
+
/*************************************************
* Expand key and cert file specs *
*************************************************/
Arguments:
sctx the SSL_CTX* to update
cbinfo various parts of session state
+ errstr error string pointer
Returns: OK/DEFER/FAIL
*/
static int
-tls_expand_session_files(SSL_CTX *sctx, tls_ext_ctx_cb *cbinfo)
+tls_expand_session_files(SSL_CTX *sctx, tls_ext_ctx_cb *cbinfo,
+ uschar ** errstr)
{
uschar *expanded;
if (!cbinfo->certificate)
{
- if (cbinfo->host) /* client */
+ if (!cbinfo->is_server) /* client */
return OK;
- /* server */
- if (tls_install_selfsign(sctx) != OK)
+ /* server */
+ if (tls_install_selfsign(sctx, errstr) != OK)
return DEFER;
}
else
{
+ int err;
+
if (Ustrstr(cbinfo->certificate, US"tls_sni") ||
Ustrstr(cbinfo->certificate, US"tls_in_sni") ||
Ustrstr(cbinfo->certificate, US"tls_out_sni")
)
reexpand_tls_files_for_sni = TRUE;
- if (!expand_check(cbinfo->certificate, US"tls_certificate", &expanded))
+ if (!expand_check(cbinfo->certificate, US"tls_certificate", &expanded, errstr))
return DEFER;
- if (expanded != NULL)
- {
- DEBUG(D_tls) debug_printf("tls_certificate file %s\n", expanded);
- if (!SSL_CTX_use_certificate_chain_file(sctx, CS expanded))
- return tls_error(string_sprintf(
- "SSL_CTX_use_certificate_chain_file file=%s", expanded),
- cbinfo->host, NULL);
- }
+ if (expanded)
+ if (cbinfo->is_server)
+ {
+ const uschar * file_list = expanded;
+ int sep = 0;
+ uschar * file;
- if (cbinfo->privatekey != NULL &&
- !expand_check(cbinfo->privatekey, US"tls_privatekey", &expanded))
+ while (file = string_nextinlist(&file_list, &sep, NULL, 0))
+ if ((err = tls_add_certfile(sctx, cbinfo, file, errstr)))
+ return err;
+ }
+ else /* would there ever be a need for multiple client certs? */
+ if ((err = tls_add_certfile(sctx, cbinfo, expanded, errstr)))
+ return err;
+
+ if ( cbinfo->privatekey
+ && !expand_check(cbinfo->privatekey, US"tls_privatekey", &expanded, errstr))
return DEFER;
/* If expansion was forced to fail, key_expanded will be NULL. If the result
key is in the same file as the certificate. */
if (expanded && *expanded)
- {
- DEBUG(D_tls) debug_printf("tls_privatekey file %s\n", expanded);
- if (!SSL_CTX_use_PrivateKey_file(sctx, CS expanded, SSL_FILETYPE_PEM))
- return tls_error(string_sprintf(
- "SSL_CTX_use_PrivateKey_file file=%s", expanded), cbinfo->host, NULL);
- }
+ if (cbinfo->is_server)
+ {
+ const uschar * file_list = expanded;
+ int sep = 0;
+ uschar * file;
+
+ while (file = string_nextinlist(&file_list, &sep, NULL, 0))
+ if ((err = tls_add_pkeyfile(sctx, cbinfo, file, errstr)))
+ return err;
+ }
+ else /* would there ever be a need for multiple client certs? */
+ if ((err = tls_add_pkeyfile(sctx, cbinfo, expanded, errstr)))
+ return err;
}
#ifndef DISABLE_OCSP
if (cbinfo->is_server && cbinfo->u_ocsp.server.file)
{
- if (!expand_check(cbinfo->u_ocsp.server.file, US"tls_ocsp_file", &expanded))
+ /*XXX stack*/
+ if (!expand_check(cbinfo->u_ocsp.server.file, US"tls_ocsp_file", &expanded, errstr))
return DEFER;
if (expanded && *expanded)
arg Callback of "our" registered data
Returns: SSL_TLSEXT_ERR_{OK,ALERT_WARNING,ALERT_FATAL,NOACK}
+
+XXX might need to change to using ClientHello callback,
+per https://www.openssl.org/docs/manmaster/man3/SSL_client_hello_cb_fn.html
*/
#ifdef EXIM_HAVE_OPENSSL_TLSEXT
tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg;
int rc;
int old_pool = store_pool;
+uschar * dummy_errstr;
if (!servername)
return SSL_TLSEXT_ERR_OK;
not confident that memcpy wouldn't break some internal reference counting.
Especially since there's a references struct member, which would be off. */
+#ifdef EXIM_HAVE_OPENSSL_TLS_METHOD
+if (!(server_sni = SSL_CTX_new(TLS_server_method())))
+#else
if (!(server_sni = SSL_CTX_new(SSLv23_server_method())))
+#endif
{
- ERR_error_string(ERR_get_error(), ssl_errstring);
+ ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
DEBUG(D_tls) debug_printf("SSL_CTX_new() failed: %s\n", ssl_errstring);
- return SSL_TLSEXT_ERR_NOACK;
+ goto bad;
}
/* Not sure how many of these are actually needed, since SSL object
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)
+if ( !init_dh(server_sni, cbinfo->dhparam, NULL, &dummy_errstr)
+ || !init_ecdh(server_sni, NULL, &dummy_errstr)
)
- return SSL_TLSEXT_ERR_NOACK;
+ goto bad;
+
+if ( cbinfo->server_cipher_list
+ && !SSL_CTX_set_cipher_list(server_sni, CS cbinfo->server_cipher_list))
+ goto bad;
-if (cbinfo->server_cipher_list)
- SSL_CTX_set_cipher_list(server_sni, CS cbinfo->server_cipher_list);
#ifndef DISABLE_OCSP
if (cbinfo->u_ocsp.server.file)
{
#endif
if ((rc = setup_certs(server_sni, tls_verify_certificates, tls_crl, NULL, FALSE,
- verify_callback_server)) != OK)
- return SSL_TLSEXT_ERR_NOACK;
+ verify_callback_server, &dummy_errstr)) != OK)
+ goto bad;
/* do this after setup_certs, because this can require the certs for verifying
OCSP information. */
-if ((rc = tls_expand_session_files(server_sni, cbinfo)) != OK)
- return SSL_TLSEXT_ERR_NOACK;
+if ((rc = tls_expand_session_files(server_sni, cbinfo, &dummy_errstr)) != OK)
+ goto bad;
DEBUG(D_tls) debug_printf("Switching SSL context.\n");
SSL_set_SSL_CTX(s, server_sni);
-
return SSL_TLSEXT_ERR_OK;
+
+bad: return SSL_TLSEXT_ERR_ALERT_FATAL;
}
#endif /* EXIM_HAVE_OPENSSL_TLSEXT */
tls_server_stapling_cb(SSL *s, void *arg)
{
const tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg;
-uschar *response_der;
+uschar *response_der; /*XXX blob */
int response_der_len;
+/*XXX stack: use SSL_get_certificate() to see which cert; from that work
+out which ocsp blob to send. Unfortunately, SSL_get_certificate is known
+buggy in current OpenSSL; it returns the last cert loaded always rather than
+the one actually presented. So we can't support a stack of OCSP proofs at
+this time. */
+
DEBUG(D_tls)
debug_printf("Received TLS status request (OCSP stapling); %s response\n",
cbinfo->u_ocsp.server.response ? "have" : "lack");
return SSL_TLSEXT_ERR_NOACK;
response_der = NULL;
-response_der_len = i2d_OCSP_RESPONSE(cbinfo->u_ocsp.server.response,
+response_der_len = i2d_OCSP_RESPONSE(cbinfo->u_ocsp.server.response, /*XXX stack*/
&response_der);
if (response_der_len <= 0)
return SSL_TLSEXT_ERR_NOACK;
return cbinfo->u_ocsp.client.verify_required ? 0 : 1;
}
-if(!(rsp = d2i_OCSP_RESPONSE(NULL, &p, len)))
- {
- tls_out.ocsp = OCSP_FAILED;
+if (!(rsp = d2i_OCSP_RESPONSE(NULL, &p, len)))
+ {
+ tls_out.ocsp = OCSP_FAILED; /*XXX should use tlsp-> to permit concurrent outbound */
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");
return 0;
- }
+ }
-if(!(bs = OCSP_response_get1_basic(rsp)))
+if (!(bs = OCSP_response_get1_basic(rsp)))
{
tls_out.ocsp = OCSP_FAILED;
if (LOGGING(tls_cipher))
int status, reason;
ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd;
- DEBUG(D_tls) bp = BIO_new_fp(stderr, BIO_NOCLOSE);
+ DEBUG(D_tls) bp = BIO_new_fp(debug_file, BIO_NOCLOSE);
/*OCSP_RESPONSE_print(bp, rsp, 0); extreme debug: stapling content */
cbinfo->u_ocsp.client.verify_store, 0)) <= 0)
{
tls_out.ocsp = OCSP_FAILED;
- if (LOGGING(tls_cipher))
- log_write(0, LOG_MAIN, "Received TLS cert status response, itself unverifiable");
+ if (LOGGING(tls_cipher)) log_write(0, LOG_MAIN,
+ "Received TLS cert status response, itself unverifiable: %s",
+ ERR_reason_error_string(ERR_peek_error()));
BIO_printf(bp, "OCSP response verify failure\n");
ERR_print_errors(bp);
+ OCSP_RESPONSE_print(bp, rsp, 0);
goto failed;
}
ocsp_file file of stapling info (server); flag for require ocsp (client)
addr address if client; NULL if server (for some randomness)
cbp place to put allocated callback context
+ errstr error string pointer
Returns: OK/DEFER/FAIL
*/
tls_init(SSL_CTX **ctxp, host_item *host, uschar *dhparam, uschar *certificate,
uschar *privatekey,
#ifndef DISABLE_OCSP
- uschar *ocsp_file,
+ uschar *ocsp_file, /*XXX stack, in server*/
#endif
- address_item *addr, tls_ext_ctx_cb ** cbp)
+ address_item *addr, tls_ext_ctx_cb ** cbp,
+ tls_support * tlsp,
+ uschar ** errstr)
{
+SSL_CTX * ctx;
long init_options;
int rc;
-BOOL okay;
tls_ext_ctx_cb * cbinfo;
cbinfo = store_malloc(sizeof(tls_ext_ctx_cb));
+cbinfo->tlsp = tlsp;
cbinfo->certificate = certificate;
cbinfo->privatekey = privatekey;
+cbinfo->is_server = host==NULL;
#ifndef DISABLE_OCSP
cbinfo->verify_stack = NULL;
-if ((cbinfo->is_server = host==NULL))
+if (!host)
{
cbinfo->u_ocsp.server.file = ocsp_file;
cbinfo->u_ocsp.server.file_expanded = NULL;
cbinfo->event_action = NULL;
#endif
+#ifdef EXIM_NEED_OPENSSL_INIT
SSL_load_error_strings(); /* basic set up */
OpenSSL_add_ssl_algorithms();
+#endif
#ifdef EXIM_HAVE_SHA256
/* SHA256 is becoming ever more popular. This makes sure it gets added to the
By disabling with openssl_options, we can let admins re-enable with the
existing knob. */
-*ctxp = SSL_CTX_new(host ? SSLv23_client_method() : SSLv23_server_method());
-
-if (!*ctxp) return tls_error(US"SSL_CTX_new", host, NULL);
+#ifdef EXIM_HAVE_OPENSSL_TLS_METHOD
+if (!(ctx = SSL_CTX_new(host ? TLS_client_method() : TLS_server_method())))
+#else
+if (!(ctx = SSL_CTX_new(host ? SSLv23_client_method() : SSLv23_server_method())))
+#endif
+ return tls_error(US"SSL_CTX_new", host, NULL, errstr);
/* It turns out that we need to seed the random number generator this early in
order to get the full complement of ciphers to work. It took me roughly a day
gettimeofday(&r.tv, NULL);
r.p = getpid();
- RAND_seed((uschar *)(&r), sizeof(r));
- RAND_seed((uschar *)big_buffer, big_buffer_size);
- if (addr != NULL) RAND_seed((uschar *)addr, sizeof(addr));
+ RAND_seed(US (&r), sizeof(r));
+ RAND_seed(US big_buffer, big_buffer_size);
+ if (addr != NULL) RAND_seed(US addr, sizeof(addr));
if (!RAND_status())
return tls_error(US"RAND_status", host,
- US"unable to seed random number generator");
+ US"unable to seed random number generator", errstr);
}
/* Set up the information callback, which outputs if debugging is at a suitable
level. */
-DEBUG(D_tls) SSL_CTX_set_info_callback(*ctxp, (void (*)())info_callback);
+DEBUG(D_tls)
+ {
+ SSL_CTX_set_info_callback(ctx, (void (*)())info_callback);
+#if defined(EXIM_HAVE_OPESSL_TRACE) && !defined(OPENSSL_NO_SSL_TRACE)
+ /* this needs a debug build of OpenSSL */
+ SSL_CTX_set_msg_callback(ctx, (void (*)())SSL_trace);
+#endif
+#ifdef OPENSSL_HAVE_KEYLOG_CB
+ SSL_CTX_set_keylog_callback(ctx, (void (*)())keylog_callback);
+#endif
+ }
/* Automatically re-try reads/writes after renegotiation. */
-(void) SSL_CTX_set_mode(*ctxp, SSL_MODE_AUTO_RETRY);
+(void) SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);
/* Apply administrator-supplied work-arounds.
Historically we applied just one requested option,
No OpenSSL version number checks: the options we accept depend upon the
availability of the option value macros from OpenSSL. */
-okay = tls_openssl_options_parse(openssl_options, &init_options);
-if (!okay)
- return tls_error(US"openssl_options parsing failed", host, NULL);
+if (!tls_openssl_options_parse(openssl_options, &init_options))
+ return tls_error(US"openssl_options parsing failed", host, NULL, errstr);
+#ifdef EXPERIMENTAL_TLS_RESUME
+tlsp->resumption = RESUME_SUPPORTED;
+#endif
if (init_options)
{
+#ifdef EXPERIMENTAL_TLS_RESUME
+ /* Should the server offer session resumption? */
+ if (!host && verify_check_host(&tls_resumption_hosts) == OK)
+ {
+ DEBUG(D_tls) debug_printf("tls_resumption_hosts overrides openssl_options\n");
+ init_options &= ~SSL_OP_NO_TICKET;
+ tlsp->resumption |= RESUME_SERVER_TICKET; /* server will give ticket on request */
+ tlsp->host_resumable = TRUE;
+ }
+#endif
+
DEBUG(D_tls) debug_printf("setting SSL CTX options: %#lx\n", init_options);
- if (!(SSL_CTX_set_options(*ctxp, init_options)))
+ if (!(SSL_CTX_set_options(ctx, init_options)))
return tls_error(string_sprintf(
- "SSL_CTX_set_option(%#lx)", init_options), host, NULL);
+ "SSL_CTX_set_option(%#lx)", init_options), host, NULL, errstr);
}
else
DEBUG(D_tls) debug_printf("no SSL CTX options to set\n");
+/* We'd like to disable session cache unconditionally, but foolish Outlook
+Express clients then give up the first TLS connection and make a second one
+(which works). Only when there is an IMAP service on the same machine.
+Presumably OE is trying to use the cache for A on B. Leave it enabled for
+now, until we work out a decent way of presenting control to the config. It
+will never be used because we use a new context every time. */
+#ifdef notdef
+(void) SSL_CTX_set_session_cache_mode(ctx, SSL_SESS_CACHE_OFF);
+#endif
+
/* Initialize with DH parameters if supplied */
/* Initialize ECDH temp key parameter selection */
-if ( !init_dh(*ctxp, dhparam, host)
- || !init_ecdh(*ctxp, host)
+if ( !init_dh(ctx, dhparam, host, errstr)
+ || !init_ecdh(ctx, host, errstr)
)
return DEFER;
/* Set up certificate and key (and perhaps OCSP info) */
-if ((rc = tls_expand_session_files(*ctxp, cbinfo)) != OK)
+if ((rc = tls_expand_session_files(ctx, cbinfo, errstr)) != OK)
return rc;
/* If we need to handle SNI or OCSP, do so */
}
# endif
-if (host == NULL) /* server */
+if (!host) /* server */
{
# ifndef DISABLE_OCSP
/* We check u_ocsp.server.file, not server.response, because we care about if
callback is invoked. */
if (cbinfo->u_ocsp.server.file)
{
- SSL_CTX_set_tlsext_status_cb(server_ctx, tls_server_stapling_cb);
- SSL_CTX_set_tlsext_status_arg(server_ctx, cbinfo);
+ SSL_CTX_set_tlsext_status_cb(ctx, tls_server_stapling_cb);
+ SSL_CTX_set_tlsext_status_arg(ctx, cbinfo);
}
# endif
/* We always do this, so that $tls_sni is available even if not used in
tls_certificate */
- SSL_CTX_set_tlsext_servername_callback(*ctxp, tls_servername_cb);
- SSL_CTX_set_tlsext_servername_arg(*ctxp, cbinfo);
+ SSL_CTX_set_tlsext_servername_callback(ctx, tls_servername_cb);
+ SSL_CTX_set_tlsext_servername_arg(ctx, cbinfo);
}
# ifndef DISABLE_OCSP
else /* client */
DEBUG(D_tls) debug_printf("failed to create store for stapling verify\n");
return FAIL;
}
- SSL_CTX_set_tlsext_status_cb(*ctxp, tls_client_stapling_cb);
- SSL_CTX_set_tlsext_status_arg(*ctxp, cbinfo);
+ SSL_CTX_set_tlsext_status_cb(ctx, tls_client_stapling_cb);
+ SSL_CTX_set_tlsext_status_arg(ctx, cbinfo);
}
# endif
#endif
#ifdef EXIM_HAVE_EPHEM_RSA_KEX
/* Set up the RSA callback */
-SSL_CTX_set_tmp_rsa_callback(*ctxp, rsa_callback);
+SSL_CTX_set_tmp_rsa_callback(ctx, rsa_callback);
#endif
-/* Finally, set the timeout, and we are done */
+/* Finally, set the session cache timeout, and we are done.
+The period appears to be also used for (server-generated) session tickets */
-SSL_CTX_set_timeout(*ctxp, ssl_session_timeout);
+SSL_CTX_set_timeout(ctx, ssl_session_timeout);
DEBUG(D_tls) debug_printf("Initialized TLS\n");
*cbp = cbinfo;
+*ctxp = ctx;
return OK;
}
/*
Argument: pointer to an SSL structure for the connection
- buffer to use for answer
- size of buffer
pointer to number of bits for cipher
-Returns: nothing
+Returns: pointer to allocated string in perm-pool
*/
-static void
-construct_cipher_name(SSL *ssl, uschar *cipherbuf, int bsize, int *bits)
+static uschar *
+construct_cipher_name(SSL * ssl, int * bits)
{
-/* With OpenSSL 1.0.0a, this needs to be const but the documentation doesn't
+int pool = store_pool;
+/* With OpenSSL 1.0.0a, 'c' needs to be const but the documentation doesn't
yet reflect that. It should be a safe change anyway, even 0.9.8 versions have
the accessor functions use const in the prototype. */
-const SSL_CIPHER *c;
-const uschar *ver;
-ver = (const uschar *)SSL_get_version(ssl);
+const uschar * ver = CUS SSL_get_version(ssl);
+const SSL_CIPHER * c = (const SSL_CIPHER *) SSL_get_current_cipher(ssl);
+uschar * s;
-c = (const SSL_CIPHER *) SSL_get_current_cipher(ssl);
SSL_CIPHER_get_bits(c, bits);
-string_format(cipherbuf, bsize, "%s:%s:%u", ver,
- SSL_CIPHER_get_name(c), *bits);
+store_pool = POOL_PERM;
+s = string_sprintf("%s:%s:%u", ver, SSL_CIPHER_get_name(c), *bits);
+store_pool = pool;
+DEBUG(D_tls) debug_printf("Cipher: %s\n", s);
+return s;
+}
+
-DEBUG(D_tls) debug_printf("Cipher: %s\n", cipherbuf);
+/* Get IETF-standard name for ciphersuite.
+Argument: pointer to an SSL structure for the connection
+Returns: pointer to string
+*/
+
+static const uschar *
+cipher_stdname_ssl(SSL * ssl)
+{
+#ifdef EXIM_HAVE_OPENSSL_CIPHER_STD_NAME
+return CUS SSL_CIPHER_standard_name(SSL_get_current_cipher(ssl));
+#else
+ushort id = 0xffff & SSL_CIPHER_get_id(SSL_get_current_cipher(ssl));
+return cipher_stdname(id >> 8, id & 0xff);
+#endif
}
static void
-peer_cert(SSL * ssl, tls_support * tlsp, uschar * peerdn, unsigned bsize)
+peer_cert(SSL * ssl, tls_support * tlsp, uschar * peerdn, unsigned siz)
{
/*XXX we might consider a list-of-certs variable for the cert chain.
SSL_get_peer_cert_chain(SSL*). We'd need a new variable type and support
in list-handling functions, also consider the difference between the entire
chain and the elements sent by the peer. */
+tlsp->peerdn = NULL;
+
/* Will have already noted peercert on a verify fail; possibly not the leaf */
if (!tlsp->peercert)
tlsp->peercert = SSL_get_peer_certificate(ssl);
/* Beware anonymous ciphers which lead to server_cert being NULL */
if (tlsp->peercert)
- {
- X509_NAME_oneline(X509_get_subject_name(tlsp->peercert), CS peerdn, bsize);
- peerdn[bsize-1] = '\0';
- tlsp->peerdn = peerdn; /*XXX a static buffer... */
- }
-else
- tlsp->peerdn = NULL;
+ if (!X509_NAME_oneline(X509_get_subject_name(tlsp->peercert), CS peerdn, siz))
+ { DEBUG(D_tls) debug_printf("X509_NAME_oneline() error\n"); }
+ else
+ {
+ int oldpool = store_pool;
+
+ peerdn[siz-1] = '\0'; /* paranoia */
+ store_pool = POOL_PERM;
+ tlsp->peerdn = string_copy(peerdn);
+ store_pool = oldpool;
+
+ /* We used to set CV in the cert-verify callbacks (either plain or dane)
+ but they don't get called on session-resumption. So use the official
+ interface, which uses the resumed value. Unfortunately this claims verified
+ when it actually failed but we're in try-verify mode, due to us wanting the
+ knowlege that it failed so needing to have the callback and forcing a
+ permissive return. If we don't force it, the TLS startup is failed.
+ The extra bit of information is set in verify_override in the cb, stashed
+ for resumption next to the TLS session, and used here. */
+
+ if (!tlsp->verify_override)
+ tlsp->certificate_verified = SSL_get_verify_result(ssl) == X509_V_OK;
+ }
}
* Set up for verifying certificates *
*************************************************/
+#ifndef DISABLE_OCSP
/* Load certs from file, return TRUE on success */
static BOOL
BIO * bp;
X509 * x;
+while (sk_X509_num(verify_stack) > 0)
+ X509_free(sk_X509_pop(verify_stack));
+
if (!(bp = BIO_new_file(CS file, "r"))) return FALSE;
while ((x = PEM_read_bio_X509(bp, NULL, 0, NULL)))
sk_X509_push(verify_stack, x);
BIO_free(bp);
return TRUE;
}
+#endif
-/* Called by both client and server startup
+/* Called by both client and server startup; on the server possibly
+repeated after a Server Name Indication.
Arguments:
sctx SSL_CTX* to initialise
optional TRUE if called from a server for a host in tls_try_verify_hosts;
otherwise passed as FALSE
cert_vfy_cb Callback function for certificate verification
+ errstr error string pointer
Returns: OK/DEFER/FAIL
*/
static int
setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional,
- int (*cert_vfy_cb)(int, X509_STORE_CTX *) )
+ int (*cert_vfy_cb)(int, X509_STORE_CTX *), uschar ** errstr)
{
uschar *expcerts, *expcrl;
-if (!expand_check(certs, US"tls_verify_certificates", &expcerts))
+if (!expand_check(certs, US"tls_verify_certificates", &expcerts, errstr))
return DEFER;
+DEBUG(D_tls) debug_printf("tls_verify_certificates: %s\n", expcerts);
if (expcerts && *expcerts)
{
CA bundle. Then add the ones specified in the config, if any. */
if (!SSL_CTX_set_default_verify_paths(sctx))
- return tls_error(US"SSL_CTX_set_default_verify_paths", host, NULL);
+ return tls_error(US"SSL_CTX_set_default_verify_paths", host, NULL, errstr);
if (Ustrcmp(expcerts, "system") != 0)
{
)
{
log_write(0, LOG_MAIN|LOG_PANIC,
- "failed to load cert hain from %s", file);
+ "failed to load cert chain from %s", file);
return DEFER;
}
#endif
if ( (!file || statbuf.st_size > 0)
&& !SSL_CTX_load_verify_locations(sctx, CS file, CS dir))
- return tls_error(US"SSL_CTX_load_verify_locations", host, NULL);
+ return tls_error(US"SSL_CTX_load_verify_locations", host, NULL, errstr);
/* Load the list of CAs for which we will accept certs, for sending
to the client. This is only for the one-file tls_verify_certificates
variant.
- If a list isn't loaded into the server, but
- some verify locations are set, the server end appears to make
- a wildcard request for client certs.
+ If a list isn't loaded into the server, but some verify locations are set,
+ the server end appears to make a wildcard request for client certs.
Meanwhile, the client library as default behaviour *ignores* the list
we send over the wire - see man SSL_CTX_set_client_cert_cb.
Because of this, and that the dir variant is likely only used for
- the public-CA bundle (not for a private CA), not worth fixing.
- */
+ the public-CA bundle (not for a private CA), not worth fixing. */
+
if (file)
{
STACK_OF(X509_NAME) * names = SSL_load_client_CA_file(CS file);
+ SSL_CTX_set_client_CA_list(sctx, names);
DEBUG(D_tls) debug_printf("Added %d certificate authorities.\n",
sk_X509_NAME_num(names));
- SSL_CTX_set_client_CA_list(sctx, names);
}
}
}
OpenSSL will then handle the verify against CA certs and CRLs by
itself in the verify callback." */
- if (!expand_check(crl, US"tls_crl", &expcrl)) return DEFER;
+ if (!expand_check(crl, US"tls_crl", &expcrl, errstr)) return DEFER;
if (expcrl && *expcrl)
{
struct stat statbufcrl;
DEBUG(D_tls) debug_printf("SSL CRL value is a file %s\n", file);
}
if (X509_STORE_load_locations(cvstore, CS file, CS dir) == 0)
- return tls_error(US"X509_STORE_load_locations", host, NULL);
+ return tls_error(US"X509_STORE_load_locations", host, NULL, errstr);
/* setting the flags to check against the complete crl chain */
/* If verification is optional, don't fail if no certificate */
SSL_CTX_set_verify(sctx,
- SSL_VERIFY_PEER | (optional? 0 : SSL_VERIFY_FAIL_IF_NO_PEER_CERT),
+ SSL_VERIFY_PEER | (optional ? 0 : SSL_VERIFY_FAIL_IF_NO_PEER_CERT),
cert_vfy_cb);
}
Arguments:
require_ciphers allowed ciphers
+ errstr pointer to error message
Returns: OK on success
DEFER for errors before the start of the negotiation
*/
int
-tls_server_start(const uschar *require_ciphers)
+tls_server_start(const uschar * require_ciphers, uschar ** errstr)
{
int rc;
-uschar *expciphers;
-tls_ext_ctx_cb *cbinfo;
+uschar * expciphers;
+tls_ext_ctx_cb * cbinfo;
static uschar peerdn[256];
-static uschar cipherbuf[256];
/* Check for previous activation */
-if (tls_in.active >= 0)
+if (tls_in.active.sock >= 0)
{
- tls_error(US"STARTTLS received after TLS started", NULL, US"");
- smtp_printf("554 Already in TLS\r\n");
+ tls_error(US"STARTTLS received after TLS started", NULL, US"", errstr);
+ smtp_printf("554 Already in TLS\r\n", FALSE);
return FAIL;
}
rc = tls_init(&server_ctx, NULL, tls_dhparam, tls_certificate, tls_privatekey,
#ifndef DISABLE_OCSP
- tls_ocsp_file,
+ tls_ocsp_file, /*XXX stack*/
#endif
- NULL, &server_static_cbinfo);
+ NULL, &server_static_cbinfo, &tls_in, errstr);
if (rc != OK) return rc;
cbinfo = server_static_cbinfo;
-if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers))
+if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers, errstr))
return FAIL;
/* In OpenSSL, cipher components are separated by hyphens. In GnuTLS, they
were historically separated by underscores. So that I can use either form in my
tests, and also for general convenience, we turn underscores into hyphens here.
+
+XXX SSL_CTX_set_cipher_list() is replaced by SSL_CTX_set_ciphersuites()
+for TLS 1.3 . Since we do not call it at present we get the default list:
+TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256
*/
if (expciphers)
{
- uschar * s = expciphers;
- while (*s != 0) { if (*s == '_') *s = '-'; s++; }
+ for (uschar * s = expciphers; *s; s++ ) if (*s == '_') *s = '-';
DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers);
if (!SSL_CTX_set_cipher_list(server_ctx, CS expciphers))
- return tls_error(US"SSL_CTX_set_cipher_list", NULL, NULL);
+ return tls_error(US"SSL_CTX_set_cipher_list", NULL, NULL, errstr);
cbinfo->server_cipher_list = expciphers;
}
optional, set up appropriately. */
tls_in.certificate_verified = FALSE;
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
tls_in.dane_verified = FALSE;
#endif
server_verify_callback_called = FALSE;
if (verify_check_host(&tls_verify_hosts) == OK)
{
rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL,
- FALSE, verify_callback_server);
+ FALSE, verify_callback_server, errstr);
if (rc != OK) return rc;
server_verify_optional = FALSE;
}
else if (verify_check_host(&tls_try_verify_hosts) == OK)
{
rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL,
- TRUE, verify_callback_server);
+ TRUE, verify_callback_server, errstr);
if (rc != OK) return rc;
server_verify_optional = TRUE;
}
+#ifdef EXPERIMENTAL_TLS_RESUME
+SSL_CTX_set_tlsext_ticket_key_cb(server_ctx, ticket_key_callback);
+/* despite working, appears to always return failure, so ignoring */
+#endif
+#ifdef OPENSSL_HAVE_NUM_TICKETS
+# ifdef EXPERIMENTAL_TLS_RESUME
+SSL_CTX_set_num_tickets(server_ctx, tls_in.host_resumable ? 1 : 0);
+# else
+SSL_CTX_set_num_tickets(server_ctx, 0); /* send no TLS1.3 stateful-tickets */
+# endif
+#endif
+
+
/* Prepare for new connection */
-if (!(server_ssl = SSL_new(server_ctx))) return tls_error(US"SSL_new", NULL, NULL);
+if (!(server_ssl = SSL_new(server_ctx)))
+ return tls_error(US"SSL_new", NULL, NULL, errstr);
/* Warning: we used to SSL_clear(ssl) here, it was removed.
*
SSL_set_session_id_context(server_ssl, sid_ctx, Ustrlen(sid_ctx));
if (!tls_in.on_connect)
{
- smtp_printf("220 TLS go ahead\r\n");
+ smtp_printf("220 TLS go ahead\r\n", FALSE);
fflush(smtp_out);
}
DEBUG(D_tls) debug_printf("Calling SSL_accept\n");
sigalrm_seen = FALSE;
-if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
+if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout);
rc = SSL_accept(server_ssl);
-alarm(0);
+ALARM_CLR(0);
if (rc <= 0)
{
- tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : NULL);
- if (ERR_get_error() == 0)
- log_write(0, LOG_MAIN,
- "TLS client disconnected cleanly (rejected our certificate?)");
+ (void) tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : NULL, errstr);
return FAIL;
}
DEBUG(D_tls) debug_printf("SSL_accept was successful\n");
+ERR_clear_error(); /* Even success can leave errors in the stack. Seen with
+ anon-authentication ciphersuite negotiated. */
+
+#ifdef EXPERIMENTAL_TLS_RESUME
+if (SSL_session_reused(server_ssl))
+ {
+ tls_in.resumption |= RESUME_USED;
+ DEBUG(D_tls) debug_printf("Session reused\n");
+ }
+#endif
/* TLS has been set up. Adjust the input functions to read via TLS,
and initialize things. */
peer_cert(server_ssl, &tls_in, peerdn, sizeof(peerdn));
-construct_cipher_name(server_ssl, cipherbuf, sizeof(cipherbuf), &tls_in.bits);
-tls_in.cipher = cipherbuf;
+tls_in.cipher = construct_cipher_name(server_ssl, &tls_in.bits);
+tls_in.cipher_stdname = cipher_stdname_ssl(server_ssl);
DEBUG(D_tls)
{
uschar buf[2048];
- if (SSL_get_shared_ciphers(server_ssl, CS buf, sizeof(buf)) != NULL)
+ if (SSL_get_shared_ciphers(server_ssl, CS buf, sizeof(buf)))
debug_printf("Shared ciphers: %s\n", buf);
+
+#ifdef EXIM_HAVE_OPENSSL_KEYLOG
+ {
+ BIO * bp = BIO_new_fp(debug_file, BIO_NOCLOSE);
+ SSL_SESSION_print_keylog(bp, SSL_get_session(server_ssl));
+ BIO_free(bp);
+ }
+#endif
+
+#ifdef EXIM_HAVE_SESSION_TICKET
+ {
+ SSL_SESSION * ss = SSL_get_session(server_ssl);
+ if (SSL_SESSION_has_ticket(ss)) /* 1.1.0 */
+ debug_printf("The session has a ticket, life %lu seconds\n",
+ SSL_SESSION_get_ticket_lifetime_hint(ss));
+ }
+#endif
}
/* Record the certificate we presented */
smtp_read_response()/ip_recv().
Hence no need to duplicate for _in and _out.
*/
-ssl_xfer_buffer = store_malloc(ssl_xfer_buffer_size);
+if (!ssl_xfer_buffer) ssl_xfer_buffer = store_malloc(ssl_xfer_buffer_size);
ssl_xfer_buffer_lwm = ssl_xfer_buffer_hwm = 0;
-ssl_xfer_eof = ssl_xfer_error = 0;
+ssl_xfer_eof = ssl_xfer_error = FALSE;
receive_getc = tls_getc;
+receive_getbuf = tls_getbuf;
receive_get_cache = tls_get_cache;
receive_ungetc = tls_ungetc;
receive_feof = tls_feof;
receive_ferror = tls_ferror;
receive_smtp_buffered = tls_smtp_buffered;
-tls_in.active = fileno(smtp_out);
+tls_in.active.sock = fileno(smtp_out);
+tls_in.active.tls_ctx = NULL; /* not using explicit ctx for server-side */
return OK;
}
static int
tls_client_basic_ctx_init(SSL_CTX * ctx,
- host_item * host, smtp_transport_options_block * ob, tls_ext_ctx_cb * cbinfo
- )
+ host_item * host, smtp_transport_options_block * ob, tls_ext_ctx_cb * cbinfo,
+ uschar ** errstr)
{
int rc;
/* stick to the old behaviour for compatibility if tls_verify_certificates is
if ( ( !ob->tls_verify_hosts
&& (!ob->tls_try_verify_hosts || !*ob->tls_try_verify_hosts)
)
- || (verify_check_given_host(&ob->tls_verify_hosts, host) == OK)
+ || verify_check_given_host(CUSS &ob->tls_verify_hosts, host) == OK
)
client_verify_optional = FALSE;
-else if (verify_check_given_host(&ob->tls_try_verify_hosts, host) == OK)
+else if (verify_check_given_host(CUSS &ob->tls_try_verify_hosts, host) == OK)
client_verify_optional = TRUE;
else
return OK;
if ((rc = setup_certs(ctx, ob->tls_verify_certificates,
- ob->tls_crl, host, client_verify_optional, verify_callback_client)) != OK)
+ ob->tls_crl, host, client_verify_optional, verify_callback_client,
+ errstr)) != OK)
return rc;
-if (verify_check_given_host(&ob->tls_verify_cert_hostnames, host) == OK)
+if (verify_check_given_host(CUSS &ob->tls_verify_cert_hostnames, host) == OK)
{
cbinfo->verify_cert_hostnames =
#ifdef SUPPORT_I18N
}
-#ifdef EXPERIMENTAL_DANE
+#ifdef SUPPORT_DANE
static int
-dane_tlsa_load(SSL * ssl, host_item * host, dns_answer * dnsa)
+dane_tlsa_load(SSL * ssl, host_item * host, dns_answer * dnsa, uschar ** errstr)
{
-dns_record * rr;
dns_scan dnss;
const char * hostnames[2] = { CS host->name, NULL };
int found = 0;
if (DANESSL_init(ssl, NULL, hostnames) != 1)
- return tls_error(US"hostnames load", host, NULL);
+ return tls_error(US"hostnames load", host, NULL, errstr);
-for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
- rr;
+for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr;
rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
- ) if (rr->type == T_TLSA)
+ ) if (rr->type == T_TLSA && rr->size > 3)
{
const uschar * p = rr->data;
uint8_t usage, selector, mtype;
switch (DANESSL_add_tlsa(ssl, usage, selector, mdname, p, rr->size - 3))
{
default:
- return tls_error(US"tlsa load", host, NULL);
+ return tls_error(US"tlsa load", host, NULL, errstr);
case 0: /* action not taken */
case 1: break;
}
log_write(0, LOG_MAIN, "DANE error: No usable TLSA records");
return DEFER;
}
-#endif /*EXPERIMENTAL_DANE*/
+#endif /*SUPPORT_DANE*/
+#ifdef EXPERIMENTAL_TLS_RESUME
+/* On the client, get any stashed session for the given IP from hints db
+and apply it to the ssl-connection for attempted resumption. */
+
+static void
+tls_retrieve_session(tls_support * tlsp, SSL * ssl, const uschar * key)
+{
+tlsp->resumption |= RESUME_SUPPORTED;
+if (tlsp->host_resumable)
+ {
+ dbdata_tls_session * dt;
+ int len;
+ open_db dbblock, * dbm_file;
+
+ tlsp->resumption |= RESUME_CLIENT_REQUESTED;
+ DEBUG(D_tls) debug_printf("checking for resumable session for %s\n", key);
+ if ((dbm_file = dbfn_open(US"tls", O_RDONLY, &dbblock, FALSE, FALSE)))
+ {
+ /* key for the db is the IP */
+ if ((dt = dbfn_read_with_length(dbm_file, key, &len)))
+ {
+ SSL_SESSION * ss = NULL;
+ const uschar * sess_asn1 = dt->session;
+
+ len -= sizeof(dbdata_tls_session);
+ if (!(d2i_SSL_SESSION(&ss, &sess_asn1, (long)len)))
+ {
+ DEBUG(D_tls)
+ {
+ ERR_error_string_n(ERR_get_error(),
+ ssl_errstring, sizeof(ssl_errstring));
+ debug_printf("decoding session: %s\n", ssl_errstring);
+ }
+ }
+#ifdef EXIM_HAVE_SESSION_TICKET
+ else if ( SSL_SESSION_get_ticket_lifetime_hint(ss) + dt->time_stamp
+ < time(NULL))
+ {
+ DEBUG(D_tls) debug_printf("session expired\n");
+ dbfn_delete(dbm_file, key);
+ }
+#endif
+ else if (!SSL_set_session(ssl, ss))
+ {
+ DEBUG(D_tls)
+ {
+ ERR_error_string_n(ERR_get_error(),
+ ssl_errstring, sizeof(ssl_errstring));
+ debug_printf("applying session to ssl: %s\n", ssl_errstring);
+ }
+ }
+ else
+ {
+ DEBUG(D_tls) debug_printf("good session\n");
+ tlsp->resumption |= RESUME_CLIENT_SUGGESTED;
+ tlsp->verify_override = dt->verify_override;
+ tlsp->ocsp = dt->ocsp;
+ }
+ }
+ else
+ DEBUG(D_tls) debug_printf("no session record\n");
+ dbfn_close(dbm_file);
+ }
+ }
+}
+
+
+/* On the client, save the session for later resumption */
+
+static int
+tls_save_session_cb(SSL * ssl, SSL_SESSION * ss)
+{
+tls_ext_ctx_cb * cbinfo = SSL_get_ex_data(ssl, tls_exdata_idx);
+tls_support * tlsp;
+
+DEBUG(D_tls) debug_printf("tls_save_session_cb\n");
+
+if (!cbinfo || !(tlsp = cbinfo->tlsp)->host_resumable) return 0;
+
+# ifdef OPENSSL_HAVE_NUM_TICKETS
+if (SSL_SESSION_is_resumable(ss)) /* 1.1.1 */
+# endif
+ {
+ int len = i2d_SSL_SESSION(ss, NULL);
+ int dlen = sizeof(dbdata_tls_session) + len;
+ dbdata_tls_session * dt = store_get(dlen, TRUE);
+ uschar * s = dt->session;
+ open_db dbblock, * dbm_file;
+
+ DEBUG(D_tls) debug_printf("session is resumable\n");
+ tlsp->resumption |= RESUME_SERVER_TICKET; /* server gave us a ticket */
+
+ dt->verify_override = tlsp->verify_override;
+ dt->ocsp = tlsp->ocsp;
+ (void) i2d_SSL_SESSION(ss, &s); /* s gets bumped to end */
+
+ if ((dbm_file = dbfn_open(US"tls", O_RDWR, &dbblock, FALSE, FALSE)))
+ {
+ const uschar * key = cbinfo->host->address;
+ dbfn_delete(dbm_file, key);
+ dbfn_write(dbm_file, key, dt, dlen);
+ dbfn_close(dbm_file);
+ DEBUG(D_tls) debug_printf("wrote session (len %u) to db\n",
+ (unsigned)dlen);
+ }
+ }
+return 1;
+}
+
+
+static void
+tls_client_ctx_resume_prehandshake(
+ exim_openssl_client_tls_ctx * exim_client_ctx, tls_support * tlsp,
+ smtp_transport_options_block * ob, host_item * host)
+{
+/* Should the client request a session resumption ticket? */
+if (verify_check_given_host(CUSS &ob->tls_resumption_hosts, host) == OK)
+ {
+ tlsp->host_resumable = TRUE;
+
+ SSL_CTX_set_session_cache_mode(exim_client_ctx->ctx,
+ SSL_SESS_CACHE_CLIENT
+ | SSL_SESS_CACHE_NO_INTERNAL | SSL_SESS_CACHE_NO_AUTO_CLEAR);
+ SSL_CTX_sess_set_new_cb(exim_client_ctx->ctx, tls_save_session_cb);
+ }
+}
+
+static BOOL
+tls_client_ssl_resume_prehandshake(SSL * ssl, tls_support * tlsp,
+ host_item * host, uschar ** errstr)
+{
+if (tlsp->host_resumable)
+ {
+ DEBUG(D_tls)
+ debug_printf("tls_resumption_hosts overrides openssl_options, enabling tickets\n");
+ SSL_clear_options(ssl, SSL_OP_NO_TICKET);
+
+ tls_exdata_idx = SSL_get_ex_new_index(0, 0, 0, 0, 0);
+ if (!SSL_set_ex_data(ssl, tls_exdata_idx, client_static_cbinfo))
+ {
+ tls_error(US"set ex_data", host, NULL, errstr);
+ return FALSE;
+ }
+ debug_printf("tls_exdata_idx %d cbinfo %p\n", tls_exdata_idx, client_static_cbinfo);
+ }
+
+tlsp->resumption = RESUME_SUPPORTED;
+/* Pick up a previous session, saved on an old ticket */
+tls_retrieve_session(tlsp, ssl, host->address);
+return TRUE;
+}
+
+static void
+tls_client_resume_posthandshake(exim_openssl_client_tls_ctx * exim_client_ctx,
+ tls_support * tlsp)
+{
+if (SSL_session_reused(exim_client_ctx->ssl))
+ {
+ DEBUG(D_tls) debug_printf("The session was reused\n");
+ tlsp->resumption |= RESUME_USED;
+ }
+}
+#endif /* EXPERIMENTAL_TLS_RESUME */
+
+
/*************************************************
* Start a TLS session in a client *
*************************************************/
/* Called from the smtp transport after STARTTLS has been accepted.
-Argument:
- fd the fd of the connection
- host connected host (for messages)
- addr the first address
- tb transport (always smtp)
- tlsa_dnsa tlsa lookup, if DANE, else null
-
-Returns: OK on success
- FAIL otherwise - note that tls_error() will not give DEFER
- because this is not a server
+Arguments:
+ cctx connection context
+ conn_args connection details
+ cookie datum for randomness; can be NULL
+ tlsp record details of TLS channel configuration here; must be non-NULL
+ errstr error string pointer
+
+Returns: TRUE for success with TLS session context set in connection context,
+ FALSE on error
*/
-int
-tls_client_start(int fd, host_item *host, address_item *addr,
- transport_instance *tb
-#ifdef EXPERIMENTAL_DANE
- , dns_answer * tlsa_dnsa
-#endif
- )
+BOOL
+tls_client_start(client_conn_ctx * cctx, smtp_connect_args * conn_args,
+ void * cookie, tls_support * tlsp, uschar ** errstr)
{
-smtp_transport_options_block * ob =
- (smtp_transport_options_block *)tb->options_block;
-static uschar peerdn[256];
+host_item * host = conn_args->host; /* for msgs and option-tests */
+transport_instance * tb = conn_args->tblock; /* always smtp or NULL */
+smtp_transport_options_block * ob = tb
+ ? (smtp_transport_options_block *)tb->options_block
+ : &smtp_transport_option_defaults;
+exim_openssl_client_tls_ctx * exim_client_ctx;
uschar * expciphers;
int rc;
-static uschar cipherbuf[256];
+static uschar peerdn[256];
#ifndef DISABLE_OCSP
BOOL request_ocsp = FALSE;
BOOL require_ocsp = FALSE;
#endif
-#ifdef EXPERIMENTAL_DANE
-tls_out.tlsa_usage = 0;
+rc = store_pool;
+store_pool = POOL_PERM;
+exim_client_ctx = store_get(sizeof(exim_openssl_client_tls_ctx), FALSE);
+exim_client_ctx->corked = NULL;
+store_pool = rc;
+
+#ifdef SUPPORT_DANE
+tlsp->tlsa_usage = 0;
#endif
#ifndef DISABLE_OCSP
{
-# ifdef EXPERIMENTAL_DANE
- if ( tlsa_dnsa
+# ifdef SUPPORT_DANE
+ if ( conn_args->dane
&& ob->hosts_request_ocsp[0] == '*'
&& ob->hosts_request_ocsp[1] == '\0'
)
# endif
if ((require_ocsp =
- verify_check_given_host(&ob->hosts_require_ocsp, host) == OK))
+ verify_check_given_host(CUSS &ob->hosts_require_ocsp, host) == OK))
request_ocsp = TRUE;
else
-# ifdef EXPERIMENTAL_DANE
+# ifdef SUPPORT_DANE
if (!request_ocsp)
# endif
request_ocsp =
- verify_check_given_host(&ob->hosts_request_ocsp, host) == OK;
+ verify_check_given_host(CUSS &ob->hosts_request_ocsp, host) == OK;
}
#endif
-rc = tls_init(&client_ctx, host, NULL,
+rc = tls_init(&exim_client_ctx->ctx, host, NULL,
ob->tls_certificate, ob->tls_privatekey,
#ifndef DISABLE_OCSP
(void *)(long)request_ocsp,
#endif
- addr, &client_static_cbinfo);
-if (rc != OK) return rc;
+ cookie, &client_static_cbinfo, tlsp, errstr);
+if (rc != OK) return FALSE;
-tls_out.certificate_verified = FALSE;
+tlsp->certificate_verified = FALSE;
client_verify_callback_called = FALSE;
-if (!expand_check(ob->tls_require_ciphers, US"tls_require_ciphers",
- &expciphers))
- return FAIL;
+expciphers = NULL;
+#ifdef SUPPORT_DANE
+if (conn_args->dane)
+ {
+ /* We fall back to tls_require_ciphers if unset, empty or forced failure, but
+ other failures should be treated as problems. */
+ if (ob->dane_require_tls_ciphers &&
+ !expand_check(ob->dane_require_tls_ciphers, US"dane_require_tls_ciphers",
+ &expciphers, errstr))
+ return FALSE;
+ if (expciphers && *expciphers == '\0')
+ expciphers = NULL;
+ }
+#endif
+if (!expciphers &&
+ !expand_check(ob->tls_require_ciphers, US"tls_require_ciphers",
+ &expciphers, errstr))
+ return FALSE;
/* In OpenSSL, cipher components are separated by hyphens. In GnuTLS, they
are separated by underscores. So that I can use either form in my tests, and
also for general convenience, we turn underscores into hyphens here. */
-if (expciphers != NULL)
+if (expciphers)
{
uschar *s = expciphers;
- while (*s != 0) { if (*s == '_') *s = '-'; s++; }
+ while (*s) { if (*s == '_') *s = '-'; s++; }
DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers);
- if (!SSL_CTX_set_cipher_list(client_ctx, CS expciphers))
- return tls_error(US"SSL_CTX_set_cipher_list", host, NULL);
+ if (!SSL_CTX_set_cipher_list(exim_client_ctx->ctx, CS expciphers))
+ {
+ tls_error(US"SSL_CTX_set_cipher_list", host, NULL, errstr);
+ return FALSE;
+ }
}
-#ifdef EXPERIMENTAL_DANE
-if (tlsa_dnsa)
+#ifdef SUPPORT_DANE
+if (conn_args->dane)
{
- SSL_CTX_set_verify(client_ctx,
+ SSL_CTX_set_verify(exim_client_ctx->ctx,
SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT,
verify_callback_client_dane);
if (!DANESSL_library_init())
- return tls_error(US"library init", host, NULL);
- if (DANESSL_CTX_init(client_ctx) <= 0)
- return tls_error(US"context init", host, NULL);
+ {
+ tls_error(US"library init", host, NULL, errstr);
+ return FALSE;
+ }
+ if (DANESSL_CTX_init(exim_client_ctx->ctx) <= 0)
+ {
+ tls_error(US"context init", host, NULL, errstr);
+ return FALSE;
+ }
}
else
#endif
- if ((rc = tls_client_basic_ctx_init(client_ctx, host, ob, client_static_cbinfo))
- != OK)
- return rc;
+ if (tls_client_basic_ctx_init(exim_client_ctx->ctx, host, ob,
+ client_static_cbinfo, errstr) != OK)
+ return FALSE;
+
+#ifdef EXPERIMENTAL_TLS_RESUME
+tls_client_ctx_resume_prehandshake(exim_client_ctx, tlsp, ob, host);
+#endif
+
+
+if (!(exim_client_ctx->ssl = SSL_new(exim_client_ctx->ctx)))
+ {
+ tls_error(US"SSL_new", host, NULL, errstr);
+ return FALSE;
+ }
+SSL_set_session_id_context(exim_client_ctx->ssl, sid_ctx, Ustrlen(sid_ctx));
-if ((client_ssl = SSL_new(client_ctx)) == NULL)
- return tls_error(US"SSL_new", host, NULL);
-SSL_set_session_id_context(client_ssl, sid_ctx, Ustrlen(sid_ctx));
-SSL_set_fd(client_ssl, fd);
-SSL_set_connect_state(client_ssl);
+SSL_set_fd(exim_client_ctx->ssl, cctx->sock);
+SSL_set_connect_state(exim_client_ctx->ssl);
if (ob->tls_sni)
{
- if (!expand_check(ob->tls_sni, US"tls_sni", &tls_out.sni))
- return FAIL;
- if (tls_out.sni == NULL)
+ if (!expand_check(ob->tls_sni, US"tls_sni", &tlsp->sni, errstr))
+ return FALSE;
+ if (!tlsp->sni)
{
DEBUG(D_tls) debug_printf("Setting TLS SNI forced to fail, not sending\n");
}
- else if (!Ustrlen(tls_out.sni))
- tls_out.sni = NULL;
+ else if (!Ustrlen(tlsp->sni))
+ tlsp->sni = NULL;
else
{
#ifdef EXIM_HAVE_OPENSSL_TLSEXT
- DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tls_out.sni);
- SSL_set_tlsext_host_name(client_ssl, tls_out.sni);
+ DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tlsp->sni);
+ SSL_set_tlsext_host_name(exim_client_ctx->ssl, tlsp->sni);
#else
log_write(0, LOG_MAIN, "SNI unusable with this OpenSSL library version; ignoring \"%s\"\n",
- tls_out.sni);
+ tlsp->sni);
#endif
}
}
-#ifdef EXPERIMENTAL_DANE
-if (tlsa_dnsa)
- if ((rc = dane_tlsa_load(client_ssl, host, tlsa_dnsa)) != OK)
- return rc;
+#ifdef SUPPORT_DANE
+if (conn_args->dane)
+ if (dane_tlsa_load(exim_client_ctx->ssl, host, &conn_args->tlsa_dnsa, errstr) != OK)
+ return FALSE;
#endif
#ifndef DISABLE_OCSP
/* Request certificate status at connection-time. If the server
does OCSP stapling we will get the callback (set in tls_init()) */
-# ifdef EXPERIMENTAL_DANE
+# ifdef SUPPORT_DANE
if (request_ocsp)
{
const uschar * s;
{ /* Re-eval now $tls_out_tlsa_usage is populated. If
this means we avoid the OCSP request, we wasted the setup
cost in tls_init(). */
- require_ocsp = verify_check_given_host(&ob->hosts_require_ocsp, host) == OK;
+ require_ocsp = verify_check_given_host(CUSS &ob->hosts_require_ocsp, host) == OK;
request_ocsp = require_ocsp
- || verify_check_given_host(&ob->hosts_request_ocsp, host) == OK;
+ || verify_check_given_host(CUSS &ob->hosts_request_ocsp, host) == OK;
}
}
# endif
if (request_ocsp)
{
- SSL_set_tlsext_status_type(client_ssl, TLSEXT_STATUSTYPE_ocsp);
+ SSL_set_tlsext_status_type(exim_client_ctx->ssl, TLSEXT_STATUSTYPE_ocsp);
client_static_cbinfo->u_ocsp.client.verify_required = require_ocsp;
- tls_out.ocsp = OCSP_NOT_RESP;
+ tlsp->ocsp = OCSP_NOT_RESP;
}
#endif
+#ifdef EXPERIMENTAL_TLS_RESUME
+if (!tls_client_ssl_resume_prehandshake(exim_client_ctx->ssl, tlsp, host,
+ errstr))
+ return FALSE;
+#endif
+
#ifndef DISABLE_EVENT
-client_static_cbinfo->event_action = tb->event_action;
+client_static_cbinfo->event_action = tb ? tb->event_action : NULL;
#endif
/* There doesn't seem to be a built-in timeout on connection. */
DEBUG(D_tls) debug_printf("Calling SSL_connect\n");
sigalrm_seen = FALSE;
-alarm(ob->command_timeout);
-rc = SSL_connect(client_ssl);
-alarm(0);
+ALARM(ob->command_timeout);
+rc = SSL_connect(exim_client_ctx->ssl);
+ALARM_CLR(0);
-#ifdef EXPERIMENTAL_DANE
-if (tlsa_dnsa)
- DANESSL_cleanup(client_ssl);
+#ifdef SUPPORT_DANE
+if (conn_args->dane)
+ DANESSL_cleanup(exim_client_ctx->ssl);
#endif
if (rc <= 0)
- return tls_error(US"SSL_connect", host, sigalrm_seen ? US"timed out" : NULL);
+ {
+ tls_error(US"SSL_connect", host, sigalrm_seen ? US"timed out" : NULL, errstr);
+ return FALSE;
+ }
-DEBUG(D_tls) debug_printf("SSL_connect succeeded\n");
+DEBUG(D_tls)
+ {
+ debug_printf("SSL_connect succeeded\n");
+#ifdef EXIM_HAVE_OPENSSL_KEYLOG
+ {
+ BIO * bp = BIO_new_fp(debug_file, BIO_NOCLOSE);
+ SSL_SESSION_print_keylog(bp, SSL_get_session(exim_client_ctx->ssl));
+ BIO_free(bp);
+ }
+#endif
+ }
+
+#ifdef EXPERIMENTAL_TLS_RESUME
+tls_client_resume_posthandshake(exim_client_ctx, tlsp);
+#endif
-peer_cert(client_ssl, &tls_out, peerdn, sizeof(peerdn));
+peer_cert(exim_client_ctx->ssl, tlsp, peerdn, sizeof(peerdn));
-construct_cipher_name(client_ssl, cipherbuf, sizeof(cipherbuf), &tls_out.bits);
-tls_out.cipher = cipherbuf;
+tlsp->cipher = construct_cipher_name(exim_client_ctx->ssl, &tlsp->bits);
+tlsp->cipher_stdname = cipher_stdname_ssl(exim_client_ctx->ssl);
/* Record the certificate we presented */
{
- X509 * crt = SSL_get_certificate(client_ssl);
- tls_out.ourcert = crt ? X509_dup(crt) : NULL;
+ X509 * crt = SSL_get_certificate(exim_client_ctx->ssl);
+ tlsp->ourcert = crt ? X509_dup(crt) : NULL;
}
-tls_out.active = fd;
-return OK;
+tlsp->active.sock = cctx->sock;
+tlsp->active.tls_ctx = exim_client_ctx;
+cctx->tls_ctx = exim_client_ctx;
+return TRUE;
}
+static BOOL
+tls_refill(unsigned lim)
+{
+int error;
+int inbytes;
+
+DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", server_ssl,
+ ssl_xfer_buffer, ssl_xfer_buffer_size);
+
+if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout);
+inbytes = SSL_read(server_ssl, CS ssl_xfer_buffer,
+ MIN(ssl_xfer_buffer_size, lim));
+error = SSL_get_error(server_ssl, inbytes);
+if (smtp_receive_timeout > 0) ALARM_CLR(0);
+
+if (had_command_timeout) /* set by signal handler */
+ smtp_command_timeout_exit(); /* does not return */
+if (had_command_sigterm)
+ smtp_command_sigterm_exit();
+if (had_data_timeout)
+ smtp_data_timeout_exit();
+if (had_data_sigint)
+ smtp_data_sigint_exit();
+
+/* SSL_ERROR_ZERO_RETURN appears to mean that the SSL session has been
+closed down, not that the socket itself has been closed down. Revert to
+non-SSL handling. */
+
+switch(error)
+ {
+ case SSL_ERROR_NONE:
+ break;
+
+ case SSL_ERROR_ZERO_RETURN:
+ DEBUG(D_tls) debug_printf("Got SSL_ERROR_ZERO_RETURN\n");
+
+ if (SSL_get_shutdown(server_ssl) == SSL_RECEIVED_SHUTDOWN)
+ SSL_shutdown(server_ssl);
+
+ tls_close(NULL, TLS_NO_SHUTDOWN);
+ return FALSE;
+
+ /* Handle genuine errors */
+ case SSL_ERROR_SSL:
+ ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
+ log_write(0, LOG_MAIN, "TLS error (SSL_read): %s", ssl_errstring);
+ ssl_xfer_error = TRUE;
+ return FALSE;
+
+ default:
+ DEBUG(D_tls) debug_printf("Got SSL error %d\n", error);
+ DEBUG(D_tls) if (error == SSL_ERROR_SYSCALL)
+ debug_printf(" - syscall %s\n", strerror(errno));
+ ssl_xfer_error = TRUE;
+ return FALSE;
+ }
+
+#ifndef DISABLE_DKIM
+dkim_exim_verify_feed(ssl_xfer_buffer, inbytes);
+#endif
+ssl_xfer_buffer_hwm = inbytes;
+ssl_xfer_buffer_lwm = 0;
+return TRUE;
+}
+
+
/*************************************************
* TLS version of getc *
*************************************************/
tls_getc(unsigned lim)
{
if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm)
- {
- int error;
- int inbytes;
-
- DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", server_ssl,
- ssl_xfer_buffer, ssl_xfer_buffer_size);
-
- if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
- inbytes = SSL_read(server_ssl, CS ssl_xfer_buffer,
- MIN(ssl_xfer_buffer_size, lim));
- error = SSL_get_error(server_ssl, inbytes);
- alarm(0);
+ if (!tls_refill(lim))
+ return ssl_xfer_error ? EOF : smtp_getc(lim);
- /* SSL_ERROR_ZERO_RETURN appears to mean that the SSL session has been
- closed down, not that the socket itself has been closed down. Revert to
- non-SSL handling. */
-
- if (error == SSL_ERROR_ZERO_RETURN)
- {
- DEBUG(D_tls) debug_printf("Got SSL_ERROR_ZERO_RETURN\n");
-
- receive_getc = smtp_getc;
- receive_get_cache = smtp_get_cache;
- receive_ungetc = smtp_ungetc;
- receive_feof = smtp_feof;
- receive_ferror = smtp_ferror;
- receive_smtp_buffered = smtp_buffered;
-
- SSL_free(server_ssl);
- server_ssl = NULL;
- tls_in.active = -1;
- tls_in.bits = 0;
- tls_in.cipher = NULL;
- tls_in.peerdn = NULL;
- tls_in.sni = NULL;
-
- return smtp_getc(lim);
- }
+/* Something in the buffer; return next uschar */
- /* Handle genuine errors */
+return ssl_xfer_buffer[ssl_xfer_buffer_lwm++];
+}
- else if (error == SSL_ERROR_SSL)
- {
- ERR_error_string(ERR_get_error(), ssl_errstring);
- log_write(0, LOG_MAIN, "TLS error (SSL_read): %s", ssl_errstring);
- ssl_xfer_error = 1;
- return EOF;
- }
+uschar *
+tls_getbuf(unsigned * len)
+{
+unsigned size;
+uschar * buf;
- else if (error != SSL_ERROR_NONE)
+if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm)
+ if (!tls_refill(*len))
{
- DEBUG(D_tls) debug_printf("Got SSL error %d\n", error);
- ssl_xfer_error = 1;
- return EOF;
+ if (!ssl_xfer_error) return smtp_getbuf(len);
+ *len = 0;
+ return NULL;
}
-#ifndef DISABLE_DKIM
- dkim_exim_verify_feed(ssl_xfer_buffer, inbytes);
-#endif
- ssl_xfer_buffer_hwm = inbytes;
- ssl_xfer_buffer_lwm = 0;
- }
-
-/* Something in the buffer; return next uschar */
-
-return ssl_xfer_buffer[ssl_xfer_buffer_lwm++];
+if ((size = ssl_xfer_buffer_hwm - ssl_xfer_buffer_lwm) > *len)
+ size = *len;
+buf = &ssl_xfer_buffer[ssl_xfer_buffer_lwm];
+ssl_xfer_buffer_lwm += size;
+*len = size;
+return buf;
}
+
void
tls_get_cache()
{
}
+BOOL
+tls_could_read(void)
+{
+return ssl_xfer_buffer_lwm < ssl_xfer_buffer_hwm || SSL_pending(server_ssl) > 0;
+}
+
/*************************************************
* Read bytes from TLS channel *
/*
Arguments:
+ ct_ctx client context pointer, or NULL for the one global server context
buff buffer of data
len size of buffer
Returns: the number of bytes read
- -1 after a failed read
+ -1 after a failed read, including EOF
Only used by the client-side TLS.
*/
int
-tls_read(BOOL is_server, uschar *buff, size_t len)
+tls_read(void * ct_ctx, uschar *buff, size_t len)
{
-SSL *ssl = is_server ? server_ssl : client_ssl;
+SSL * ssl = ct_ctx ? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl : server_ssl;
int inbytes;
int error;
return -1;
}
else if (error != SSL_ERROR_NONE)
- {
return -1;
- }
return inbytes;
}
/*
Arguments:
- is_server channel specifier
+ ct_ctx client context pointer, or NULL for the one global server context
buff buffer of data
len number of bytes
+ more further data expected soon
Returns: the number of bytes after a successful write,
-1 after a failed write
*/
int
-tls_write(BOOL is_server, const uschar *buff, size_t len)
+tls_write(void * ct_ctx, const uschar *buff, size_t len, BOOL more)
{
-int outbytes;
-int error;
-int left = len;
-SSL *ssl = is_server ? server_ssl : client_ssl;
+size_t olen = len;
+int outbytes, error;
+SSL * ssl = ct_ctx
+ ? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl : server_ssl;
+static gstring * server_corked = NULL;
+gstring ** corkedp = ct_ctx
+ ? &((exim_openssl_client_tls_ctx *)ct_ctx)->corked : &server_corked;
+gstring * corked = *corkedp;
+
+DEBUG(D_tls) debug_printf("%s(%p, %lu%s)\n", __FUNCTION__,
+ buff, (unsigned long)len, more ? ", more" : "");
+
+/* Lacking a CORK or MSG_MORE facility (such as GnuTLS has) we copy data when
+"more" is notified. This hack is only ok if small amounts are involved AND only
+one stream does it, in one context (i.e. no store reset). Currently it is used
+for the responses to the received SMTP MAIL , RCPT, DATA sequence, only.
+We support callouts done by the server process by using a separate client
+context for the stashed information. */
+/* + if PIPE_COMMAND, banner & ehlo-resp for smmtp-on-connect. Suspect there's
+a store reset there, so use POOL_PERM. */
+/* + if CHUNKING, cmds EHLO,MAIL,RCPT(s),BDAT */
+
+if ((more || corked))
+ {
+#ifdef SUPPORT_PIPE_CONNECT
+ int save_pool = store_pool;
+ store_pool = POOL_PERM;
+#endif
+
+ corked = string_catn(corked, buff, len);
+
+#ifdef SUPPORT_PIPE_CONNECT
+ store_pool = save_pool;
+#endif
-DEBUG(D_tls) debug_printf("tls_do_write(%p, %d)\n", buff, left);
-while (left > 0)
+ if (more)
+ {
+ *corkedp = corked;
+ return len;
+ }
+ buff = CUS corked->s;
+ len = corked->ptr;
+ *corkedp = NULL;
+ }
+
+for (int left = len; left > 0;)
{
- DEBUG(D_tls) debug_printf("SSL_write(SSL, %p, %d)\n", buff, left);
+ DEBUG(D_tls) debug_printf("SSL_write(%p, %p, %d)\n", ssl, buff, left);
outbytes = SSL_write(ssl, CS buff, left);
error = SSL_get_error(ssl, outbytes);
DEBUG(D_tls) debug_printf("outbytes=%d error=%d\n", outbytes, error);
switch (error)
{
case SSL_ERROR_SSL:
- ERR_error_string(ERR_get_error(), ssl_errstring);
+ ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
log_write(0, LOG_MAIN, "TLS error (SSL_write): %s", ssl_errstring);
return -1;
return -1;
}
}
-return len;
+return olen;
}
daemon, to shut down the TLS library, without actually doing a shutdown (which
would tamper with the SSL session in the parent process).
-Arguments: TRUE if SSL_shutdown is to be called
+Arguments:
+ ct_ctx client TLS context pointer, or NULL for the one global server context
+ shutdown 1 if TLS close-alert is to be sent,
+ 2 if also response to be waited for
+
Returns: nothing
Used by both server-side and client-side TLS.
*/
void
-tls_close(BOOL is_server, BOOL shutdown)
+tls_close(void * ct_ctx, int shutdown)
{
-SSL **sslp = is_server ? &server_ssl : &client_ssl;
-int *fdp = is_server ? &tls_in.active : &tls_out.active;
+exim_openssl_client_tls_ctx * o_ctx = ct_ctx;
+SSL_CTX **ctxp = o_ctx ? &o_ctx->ctx : &server_ctx;
+SSL **sslp = o_ctx ? &o_ctx->ssl : &server_ssl;
+int *fdp = o_ctx ? &tls_out.active.sock : &tls_in.active.sock;
if (*fdp < 0) return; /* TLS was not active */
if (shutdown)
{
- DEBUG(D_tls) debug_printf("tls_close(): shutting down SSL\n");
- SSL_shutdown(*sslp);
+ int rc;
+ DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS%s\n",
+ shutdown > 1 ? " (with response-wait)" : "");
+
+ if ( (rc = SSL_shutdown(*sslp)) == 0 /* send "close notify" alert */
+ && shutdown > 1)
+ {
+ ALARM(2);
+ rc = SSL_shutdown(*sslp); /* wait for response */
+ ALARM_CLR(0);
+ }
+
+ if (rc < 0) DEBUG(D_tls)
+ {
+ ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
+ debug_printf("SSL_shutdown: %s\n", ssl_errstring);
+ }
+ }
+
+if (!o_ctx) /* server side */
+ {
+#ifndef DISABLE_OCSP
+ sk_X509_pop_free(server_static_cbinfo->verify_stack, X509_free);
+ server_static_cbinfo->verify_stack = NULL;
+#endif
+
+ receive_getc = smtp_getc;
+ receive_getbuf = smtp_getbuf;
+ receive_get_cache = smtp_get_cache;
+ receive_ungetc = smtp_ungetc;
+ receive_feof = smtp_feof;
+ receive_ferror = smtp_ferror;
+ receive_smtp_buffered = smtp_buffered;
+ tls_in.active.tls_ctx = NULL;
+ tls_in.sni = NULL;
+ /* Leave bits, peercert, cipher, peerdn, certificate_verified set, for logging */
}
+SSL_CTX_free(*ctxp);
SSL_free(*sslp);
+*ctxp = NULL;
*sslp = NULL;
-
*fdp = -1;
}
/* this duplicates from tls_init(), we need a better "init just global
state, for no specific purpose" singleton function of our own */
+#ifdef EXIM_NEED_OPENSSL_INIT
SSL_load_error_strings();
OpenSSL_add_ssl_algorithms();
+#endif
#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) && !defined(OPENSSL_NO_SHA256)
/* SHA256 is becoming ever more popular. This makes sure it gets added to the
list of available digests. */
if (!(tls_require_ciphers && *tls_require_ciphers))
return NULL;
-if (!expand_check(tls_require_ciphers, US"tls_require_ciphers", &expciphers))
+if (!expand_check(tls_require_ciphers, US"tls_require_ciphers", &expciphers,
+ &err))
return US"failed to expand tls_require_ciphers";
if (!(expciphers && *expciphers))
err = NULL;
-ctx = SSL_CTX_new(SSLv23_server_method());
-if (!ctx)
+#ifdef EXIM_HAVE_OPENSSL_TLS_METHOD
+if (!(ctx = SSL_CTX_new(TLS_server_method())))
+#else
+if (!(ctx = SSL_CTX_new(SSLv23_server_method())))
+#endif
{
- ERR_error_string(ERR_get_error(), ssl_errstring);
+ ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
return string_sprintf("SSL_CTX_new() failed: %s", ssl_errstring);
}
if (!SSL_CTX_set_cipher_list(ctx, CS expciphers))
{
- ERR_error_string(ERR_get_error(), ssl_errstring);
- err = string_sprintf("SSL_CTX_set_cipher_list(%s) failed", expciphers);
+ ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
+ err = string_sprintf("SSL_CTX_set_cipher_list(%s) failed: %s",
+ expciphers, ssl_errstring);
}
SSL_CTX_free(ctx);
int i, needed_len;
static pid_t pidlast = 0;
pid_t pidnow;
-uschar *p;
uschar smallbuf[sizeof(r)];
if (max <= 1)
gettimeofday(&r.tv, NULL);
r.p = getpid();
- RAND_seed((uschar *)(&r), sizeof(r));
+ RAND_seed(US (&r), sizeof(r));
}
/* We're after pseudo-random, not random; if we still don't have enough data
in the internal PRNG then our options are limited. We could sleep and hope
}
r = 0;
-for (p = smallbuf; needed_len; --needed_len, ++p)
- {
- r *= 256;
- r += *p;
- }
+for (uschar * p = smallbuf; needed_len; --needed_len, ++p)
+ r = 256 * r + *p;
/* We don't particularly care about weighted results; if someone wants
smooth distribution and cares enough then they should submit a patch then. */
Returns success or failure in parsing
*/
-struct exim_openssl_option {
- uschar *name;
- long value;
-};
-/* We could use a macro to expand, but we need the ifdef and not all the
-options document which version they were introduced in. Policylet: include
-all options unless explicitly for DTLS, let the administrator choose which
-to apply.
-
-This list is current as of:
- ==> 1.0.1b <==
-Plus SSL_OP_SAFARI_ECDHE_ECDSA_BUG from 2013-June patch/discussion on openssl-dev
-*/
-static struct exim_openssl_option exim_openssl_options[] = {
-/* KEEP SORTED ALPHABETICALLY! */
-#ifdef SSL_OP_ALL
- { US"all", SSL_OP_ALL },
-#endif
-#ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION
- { US"allow_unsafe_legacy_renegotiation", SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION },
-#endif
-#ifdef SSL_OP_CIPHER_SERVER_PREFERENCE
- { US"cipher_server_preference", SSL_OP_CIPHER_SERVER_PREFERENCE },
-#endif
-#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
- { US"dont_insert_empty_fragments", SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS },
-#endif
-#ifdef SSL_OP_EPHEMERAL_RSA
- { US"ephemeral_rsa", SSL_OP_EPHEMERAL_RSA },
-#endif
-#ifdef SSL_OP_LEGACY_SERVER_CONNECT
- { US"legacy_server_connect", SSL_OP_LEGACY_SERVER_CONNECT },
-#endif
-#ifdef SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER
- { US"microsoft_big_sslv3_buffer", SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER },
-#endif
-#ifdef SSL_OP_MICROSOFT_SESS_ID_BUG
- { US"microsoft_sess_id_bug", SSL_OP_MICROSOFT_SESS_ID_BUG },
-#endif
-#ifdef SSL_OP_MSIE_SSLV2_RSA_PADDING
- { US"msie_sslv2_rsa_padding", SSL_OP_MSIE_SSLV2_RSA_PADDING },
-#endif
-#ifdef SSL_OP_NETSCAPE_CHALLENGE_BUG
- { US"netscape_challenge_bug", SSL_OP_NETSCAPE_CHALLENGE_BUG },
-#endif
-#ifdef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG
- { US"netscape_reuse_cipher_change_bug", SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG },
-#endif
-#ifdef SSL_OP_NO_COMPRESSION
- { US"no_compression", SSL_OP_NO_COMPRESSION },
-#endif
-#ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
- { US"no_session_resumption_on_renegotiation", SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION },
-#endif
-#ifdef SSL_OP_NO_SSLv2
- { US"no_sslv2", SSL_OP_NO_SSLv2 },
-#endif
-#ifdef SSL_OP_NO_SSLv3
- { US"no_sslv3", SSL_OP_NO_SSLv3 },
-#endif
-#ifdef SSL_OP_NO_TICKET
- { US"no_ticket", SSL_OP_NO_TICKET },
-#endif
-#ifdef SSL_OP_NO_TLSv1
- { US"no_tlsv1", SSL_OP_NO_TLSv1 },
-#endif
-#ifdef SSL_OP_NO_TLSv1_1
-#if SSL_OP_NO_TLSv1_1 == 0x00000400L
- /* Error in chosen value in 1.0.1a; see first item in CHANGES for 1.0.1b */
-#warning OpenSSL 1.0.1a uses a bad value for SSL_OP_NO_TLSv1_1, ignoring
-#else
- { US"no_tlsv1_1", SSL_OP_NO_TLSv1_1 },
-#endif
-#endif
-#ifdef SSL_OP_NO_TLSv1_2
- { US"no_tlsv1_2", SSL_OP_NO_TLSv1_2 },
-#endif
-#ifdef SSL_OP_SAFARI_ECDHE_ECDSA_BUG
- { US"safari_ecdhe_ecdsa_bug", SSL_OP_SAFARI_ECDHE_ECDSA_BUG },
-#endif
-#ifdef SSL_OP_SINGLE_DH_USE
- { US"single_dh_use", SSL_OP_SINGLE_DH_USE },
-#endif
-#ifdef SSL_OP_SINGLE_ECDH_USE
- { US"single_ecdh_use", SSL_OP_SINGLE_ECDH_USE },
-#endif
-#ifdef SSL_OP_SSLEAY_080_CLIENT_DH_BUG
- { US"ssleay_080_client_dh_bug", SSL_OP_SSLEAY_080_CLIENT_DH_BUG },
-#endif
-#ifdef SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG
- { US"sslref2_reuse_cert_type_bug", SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG },
-#endif
-#ifdef SSL_OP_TLS_BLOCK_PADDING_BUG
- { US"tls_block_padding_bug", SSL_OP_TLS_BLOCK_PADDING_BUG },
-#endif
-#ifdef SSL_OP_TLS_D5_BUG
- { US"tls_d5_bug", SSL_OP_TLS_D5_BUG },
-#endif
-#ifdef SSL_OP_TLS_ROLLBACK_BUG
- { US"tls_rollback_bug", SSL_OP_TLS_ROLLBACK_BUG },
-#endif
-};
-static int exim_openssl_options_size =
- sizeof(exim_openssl_options)/sizeof(struct exim_openssl_option);
static BOOL
tls_openssl_options_parse(uschar *option_spec, long *results)
{
long result, item;
-uschar *s, *end;
+uschar *end;
uschar keep_c;
BOOL adding, item_parsed;
-result = 0L;
+/* Server: send no (<= TLS1.2) session tickets */
+result = SSL_OP_NO_TICKET;
+
/* Prior to 4.80 we or'd in SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; removed
* from default because it increases BEAST susceptibility. */
#ifdef SSL_OP_NO_SSLv2
result |= SSL_OP_NO_SSLv2;
#endif
+#ifdef SSL_OP_NO_SSLv3
+result |= SSL_OP_NO_SSLv3;
+#endif
#ifdef SSL_OP_SINGLE_DH_USE
result |= SSL_OP_SINGLE_DH_USE;
#endif
-if (option_spec == NULL)
+if (!option_spec)
{
*results = result;
return TRUE;
}
-for (s=option_spec; *s != '\0'; /**/)
+for (uschar * s = option_spec; *s; /**/)
{
while (isspace(*s)) ++s;
if (*s == '\0')
DEBUG(D_tls) debug_printf("openssl option setting unrecognised: \"%s\"\n", s);
return FALSE;
}
- DEBUG(D_tls) debug_printf("openssl option, %s from %lx: %lx (%s)\n",
- adding ? "adding" : "removing", result, item, s);
+ DEBUG(D_tls) debug_printf("openssl option, %s %8lx: %lx (%s)\n",
+ adding ? "adding to " : "removing from", result, item, s);
if (adding)
result |= item;
else
return TRUE;
}
+#endif /*!MACRO_PREDEF*/
/* vi: aw ai sw=2
*/
/* End of tls-openssl.c */