X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/0d81dabc92972f340421d0f80fc04156215e2eb8..f3ebb786e451da973560f1c9d8cdb151d25108b5:/src/src/tls-openssl.c diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index f17d94b5f..9542a1ed4 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2017 */ +/* Copyright (c) University of Cambridge 1995 - 2019 */ /* See the file NOTICE for conditions of use and distribution. */ /* Portions Copyright (c) The OpenSSL Project 1999 */ @@ -28,8 +28,8 @@ functions from the OpenSSL library. */ #ifndef DISABLE_OCSP # include #endif -#ifdef EXPERIMENTAL_DANE -# include +#ifdef SUPPORT_DANE +# include "danessl.h" #endif @@ -54,21 +54,28 @@ functions from the OpenSSL library. */ # 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 @@ -88,11 +95,177 @@ functions from the OpenSSL library. */ # 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 +#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", 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 { @@ -111,7 +284,9 @@ static const uschar *sid_ctx = US"exim"; 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. @@ -125,9 +300,13 @@ Server: 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 @@ -136,7 +315,7 @@ static SSL_CTX *server_sni = NULL; 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; @@ -144,10 +323,11 @@ static BOOL reexpand_tls_files_for_sni = 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 { @@ -175,7 +355,7 @@ typedef struct tls_ext_ctx_cb { /* 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 @@ -191,6 +371,23 @@ static int tls_server_stapling_cb(SSL *s, void *arg); #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 * *************************************************/ @@ -217,24 +414,25 @@ tls_error(uschar * prefix, const host_item * host, uschar * msg, uschar ** errst { if (!msg) { - ERR_error_string(ERR_get_error(), ssl_errstring); + ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring)); msg = US ssl_errstring; } -if (errstr) *errstr = string_sprintf("(%s): %s", prefix, msg); +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 @@ -262,14 +460,13 @@ if (!(rsa_key = RSA_generate_key(keylength, RSA_F4, NULL, NULL))) #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 @@ -279,18 +476,19 @@ void 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; itype == 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); + } } } } @@ -328,6 +526,7 @@ if (ev) } 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; @@ -371,23 +570,32 @@ Returns: 0 if verification should fail, otherwise 1 */ 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) { @@ -397,6 +605,7 @@ if (preverify_ok == 0) } DEBUG(D_tls) debug_printf("SSL verify failure overridden (host in " "tls_try_verify_hosts)\n"); + tlsp->verify_override = TRUE; } else if (depth != 0) @@ -425,7 +634,7 @@ else 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 @@ -448,7 +657,7 @@ else 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; @@ -458,10 +667,14 @@ else 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) { @@ -469,8 +682,9 @@ else 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; } } @@ -481,7 +695,6 @@ else 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; } @@ -503,7 +716,7 @@ return verify_callback(preverify_ok, x509ctx, &tls_in, } -#ifdef EXPERIMENTAL_DANE +#ifdef SUPPORT_DANE /* This gets called *by* the dane library verify callback, which interposes itself. @@ -518,7 +731,13 @@ int depth = X509_STORE_CTX_get_error_depth(x509ctx); 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", @@ -531,8 +750,21 @@ 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); @@ -544,7 +776,7 @@ else return preverify_ok; } -#endif /*EXPERIMENTAL_DANE*/ +#endif /*SUPPORT_DANE*/ /************************************************* @@ -566,10 +798,162 @@ Returns: nothing 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 @@ -595,6 +979,7 @@ BIO *bio; DH *dh; uschar *dhexpanded; const char *pem; +int dh_bitsize; if (!expand_check(dhparam, US"tls_dhparam", &dhexpanded, errstr)) return FALSE; @@ -635,21 +1020,34 @@ if (!(dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL))) 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\n", - 8*DH_size(dh), tls_dh_max_bits); + 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); @@ -868,7 +1266,7 @@ We do not free the stack since it could be needed a second time for 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? */ @@ -877,7 +1275,7 @@ if ((i = OCSP_basic_verify(basic_response, sk, NULL, verify_flags)) < 0) { 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; @@ -914,15 +1312,14 @@ if (!OCSP_check_validity(thisupd, nextupd, EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX } 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"); @@ -956,8 +1353,7 @@ if (!(x509 = X509_new())) 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"; @@ -965,7 +1361,7 @@ if (!EVP_PKEY_assign_RSA(pkey, rsa)) 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); @@ -1003,6 +1399,30 @@ err: +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 * *************************************************/ @@ -1027,14 +1447,16 @@ uschar *expanded; if (!cbinfo->certificate) { - if (cbinfo->host) /* client */ + if (!cbinfo->is_server) /* client */ return OK; - /* server */ + /* 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") @@ -1044,17 +1466,23 @@ else 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, errstr); - } + if (expanded) + 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_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 != NULL && - !expand_check(cbinfo->privatekey, US"tls_privatekey", &expanded, errstr)) + 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 @@ -1062,17 +1490,25 @@ else 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, errstr); - } + 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) { + /*XXX stack*/ if (!expand_check(cbinfo->u_ocsp.server.file, US"tls_ocsp_file", &expanded, errstr)) return DEFER; @@ -1111,6 +1547,9 @@ Arguments: 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 @@ -1141,11 +1580,15 @@ if (!reexpand_tls_files_for_sni) 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 @@ -1161,10 +1604,12 @@ SSL_CTX_set_tlsext_servername_arg(server_sni, cbinfo); 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) { @@ -1175,17 +1620,18 @@ if (cbinfo->u_ocsp.server.file) if ((rc = setup_certs(server_sni, tls_verify_certificates, tls_crl, NULL, FALSE, verify_callback_server, &dummy_errstr)) != OK) - return SSL_TLSEXT_ERR_NOACK; + 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, &dummy_errstr)) != OK) - return SSL_TLSEXT_ERR_NOACK; + 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 */ @@ -1210,9 +1656,15 @@ static int 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"); @@ -1222,7 +1674,7 @@ if (!cbinfo->u_ocsp.server.response) 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; @@ -1263,17 +1715,17 @@ if(!p) 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)) @@ -1296,7 +1748,7 @@ if(!(bs = OCSP_response_get1_basic(rsp))) 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 */ @@ -1307,10 +1759,12 @@ if(!(bs = OCSP_response_get1_basic(rsp))) 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; } @@ -1414,9 +1868,11 @@ static int 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, uschar ** errstr) + address_item *addr, tls_ext_ctx_cb ** cbp, + tls_support * tlsp, + uschar ** errstr) { SSL_CTX * ctx; long init_options; @@ -1424,11 +1880,13 @@ int rc; 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; @@ -1444,8 +1902,10 @@ cbinfo->host = host; 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 @@ -1461,7 +1921,11 @@ when OpenSSL is built without SSLv2 support. By disabling with openssl_options, we can let admins re-enable with the existing knob. */ +#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 @@ -1478,9 +1942,9 @@ if (!RAND_status()) 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, @@ -1490,7 +1954,17 @@ if (!RAND_status()) /* Set up the information callback, which outputs if debugging is at a suitable level. */ -DEBUG(D_tls) SSL_CTX_set_info_callback(ctx, (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(ctx, SSL_MODE_AUTO_RETRY); @@ -1507,8 +1981,22 @@ availability of the option value macros from OpenSSL. */ 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(ctx, init_options))) return tls_error(string_sprintf( @@ -1517,9 +2005,15 @@ if (init_options) else DEBUG(D_tls) debug_printf("no SSL CTX options to set\n"); -/* Disable session cache unconditionally */ - +/* 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 */ @@ -1545,7 +2039,7 @@ if ((rc = tls_expand_session_files(ctx, cbinfo, errstr)) != OK) } # 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 @@ -1585,7 +2079,8 @@ cbinfo->verify_cert_hostnames = NULL; 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(ctx, ssl_session_timeout); DEBUG(D_tls) debug_printf("Initialized TLS\n"); @@ -1605,53 +2100,87 @@ 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; + } } @@ -1662,6 +2191,7 @@ else * Set up for verifying certificates * *************************************************/ +#ifndef DISABLE_OCSP /* Load certs from file, return TRUE on success */ static BOOL @@ -1670,16 +2200,21 @@ chain_from_pem_file(const uschar * file, STACK_OF(X509) * verify_stack) 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 @@ -1741,7 +2276,7 @@ if (expcerts && *expcerts) ) { 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 @@ -1759,21 +2294,20 @@ if (expcerts && *expcerts) /* 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); } } } @@ -1834,7 +2368,7 @@ if (expcerts && *expcerts) /* 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); } @@ -1868,14 +2402,13 @@ int rc; 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"", errstr); - smtp_printf("554 Already in TLS\r\n"); + smtp_printf("554 Already in TLS\r\n", FALSE); return FAIL; } @@ -1884,9 +2417,9 @@ the error. */ 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, errstr); + NULL, &server_static_cbinfo, &tls_in, errstr); if (rc != OK) return rc; cbinfo = server_static_cbinfo; @@ -1896,12 +2429,15 @@ if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers, errstr) /* 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, errstr); @@ -1912,7 +2448,7 @@ if (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; @@ -1920,18 +2456,31 @@ 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, errstr); + 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, errstr); + 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))) @@ -1959,7 +2508,7 @@ mode, the fflush() happens when smtp_getc() is called. */ 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); } @@ -1973,9 +2522,9 @@ SSL_set_accept_state(server_ssl); 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) { @@ -1984,20 +2533,47 @@ if (rc <= 0) } 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 */ @@ -2011,9 +2587,9 @@ DEBUG(D_tls) 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; @@ -2023,7 +2599,8 @@ 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; } @@ -2043,10 +2620,10 @@ int rc; 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; @@ -2056,7 +2633,7 @@ if ((rc = setup_certs(ctx, ob->tls_verify_certificates, 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 @@ -2071,11 +2648,10 @@ return OK; } -#ifdef EXPERIMENTAL_DANE +#ifdef SUPPORT_DANE static int 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; @@ -2083,10 +2659,9 @@ int found = 0; if (DANESSL_init(ssl, NULL, hostnames) != 1) 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; @@ -2126,9 +2701,174 @@ if (found) 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 * @@ -2136,47 +2876,50 @@ return DEFER; /* 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 - errstr error string pointer - -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 - uschar ** errstr) +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' ) @@ -2190,31 +2933,46 @@ tls_out.tlsa_usage = 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, errstr); -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, errstr)) - 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 @@ -2225,68 +2983,86 @@ if (expciphers) uschar *s = expciphers; 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, errstr); + 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, errstr); - if (DANESSL_CTX_init(client_ctx) <= 0) - return tls_error(US"context init", host, NULL, errstr); + { + 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, errstr)) != 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, errstr); -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, errstr)) - return FAIL; - if (!tls_out.sni) + 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, errstr)) != 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; @@ -2296,57 +3072,81 @@ if (request_ocsp) { /* 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, - errstr); + { + tls_error(US"SSL_connect", host, sigalrm_seen ? US"timed out" : NULL, errstr); + return FALSE; + } + +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 + } -DEBUG(D_tls) debug_printf("SSL_connect succeeded\n"); +#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; } @@ -2362,54 +3162,74 @@ 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); +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 (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. */ -if (error == SSL_ERROR_ZERO_RETURN) +switch(error) { - DEBUG(D_tls) debug_printf("Got SSL_ERROR_ZERO_RETURN\n"); + case SSL_ERROR_NONE: + break; - 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; - - 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; + case SSL_ERROR_ZERO_RETURN: + DEBUG(D_tls) debug_printf("Got SSL_ERROR_ZERO_RETURN\n"); - return FALSE; - } + 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; -/* Handle genuine errors */ + if (SSL_get_shutdown(server_ssl) == SSL_RECEIVED_SHUTDOWN) + SSL_shutdown(server_ssl); -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 FALSE; - } +#ifndef DISABLE_OCSP + sk_X509_pop_free(server_static_cbinfo->verify_stack, X509_free); + server_static_cbinfo->verify_stack = NULL; +#endif + SSL_free(server_ssl); + SSL_CTX_free(server_ctx); + server_ctx = NULL; + server_ssl = NULL; + tls_in.active.sock = -1; + tls_in.active.tls_ctx = NULL; + tls_in.bits = 0; + tls_in.cipher = NULL; + tls_in.peerdn = NULL; + tls_in.sni = NULL; -else if (error != SSL_ERROR_NONE) - { - DEBUG(D_tls) debug_printf("Got SSL error %d\n", error); - ssl_xfer_error = 1; - return FALSE; + 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 @@ -2480,6 +3300,12 @@ if (n > 0) } +BOOL +tls_could_read(void) +{ +return ssl_xfer_buffer_lwm < ssl_xfer_buffer_hwm || SSL_pending(server_ssl) > 0; +} + /************************************************* * Read bytes from TLS channel * @@ -2487,19 +3313,20 @@ if (n > 0) /* 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; @@ -2530,9 +3357,10 @@ 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 @@ -2541,24 +3369,63 @@ Used by both server-side and client-side TLS. */ 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 */ -DEBUG(D_tls) debug_printf("tls_do_write(%p, %d)\n", buff, left); -while (left > 0) +if ((more || corked)) { - DEBUG(D_tls) debug_printf("SSL_write(SSL, %p, %d)\n", buff, left); +#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 + + 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(%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; @@ -2582,7 +3449,7 @@ while (left > 0) return -1; } } -return len; +return olen; } @@ -2595,29 +3462,59 @@ return len; 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); + } + } + +#ifndef DISABLE_OCSP +if (!o_ctx) /* server side */ + { + sk_X509_pop_free(server_static_cbinfo->verify_stack, X509_free); + server_static_cbinfo->verify_stack = NULL; } +#endif +SSL_CTX_free(*ctxp); SSL_free(*sslp); +*ctxp = NULL; *sslp = NULL; - *fdp = -1; } @@ -2643,8 +3540,10 @@ uschar *s, *expciphers, *err; /* 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. */ @@ -2667,10 +3566,13 @@ while (*s != 0) { if (*s == '_') *s = '-'; s++; } 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); } @@ -2679,7 +3581,7 @@ DEBUG(D_tls) if (!SSL_CTX_set_cipher_list(ctx, CS expciphers)) { - ERR_error_string(ERR_get_error(), ssl_errstring); + 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); } @@ -2749,7 +3651,6 @@ unsigned int r; int i, needed_len; static pid_t pidlast = 0; pid_t pidnow; -uschar *p; uschar smallbuf[sizeof(r)]; if (max <= 1) @@ -2774,7 +3675,7 @@ if (!RAND_status()) 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 @@ -2807,11 +3708,8 @@ if (i < 0) } 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. */ @@ -2833,110 +3731,6 @@ Arguments: 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 @@ -2982,16 +3776,21 @@ 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; +/* 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 @@ -3002,7 +3801,7 @@ if (!option_spec) return TRUE; } -for (s=option_spec; *s != '\0'; /**/) +for (uschar * s = option_spec; *s; /**/) { while (isspace(*s)) ++s; if (*s == '\0') @@ -3024,8 +3823,8 @@ for (s=option_spec; *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 @@ -3037,6 +3836,7 @@ for (s=option_spec; *s != '\0'; /**/) return TRUE; } +#endif /*!MACRO_PREDEF*/ /* vi: aw ai sw=2 */ /* End of tls-openssl.c */