X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/c6a290f4d8df3734b3cdc2232b4334ff8386c1da..582f61444fa806694c2caae7c156830dbf8531d7:/src/src/tls-openssl.c diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index 4bc92bd05..237303ba9 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -2,9 +2,10 @@ * Exim - an Internet mail transport agent * *************************************************/ +/* Copyright (c) The Exim Maintainers 2020 - 2023 */ /* Copyright (c) University of Cambridge 1995 - 2019 */ -/* Copyright (c) The Exim Maintainers 2020 - 2021 */ /* See the file NOTICE for conditions of use and distribution. */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* Portions Copyright (c) The OpenSSL Project 1999 */ @@ -48,6 +49,7 @@ functions from the OpenSSL library. */ #if OPENSSL_VERSION_NUMBER >= 0x10100000L # define EXIM_HAVE_OCSP_RESP_COUNT # define OPENSSL_AUTO_SHA256 +# define OPENSSL_MIN_PROTO_VERSION #else # define EXIM_HAVE_EPHEM_RSA_KEX # define EXIM_HAVE_RAND_PSEUDO @@ -75,8 +77,10 @@ change this guard and punt the issue for a while longer. */ # 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 +# define EXIM_HAVE_OPENSSL_TRACE +# define EXIM_HAVE_OPENSSL_GET0_SERIAL +# define EXIM_HAVE_OPENSSL_OCSP_RESP_GET0_CERTS +# define EXIM_HAVE_SSL_GET0_VERIFIED_CHAIN # ifndef DISABLE_OCSP # define EXIM_HAVE_OCSP # endif @@ -94,6 +98,11 @@ change this guard and punt the issue for a while longer. */ # define EXIM_HAVE_OPENSSL_CIPHER_GET_ID #endif +#if !defined(LIBRESSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER >= 0x030000000L) +# define EXIM_HAVE_EXPORT_CHNL_BNGNG +# define EXIM_HAVE_OPENSSL_X509_STORE_GET1_ALL_CERTS +#endif + #if !defined(LIBRESSL_VERSION_NUMBER) \ || LIBRESSL_VERSION_NUMBER >= 0x20010000L # if !defined(OPENSSL_NO_ECDH) @@ -111,11 +120,18 @@ change this guard and punt the issue for a while longer. */ # define OPENSSL_HAVE_KEYLOG_CB # define OPENSSL_HAVE_NUM_TICKETS # define EXIM_HAVE_OPENSSL_CIPHER_STD_NAME +# define EXIM_HAVE_EXP_CHNL_BNGNG +# define EXIM_HAVE_OPENSSL_OCSP_RESP_GET0_SIGNER +# define EXIM_HAVE_OPENSSL_SET1_GROUPS # else # define OPENSSL_BAD_SRVR_OURCERT # endif #endif +#if !defined(LIBRESSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER >= 0x010002000L) +# define EXIM_HAVE_EXPORT_CHNL_BNGNG +#endif + #if !defined(EXIM_HAVE_OPENSSL_TLSEXT) && !defined(DISABLE_OCSP) # warning "OpenSSL library version too old; define DISABLE_OCSP in Makefile" # define DISABLE_OCSP @@ -140,6 +156,7 @@ change this guard and punt the issue for a while longer. */ # endif #endif +#define TESTSUITE_TICKET_LIFE 10 /* seconds */ /************************************************* * OpenSSL option parse * *************************************************/ @@ -398,15 +415,16 @@ typedef struct exim_openssl_state { uschar * privatekey; BOOL is_server; #ifndef DISABLE_OCSP - STACK_OF(X509) *verify_stack; /* chain for verifying the proof */ union { struct { uschar *file; const uschar *file_expanded; ocsp_resplist *olist; + STACK_OF(X509) *verify_stack; /* chain for verifying the proof */ } server; struct { X509_STORE *verify_store; /* non-null if status requested */ + uschar *verify_errstr; /* only if _required */ BOOL verify_required; } client; } u_ocsp; @@ -429,12 +447,14 @@ exim_openssl_state_st *client_static_state = NULL; /*XXX should not use static; exim_openssl_state_st state_server = {.is_server = TRUE}; static int -setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, - uschar ** errstr ); +setup_certs(SSL_CTX * sctx, uschar ** certs, uschar * crl, host_item * host, + uschar ** errstr); /* Callbacks */ #ifndef DISABLE_OCSP static int tls_server_stapling_cb(SSL *s, void *arg); +static void x509_stack_dump_cert_s_names(const STACK_OF(X509) * sk); +static void x509_store_dump_cert_s_names(X509_STORE * store); #endif @@ -657,12 +677,12 @@ if (dh_bitsize <= tls_dh_max_bits) } else DEBUG(D_tls) - debug_printf("Diffie-Hellman initialized from %s with %d-bit prime\n", + debug_printf(" Diffie-Hellman initialized from %s with %d-bit prime\n", dhexpanded ? dhexpanded : US"default", dh_bitsize); } else DEBUG(D_tls) - debug_printf("dhparams '%s' %d bits, is > tls_dh_max_bits limit of %d\n", + debug_printf(" dhparams '%s' %d bits, is > tls_dh_max_bits limit of %d\n", dhexpanded ? dhexpanded : US"default", dh_bitsize, tls_dh_max_bits); #if OPENSSL_VERSION_NUMBER < 0x30000000L @@ -681,6 +701,41 @@ return TRUE; * Initialize for ECDH * *************************************************/ +/* "auto" needs to be handled carefully. +OpenSSL < 1.0.2: we do not select anything, but fallback to prime256v1 +OpenSSL < 1.1.0: we have to call SSL_CTX_set_ecdh_auto + (openssl/ssl.h defines SSL_CTRL_SET_ECDH_AUTO) +OpenSSL >= 1.1.0: we do not set anything, the libray does autoselection + https://github.com/openssl/openssl/commit/fe6ef2472db933f01b59cad82aa925736935984b + +*/ + +static uschar * +init_ecdh_auto(SSL_CTX * sctx) +{ +#if OPENSSL_VERSION_NUMBER < 0x10002000L +DEBUG(D_tls) debug_printf( + " ECDH OpenSSL < 1.0.2: temp key parameter settings: overriding \"auto\" with \"prime256v1\"\n"); +return US"prime256v1"; + +#else +# if defined SSL_CTRL_SET_ECDH_AUTO + +DEBUG(D_tls) debug_printf( + " ECDH OpenSSL 1.0.2+: temp key parameter settings: autoselection\n"); +SSL_CTX_set_ecdh_auto(sctx, 1); +return NULL; + +# else + +DEBUG(D_tls) debug_printf( + " ECDH OpenSSL 1.1.0+: temp key parameter settings: library default selection\n"); +return NULL; + +# endif +#endif +} + /* Load parameters for ECDH encryption. Server only. For now, we stick to NIST P-256 because: it's simple and easy to configure; @@ -711,64 +766,76 @@ init_ecdh(SSL_CTX * sctx, uschar ** errstr) return TRUE; #else -uschar * exp_curve; -int nid; -BOOL rv; - # ifndef EXIM_HAVE_ECDH DEBUG(D_tls) - debug_printf("No OpenSSL API to define ECDH parameters, skipping\n"); + debug_printf(" No OpenSSL API to define ECDH parameters, skipping\n"); return TRUE; # else +uschar * exp_curve; +int ngroups, rc, sep; +const uschar * curves_list, * curve; +# ifdef EXIM_HAVE_OPENSSL_SET1_GROUPS +int nids[16]; +# else +int nids[1]; +# endif + if (!expand_check(tls_eccurve, US"tls_eccurve", &exp_curve, errstr)) return FALSE; + +/* Is the option deliberately empty? */ + if (!exp_curve || !*exp_curve) return TRUE; -/* "auto" needs to be handled carefully. - * OpenSSL < 1.0.2: we do not select anything, but fallback to prime256v1 - * OpenSSL < 1.1.0: we have to call SSL_CTX_set_ecdh_auto - * (openssl/ssl.h defines SSL_CTRL_SET_ECDH_AUTO) - * OpenSSL >= 1.1.0: we do not set anything, the libray does autoselection - * https://github.com/openssl/openssl/commit/fe6ef2472db933f01b59cad82aa925736935984b - */ -if (Ustrcmp(exp_curve, "auto") == 0) - { -#if OPENSSL_VERSION_NUMBER < 0x10002000L - DEBUG(D_tls) debug_printf( - "ECDH OpenSSL < 1.0.2: temp key parameter settings: overriding \"auto\" with \"prime256v1\"\n"); - exp_curve = US"prime256v1"; -#else -# if defined SSL_CTRL_SET_ECDH_AUTO - DEBUG(D_tls) debug_printf( - "ECDH OpenSSL 1.0.2+: temp key parameter settings: autoselection\n"); - SSL_CTX_set_ecdh_auto(sctx, 1); - return TRUE; -# else - DEBUG(D_tls) debug_printf( - "ECDH OpenSSL 1.1.0+: temp key parameter settings: default selection\n"); - return TRUE; -# endif -#endif - } +/* Limit the list to hardwired array size. Drop out if any element is "suto". */ -DEBUG(D_tls) debug_printf("ECDH: curve '%s'\n", exp_curve); -if ( (nid = OBJ_sn2nid (CCS exp_curve)) == NID_undef -# ifdef EXIM_HAVE_OPENSSL_EC_NIST2NID - && (nid = EC_curve_nist2nid(CCS exp_curve)) == NID_undef -# endif - ) - { - tls_error(string_sprintf("Unknown curve name tls_eccurve '%s'", exp_curve), - NULL, NULL, errstr); - return FALSE; - } +curves_list = exp_curve; +sep = 0; +for (ngroups = 0; + ngroups < nelem(nids) + && (curve = string_nextinlist(&curves_list, &sep, NULL, 0)); + ) + if (Ustrcmp(curve, "auto") == 0) + { + DEBUG(D_tls) if (ngroups > 0) + debug_printf(" tls_eccurve 'auto' item takes precedence\n"); + if ((exp_curve = init_ecdh_auto(sctx))) break; /* have a curve name to set */ + return TRUE; /* all done */ + } + else + ngroups++; -# if OPENSSL_VERSION_NUMBER < 0x30000000L +/* Translate to NIDs */ + +curves_list = exp_curve; +for (ngroups = 0; curve = string_nextinlist(&curves_list, &sep, NULL, 0); + ngroups++) + if ( (nids[ngroups] = OBJ_sn2nid (CCS curve)) == NID_undef +# ifdef EXIM_HAVE_OPENSSL_EC_NIST2NID + && (nids[ngroups] = EC_curve_nist2nid(CCS curve)) == NID_undef +# endif + ) + { + uschar * s = string_sprintf("Unknown curve name in tls_eccurve '%s'", curve); + DEBUG(D_tls) debug_printf("TLS error: %s\n", s); + if (errstr) *errstr = s; + return FALSE; + } + +# ifdef EXIM_HAVE_OPENSSL_SET1_GROUPS +/* Set the groups */ + +if ((rc = SSL_CTX_set1_groups(sctx, nids, ngroups)) == 0) + tls_error(string_sprintf("Error enabling '%s' group(s)", exp_curve), NULL, NULL, errstr); +else + DEBUG(D_tls) debug_printf(" ECDH: enabled '%s' group(s)\n", exp_curve); + +# else /* Cannot handle a list; only 1 element nids array */ { EC_KEY * ecdh; - if (!(ecdh = EC_KEY_new_by_curve_name(nid))) + if (!(ecdh = EC_KEY_new_by_curve_name(nids[0]))) { tls_error(US"Unable to create ec curve", NULL, NULL, errstr); return FALSE; @@ -777,23 +844,15 @@ if ( (nid = OBJ_sn2nid (CCS exp_curve)) == NID_undef /* The "tmp" in the name here refers to setting a temporary key not to the stability of the interface. */ - if ((rv = SSL_CTX_set_tmp_ecdh(sctx, ecdh) == 0)) + if ((rc = SSL_CTX_set_tmp_ecdh(sctx, ecdh)) == 0) tls_error(string_sprintf("Error enabling '%s' curve", exp_curve), NULL, NULL, errstr); else - DEBUG(D_tls) debug_printf("ECDH: enabled '%s' curve\n", exp_curve); + DEBUG(D_tls) debug_printf(" ECDH: enabled '%s' curve\n", exp_curve); EC_KEY_free(ecdh); } +# endif /*!EXIM_HAVE_OPENSSL_SET1_GROUPS*/ -#else /* v 3.0.0 + */ - -if ((rv = SSL_CTX_set1_groups(sctx, &nid, 1)) == 0) - tls_error(string_sprintf("Error enabling '%s' group", exp_curve), NULL, NULL, errstr); -else - DEBUG(D_tls) debug_printf("ECDH: enabled '%s' group\n", exp_curve); - -#endif - -return !rv; +return !!rc; # endif /*EXIM_HAVE_ECDH*/ #endif /*OPENSSL_NO_ECDH*/ @@ -805,6 +864,7 @@ return !rv; * Expand key and cert file specs * *************************************************/ +#if OPENSSL_VERSION_NUMBER < 0x30000000L /* Arguments: s SSL connection (not used) @@ -824,14 +884,14 @@ BIGNUM *bn = BN_new(); DEBUG(D_tls) debug_printf("Generating %d bit RSA key...\n", keylength); -#ifdef EXIM_HAVE_RSA_GENKEY_EX +# ifdef EXIM_HAVE_RSA_GENKEY_EX if ( !BN_set_word(bn, (unsigned long)RSA_F4) || !(rsa_key = RSA_new()) || !RSA_generate_key_ex(rsa_key, keylength, bn, NULL) ) -#else +# else if (!(rsa_key = RSA_generate_key(keylength, RSA_F4, NULL, NULL))) -#endif +# endif { ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring)); @@ -841,6 +901,7 @@ if (!(rsa_key = RSA_generate_key(keylength, RSA_F4, NULL, NULL))) } return rsa_key; } +#endif /* pre-3.0.0 */ @@ -854,7 +915,6 @@ tls_install_selfsign(SSL_CTX * sctx, uschar ** errstr) { X509 * x509 = NULL; EVP_PKEY * pkey; -RSA * rsa; X509_NAME * name; uschar * where; @@ -868,12 +928,19 @@ if (!(x509 = X509_new())) goto err; where = US"generating pkey"; -if (!(rsa = rsa_callback(NULL, 0, 2048))) - goto err; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + { + RSA * rsa; + if (!(rsa = rsa_callback(NULL, 0, 2048))) + goto err; -where = US"assigning pkey"; -if (!EVP_PKEY_assign_RSA(pkey, rsa)) - goto err; + where = US"assigning pkey"; + if (!EVP_PKEY_assign_RSA(pkey, rsa)) + goto err; + } +#else +pkey = EVP_RSA_gen(2048); +#endif X509_set_version(x509, 2); /* N+1 - version 3 */ ASN1_INTEGER_set(X509_get_serialNumber(x509), 1); @@ -934,36 +1001,35 @@ Returns: nothing */ static void -info_callback(SSL *s, int where, int ret) +info_callback(const SSL * s, int where, int ret) { 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)"; + gstring * g = NULL; + + if (where & SSL_ST_CONNECT) g = string_append_listele(g, ',', US"SSL_connect"); + if (where & SSL_ST_ACCEPT) g = string_append_listele(g, ',', US"SSL_accept"); + if (where & SSL_CB_LOOP) g = string_append_listele(g, ',', US"state_chg"); + if (where & SSL_CB_EXIT) g = string_append_listele(g, ',', US"hshake_exit"); + if (where & SSL_CB_READ) g = string_append_listele(g, ',', US"read"); + if (where & SSL_CB_WRITE) g = string_append_listele(g, ',', US"write"); + if (where & SSL_CB_ALERT) g = string_append_listele(g, ',', US"alert"); + if (where & SSL_CB_HANDSHAKE_START) g = string_append_listele(g, ',', US"hshake_start"); + if (where & SSL_CB_HANDSHAKE_DONE) g = string_append_listele(g, ',', US"hshake_done"); if (where & SSL_CB_LOOP) - debug_printf("%s: %s\n", str, SSL_state_string_long(s)); + debug_printf("SSL %s: %s\n", g->s, 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", + debug_printf("SSL %s %s:%s\n", g->s, 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)); + if (ret <= 0) + debug_printf("SSL %s: %s in %s\n", g->s, + ret == 0 ? "failed" : "error", 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)); + else if (where & (SSL_CB_HANDSHAKE_START | SSL_CB_HANDSHAKE_DONE)) + debug_printf("SSL %s: %s\n", g->s, SSL_state_string_long(s)); } } @@ -1001,7 +1067,7 @@ if (ev) old_cert = tlsp->peercert; tlsp->peercert = X509_dup(cert); /* NB we do not bother setting peerdn */ - if ((yield = event_raise(ev, US"tls:cert", string_sprintf("%d", depth)))) + if ((yield = event_raise(ev, US"tls:cert", string_sprintf("%d", depth), &errno))) { log_write(0, LOG_MAIN, "[%s] %s verify denied by event-action: " "depth=%d cert=%s: %s", @@ -1100,18 +1166,6 @@ if (preverify_ok == 0) else if (depth != 0) { DEBUG(D_tls) debug_printf("SSL verify ok: depth=%d SN=%s\n", depth, dn); -#ifndef DISABLE_OCSP - if (tlsp == &tls_out && client_static_state->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_state->u_ocsp.client.verify_store, - cert)) - ERR_clear_error(); - sk_X509_push(client_static_state->verify_stack, cert); - } -#endif #ifndef DISABLE_EVENT if (verify_event(tlsp, cert, depth, dn, calledp, optionalp, US"SSL")) return 0; /* reject, with peercert set */ @@ -1138,6 +1192,8 @@ else uschar * name; int rc; while ((name = string_nextinlist(&list, &sep, NULL, 0))) + { + DEBUG(D_tls|D_lookup) debug_printf_indent("%s suitable for cert, per OpenSSL?", name); if ((rc = X509_check_host(cert, CCS name, 0, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS | X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS, @@ -1149,8 +1205,11 @@ else tlsp == &tls_out ? deliver_host_address : sender_host_address); name = NULL; } + DEBUG(D_tls|D_lookup) debug_printf_indent(" yes\n"); break; } + else DEBUG(D_tls|D_lookup) debug_printf_indent(" no\n"); + } if (!name) #else if (!tls_is_name_for_cert(verify_cert_hostnames, cert)) @@ -1239,21 +1298,7 @@ DEBUG(D_tls) debug_printf("verify_callback_client_dane: %s depth %d %s\n", #endif if (preverify_ok == 1) - { tls_out.dane_verified = TRUE; -#ifndef DISABLE_OCSP - if (client_static_state->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_state->u_ocsp.client.verify_store, - cert)) - ERR_clear_error(); - sk_X509_push(client_static_state->verify_stack, cert); - } -#endif - } else { int err = X509_STORE_CTX_get_error(x509ctx); @@ -1269,6 +1314,14 @@ return preverify_ok; #ifndef DISABLE_OCSP +static void +time_print(BIO * bp, const char * str, ASN1_GENERALIZEDTIME * time) +{ +BIO_printf(bp, "\t%s: ", str); +ASN1_GENERALIZEDTIME_print(bp, time); +BIO_puts(bp, "\n"); +} + /************************************************* * Load OCSP information into state * *************************************************/ @@ -1294,7 +1347,6 @@ OCSP_BASICRESP * basic_response; OCSP_SINGLERESP * single_response; ASN1_GENERALIZEDTIME * rev, * thisupd, * nextupd; STACK_OF(X509) * sk; -unsigned long verify_flags; int status, reason, i; DEBUG(D_tls) @@ -1359,20 +1411,20 @@ if (!(basic_response = OCSP_response_get1_basic(resp))) goto bad; } -sk = state->verify_stack; -verify_flags = OCSP_NOVERIFY; /* check sigs, but not purpose */ +sk = state->u_ocsp.server.verify_stack; /* set by setup_certs() / chain_from_pem_file() */ /* May need to expose ability to adjust those flags? OCSP_NOSIGS OCSP_NOVERIFY OCSP_NOCHAIN OCSP_NOCHECKS OCSP_NOEXPLICIT OCSP_TRUSTOTHER OCSP_NOINTERN */ -/* This does a full verify on the OCSP proof before we load it for serving -up; possibly overkill - just date-checks might be nice enough. +/* This does a partial verify (only the signer link, not the whole chain-to-CA) +on the OCSP proof before we load it for serving up; possibly overkill - +just date-checks might be nice enough. OCSP_basic_verify takes a "store" arg, but does not -use it for the chain verification, which is all we do -when OCSP_NOVERIFY is set. The content from the wire -"basic_response" and a cert-stack "sk" are all that is used. +use it for the chain verification, when OCSP_NOVERIFY is set. +The content from the wire "basic_response" and a cert-stack "sk" are all +that is used. We have a stack, loaded in setup_certs() if tls_verify_certificates was a file (not a directory, or "system"). It is unfortunate we @@ -1380,21 +1432,23 @@ cannot used the connection context store, as that would neatly handle the "system" case too, but there seems to be no library function for getting a stack from a store. [ In OpenSSL 1.1 - ? X509_STORE_CTX_get0_chain(ctx) ? ] +[ 3.0.0 - sk = X509_STORE_get1_all_certs(store) ] 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) - +(in 3.0.0 + it is public) 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? */ -if ((i = OCSP_basic_verify(basic_response, sk, NULL, verify_flags)) < 0) +if ((i = OCSP_basic_verify(basic_response, sk, NULL, OCSP_NOVERIFY)) < 0) { DEBUG(D_tls) { ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring)); - debug_printf("OCSP response verify failure: %s\n", US ssl_errstring); + debug_printf("OCSP response has bad signature: %s\n", US ssl_errstring); } goto bad; } @@ -1428,7 +1482,16 @@ if (status != V_OCSP_CERTSTATUS_GOOD) if (!OCSP_check_validity(thisupd, nextupd, EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX_AGE)) { - DEBUG(D_tls) debug_printf("OCSP status invalid times.\n"); + DEBUG(D_tls) + { + BIO * bp = BIO_new(BIO_s_mem()); + uschar * s = NULL; + int len; + time_print(bp, "This OCSP Update", thisupd); + if (nextupd) time_print(bp, "Next OCSP Update", nextupd); + if ((len = (int) BIO_get_mem_data(bp, CSS &s)) > 0) debug_printf("%.*s", len, s); + debug_printf("OCSP status invalid times.\n"); + } goto bad; } @@ -1438,7 +1501,7 @@ supply_response: ocsp_resplist ** op = &state->u_ocsp.server.olist, * oentry; while (oentry = *op) op = &oentry->next; - *op = oentry = store_get(sizeof(ocsp_resplist), FALSE); + *op = oentry = store_get(sizeof(ocsp_resplist), GET_UNTAINTED); oentry->next = NULL; oentry->resp = resp; } @@ -1460,12 +1523,12 @@ return; static void -ocsp_free_response_list(exim_openssl_state_st * cbinfo) +ocsp_free_response_list(exim_openssl_state_st * state) { -for (ocsp_resplist * olist = cbinfo->u_ocsp.server.olist; olist; +for (ocsp_resplist * olist = state->u_ocsp.server.olist; olist; olist = olist->next) OCSP_RESPONSE_free(olist->resp); -cbinfo->u_ocsp.server.olist = NULL; +state->u_ocsp.server.olist = NULL; } #endif /*!DISABLE_OCSP*/ @@ -1536,8 +1599,13 @@ else ) ) reexpand_tls_files_for_sni = TRUE; - if (!expand_check(state->certificate, US"tls_certificate", &expanded, errstr)) + if ( !expand_check(state->certificate, US"tls_certificate", &expanded, errstr) + || f.expand_string_forcedfail) + { + if (f.expand_string_forcedfail) + *errstr = US"expansion of tls_certificate failed"; return DEFER; + } if (expanded) if (state->is_server) @@ -1557,6 +1625,11 @@ else if (olist && !*olist) olist = NULL; + /* If doing a re-expand after SNI, avoid reloading the OCSP + responses when the list of filenames has not changed. + The creds-invali on content change wipes file_expanded, so that + always reloads here. */ + if ( state->u_ocsp.server.file_expanded && olist && (Ustrcmp(olist, state->u_ocsp.server.file_expanded) == 0)) { @@ -1600,9 +1673,14 @@ else if ((err = tls_add_certfile(sctx, state, expanded, errstr))) return err; - if ( state->privatekey - && !expand_check(state->privatekey, US"tls_privatekey", &expanded, errstr)) + if ( state->privatekey + && !expand_check(state->privatekey, US"tls_privatekey", &expanded, errstr) + || f.expand_string_forcedfail) + { + if (f.expand_string_forcedfail) + *errstr = US"expansion of tls_privatekey failed"; return DEFER; + } /* If expansion was forced to fail, key_expanded will be NULL. If the result of the expansion is an empty string, ignore it also, and assume the private @@ -1634,11 +1712,24 @@ return OK; * One-time init credentials for server and client * **************************************************/ +static void +normalise_ciphers(uschar ** ciphers, const uschar * pre_expansion_ciphers) +{ +uschar * s = *ciphers; + +if (!s || !Ustrchr(s, '_')) return; /* no change needed */ + +if (s == pre_expansion_ciphers) + s = string_copy(s); /* get writable copy */ + +for (uschar * t = s; *t; t++) if (*t == '_') *t = '-'; +*ciphers = s; +} + static int server_load_ciphers(SSL_CTX * ctx, exim_openssl_state_st * state, uschar * ciphers, uschar ** errstr) { -for (uschar * s = ciphers; *s; s++ ) if (*s == '_') *s = '-'; DEBUG(D_tls) debug_printf("required ciphers: %s\n", ciphers); if (!SSL_CTX_set_cipher_list(ctx, CS ciphers)) return tls_error(US"SSL_CTX_set_cipher_list", NULL, NULL, errstr); @@ -1664,13 +1755,13 @@ level. */ DEBUG(D_tls) { - SSL_CTX_set_info_callback(ctx, (void (*)())info_callback); -#if defined(EXIM_HAVE_OPESSL_TRACE) && !defined(OPENSSL_NO_SSL_TRACE) + SSL_CTX_set_info_callback(ctx, info_callback); +#if defined(EXIM_HAVE_OPENSSL_TRACE) && !defined(OPENSSL_NO_SSL_TRACE) /* this needs a debug build of OpenSSL */ - SSL_CTX_set_msg_callback(ctx, (void (*)())SSL_trace); + SSL_CTX_set_msg_callback(ctx, SSL_trace); #endif #ifdef OPENSSL_HAVE_KEYLOG_CB - SSL_CTX_set_keylog_callback(ctx, (void (*)())keylog_callback); + SSL_CTX_set_keylog_callback(ctx, keylog_callback); #endif } @@ -1700,7 +1791,7 @@ state_server.lib_state.lib_ctx = ctx; if (opt_unset_or_noexpand(tls_dhparam)) { - DEBUG(D_tls) debug_printf("TLS: preloading DH params for server\n"); + DEBUG(D_tls) debug_printf("TLS: preloading DH params '%s' for server\n", tls_dhparam); if (init_dh(ctx, tls_dhparam, &dummy_errstr)) state_server.lib_state.dh = TRUE; } @@ -1708,7 +1799,7 @@ else DEBUG(D_tls) debug_printf("TLS: not preloading DH params for server\n"); if (opt_unset_or_noexpand(tls_eccurve)) { - DEBUG(D_tls) debug_printf("TLS: preloading ECDH curve for server\n"); + DEBUG(D_tls) debug_printf("TLS: preloading ECDH curve '%s' for server\n", tls_eccurve); if (init_ecdh(ctx, &dummy_errstr)) state_server.lib_state.ecdh = TRUE; } @@ -1716,53 +1807,10 @@ else DEBUG(D_tls) debug_printf("TLS: not preloading ECDH curve for server\n"); #if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT) -/* If we can, preload the server-side cert, key and ocsp */ - -if ( opt_set_and_noexpand(tls_certificate) -# ifndef DISABLE_OCSP - && opt_unset_or_noexpand(tls_ocsp_file) -#endif - && opt_unset_or_noexpand(tls_privatekey)) - { - /* Set watches on the filenames. The implementation does de-duplication - so we can just blindly do them all. */ - - if ( tls_set_watch(tls_certificate, TRUE) -# ifndef DISABLE_OCSP - && tls_set_watch(tls_ocsp_file, TRUE) -#endif - && tls_set_watch(tls_privatekey, TRUE)) - { - state_server.certificate = tls_certificate; - state_server.privatekey = tls_privatekey; -#ifndef DISABLE_OCSP - state_server.u_ocsp.server.file = tls_ocsp_file; -#endif - - DEBUG(D_tls) debug_printf("TLS: preloading server certs\n"); - if (tls_expand_session_files(ctx, &state_server, &dummy_errstr) == OK) - state_server.lib_state.conn_certs = TRUE; - } - } -else if ( !tls_certificate && !tls_privatekey -# ifndef DISABLE_OCSP - && !tls_ocsp_file -#endif - ) - { /* Generate & preload a selfsigned cert. No files to watch. */ - if (tls_expand_session_files(ctx, &state_server, &dummy_errstr) == OK) - { - state_server.lib_state.conn_certs = TRUE; - lifetime = f.running_in_test_harness ? 2 : 60 * 60; /* 1 hour */ - } - } -else - DEBUG(D_tls) debug_printf("TLS: not preloading server certs\n"); - - /* If we can, preload the Authorities for checking client certs against. Actual choice to do verify is made (tls_{,try_}verify_hosts) -at TLS conn startup */ +at TLS conn startup. +Do this before the server ocsp so that its info can verify the ocsp. */ if ( opt_set_and_noexpand(tls_verify_certificates) && opt_unset_or_noexpand(tls_crl)) @@ -1773,15 +1821,60 @@ if ( opt_set_and_noexpand(tls_verify_certificates) && tls_set_watch(tls_verify_certificates, FALSE) && tls_set_watch(tls_crl, FALSE)) { + uschar * v_certs = tls_verify_certificates; DEBUG(D_tls) debug_printf("TLS: preloading CA bundle for server\n"); - if (setup_certs(ctx, tls_verify_certificates, tls_crl, NULL, &dummy_errstr) - == OK) + if (setup_certs(ctx, &v_certs, tls_crl, NULL, &dummy_errstr) == OK) state_server.lib_state.cabundle = TRUE; - } + + /* If we can, preload the server-side cert, key and ocsp */ + + if ( opt_set_and_noexpand(tls_certificate) +# ifndef DISABLE_OCSP + && opt_unset_or_noexpand(tls_ocsp_file) +# endif + && opt_unset_or_noexpand(tls_privatekey)) + { + /* Set watches on the filenames. The implementation does de-duplication + so we can just blindly do them all. */ + + if ( tls_set_watch(tls_certificate, TRUE) +# ifndef DISABLE_OCSP + && tls_set_watch(tls_ocsp_file, TRUE) +# endif + && tls_set_watch(tls_privatekey, TRUE)) + { + state_server.certificate = tls_certificate; + state_server.privatekey = tls_privatekey; +#ifndef DISABLE_OCSP + state_server.u_ocsp.server.file = tls_ocsp_file; +# endif + + DEBUG(D_tls) debug_printf("TLS: preloading server certs\n"); + if (tls_expand_session_files(ctx, &state_server, &dummy_errstr) == OK) + state_server.lib_state.conn_certs = TRUE; + } + } + else if ( !tls_certificate && !tls_privatekey +# ifndef DISABLE_OCSP + && !tls_ocsp_file +# endif + ) + { /* Generate & preload a selfsigned cert. No files to watch. */ + if (tls_expand_session_files(ctx, &state_server, &dummy_errstr) == OK) + { + state_server.lib_state.conn_certs = TRUE; + lifetime = f.running_in_test_harness ? 2 : 60 * 60; /* 1 hour */ + } + } + else + DEBUG(D_tls) debug_printf("TLS: not preloading server certs\n"); + } } else DEBUG(D_tls) debug_printf("TLS: not preloading CA bundle for server\n"); + + #endif /* EXIM_HAVE_INOTIFY */ @@ -1790,6 +1883,7 @@ else if (opt_set_and_noexpand(tls_require_ciphers)) { DEBUG(D_tls) debug_printf("TLS: preloading cipher list for server\n"); + normalise_ciphers(&tls_require_ciphers, tls_require_ciphers); if (server_load_ciphers(ctx, &state_server, tls_require_ciphers, &dummy_errstr) == OK) state_server.lib_state.pri_string = TRUE; @@ -1862,10 +1956,11 @@ if ( opt_set_and_noexpand(ob->tls_verify_certificates) && tls_set_watch(ob->tls_crl, FALSE) ) { + uschar * v_certs = ob->tls_verify_certificates; DEBUG(D_tls) debug_printf("TLS: preloading CA bundle for transport '%s'\n", t->name); - if (setup_certs(ctx, ob->tls_verify_certificates, + if (setup_certs(ctx, &v_certs, ob->tls_crl, dummy_host, &dummy_errstr) == OK) ob->tls_preload.cabundle = TRUE; } @@ -1881,12 +1976,15 @@ else #if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT) /* Invalidate the creds cached, by dropping the current ones. Call when we notice one of the source files has changed. */ - + static void tls_server_creds_invalidate(void) { SSL_CTX_free(state_server.lib_state.lib_ctx); state_server.lib_state = null_tls_preload; +#ifndef DISABLE_OCSP +state_server.u_ocsp.server.file_expanded = NULL; +#endif } @@ -1912,28 +2010,51 @@ tls_client_creds_invalidate(transport_instance * t) /* Extreme debug + * */ #ifndef DISABLE_OCSP -void -x509_store_dump_cert_s_names(X509_STORE * store) +static void +debug_print_sn(const X509 * cert) { -STACK_OF(X509_OBJECT) * roots= store->objs; +X509_NAME * sn = X509_get_subject_name((X509 *)cert); static uschar name[256]; +if (X509_NAME_oneline(sn, CS name, sizeof(name))) + { + name[sizeof(name)-1] = '\0'; + debug_printf(" %s\n", name); + } +} -for (int i= 0; i < sk_X509_OBJECT_num(roots); i++) +static void +x509_stack_dump_cert_s_names(const STACK_OF(X509) * sk) +{ +if (!sk) + debug_printf(" (null)\n"); +else { - X509_OBJECT * tmp_obj= sk_X509_OBJECT_value(roots, i); - if(tmp_obj->type == X509_LU_X509) - { - 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); - } - } + int idx = sk_X509_num(sk); + if (!idx) + debug_printf(" (empty)\n"); + else + while (--idx >= 0) debug_print_sn(sk_X509_value(sk, idx)); } } -#endif + +static void +x509_store_dump_cert_s_names(X509_STORE * store) +{ +# ifdef EXIM_HAVE_OPENSSL_X509_STORE_GET1_ALL_CERTS +if (!store) + debug_printf(" (no store)\n"); +else + { + STACK_OF(X509) * sk = X509_STORE_get1_all_certs(store); + x509_stack_dump_cert_s_names(sk); + sk_X509_pop_free(sk, X509_free); + } +# endif +} +#endif /*!DISABLE_OCSP*/ +/* */ @@ -1945,7 +2066,11 @@ typedef struct { /* Session ticket encryption key */ const EVP_CIPHER * aes_cipher; uschar aes_key[32]; /* size needed depends on cipher. aes_128 implies 128/8 = 16? */ +# if OPENSSL_VERSION_NUMBER < 0x30000000L const EVP_MD * hmac_hash; +# else + const uschar * hmac_hashname; +# endif uschar hmac_key[16]; time_t renew; time_t expire; @@ -1965,7 +2090,7 @@ if (exim_tk.name[0]) exim_tk_old = exim_tk; } -if (f.running_in_test_harness) ssl_session_timeout = 6; +if (f.running_in_test_harness) ssl_session_timeout = TESTSUITE_TICKET_LIFE; 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; @@ -1974,7 +2099,11 @@ 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(); +# if OPENSSL_VERSION_NUMBER < 0x30000000L exim_tk.hmac_hash = EVP_sha256(); +# else +exim_tk.hmac_hashname = US "sha256"; +# endif exim_tk.expire = t + ssl_session_timeout; exim_tk.renew = t + ssl_session_timeout/2; } @@ -1994,10 +2123,49 @@ return memcmp(name, exim_tk.name, sizeof(exim_tk.name)) == 0 ? &exim_tk : NULL; } + +static int +tk_hmac_init( +# if OPENSSL_VERSION_NUMBER < 0x30000000L + HMAC_CTX * hctx, +#else + EVP_MAC_CTX * hctx, +#endif + exim_stek * key + ) +{ +/*XXX will want these dependent on the ssl session strength */ +# if OPENSSL_VERSION_NUMBER < 0x30000000L + HMAC_Init_ex(hctx, key->hmac_key, sizeof(key->hmac_key), + key->hmac_hash, NULL); +#else + { + OSSL_PARAM params[3]; + uschar * hk = string_copy(key->hmac_hashname); /* need nonconst */ + params[0] = OSSL_PARAM_construct_octet_string("key", key->hmac_key, sizeof(key->hmac_key)); + params[1] = OSSL_PARAM_construct_utf8_string("digest", CS hk, 0); + params[2] = OSSL_PARAM_construct_end(); + if (EVP_MAC_CTX_set_params(hctx, params) == 0) + { + DEBUG(D_tls) debug_printf("EVP_MAC_CTX_set_params: %s\n", + ERR_reason_error_string(ERR_get_error())); + return 0; /* error in mac initialisation */ + } +} +#endif +return 1; +} + /* Callback for session tickets, on server */ static int ticket_key_callback(SSL * ssl, uschar key_name[16], - uschar * iv, EVP_CIPHER_CTX * c_ctx, HMAC_CTX * hctx, int enc) + uschar * iv, EVP_CIPHER_CTX * c_ctx, +# if OPENSSL_VERSION_NUMBER < 0x30000000L + HMAC_CTX * hctx, +#else + EVP_MAC_CTX * hctx, +#endif + int enc) { tls_support * tlsp = state_server.tlsp; exim_stek * key; @@ -2015,9 +2183,7 @@ if (enc) 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); + if (tk_hmac_init(hctx, key) == 0) return 0; EVP_EncryptInit_ex(c_ctx, key->aes_cipher, NULL, key->aes_key, iv); DEBUG(D_tls) debug_printf("ticket created\n"); @@ -2040,8 +2206,7 @@ else return 0; } - HMAC_Init_ex(hctx, key->hmac_key, sizeof(key->hmac_key), - key->hmac_hash, NULL); + if (tk_hmac_init(hctx, key) == 0) return 0; EVP_DecryptInit_ex(c_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); @@ -2054,7 +2219,7 @@ else return key->renew < now ? 2 : 1; } } -#endif +#endif /* !DISABLE_TLS_RESUME */ @@ -2092,13 +2257,13 @@ per https://www.openssl.org/docs/manmaster/man3/SSL_client_hello_cb_fn.html #ifdef EXIM_HAVE_OPENSSL_TLSEXT static int -tls_servername_cb(SSL *s, int *ad ARG_UNUSED, void *arg) +tls_servername_cb(SSL * s, int * ad ARG_UNUSED, void * arg) { -const char *servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name); -exim_openssl_state_st *state = (exim_openssl_state_st *) arg; +const char * servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name); +exim_openssl_state_st * state = (exim_openssl_state_st *) arg; int rc; int old_pool = store_pool; -uschar * dummy_errstr; +uschar * errstr; if (!servername) return SSL_TLSEXT_ERR_OK; @@ -2108,7 +2273,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_taint(US servername, TRUE); +tls_in.sni = string_copy_taint(US servername, GET_TAINTED); store_pool = old_pool; if (!reexpand_tls_files_for_sni) @@ -2118,24 +2283,28 @@ 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. */ -if (lib_ctx_new(&server_sni, NULL, &dummy_errstr) != OK) +if (lib_ctx_new(&server_sni, NULL, &errstr) != OK) goto bad; /* Not sure how many of these are actually needed, since SSL object already exists. Might even need this selfsame callback, for reneg? */ - { + { SSL_CTX * ctx = state_server.lib_state.lib_ctx; SSL_CTX_set_info_callback(server_sni, SSL_CTX_get_info_callback(ctx)); SSL_CTX_set_mode(server_sni, SSL_CTX_get_mode(ctx)); +#ifdef OPENSSL_MIN_PROTO_VERSION + SSL_CTX_set_min_proto_version(server_sni, SSL3_VERSION); +#endif SSL_CTX_set_options(server_sni, SSL_CTX_get_options(ctx)); + SSL_CTX_clear_options(server_sni, ~SSL_CTX_get_options(ctx)); SSL_CTX_set_timeout(server_sni, SSL_CTX_get_timeout(ctx)); SSL_CTX_set_tlsext_servername_callback(server_sni, tls_servername_cb); SSL_CTX_set_tlsext_servername_arg(server_sni, state); - } + } -if ( !init_dh(server_sni, state->dhparam, &dummy_errstr) - || !init_ecdh(server_sni, &dummy_errstr) +if ( !init_dh(server_sni, state->dhparam, &errstr) + || !init_ecdh(server_sni, &errstr) ) goto bad; @@ -2152,27 +2321,27 @@ if (state->u_ocsp.server.file) #endif { - uschar * expcerts; - if ( !expand_check(tls_verify_certificates, US"tls_verify_certificates", - &expcerts, &dummy_errstr) - || (rc = setup_certs(server_sni, expcerts, tls_crl, NULL, - &dummy_errstr)) != OK) + uschar * v_certs = tls_verify_certificates; + if ((rc = setup_certs(server_sni, &v_certs, tls_crl, NULL, + &errstr)) != OK) goto bad; - if (expcerts && *expcerts) + if (v_certs && *v_certs) setup_cert_verify(server_sni, FALSE, verify_callback_server); } /* do this after setup_certs, because this can require the certs for verifying OCSP information. */ -if ((rc = tls_expand_session_files(server_sni, state, &dummy_errstr)) != OK) +if ((rc = tls_expand_session_files(server_sni, state, &errstr)) != OK) goto bad; DEBUG(D_tls) debug_printf("Switching SSL context.\n"); SSL_set_SSL_CTX(s, server_sni); return SSL_TLSEXT_ERR_OK; -bad: return SSL_TLSEXT_ERR_ALERT_FATAL; +bad: + log_write(0, LOG_MAIN|LOG_PANIC, "%s", errstr); + return SSL_TLSEXT_ERR_ALERT_FATAL; } #endif /* EXIM_HAVE_OPENSSL_TLSEXT */ @@ -2192,6 +2361,8 @@ static int tls_server_alpn_cb(SSL *ssl, const uschar ** out, uschar * outlen, const uschar * in, unsigned int inlen, void * arg) { +gstring * g = NULL; + server_seen_alpn = TRUE; DEBUG(D_tls) { @@ -2222,12 +2393,19 @@ if ( inlen > 1 /* at least one name */ } } -/* More than one name from clilent, or name did not match our list. */ +/* More than one name from client, or name did not match our list. */ /* This will be fatal to the TLS conn; would be nice to kill TCP also. Maybe as an option in future; for now leave control to the config (must-tls). */ -DEBUG(D_tls) debug_printf("TLS ALPN rejected\n"); +for (int pos = 0, siz; pos < inlen; pos += siz+1) + { + siz = in[pos]; + if (pos + 1 + siz > inlen) siz = inlen - pos - 1; + g = string_append_listele_n(g, ':', in + pos + 1, siz); + } +log_write(0, LOG_MAIN, "TLS ALPN (%Y) rejected", g); +gstring_release_unused(g); return SSL_TLSEXT_ERR_ALERT_FATAL; } #endif /* EXIM_HAVE_ALPN */ @@ -2264,7 +2442,7 @@ tls_in.ocsp = OCSP_NOT_RESP; if (!olist) return SSL_TLSEXT_ERR_NOACK; -#ifdef EXIM_HAVE_OPESSL_GET0_SERIAL +#ifdef EXIM_HAVE_OPENSSL_GET0_SERIAL { const X509 * cert_sent = SSL_get_certificate(s); const ASN1_INTEGER * cert_serial = X509_get0_serialNumber(cert_sent); @@ -2331,15 +2509,25 @@ return SSL_TLSEXT_ERR_OK; static void -time_print(BIO * bp, const char * str, ASN1_GENERALIZEDTIME * time) +add_chain_to_store(X509_STORE * store, STACK_OF(X509) * sk, + const char * debug_text) { -BIO_printf(bp, "\t%s: ", str); -ASN1_GENERALIZEDTIME_print(bp, time); -BIO_puts(bp, "\n"); +int idx; + +DEBUG(D_tls) + { + debug_printf("chain for %s:\n", debug_text); + x509_stack_dump_cert_s_names(sk); + } +if (sk) + if ((idx = sk_X509_num(sk)) > 0) + while (--idx >= 0) + X509_STORE_add_cert(store, sk_X509_value(sk, idx)); + } static int -tls_client_stapling_cb(SSL *s, void *arg) +tls_client_stapling_cb(SSL * ssl, void * arg) { exim_openssl_state_st * cbinfo = arg; const unsigned char * p; @@ -2349,16 +2537,26 @@ OCSP_BASICRESP * bs; int i; DEBUG(D_tls) debug_printf("Received TLS status callback (OCSP stapling):\n"); -len = SSL_get_tlsext_status_ocsp_resp(s, &p); +len = SSL_get_tlsext_status_ocsp_resp(ssl, &p); if(!p) - { - /* Expect this when we requested ocsp but got none */ + { /* Expect this when we requested ocsp but got none */ + if (SSL_session_reused(ssl) && tls_out.ocsp == OCSP_VFIED) + { + DEBUG(D_tls) debug_printf(" null, but resumed; ocsp vfy stored with session is good\n"); + return 1; + } + if (cbinfo->u_ocsp.client.verify_required && LOGGING(tls_cipher)) log_write(0, LOG_MAIN, "Required TLS certificate status not received"); else DEBUG(D_tls) debug_printf(" null\n"); - return cbinfo->u_ocsp.client.verify_required ? 0 : 1; - } + + if (!cbinfo->u_ocsp.client.verify_required) + return 1; + cbinfo->u_ocsp.client.verify_errstr = + US"(SSL_connect) Required TLS certificate status not received"; + return 0; + } if (!(rsp = d2i_OCSP_RESPONSE(NULL, &p, len))) { @@ -2390,33 +2588,160 @@ if (!(bs = OCSP_response_get1_basic(rsp))) */ { BIO * bp = NULL; + X509_STORE * verify_store = NULL; + BOOL have_verified_OCSP_signer = FALSE; #ifndef EXIM_HAVE_OCSP_RESP_COUNT STACK_OF(OCSP_SINGLERESP) * sresp = bs->tbsResponseData->responses; #endif - DEBUG(D_tls) bp = BIO_new_fp(debug_file, BIO_NOCLOSE); + DEBUG(D_tls) bp = BIO_new(BIO_s_mem()); + + /* Use the CA & chain that verified the server cert to verify the stapled info */ + /*XXX could we do an event here, for observability of ocsp? What reasonable data could we give access to? */ + /* Dates would be a start. Do we need another opaque variable type, as for certs, plus an extract expansion? */ + + { + /* If this routine is not available, we've avoided [in tls_client_start()] + asking for certificate-status under DANE, so this callback won't run for + that combination. It still will for non-DANE. */ + +#if defined(EXIM_HAVE_OPENSSL_OCSP_RESP_GET0_SIGNER) && defined(SUPPORT_DANE) + X509 * signer; + + if ( tls_out.dane_verified + && (have_verified_OCSP_signer = + OCSP_resp_get0_signer(bs, &signer, SSL_get0_verified_chain(ssl)) == 1)) + { + DEBUG(D_tls) + debug_printf("signer for OCSP basicres is in the verified chain;" + " shortcut its verification\n"); + } + else +#endif + { + STACK_OF(X509) * verified_chain; + + verify_store = X509_STORE_new(); + + SSL_get0_chain_certs(ssl, &verified_chain); + add_chain_to_store(verify_store, verified_chain, + "'current cert' per SSL_get0_chain_certs()"); +#ifdef EXIM_HAVE_SSL_GET0_VERIFIED_CHAIN + verified_chain = SSL_get0_verified_chain(ssl); + add_chain_to_store(verify_store, verified_chain, + "SSL_get0_verified_chain()"); +#endif + } + } + + DEBUG(D_tls) + { + debug_printf("Untrusted intermediate cert stack (from SSL_get_peer_cert_chain()):\n"); + x509_stack_dump_cert_s_names(SSL_get_peer_cert_chain(ssl)); + + debug_printf("will use this CA store for verifying basicresp:\n"); + x509_store_dump_cert_s_names(verify_store); + + /* OCSP_RESPONSE_print(bp, rsp, 0); extreme debug: stapling content */ + + debug_printf("certs contained in basicresp:\n"); + x509_stack_dump_cert_s_names( +#ifdef EXIM_HAVE_OPENSSL_OCSP_RESP_GET0_CERTS + OCSP_resp_get0_certs(bs) +#else + bs->certs +#endif + ); + +#ifdef EXIM_HAVE_OPENSSL_X509_STORE_GET1_ALL_CERTS +/* could do via X509_STORE_get0_objects(); not worth it just for debug info */ + { + X509 * signer; + if (OCSP_resp_get0_signer(bs, &signer, X509_STORE_get1_all_certs(verify_store)) == 1) + { + debug_printf("found signer for basicres:\n"); + debug_print_sn(signer); + } + else + { + debug_printf("failed to find signer for basicres:\n"); + ERR_print_errors(bp); + } + } +#endif + + } - /*OCSP_RESPONSE_print(bp, rsp, 0); extreme debug: stapling content */ + ERR_clear_error(); - /* Use the chain that verified the server cert to verify the stapled info */ - /* DEBUG(D_tls) x509_store_dump_cert_s_names(cbinfo->u_ocsp.client.verify_store); */ + /* Under DANE the trust-anchor (at least in TA mode) is indicated by the TLSA + record in DNS, and probably is not the root of the chain of certificates. So + accept a partial chain for that case (and hope that anchor is visible for + verifying the OCSP stapling). + XXX for EE mode it won't even be that. Does that make OCSP useless for EE? + + Worse, for LetsEncrypt-mode (ocsp signer is leaf-signer) under DANE, the + data used within OpenSSL for the signer has nil pointers for signing + algorithms - and a crash results. Avoid this by shortcutting verification, + having determined that the OCSP signer is in the (DANE-)validated set. + */ + +#ifndef OCSP_PARTIAL_CHAIN /* defined for 3.0.0 onwards */ +# define OCSP_PARTIAL_CHAIN 0 +#endif - if ((i = OCSP_basic_verify(bs, cbinfo->verify_stack, - cbinfo->u_ocsp.client.verify_store, OCSP_NOEXPLICIT)) <= 0) + if ((i = OCSP_basic_verify(bs, SSL_get_peer_cert_chain(ssl), + verify_store, +#ifdef SUPPORT_DANE + tls_out.dane_verified + ? have_verified_OCSP_signer + ? OCSP_NOVERIFY | OCSP_NOEXPLICIT + : OCSP_PARTIAL_CHAIN | OCSP_NOEXPLICIT + : +#endif + OCSP_NOEXPLICIT)) <= 0) + { + DEBUG(D_tls) debug_printf("OCSP_basic_verify() fail: returned %d\n", i); if (ERR_peek_error()) { tls_out.ocsp = OCSP_FAILED; - 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); + if (LOGGING(tls_cipher)) + { + static uschar peerdn[256]; + const uschar * errstr;; + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + ERR_peek_error_all(NULL, NULL, NULL, CCSS &errstr, NULL); + if (!errstr) +#endif + errstr = CUS ERR_reason_error_string(ERR_peek_error()); + + X509_NAME_oneline(X509_get_subject_name(SSL_get_peer_certificate(ssl)), + CS peerdn, sizeof(peerdn)); + log_write(0, LOG_MAIN, + "[%s] %s Received TLS cert (DN: '%.*s') status response, " + "itself unverifiable: %s", + deliver_host_address, deliver_host, + (int)sizeof(peerdn), peerdn, errstr); + } + DEBUG(D_tls) + { + BIO_printf(bp, "OCSP response verify failure\n"); + ERR_print_errors(bp); + { + uschar * s = NULL; + int len = (int) BIO_get_mem_data(bp, CSS &s); + if (len > 0) debug_printf("%.*s", len, s); + BIO_reset(bp); + } + OCSP_RESPONSE_print(bp, rsp, 0); + } goto failed; } else DEBUG(D_tls) debug_printf("no explicit trust for OCSP signing" " in the root CA certificate; ignoring\n"); + } DEBUG(D_tls) debug_printf("OCSP response well-formed and signed OK\n"); @@ -2449,14 +2774,19 @@ if (!(bs = OCSP_response_get1_basic(rsp))) status = OCSP_single_get0_status(single, &reason, &rev, &thisupd, &nextupd); - DEBUG(D_tls) time_print(bp, "This OCSP Update", thisupd); - DEBUG(D_tls) if(nextupd) time_print(bp, "Next OCSP Update", nextupd); + DEBUG(D_tls) + { + time_print(bp, "This OCSP Update", thisupd); + if (nextupd) time_print(bp, "Next OCSP Update", nextupd); + } if (!OCSP_check_validity(thisupd, nextupd, EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX_AGE)) { tls_out.ocsp = OCSP_FAILED; DEBUG(D_tls) ERR_print_errors(bp); - log_write(0, LOG_MAIN, "Server OSCP dates invalid"); + cbinfo->u_ocsp.client.verify_errstr = + US"(SSL_connect) Server certificate status is out-of-date"; + log_write(0, LOG_MAIN, "OCSP dates invalid"); goto failed; } @@ -2467,12 +2797,16 @@ if (!(bs = OCSP_response_get1_basic(rsp))) case V_OCSP_CERTSTATUS_GOOD: continue; /* the idx loop */ case V_OCSP_CERTSTATUS_REVOKED: + cbinfo->u_ocsp.client.verify_errstr = + US"(SSL_connect) Server certificate revoked"; log_write(0, LOG_MAIN, "Server certificate revoked%s%s", reason != -1 ? "; reason: " : "", reason != -1 ? OCSP_crl_reason_str(reason) : ""); DEBUG(D_tls) time_print(bp, "Revocation Time", rev); break; default: + cbinfo->u_ocsp.client.verify_errstr = + US"(SSL_connect) Server certificate has unknown status"; log_write(0, LOG_MAIN, "Server certificate status unknown, in OCSP stapling"); break; @@ -2489,6 +2823,11 @@ if (!(bs = OCSP_response_get1_basic(rsp))) tls_out.ocsp = OCSP_FAILED; i = cbinfo->u_ocsp.client.verify_required ? 0 : 1; good: + { + uschar * s = NULL; + int len = (int) BIO_get_mem_data(bp, CSS &s); + if (len > 0) debug_printf("%.*s", len, s); + } BIO_free(bp); } @@ -2521,8 +2860,7 @@ tls_init(host_item * host, smtp_transport_options_block * ob, uschar *ocsp_file, #endif address_item *addr, exim_openssl_state_st ** caller_state, - tls_support * tlsp, - uschar ** errstr) + tls_support * tlsp, uschar ** errstr) { SSL_CTX * ctx; exim_openssl_state_st * state; @@ -2619,10 +2957,17 @@ if (init_options) } #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( +#ifdef OPENSSL_MIN_PROTO_VERSION + SSL_CTX_set_min_proto_version(ctx, SSL3_VERSION); +#endif + DEBUG(D_tls) debug_printf("setting SSL CTX options: %016lx\n", init_options); + SSL_CTX_set_options(ctx, init_options); + { + uint64_t readback = SSL_CTX_clear_options(ctx, ~init_options); + if (readback != init_options) + return tls_error(string_sprintf( "SSL_CTX_set_option(%#lx)", init_options), host, NULL, errstr); + } } else DEBUG(D_tls) debug_printf("no SSL CTX options to set\n"); @@ -2663,7 +3008,7 @@ if (state->lib_state.conn_certs) else { #ifndef DISABLE_OCSP - if (!host) + if (!host) /* server */ { state->u_ocsp.server.file = ocsp_file; state->u_ocsp.server.file_expanded = NULL; @@ -2677,7 +3022,7 @@ else #ifdef EXIM_HAVE_OPENSSL_TLSEXT # ifndef DISABLE_OCSP - if (!(state->verify_stack = sk_X509_new_null())) + if (!host && !(state->u_ocsp.server.verify_stack = sk_X509_new_null())) { DEBUG(D_tls) debug_printf("failed to create stack for stapling verify\n"); return FAIL; @@ -2726,11 +3071,12 @@ else /* client */ DEBUG(D_tls) debug_printf("failed to create store for stapling verify\n"); return FAIL; } + SSL_CTX_set_tlsext_status_cb(ctx, tls_client_stapling_cb); SSL_CTX_set_tlsext_status_arg(ctx, state); } # endif -#endif +#endif /*EXIM_HAVE_OPENSSL_TLSEXT*/ state->verify_cert_hostnames = NULL; @@ -2869,7 +3215,7 @@ if (tlsp->peercert) *************************************************/ #ifndef DISABLE_OCSP -/* Load certs from file, return TRUE on success */ +/* In the server, load certs from file, return TRUE on success */ static BOOL chain_from_pem_file(const uschar * file, STACK_OF(X509) ** vp) @@ -2899,7 +3245,7 @@ repeated after a Server Name Indication. Arguments: sctx SSL_CTX* to initialise - certs certs file, expanded + certsp certs file, returned expanded crl CRL file or NULL host NULL in a server; the remote host in a client errstr error string pointer @@ -2908,15 +3254,16 @@ Returns: OK/DEFER/FAIL */ static int -setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, +setup_certs(SSL_CTX * sctx, uschar ** certsp, uschar * crl, host_item * host, uschar ** errstr) { -uschar *expcerts, *expcrl; +uschar * expcerts, * expcrl; -if (!expand_check(certs, US"tls_verify_certificates", &expcerts, errstr)) +if (!expand_check(*certsp, US"tls_verify_certificates", &expcerts, errstr)) return DEFER; DEBUG(D_tls) debug_printf("tls_verify_certificates: %s\n", expcerts); +*certsp = expcerts; if (expcerts && *expcerts) { /* Tell the library to use its compiled-in location for the system default @@ -2944,19 +3291,20 @@ if (expcerts && *expcerts) { STACK_OF(X509) * verify_stack = #ifndef DISABLE_OCSP - !host ? state_server.verify_stack : + !host ? state_server.u_ocsp.server.verify_stack : #endif NULL; STACK_OF(X509) ** vp = &verify_stack; file = expcerts; dir = NULL; #ifndef DISABLE_OCSP - /* In the server if we will be offering an OCSP proof, load chain from + /* 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. */ +/* *debug_printf("file for checking server ocsp stapling is: %s\n", file); */ if ( !host && statbuf.st_size > 0 && state_server.u_ocsp.server.file @@ -3064,6 +3412,67 @@ return OK; +static void +tls_dump_keylog(SSL * ssl) +{ +#ifdef EXIM_HAVE_OPENSSL_KEYLOG + BIO * bp = BIO_new(BIO_s_mem()); + uschar * s = NULL; + int len; + SSL_SESSION_print_keylog(bp, SSL_get_session(ssl)); + len = (int) BIO_get_mem_data(bp, CSS &s); + if (len > 0) debug_printf("%.*s", len, s); + BIO_free(bp); +#endif +} + + +/* Channel-binding info for authenticators +See description in https://paquier.xyz/postgresql-2/channel-binding-openssl/ +for pre-TLS1.3 +*/ + +static void +tls_get_channel_binding(SSL * ssl, tls_support * tlsp, const void * taintval) +{ +uschar c, * s; +size_t len; + +#ifdef EXIM_HAVE_EXPORT_CHNL_BNGNG +if (SSL_version(ssl) > TLS1_2_VERSION) + { + /* It's not documented by OpenSSL how big the output buffer must be. + The OpenSSL testcases use 80 bytes but don't say why. The GnuTLS impl only + serves out 32B. RFC 9266 says it is 32B. + Interop fails unless we use the same each end. */ + len = 32; + + tlsp->channelbind_exporter = TRUE; + taintval = GET_UNTAINTED; + if (SSL_export_keying_material(ssl, + s = store_get((int)len, taintval), len, + "EXPORTER-Channel-Binding", (size_t) 24, + NULL, 0, 0) != 1) + len = 0; + } +else +#endif + { + len = SSL_get_peer_finished(ssl, &c, 0); + len = SSL_get_peer_finished(ssl, s = store_get((int)len, taintval), len); + } + +if (len > 0) + { + int old_pool = store_pool; + store_pool = POOL_PERM; + tlsp->channelbinding = b64encode_taint(CUS s, (int)len, taintval); + store_pool = old_pool; + DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage %p %p\n", tlsp->channelbinding, tlsp); + } +} + + /************************************************* * Start a TLS session in a server * *************************************************/ @@ -3095,7 +3504,7 @@ static uschar peerdn[256]; 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", FALSE); + smtp_printf("554 Already in TLS\r\n", SP_NO_MORE); return FAIL; } @@ -3121,14 +3530,17 @@ TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256 if (state_server.lib_state.pri_string) { DEBUG(D_tls) debug_printf("TLS: cipher list was preloaded\n"); } -else +else { if (!expand_check(tls_require_ciphers, US"tls_require_ciphers", &expciphers, errstr)) return FAIL; - if ( expciphers - && (rc = server_load_ciphers(ctx, &state_server, expciphers, errstr)) != OK) - return rc; + if (expciphers) + { + normalise_ciphers(&expciphers, tls_require_ciphers); + if ((rc = server_load_ciphers(ctx, &state_server, expciphers, errstr)) != OK) + return rc; + } } /* If this is a host for which certificate verification is mandatory or @@ -3148,27 +3560,33 @@ else goto skip_certs; { - uschar * expcerts; - if (!expand_check(tls_verify_certificates, US"tls_verify_certificates", - &expcerts, errstr)) - return DEFER; - DEBUG(D_tls) debug_printf("tls_verify_certificates: %s\n", expcerts); + uschar * v_certs = tls_verify_certificates; if (state_server.lib_state.cabundle) - { DEBUG(D_tls) debug_printf("TLS: CA bundle for server was preloaded\n"); } + { + DEBUG(D_tls) debug_printf("TLS: CA bundle for server was preloaded\n"); + setup_cert_verify(ctx, server_verify_optional, verify_callback_server); + } else - if ((rc = setup_certs(ctx, expcerts, tls_crl, NULL, errstr)) != OK) + { + if ((rc = setup_certs(ctx, &v_certs, tls_crl, NULL, errstr)) != OK) return rc; - - if (expcerts && *expcerts) - setup_cert_verify(ctx, server_verify_optional, verify_callback_server); + if (v_certs && *v_certs) + setup_cert_verify(ctx, server_verify_optional, verify_callback_server); + } } skip_certs: ; #ifndef DISABLE_TLS_RESUME +# if OPENSSL_VERSION_NUMBER < 0x30000000L SSL_CTX_set_tlsext_ticket_key_cb(ctx, ticket_key_callback); /* despite working, appears to always return failure, so ignoring */ +# else +SSL_CTX_set_tlsext_ticket_key_evp_cb(ctx, ticket_key_callback); +/* despite working, appears to always return failure, so ignoring */ +# endif #endif + #ifdef OPENSSL_HAVE_NUM_TICKETS # ifndef DISABLE_TLS_RESUME SSL_CTX_set_num_tickets(ctx, tls_in.host_resumable ? 1 : 0); @@ -3206,7 +3624,7 @@ mode, the fflush() happens when smtp_getc() is called. */ SSL_set_session_id_context(ssl, sid_ctx, Ustrlen(sid_ctx)); if (!tls_in.on_connect) { - smtp_printf("220 TLS go ahead\r\n", FALSE); + smtp_printf("220 TLS go ahead\r\n", SP_NO_MORE); fflush(smtp_out); } @@ -3236,9 +3654,11 @@ if (rc <= 0) case SSL_ERROR_ZERO_RETURN: DEBUG(D_tls) debug_printf("Got SSL_ERROR_ZERO_RETURN\n"); (void) tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : NULL, errstr); - +#ifndef DISABLE_EVENT + (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL); +#endif if (SSL_get_shutdown(ssl) == SSL_RECEIVED_SHUTDOWN) - SSL_shutdown(ssl); + SSL_shutdown(ssl); tls_close(NULL, TLS_NO_SHUTDOWN); return FAIL; @@ -3253,8 +3673,11 @@ if (rc <= 0) || r == SSL_R_VERSION_TOO_LOW #endif || r == SSL_R_UNKNOWN_PROTOCOL || r == SSL_R_UNSUPPORTED_PROTOCOL) - s = string_sprintf("%s (%s)", s, SSL_get_version(ssl)); + s = string_sprintf("(%s)", SSL_get_version(ssl)); (void) tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : s, errstr); +#ifndef DISABLE_EVENT + (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL); +#endif return FAIL; } @@ -3265,6 +3688,9 @@ if (rc <= 0) if (!errno) { *errstr = US"SSL_accept: TCP connection closed by peer"; +#ifndef DISABLE_EVENT + (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL); +#endif return FAIL; } DEBUG(D_tls) debug_printf(" - syscall %s\n", strerror(errno)); @@ -3273,6 +3699,9 @@ if (rc <= 0) sigalrm_seen ? US"timed out" : ERR_peek_error() ? NULL : string_sprintf("ret %d", error), errstr); +#ifndef DISABLE_EVENT + (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL); +#endif return FAIL; } } @@ -3320,6 +3749,7 @@ else DEBUG(D_tls) adjust the input functions to read via TLS, and initialize things. */ #ifdef SSL_get_extms_support +/*XXX what does this return for tls1.3 ? */ tls_in.ext_master_secret = SSL_get_extms_support(ssl) == 1; #endif peer_cert(ssl, &tls_in, peerdn, sizeof(peerdn)); @@ -3334,13 +3764,7 @@ DEBUG(D_tls) if (SSL_get_shared_ciphers(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(ssl)); - BIO_free(bp); - } -#endif + tls_dump_keylog(ssl); #ifdef EXIM_HAVE_SESSION_TICKET { @@ -3358,19 +3782,7 @@ DEBUG(D_tls) tls_in.ourcert = crt ? X509_dup(crt) : NULL; } -/* Channel-binding info for authenticators -See description in https://paquier.xyz/postgresql-2/channel-binding-openssl/ */ - { - uschar c, * s; - size_t len = SSL_get_peer_finished(ssl, &c, 0); - int old_pool = store_pool; - - SSL_get_peer_finished(ssl, s = store_get((int)len, FALSE), len); - store_pool = POOL_PERM; - tls_in.channelbinding = b64encode_taint(CUS s, (int)len, FALSE); - store_pool = old_pool; - DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage %p\n", tls_in.channelbinding); - } +tls_get_channel_binding(ssl, &tls_in, GET_UNTAINTED); /* Only used by the server-side tls (tls_in), including tls_getc. Client-side (tls_out) reads (seem to?) go via @@ -3424,20 +3836,20 @@ else return OK; { - uschar * expcerts; - if (!expand_check(ob->tls_verify_certificates, US"tls_verify_certificates", - &expcerts, errstr)) - return DEFER; - DEBUG(D_tls) debug_printf("tls_verify_certificates: %s\n", expcerts); + uschar * v_certs = ob->tls_verify_certificates; if (state->lib_state.cabundle) - { DEBUG(D_tls) debug_printf("TLS: CA bundle was preloaded\n"); } + { + DEBUG(D_tls) debug_printf("TLS: CA bundle for tpt was preloaded\n"); + setup_cert_verify(ctx, client_verify_optional, verify_callback_client); + } else - if ((rc = setup_certs(ctx, expcerts, ob->tls_crl, host, errstr)) != OK) + { + if ((rc = setup_certs(ctx, &v_certs, ob->tls_crl, host, errstr)) != OK) return rc; - - if (expcerts && *expcerts) - setup_cert_verify(ctx, client_verify_optional, verify_callback_client); + if (v_certs && *v_certs) + setup_cert_verify(ctx, client_verify_optional, verify_callback_client); + } } if (verify_check_given_host(CUSS &ob->tls_verify_cert_hostnames, host) == OK) @@ -3517,9 +3929,8 @@ return DEFER; and apply it to the ssl-connection for attempted resumption. */ static void -tls_retrieve_session(tls_support * tlsp, SSL * ssl, const uschar * key) +tls_retrieve_session(tls_support * tlsp, SSL * ssl) { -tlsp->resumption |= RESUME_SUPPORTED; if (tlsp->host_resumable) { dbdata_tls_session * dt; @@ -3527,11 +3938,11 @@ if (tlsp->host_resumable) open_db dbblock, * dbm_file; tlsp->resumption |= RESUME_CLIENT_REQUESTED; - DEBUG(D_tls) debug_printf("checking for resumable session for %s\n", key); + DEBUG(D_tls) + debug_printf("checking for resumable session for %s\n", tlsp->resume_index); if ((dbm_file = dbfn_open(US"tls", O_RDWR, &dbblock, FALSE, FALSE))) { - /* key for the db is the IP */ - if ((dt = dbfn_read_with_length(dbm_file, key, &len))) + if ((dt = dbfn_read_with_length(dbm_file, tlsp->resume_index, &len))) { SSL_SESSION * ss = NULL; const uschar * sess_asn1 = dt->session; @@ -3552,29 +3963,27 @@ if (tlsp->host_resumable) #ifdef EXIM_HAVE_SESSION_TICKET SSL_SESSION_get_ticket_lifetime_hint(ss); #else /* Use, fairly arbitrilarily, what we as server would */ - f.running_in_test_harness ? 6 : ssl_session_timeout; + f.running_in_test_harness ? TESTSUITE_TICKET_LIFE : ssl_session_timeout; #endif - if (lifetime + dt->time_stamp < time(NULL)) - { - DEBUG(D_tls) debug_printf("session expired\n"); - dbfn_delete(dbm_file, key); - } - else if (!SSL_set_session(ssl, ss)) + time_t now = time(NULL), expires = lifetime + dt->time_stamp; + if (expires < now) { - 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); - } + DEBUG(D_tls) debug_printf("session expired (by " TIME_T_FMT "s from %lus)\n", now - expires, lifetime); + dbfn_delete(dbm_file, tlsp->resume_index); } - else + else if (SSL_set_session(ssl, ss)) { - DEBUG(D_tls) debug_printf("good session\n"); + DEBUG(D_tls) debug_printf("good session (" TIME_T_FMT "s left of %lus)\n", expires - now, lifetime); tlsp->resumption |= RESUME_CLIENT_SUGGESTED; tlsp->verify_override = dt->verify_override; tlsp->ocsp = dt->ocsp; } + else 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 @@ -3603,7 +4012,7 @@ if (SSL_SESSION_is_resumable(ss)) /* 1.1.1 */ { int len = i2d_SSL_SESSION(ss, NULL); int dlen = sizeof(dbdata_tls_session) + len; - dbdata_tls_session * dt = store_get(dlen, TRUE); + dbdata_tls_session * dt = store_get(dlen, GET_TAINTED); uschar * s = dt->session; open_db dbblock, * dbm_file; @@ -3616,9 +4025,7 @@ if (SSL_SESSION_is_resumable(ss)) /* 1.1.1 */ 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_write(dbm_file, tlsp->resume_index, dt, dlen); dbfn_close(dbm_file); DEBUG(D_tls) debug_printf("wrote session (len %u) to db\n", (unsigned)dlen); @@ -3628,21 +4035,20 @@ return 1; } +/* Construct a key for session DB lookup, and setup the SSL_CTX for resumption */ + 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) + exim_openssl_client_tls_ctx * exim_client_ctx, smtp_connect_args * conn_args, + tls_support * tlsp, smtp_transport_options_block * ob) { -/* Should the client request a session resumption ticket? */ -if (verify_check_given_host(CUSS &ob->tls_resumption_hosts, host) == OK) - { - tlsp->host_resumable = TRUE; +tlsp->host_resumable = TRUE; +tls_client_resmption_key(tlsp, conn_args, ob); - 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); - } +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 @@ -3661,12 +4067,12 @@ if (tlsp->host_resumable) 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_state); + /* debug_printf("tls_exdata_idx %d cbinfo %p\n", tls_exdata_idx, client_static_state); */ } tlsp->resumption = RESUME_SUPPORTED; /* Pick up a previous session, saved on an old ticket */ -tls_retrieve_session(tlsp, ssl, host->address); +tls_retrieve_session(tlsp, ssl); return TRUE; } @@ -3686,16 +4092,19 @@ if (SSL_session_reused(exim_client_ctx->ssl)) #ifdef EXIM_HAVE_ALPN /* Expand and convert an Exim list to an ALPN list. False return for fail. NULL plist return for silent no-ALPN. + +Overwite the passed-in list with the expanded version. */ static BOOL -tls_alpn_plist(const uschar * tls_alpn, const uschar ** plist, unsigned * plen, +tls_alpn_plist(uschar ** tls_alpn, const uschar ** plist, unsigned * plen, uschar ** errstr) { uschar * exp_alpn; -if (!expand_check(tls_alpn, US"tls_alpn", &exp_alpn, errstr)) +if (!expand_check(*tls_alpn, US"tls_alpn", &exp_alpn, errstr)) return FALSE; +*tls_alpn = exp_alpn; if (!exp_alpn) { @@ -3708,7 +4117,7 @@ else but it's little extra code complexity in the client. */ const uschar * list = exp_alpn; - uschar * p = store_get(Ustrlen(exp_alpn), is_tainted(exp_alpn)), * s, * t; + uschar * p = store_get(Ustrlen(exp_alpn), exp_alpn), * s, * t; int sep = 0; uschar len; @@ -3762,7 +4171,7 @@ BOOL require_ocsp = FALSE; rc = store_pool; store_pool = POOL_PERM; -exim_client_ctx = store_get(sizeof(exim_openssl_client_tls_ctx), FALSE); +exim_client_ctx = store_get(sizeof(exim_openssl_client_tls_ctx), GET_UNTAINTED); exim_client_ctx->corked = NULL; store_pool = rc; @@ -3773,7 +4182,6 @@ tlsp->tlsa_usage = 0; #ifndef DISABLE_OCSP { # ifdef SUPPORT_DANE - /*XXX this should be moved to caller, to be common across gnutls/openssl */ if ( conn_args->dane && ob->hosts_request_ocsp[0] == '*' && ob->hosts_request_ocsp[1] == '\0' @@ -3796,6 +4204,15 @@ tlsp->tlsa_usage = 0; # endif request_ocsp = verify_check_given_host(CUSS &ob->hosts_request_ocsp, host) == OK; + +# if defined(SUPPORT_DANE) && !defined(EXIM_HAVE_OPENSSL_OCSP_RESP_GET0_SIGNER) + if (conn_args->dane && (require_ocsp || request_ocsp)) + { + DEBUG(D_tls) debug_printf("OpenSSL version to early to combine OCSP" + " and DANE; disabling OCSP\n"); + require_ocsp = request_ocsp = FALSE; + } +# endif } #endif @@ -3823,21 +4240,25 @@ if (conn_args->dane) return FALSE; if (expciphers && *expciphers == '\0') expciphers = NULL; + + normalise_ciphers(&expciphers, ob->dane_require_tls_ciphers); } #endif -if (!expciphers && - !expand_check(ob->tls_require_ciphers, US"tls_require_ciphers", +if (!expciphers) + { + if (!expand_check(ob->tls_require_ciphers, US"tls_require_ciphers", &expciphers, errstr)) - return FALSE; + return FALSE; -/* In OpenSSL, cipher components are separated by hyphens. In GnuTLS, they -are separated by underscores. So that I can use either form in my tests, and -also for general convenience, we turn underscores into hyphens here. */ + /* In OpenSSL, cipher components are separated by hyphens. In GnuTLS, they + are separated by underscores. So that I can use either form in my tests, and + also for general convenience, we turn underscores into hyphens here. */ + + normalise_ciphers(&expciphers, ob->tls_require_ciphers); + } 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(exim_client_ctx->ctx, CS expciphers)) { @@ -3863,6 +4284,7 @@ if (conn_args->dane) tls_error(US"context init", host, NULL, errstr); return FALSE; } + DEBUG(D_tls) debug_printf("since dane-mode conn, not loading the usual CA bundle\n"); } else @@ -3872,39 +4294,20 @@ if (tls_client_basic_ctx_init(exim_client_ctx->ctx, host, ob, client_static_state, errstr) != OK) return FALSE; -#ifndef DISABLE_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)); - -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 FALSE; if (!tlsp->sni) - { - DEBUG(D_tls) debug_printf("Setting TLS SNI forced to fail, not sending\n"); - } + { DEBUG(D_tls) debug_printf("Setting TLS SNI forced to fail, not sending\n"); } else if (!Ustrlen(tlsp->sni)) tlsp->sni = NULL; else { -#ifdef EXIM_HAVE_OPENSSL_TLSEXT - DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tlsp->sni); - SSL_set_tlsext_host_name(exim_client_ctx->ssl, tlsp->sni); -#else +#ifndef EXIM_HAVE_OPENSSL_TLSEXT log_write(0, LOG_MAIN, "SNI unusable with this OpenSSL library version; ignoring \"%s\"\n", tlsp->sni); + tlsp->sni = NULL; #endif } } @@ -3915,10 +4318,10 @@ if (ob->tls_alpn) const uschar * plist; unsigned plen; - if (!tls_alpn_plist(ob->tls_alpn, &plist, &plen, errstr)) + if (!tls_alpn_plist(&ob->tls_alpn, &plist, &plen, errstr)) return FALSE; if (plist) - if (SSL_set_alpn_protos(exim_client_ctx->ssl, plist, plen) != 0) + if (SSL_CTX_set_alpn_protos(exim_client_ctx->ctx, plist, plen) != 0) { tls_error(US"alpn init", host, NULL, errstr); return FALSE; @@ -3931,6 +4334,34 @@ if (ob->tls_alpn) ob->tls_alpn); #endif +#ifndef DISABLE_TLS_RESUME +/*XXX have_lbserver: another cmdline arg possibly, for continued-conn, but use +will be very low. */ + +if (!conn_args->have_lbserver) /* wanted for tls_client_resmption_key() */ + { DEBUG(D_tls) debug_printf("resumption not supported on continued-connection\n"); } +else if (verify_check_given_host(CUSS &ob->tls_resumption_hosts, host) == OK) + tls_client_ctx_resume_prehandshake(exim_client_ctx, conn_args, tlsp, ob); +#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)); +SSL_set_fd(exim_client_ctx->ssl, cctx->sock); +SSL_set_connect_state(exim_client_ctx->ssl); + +#ifdef EXIM_HAVE_OPENSSL_TLSEXT +if (tlsp->sni) + { + DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tlsp->sni); + SSL_set_tlsext_host_name(exim_client_ctx->ssl, tlsp->sni); + } +#endif + #ifdef SUPPORT_DANE if (conn_args->dane) if (dane_tlsa_load(exim_client_ctx->ssl, host, &conn_args->tlsa_dnsa, errstr) != OK) @@ -3990,20 +4421,19 @@ if (conn_args->dane) if (rc <= 0) { - tls_error(US"SSL_connect", host, sigalrm_seen ? US"timed out" : NULL, errstr); +#ifndef DISABLE_OCSP + if (client_static_state->u_ocsp.client.verify_errstr) + { if (errstr) *errstr = client_static_state->u_ocsp.client.verify_errstr; } + else +#endif + 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 + tls_dump_keylog(exim_client_ctx->ssl); } #ifndef DISABLE_TLS_RESUME @@ -4044,18 +4474,7 @@ tlsp->cipher_stdname = cipher_stdname_ssl(exim_client_ctx->ssl); } /*XXX will this work with continued-TLS? */ -/* Channel-binding info for authenticators */ - { - uschar c, * s; - size_t len = SSL_get_finished(exim_client_ctx->ssl, &c, 0); - int old_pool = store_pool; - - SSL_get_finished(exim_client_ctx->ssl, s = store_get((int)len, TRUE), len); - store_pool = POOL_PERM; - tlsp->channelbinding = b64encode_taint(CUS s, (int)len, TRUE); - store_pool = old_pool; - DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage %p %p\n", tlsp->channelbinding, tlsp); - } +tls_get_channel_binding(exim_client_ctx->ssl, tlsp, GET_TAINTED); tlsp->active.sock = cctx->sock; tlsp->active.tls_ctx = exim_client_ctx; @@ -4113,10 +4532,15 @@ switch(error) /* Handle genuine errors */ case SSL_ERROR_SSL: + { + uschar * conn_info = smtp_get_connection_info(); + if (Ustrncmp(conn_info, US"SMTP ", 5) == 0) conn_info += 5; + /* I'd like to get separated H= here, but too hard for now */ ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring)); - log_write(0, LOG_MAIN, "TLS error (SSL_read): %s", ssl_errstring); + log_write(0, LOG_MAIN, "TLS error (SSL_read): on %s %s", conn_info, ssl_errstring); ssl_xfer_error = TRUE; return FALSE; + } default: DEBUG(D_tls) debug_printf("Got SSL error %d\n", error); @@ -4194,7 +4618,6 @@ tls_get_cache(unsigned lim) { #ifndef DISABLE_DKIM int n = ssl_xfer_buffer_hwm - ssl_xfer_buffer_lwm; -debug_printf("tls_get_cache\n"); if (n > lim) n = lim; if (n > 0) @@ -4422,19 +4845,25 @@ int * fdp = o_ctx ? &tls_out.active.sock : &tls_in.active.sock; if (*fdp < 0) return; /* TLS was not active */ -if (do_shutdown) +if (do_shutdown > TLS_NO_SHUTDOWN) { int rc; DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS%s\n", - do_shutdown > 1 ? " (with response-wait)" : ""); + do_shutdown > TLS_SHUTDOWN_NOWAIT ? " (with response-wait)" : ""); tls_write(ct_ctx, NULL, 0, FALSE); /* flush write buffer */ - if ( (rc = SSL_shutdown(*sslp)) == 0 /* send "close notify" alert */ - && do_shutdown > 1) + if ( ( do_shutdown >= TLS_SHUTDOWN_WONLY + || (rc = SSL_shutdown(*sslp)) == 0 /* send "close notify" alert */ + ) + && do_shutdown > TLS_SHUTDOWN_NOWAIT + ) { +#ifdef EXIM_TCP_CORK + (void) setsockopt(*fdp, IPPROTO_TCP, EXIM_TCP_CORK, US &off, sizeof(off)); +#endif ALARM(2); - rc = SSL_shutdown(*sslp); /* wait for response */ + rc = SSL_shutdown(*sslp); /* wait for response */ ALARM_CLR(0); } @@ -4448,8 +4877,8 @@ if (do_shutdown) if (!o_ctx) /* server side */ { #ifndef DISABLE_OCSP - sk_X509_pop_free(state_server.verify_stack, X509_free); - state_server.verify_stack = NULL; + sk_X509_pop_free(state_server.u_ocsp.server.verify_stack, X509_free); + state_server.u_ocsp.server.verify_stack = NULL; #endif receive_getc = smtp_getc; @@ -4485,8 +4914,8 @@ Returns: NULL on success, or error message uschar * tls_validate_require_cipher(void) { -SSL_CTX *ctx; -uschar *s, *expciphers, *err; +SSL_CTX * ctx; +uschar * expciphers, * err; tls_openssl_init(); @@ -4500,12 +4929,9 @@ if (!expand_check(tls_require_ciphers, US"tls_require_ciphers", &expciphers, if (!(expciphers && *expciphers)) return NULL; -/* normalisation ripped from above */ -s = expciphers; -while (*s != 0) { if (*s == '_') *s = '-'; s++; } +normalise_ciphers(&expciphers, tls_require_ciphers); err = NULL; - if (lib_ctx_new(&ctx, NULL, &err) == OK) { DEBUG(D_tls) @@ -4541,21 +4967,22 @@ number/string, and the version date remains unchanged. The _build_ date will change, so we can more usefully assist with version diagnosis by also reporting the build date. -Arguments: a FILE* to print the results to -Returns: nothing +Arguments: string to append to +Returns: string */ -void -tls_version_report(FILE *f) +gstring * +tls_version_report(gstring * g) { -fprintf(f, "Library version: OpenSSL: Compile: %s\n" - " Runtime: %s\n" - " : %s\n", - OPENSSL_VERSION_TEXT, - SSLeay_version(SSLEAY_VERSION), - SSLeay_version(SSLEAY_BUILT_ON)); -/* third line is 38 characters for the %s and the line is 73 chars long; -the OpenSSL output includes a "built on: " prefix already. */ +return string_fmt_append(g, + "Library version: OpenSSL: Compile: %s\n" + " Runtime: %s\n" + " : %s\n", + OPENSSL_VERSION_TEXT, + SSLeay_version(SSLEAY_VERSION), + SSLeay_version(SSLEAY_BUILT_ON)); + /* third line is 38 characters for the %s and the line is 73 chars long; + the OpenSSL output includes a "built on: " prefix already. */ }