X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/62b97c2ecf148ee86053d82e5509e4c3a5a20054..3f77bd134b64c532748b83c3931df07058268b5e:/src/src/tls-openssl.c diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index 2e09882d2..34612adea 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 - 2022 */ +/* Copyright (c) The Exim Maintainers 2020 - 2023 */ /* Copyright (c) University of Cambridge 1995 - 2019 */ /* 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 */ @@ -76,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,9 +97,13 @@ change this guard and punt the issue for a while longer. */ #if LIBRESSL_VERSION_NUMBER >= 0x3040000fL # define EXIM_HAVE_OPENSSL_CIPHER_GET_ID #endif +#if LIBRESSL_VERSION_NUMBER >= 0x3050000fL +# define EXIM_HAVE_OPENSSL_OCSP_RESP_GET0_CERTS +#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) \ @@ -117,6 +124,8 @@ change this guard and punt the issue for a while longer. */ # 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 @@ -150,6 +159,7 @@ change this guard and punt the issue for a while longer. */ # endif #endif +#define TESTSUITE_TICKET_LIFE 10 /* seconds */ /************************************************* * OpenSSL option parse * *************************************************/ @@ -408,15 +418,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; @@ -440,7 +451,7 @@ 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 ); + uschar ** errstr); /* Callbacks */ #ifndef DISABLE_OCSP @@ -669,12 +680,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 @@ -693,6 +704,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; @@ -723,64 +769,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; @@ -789,23 +847,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*/ @@ -954,7 +1004,7 @@ Returns: nothing */ static void -info_callback(SSL * s, int where, int ret) +info_callback(const SSL * s, int where, int ret) { DEBUG(D_tls) { @@ -1119,18 +1169,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 */ @@ -1157,6 +1195,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, @@ -1168,8 +1208,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)) @@ -1258,21 +1301,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); @@ -1288,6 +1317,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 * *************************************************/ @@ -1377,7 +1414,7 @@ if (!(basic_response = OCSP_response_get1_basic(resp))) goto bad; } -sk = state->verify_stack; /* set by setup_certs() / chain_from_pem_file() */ +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 @@ -1398,11 +1435,13 @@ 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? */ @@ -1412,7 +1451,7 @@ 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; } @@ -1446,7 +1485,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; } @@ -1554,8 +1602,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) @@ -1623,9 +1676,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 @@ -1700,13 +1758,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 } @@ -1736,7 +1794,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; } @@ -1744,7 +1802,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; } @@ -1921,7 +1979,7 @@ 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) { @@ -1955,28 +2013,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*/ +/* */ @@ -2012,7 +2093,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; @@ -2179,13 +2260,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; @@ -2205,13 +2286,13 @@ 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)); @@ -2223,10 +2304,10 @@ already exists. Might even need this selfsame callback, for reneg? */ 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; @@ -2245,7 +2326,7 @@ if (state->u_ocsp.server.file) { uschar * v_certs = tls_verify_certificates; if ((rc = setup_certs(server_sni, &v_certs, tls_crl, NULL, - &dummy_errstr)) != OK) + &errstr)) != OK) goto bad; if (v_certs && *v_certs) @@ -2254,14 +2335,16 @@ if (state->u_ocsp.server.file) /* 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 */ @@ -2281,6 +2364,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) { @@ -2311,12 +2396,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 */ @@ -2353,7 +2445,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); @@ -2420,11 +2512,21 @@ 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 @@ -2440,18 +2542,24 @@ int i; DEBUG(D_tls) debug_printf("Received TLS status callback (OCSP stapling):\n"); 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))) { @@ -2483,39 +2591,152 @@ 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(BIO_s_mem()); - /*OCSP_RESPONSE_print(bp, rsp, 0); extreme debug: stapling content */ + /* 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(); - /* 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); */ + 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 + + } + + ERR_clear_error(); + + /* 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)) { - const uschar * errstr = CUS ERR_reason_error_string(ERR_peek_error()); 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", - sender_host_address, sender_host_name, - (int)sizeof(peerdn), peerdn, - errstr); + 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; @@ -2523,6 +2744,7 @@ if (!(bs = OCSP_response_get1_basic(rsp))) 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"); @@ -2565,6 +2787,8 @@ if (!(bs = OCSP_response_get1_basic(rsp))) { tls_out.ocsp = OCSP_FAILED; DEBUG(D_tls) ERR_print_errors(bp); + 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; } @@ -2576,12 +2800,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; @@ -2635,8 +2863,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; @@ -2798,7 +3025,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; @@ -2847,11 +3074,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; @@ -2990,7 +3218,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) @@ -3020,7 +3248,7 @@ repeated after a Server Name Indication. Arguments: sctx SSL_CTX* to initialise - certs certs file, returned 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 @@ -3066,19 +3294,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 @@ -3278,7 +3507,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; } @@ -3304,7 +3533,7 @@ 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; @@ -3398,7 +3627,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); } @@ -3707,7 +3936,6 @@ tls_retrieve_session(tls_support * tlsp, SSL * ssl) { if (tlsp->host_resumable) { - const uschar * key = tlsp->resume_index; dbdata_tls_session * dt; int len; open_db dbblock, * dbm_file; @@ -3738,16 +3966,17 @@ 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)) + time_t now = time(NULL), expires = lifetime + dt->time_stamp; + if (expires < now) { - DEBUG(D_tls) debug_printf("session expired\n"); + 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 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; @@ -3841,7 +4070,7 @@ 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; @@ -3956,7 +4185,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' @@ -3979,6 +4207,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 @@ -4050,6 +4287,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 @@ -4186,7 +4424,12 @@ 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; } @@ -4292,10 +4535,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); @@ -4373,7 +4621,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) @@ -4633,8 +4880,8 @@ if (do_shutdown > TLS_NO_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; @@ -4923,8 +5170,7 @@ if (!expand_check(option_spec, US"openssl_options", &exp, &end)) for (uschar * s = exp; *s; /**/) { - while (isspace(*s)) ++s; - if (*s == '\0') + if (!Uskip_whitespace(&s)) break; if (*s != '+' && *s != '-') { @@ -4933,7 +5179,8 @@ for (uschar * s = exp; *s; /**/) return FALSE; } adding = *s++ == '+'; - for (end = s; *end && !isspace(*end); ) end++; + end = s; + Uskip_nonwhite(&end); item_parsed = tls_openssl_one_option_parse(string_copyn(s, end-s), &item); if (!item_parsed) {