X-Git-Url: https://vcs.fsf.org/?p=exim.git;a=blobdiff_plain;f=src%2Fsrc%2Ftls-openssl.c;h=d165eb2c02908bfd28b92a68683f29c6ddad4823;hp=0cf66279a506597ed1fb6cac51f0663cc22b2337;hb=012dd02e8436a8451afc4a8f69e128e257566c80;hpb=ca954d7feca8d9a8cc754ef2a2bfba14ef5428da diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index 0cf66279a..d165eb2c0 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 - 2018 */ +/* Copyright (c) University of Cambridge 1995 - 2019 */ /* See the file NOTICE for conditions of use and distribution. */ /* Portions Copyright (c) The OpenSSL Project 1999 */ @@ -51,26 +51,32 @@ functions from the OpenSSL library. */ # define EXIM_HAVE_RAND_PSEUDO #endif #if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) && !defined(OPENSSL_NO_SHA256) -# define EXIM_HAVE_SHA256 /*MMMM*/ +# 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 +# define EXIM_HAVE_OPESSL_GET0_SERIAL +# else +# define EXIM_NEED_OPENSSL_INIT # endif # if OPENSSL_VERSION_NUMBER >= 0x010000000L \ && (OPENSSL_VERSION_NUMBER & 0x0000ff000L) >= 0x000002000L @@ -82,7 +88,7 @@ functions from the OpenSSL library. */ || LIBRESSL_VERSION_NUMBER >= 0x20010000L # if !defined(OPENSSL_NO_ECDH) # if OPENSSL_VERSION_NUMBER >= 0x0090800fL -# define EXIM_HAVE_ECDH /*MMMM*/ +# define EXIM_HAVE_ECDH # endif # if OPENSSL_VERSION_NUMBER >= 0x10002000L # define EXIM_HAVE_OPENSSL_EC_NIST2NID @@ -90,15 +96,180 @@ 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", (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 +# ifdef SSL_OP_NO_TLSv1_3 +builtin_macro_create(US"_HAVE_TLS1_3"); +# endif +} +#else + +/******************************************************************************/ + /* Structure for collecting random data for seeding. */ typedef struct randstuff { @@ -136,6 +307,7 @@ Server: typedef struct { SSL_CTX * ctx; SSL * ssl; + gstring * corked; } exim_openssl_client_tls_ctx; static SSL_CTX *server_ctx = NULL; @@ -147,14 +319,20 @@ 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; static BOOL reexpand_tls_files_for_sni = FALSE; +typedef struct ocsp_resp { + struct ocsp_resp * next; + OCSP_RESPONSE * resp; +} ocsp_resplist; + typedef struct tls_ext_ctx_cb { + tls_support * tlsp; uschar *certificate; uschar *privatekey; BOOL is_server; @@ -163,8 +341,8 @@ typedef struct tls_ext_ctx_cb { union { struct { uschar *file; - uschar *file_expanded; - OCSP_RESPONSE *response; + const uschar *file_expanded; + ocsp_resplist *olist; } server; struct { X509_STORE *verify_store; /* non-null if status requested */ @@ -186,7 +364,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 @@ -202,6 +380,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 * *************************************************/ @@ -228,11 +423,13 @@ 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; } @@ -272,7 +469,7 @@ 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; @@ -288,18 +485,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); + } } } } @@ -337,6 +535,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; @@ -380,16 +579,23 @@ 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) { uschar * extra = verify_mode ? string_sprintf(" (during %c-verify for [%s])", @@ -408,6 +614,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) @@ -484,8 +691,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; } } @@ -496,7 +704,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; } @@ -533,7 +740,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", @@ -547,7 +760,7 @@ DEBUG(D_tls) debug_printf("verify_callback_client_dane: %s depth %d %s\n", 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 */ @@ -594,10 +807,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 " TIME_T_FMT "\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 " TIME_T_FMT "\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 " TIME_T_FMT "\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 @@ -833,12 +1198,13 @@ ASSUMES: single response, for single cert. Arguments: sctx the SSL_CTX* to update cbinfo various parts of session state - expanded the filename putatively holding an OCSP response + filename the filename putatively holding an OCSP response */ static void -ocsp_load_response(SSL_CTX *sctx, tls_ext_ctx_cb *cbinfo, const uschar *expanded) +ocsp_load_response(SSL_CTX * sctx, tls_ext_ctx_cb * cbinfo, + const uschar * filename) { BIO * bio; OCSP_RESPONSE * resp; @@ -849,17 +1215,12 @@ STACK_OF(X509) * sk; unsigned long verify_flags; int status, reason, i; -cbinfo->u_ocsp.server.file_expanded = string_copy(expanded); -if (cbinfo->u_ocsp.server.response) - { - OCSP_RESPONSE_free(cbinfo->u_ocsp.server.response); - cbinfo->u_ocsp.server.response = NULL; - } +DEBUG(D_tls) debug_printf("tls_ocsp_file '%s'\n", filename); -if (!(bio = BIO_new_file(CS cbinfo->u_ocsp.server.file_expanded, "rb"))) +if (!(bio = BIO_new_file(CS filename, "rb"))) { DEBUG(D_tls) debug_printf("Failed to open OCSP response file \"%s\"\n", - cbinfo->u_ocsp.server.file_expanded); + filename); return; } @@ -878,6 +1239,14 @@ if ((status = OCSP_response_status(resp)) != OCSP_RESPONSE_STATUS_SUCCESSFUL) goto bad; } +#ifdef notdef + { + BIO * bp = BIO_new_fp(debug_file, BIO_NOCLOSE); + OCSP_RESPONSE_print(bp, resp, 0); /* extreme debug: stapling content */ + BIO_free(bp); + } +#endif + if (!(basic_response = OCSP_response_get1_basic(resp))) { DEBUG(D_tls) @@ -919,7 +1288,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; @@ -931,7 +1300,10 @@ proves false, we need to extract a cert id from our issued cert (tls_certificate) and use that for OCSP_resp_find_status() (which finds the right cert in the stack and then calls OCSP_single_get0_status()). -I'm hoping to avoid reworking a bunch more of how we handle state here. */ +I'm hoping to avoid reworking a bunch more of how we handle state here. + +XXX that will change when we add support for (TLS1.3) whole-chain stapling +*/ if (!(single_response = OCSP_resp_get0(basic_response, 0))) { @@ -956,15 +1328,22 @@ if (!OCSP_check_validity(thisupd, nextupd, EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX } supply_response: - cbinfo->u_ocsp.server.response = resp; /*XXX stack?*/ + /* Add the resp to the list used by tls_server_stapling_cb() */ + { + ocsp_resplist ** op = &cbinfo->u_ocsp.server.olist, * oentry; + while (oentry = *op) + op = &oentry->next; + *op = oentry = store_get(sizeof(ocsp_resplist), FALSE); + oentry->next = NULL; + oentry->resp = resp; + } return; bad: if (f.running_in_test_harness) { extern char ** environ; - uschar ** p; - if (environ) for (p = USS environ; *p; 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"); @@ -973,6 +1352,16 @@ bad: } return; } + + +static void +ocsp_free_response_list(tls_ext_ctx_cb * cbinfo) +{ +for (ocsp_resplist * olist = cbinfo->u_ocsp.server.olist; olist; + olist = olist->next) + OCSP_RESPONSE_free(olist->resp); +cbinfo->u_ocsp.server.olist = NULL; +} #endif /*!DISABLE_OCSP*/ @@ -998,7 +1387,7 @@ if (!(x509 = X509_new())) goto err; where = US"generating pkey"; -if (!(rsa = rsa_callback(NULL, 0, 1024))) +if (!(rsa = rsa_callback(NULL, 0, 2048))) goto err; where = US"assigning pkey"; @@ -1048,7 +1437,7 @@ 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); +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), @@ -1060,7 +1449,7 @@ 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); +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); @@ -1085,10 +1474,10 @@ 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; +uschar * expanded; if (!cbinfo->certificate) { @@ -1102,10 +1491,11 @@ else { int err; - if (Ustrstr(cbinfo->certificate, US"tls_sni") || - Ustrstr(cbinfo->certificate, US"tls_in_sni") || - Ustrstr(cbinfo->certificate, US"tls_out_sni") - ) + if ( !reexpand_tls_files_for_sni + && ( 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, errstr)) @@ -1117,17 +1507,50 @@ else const uschar * file_list = expanded; int sep = 0; uschar * file; +#ifndef DISABLE_OCSP + const uschar * olist = cbinfo->u_ocsp.server.file; + int osep = 0; + uschar * ofile; + + if (olist) + if (!expand_check(olist, US"tls_ocsp_file", USS &olist, errstr)) + return DEFER; + if (olist && !*olist) + olist = NULL; + + if ( cbinfo->u_ocsp.server.file_expanded && olist + && (Ustrcmp(olist, cbinfo->u_ocsp.server.file_expanded) == 0)) + { + DEBUG(D_tls) debug_printf(" - value unchanged, using existing values\n"); + olist = NULL; + } + else + { + ocsp_free_response_list(cbinfo); + cbinfo->u_ocsp.server.file_expanded = olist; + } +#endif while (file = string_nextinlist(&file_list, &sep, NULL, 0)) + { if ((err = tls_add_certfile(sctx, cbinfo, file, errstr))) return err; + +#ifndef DISABLE_OCSP + if (olist) + if ((ofile = string_nextinlist(&olist, &osep, NULL, 0))) + ocsp_load_response(sctx, cbinfo, ofile); + else + DEBUG(D_tls) debug_printf("ran out of ocsp file list\n"); +#endif + } } 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 @@ -1150,27 +1573,6 @@ else 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; - - if (expanded && *expanded) - { - DEBUG(D_tls) debug_printf("tls_ocsp_file %s\n", expanded); - if ( cbinfo->u_ocsp.server.file_expanded - && (Ustrcmp(expanded, cbinfo->u_ocsp.server.file_expanded) == 0)) - { - DEBUG(D_tls) debug_printf(" - value unchanged, using existing values\n"); - } - else - ocsp_load_response(sctx, cbinfo, expanded); - } - } -#endif - return OK; } @@ -1192,6 +1594,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 @@ -1212,7 +1617,7 @@ DEBUG(D_tls) debug_printf("Received TLS SNI \"%s\"%s\n", servername, /* Make the extension value available for expansion */ store_pool = POOL_PERM; -tls_in.sni = string_copy(US servername); +tls_in.sni = string_copy_taint(US servername, TRUE); store_pool = old_pool; if (!reexpand_tls_files_for_sni) @@ -1228,9 +1633,9 @@ if (!(server_sni = SSL_CTX_new(TLS_server_method()))) 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 @@ -1246,11 +1651,11 @@ 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)) - return SSL_TLSEXT_ERR_NOACK; + goto bad; #ifndef DISABLE_OCSP if (cbinfo->u_ocsp.server.file) @@ -1262,17 +1667,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 */ @@ -1296,27 +1702,78 @@ project. static int tls_server_stapling_cb(SSL *s, void *arg) { -const tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg; -uschar *response_der; /*XXX blob */ +const tls_ext_ctx_cb * cbinfo = (tls_ext_ctx_cb *) arg; +ocsp_resplist * olist = cbinfo->u_ocsp.server.olist; +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"); + debug_printf("Received TLS status request (OCSP stapling); %s response list\n", + olist ? "have" : "lack"); tls_in.ocsp = OCSP_NOT_RESP; -if (!cbinfo->u_ocsp.server.response) +if (!olist) + return SSL_TLSEXT_ERR_NOACK; + +#ifdef EXIM_HAVE_OPESSL_GET0_SERIAL + { + const X509 * cert_sent = SSL_get_certificate(s); + const ASN1_INTEGER * cert_serial = X509_get0_serialNumber(cert_sent); + const BIGNUM * cert_bn = ASN1_INTEGER_to_BN(cert_serial, NULL); + const X509_NAME * cert_issuer = X509_get_issuer_name(cert_sent); + uschar * chash; + uint chash_len; + + for (; olist; olist = olist->next) + { + OCSP_BASICRESP * bs = OCSP_response_get1_basic(olist->resp); + const OCSP_SINGLERESP * single = OCSP_resp_get0(bs, 0); + const OCSP_CERTID * cid = OCSP_SINGLERESP_get0_id(single); + ASN1_INTEGER * res_cert_serial; + const BIGNUM * resp_bn; + ASN1_OCTET_STRING * res_cert_iNameHash; + + + (void) OCSP_id_get0_info(&res_cert_iNameHash, NULL, NULL, &res_cert_serial, + (OCSP_CERTID *) cid); + resp_bn = ASN1_INTEGER_to_BN(res_cert_serial, NULL); + + DEBUG(D_tls) + { + debug_printf("cert serial: %s\n", BN_bn2hex(cert_bn)); + debug_printf("resp serial: %s\n", BN_bn2hex(resp_bn)); + } + + if (BN_cmp(cert_bn, resp_bn) == 0) + { + DEBUG(D_tls) debug_printf("matched serial for ocsp\n"); + + /*XXX TODO: check the rest of the list for duplicate matches. + If any, need to also check the Issuer Name hash. + Without this, we will provide the wrong status in the case of + duplicate id. */ + + break; + } + DEBUG(D_tls) debug_printf("not match serial for ocsp\n"); + } + if (!olist) + { + DEBUG(D_tls) debug_printf("failed to find match for ocsp\n"); + return SSL_TLSEXT_ERR_NOACK; + } + } +#else +if (olist->next) + { + DEBUG(D_tls) debug_printf("OpenSSL version too early to support multi-leaf OCSP\n"); return SSL_TLSEXT_ERR_NOACK; + } +#endif +/*XXX could we do the i2d earlier, rather than during the callback? */ response_der = NULL; -response_der_len = i2d_OCSP_RESPONSE(cbinfo->u_ocsp.server.response, /*XXX stack*/ - &response_der); +response_der_len = i2d_OCSP_RESPONSE(olist->resp, &response_der); if (response_der_len <= 0) return SSL_TLSEXT_ERR_NOACK; @@ -1356,17 +1813,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)) @@ -1509,9 +1966,11 @@ static int tls_init(SSL_CTX **ctxp, host_item *host, uschar *dhparam, uschar *certificate, uschar *privatekey, #ifndef DISABLE_OCSP - uschar *ocsp_file, /*XXX stack, in server*/ + uschar *ocsp_file, #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; @@ -1519,6 +1978,7 @@ 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; @@ -1528,7 +1988,7 @@ if (!host) { cbinfo->u_ocsp.server.file = ocsp_file; cbinfo->u_ocsp.server.file_expanded = NULL; - cbinfo->u_ocsp.server.response = NULL; + cbinfo->u_ocsp.server.olist = NULL; } else cbinfo->u_ocsp.client.verify_store = NULL; @@ -1540,8 +2000,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 @@ -1590,7 +2052,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); @@ -1607,8 +2079,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( @@ -1654,7 +2140,7 @@ if ((rc = tls_expand_session_files(ctx, cbinfo, errstr)) != OK) if (!host) /* server */ { # ifndef DISABLE_OCSP - /* We check u_ocsp.server.file, not server.response, because we care about if + /* We check u_ocsp.server.file, not server.olist, because we care about if the option exists, not what the current expansion might be, as SNI might change the certificate and OCSP file in use between now and the time the callback is invoked. */ @@ -1691,7 +2177,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"); @@ -1711,51 +2198,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) { +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 uschar * ver = CUS SSL_get_version(ssl); const SSL_CIPHER * c = (const SSL_CIPHER *) SSL_get_current_cipher(ssl); +uschar * s; 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; + } } @@ -1844,6 +2367,10 @@ if (expcerts && *expcerts) /* In the server if we will be offering an OCSP proof, load chain from file for verifying the OCSP proof at load time. */ +/*XXX Glitch! The file here is tls_verify_certs: the chain for verifying the client cert. +This is inconsistent with the need to verify the OCSP proof of the server cert. +*/ + if ( !host && statbuf.st_size > 0 && server_static_cbinfo->u_ocsp.server.file @@ -1869,14 +2396,13 @@ 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); @@ -1944,7 +2470,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); } @@ -1978,7 +2504,6 @@ int rc; uschar * expciphers; tls_ext_ctx_cb * cbinfo; static uschar peerdn[256]; -static uschar cipherbuf[256]; /* Check for previous activation */ @@ -1994,9 +2519,9 @@ the error. */ rc = tls_init(&server_ctx, NULL, tls_dhparam, tls_certificate, tls_privatekey, #ifndef DISABLE_OCSP - tls_ocsp_file, /*XXX stack*/ + tls_ocsp_file, #endif - NULL, &server_static_cbinfo, errstr); + NULL, &server_static_cbinfo, &tls_in, errstr); if (rc != OK) return rc; cbinfo = server_static_cbinfo; @@ -2014,8 +2539,7 @@ 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); @@ -2046,6 +2570,19 @@ else if (verify_check_host(&tls_try_verify_hosts) == OK) 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))) @@ -2087,9 +2624,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) { @@ -2098,20 +2635,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 */ @@ -2158,10 +2722,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; @@ -2171,7 +2735,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 @@ -2190,7 +2754,6 @@ return OK; 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; @@ -2198,10 +2761,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; @@ -2245,40 +2807,201 @@ return DEFER; +#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 and option-tests) - addr the first address (for some randomness; can be NULL) - tb transport (always smtp) - tlsa_dnsa tlsa lookup, if DANE, else null - tlsp record details of channel configuration here; must be non-NULL - errstr error string pointer - -Returns: Pointer to TLS session context, or NULL on error +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 */ -void * -tls_client_start(int fd, host_item *host, address_item *addr, - transport_instance * tb, -#ifdef SUPPORT_DANE - dns_answer * tlsa_dnsa, -#endif - tls_support * tlsp, uschar ** errstr) +BOOL +tls_client_start(client_conn_ctx * cctx, smtp_connect_args * conn_args, + void * cookie, tls_support * tlsp, uschar ** errstr) { +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; -static uschar peerdn[256]; uschar * expciphers; int rc; -static uschar cipherbuf[256]; +static uschar peerdn[256]; #ifndef DISABLE_OCSP BOOL request_ocsp = FALSE; @@ -2287,7 +3010,8 @@ BOOL require_ocsp = FALSE; rc = store_pool; store_pool = POOL_PERM; -exim_client_ctx = store_get(sizeof(exim_openssl_client_tls_ctx)); +exim_client_ctx = store_get(sizeof(exim_openssl_client_tls_ctx), FALSE); +exim_client_ctx->corked = NULL; store_pool = rc; #ifdef SUPPORT_DANE @@ -2297,7 +3021,7 @@ tlsp->tlsa_usage = 0; #ifndef DISABLE_OCSP { # ifdef SUPPORT_DANE - if ( tlsa_dnsa + if ( conn_args->dane && ob->hosts_request_ocsp[0] == '*' && ob->hosts_request_ocsp[1] == '\0' ) @@ -2311,14 +3035,14 @@ tlsp->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 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 @@ -2327,22 +3051,22 @@ rc = tls_init(&exim_client_ctx->ctx, host, NULL, #ifndef DISABLE_OCSP (void *)(long)request_ocsp, #endif - addr, &client_static_cbinfo, errstr); -if (rc != OK) return NULL; + cookie, &client_static_cbinfo, tlsp, errstr); +if (rc != OK) return FALSE; tlsp->certificate_verified = FALSE; client_verify_callback_called = FALSE; expciphers = NULL; #ifdef SUPPORT_DANE -if (tlsa_dnsa) +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 NULL; + return FALSE; if (expciphers && *expciphers == '\0') expciphers = NULL; } @@ -2350,7 +3074,7 @@ if (tlsa_dnsa) if (!expciphers && !expand_check(ob->tls_require_ciphers, US"tls_require_ciphers", &expciphers, errstr)) - return NULL; + 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 @@ -2364,12 +3088,12 @@ if (expciphers) if (!SSL_CTX_set_cipher_list(exim_client_ctx->ctx, CS expciphers)) { tls_error(US"SSL_CTX_set_cipher_list", host, NULL, errstr); - return NULL; + return FALSE; } } #ifdef SUPPORT_DANE -if (tlsa_dnsa) +if (conn_args->dane) { SSL_CTX_set_verify(exim_client_ctx->ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, @@ -2378,12 +3102,12 @@ if (tlsa_dnsa) if (!DANESSL_library_init()) { tls_error(US"library init", host, NULL, errstr); - return NULL; + return FALSE; } if (DANESSL_CTX_init(exim_client_ctx->ctx) <= 0) { tls_error(US"context init", host, NULL, errstr); - return NULL; + return FALSE; } } else @@ -2392,21 +3116,27 @@ else if (tls_client_basic_ctx_init(exim_client_ctx->ctx, host, ob, client_static_cbinfo, errstr) != OK) - return NULL; + 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 NULL; + return FALSE; } SSL_set_session_id_context(exim_client_ctx->ssl, sid_ctx, Ustrlen(sid_ctx)); -SSL_set_fd(exim_client_ctx->ssl, fd); + +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", &tlsp->sni, errstr)) - return NULL; + return FALSE; if (!tlsp->sni) { DEBUG(D_tls) debug_printf("Setting TLS SNI forced to fail, not sending\n"); @@ -2426,9 +3156,9 @@ if (ob->tls_sni) } #ifdef SUPPORT_DANE -if (tlsa_dnsa) - if (dane_tlsa_load(exim_client_ctx->ssl, host, tlsa_dnsa, errstr) != OK) - return NULL; +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 @@ -2444,9 +3174,9 @@ 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 @@ -2459,6 +3189,12 @@ if (request_ocsp) } #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 ? tb->event_action : NULL; #endif @@ -2467,27 +3203,41 @@ client_static_cbinfo->event_action = tb ? tb->event_action : NULL; DEBUG(D_tls) debug_printf("Calling SSL_connect\n"); sigalrm_seen = FALSE; -alarm(ob->command_timeout); +ALARM(ob->command_timeout); rc = SSL_connect(exim_client_ctx->ssl); -alarm(0); +ALARM_CLR(0); #ifdef SUPPORT_DANE -if (tlsa_dnsa) +if (conn_args->dane) DANESSL_cleanup(exim_client_ctx->ssl); #endif if (rc <= 0) { tls_error(US"SSL_connect", host, sigalrm_seen ? US"timed out" : NULL, errstr); - return NULL; + 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(exim_client_ctx->ssl, tlsp, peerdn, sizeof(peerdn)); -construct_cipher_name(exim_client_ctx->ssl, cipherbuf, sizeof(cipherbuf), &tlsp->bits); -tlsp->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 */ { @@ -2495,9 +3245,10 @@ tlsp->cipher = cipherbuf; tlsp->ourcert = crt ? X509_dup(crt) : NULL; } -tlsp->active.sock = fd; +tlsp->active.sock = cctx->sock; tlsp->active.tls_ctx = exim_client_ctx; -return exim_client_ctx; +cctx->tls_ctx = exim_client_ctx; +return TRUE; } @@ -2513,11 +3264,11 @@ 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); -if (smtp_receive_timeout > 0) 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 */ @@ -2540,37 +3291,15 @@ switch(error) case SSL_ERROR_ZERO_RETURN: DEBUG(D_tls) debug_printf("Got SSL_ERROR_ZERO_RETURN\n"); - 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; - if (SSL_get_shutdown(server_ssl) == SSL_RECEIVED_SHUTDOWN) SSL_shutdown(server_ssl); -#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; - + tls_close(NULL, TLS_NO_SHUTDOWN); return FALSE; /* Handle genuine errors */ 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_read): %s", ssl_errstring); ssl_xfer_error = TRUE; return FALSE; @@ -2722,9 +3451,14 @@ Used by both server-side and client-side TLS. int tls_write(void * ct_ctx, const uschar *buff, size_t len, BOOL more) { -int outbytes, error, left; -SSL * ssl = ct_ctx ? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl : server_ssl; -static gstring * corked = NULL; +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" : ""); @@ -2732,19 +3466,37 @@ DEBUG(D_tls) debug_printf("%s(%p, %lu%s)\n", __FUNCTION__, /* 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. */ +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 (!ct_ctx && (more || corked)) +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 + if (more) + { + *corkedp = corked; return len; + } buff = CUS corked->s; len = corked->ptr; - corked = NULL; + *corkedp = NULL; } -for (left = len; left > 0;) +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); @@ -2753,7 +3505,7 @@ for (left = len; left > 0;) 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; @@ -2777,7 +3529,7 @@ for (left = len; left > 0;) return -1; } } -return len; +return olen; } @@ -2819,26 +3571,37 @@ if (shutdown) if ( (rc = SSL_shutdown(*sslp)) == 0 /* send "close notify" alert */ && shutdown > 1) { - alarm(2); + ALARM(2); rc = SSL_shutdown(*sslp); /* wait for response */ - alarm(0); + ALARM_CLR(0); } if (rc < 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("SSL_shutdown: %s\n", ssl_errstring); } } -#ifndef DISABLE_OCSP 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; @@ -2868,8 +3631,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. */ @@ -2898,7 +3663,7 @@ if (!(ctx = SSL_CTX_new(TLS_server_method()))) 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); } @@ -2907,7 +3672,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); } @@ -2977,7 +3742,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) @@ -3035,11 +3799,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. */ @@ -3061,110 +3822,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 @@ -3210,16 +3867,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 @@ -3230,7 +3892,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') @@ -3252,8 +3914,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 @@ -3265,6 +3927,7 @@ for (s=option_spec; *s != '\0'; /**/) return TRUE; } +#endif /*!MACRO_PREDEF*/ /* vi: aw ai sw=2 */ /* End of tls-openssl.c */