X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/b008f54f89a3e1a0aae0c301122820b87a84d21a..3327394394a45c79cd48b2091536f6a6b8ba32a3:/src/src/tls-openssl.c diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index f7ff459b0..eb18d64d3 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -3,6 +3,7 @@ *************************************************/ /* Copyright (c) University of Cambridge 1995 - 2019 */ +/* Copyright (c) The Exim Maintainers 2020 */ /* See the file NOTICE for conditions of use and distribution. */ /* Portions Copyright (c) The OpenSSL Project 1999 */ @@ -115,7 +116,7 @@ change this guard and punt the issue for a while longer. */ # define DISABLE_OCSP #endif -#ifdef EXPERIMENTAL_TLS_RESUME +#ifndef DISABLE_TLS_RESUME # if OPENSSL_VERSION_NUMBER < 0x0101010L # error OpenSSL version too old for session-resumption # endif @@ -273,6 +274,7 @@ static exim_openssl_option exim_openssl_options[] = { #ifndef MACRO_PREDEF static int exim_openssl_options_size = nelem(exim_openssl_options); +static long init_options = 0; #endif #ifdef MACRO_PREDEF @@ -291,7 +293,7 @@ for (struct exim_openssl_option * o = exim_openssl_options; builtin_macro_create(buf); } -# ifdef EXPERIMENTAL_TLS_RESUME +# ifndef DISABLE_TLS_RESUME builtin_macro_create_var(US"_RESUME_DECODE", RESUME_DECODE_STRING ); # endif # ifdef SSL_OP_NO_TLSv1_3 @@ -349,8 +351,9 @@ typedef struct { gstring * corked; } exim_openssl_client_tls_ctx; -static SSL_CTX *server_ctx = NULL; -static SSL *server_ssl = NULL; + +/* static SSL_CTX *server_ctx = NULL; */ +/* static SSL *server_ssl = NULL; */ #ifdef EXIM_HAVE_OPENSSL_TLSEXT static SSL_CTX *server_sni = NULL; @@ -370,11 +373,15 @@ typedef struct ocsp_resp { OCSP_RESPONSE * resp; } ocsp_resplist; -typedef struct tls_ext_ctx_cb { - tls_support * tlsp; - uschar *certificate; - uschar *privatekey; - BOOL is_server; +typedef struct exim_openssl_state { + exim_tlslib_state lib_state; +#define lib_ctx libdata0 +#define lib_ssl libdata1 + + tls_support * tlsp; + uschar * certificate; + uschar * privatekey; + BOOL is_server; #ifndef DISABLE_OCSP STACK_OF(X509) *verify_stack; /* chain for verifying the proof */ union { @@ -389,26 +396,26 @@ typedef struct tls_ext_ctx_cb { } client; } u_ocsp; #endif - uschar *dhparam; + uschar * dhparam; /* these are cached from first expand */ - uschar *server_cipher_list; + uschar * server_cipher_list; /* only passed down to tls_error: */ - host_item *host; + host_item * host; const uschar * verify_cert_hostnames; #ifndef DISABLE_EVENT - uschar * event_action; + uschar * event_action; #endif -} tls_ext_ctx_cb; +} exim_openssl_state_st; /* should figure out a cleanup of API to handle state preserved per implementation, for various reasons, which can be void * in the APIs. For now, we hack around it. */ -tls_ext_ctx_cb *client_static_cbinfo = NULL; /*XXX should not use static; multiple concurrent clients! */ -tls_ext_ctx_cb *server_static_cbinfo = NULL; +exim_openssl_state_st *client_static_state = NULL; /*XXX should not use static; multiple concurrent clients! */ +exim_openssl_state_st state_server = {.is_server = TRUE}; static int -setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional, - int (*cert_vfy_cb)(int, X509_STORE_CTX *), uschar ** errstr ); +setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, + uschar ** errstr ); /* Callbacks */ #ifdef EXIM_HAVE_OPENSSL_TLSEXT @@ -421,18 +428,25 @@ static int tls_server_stapling_cb(SSL *s, void *arg); /* Daemon-called, before every connection, key create/rotate */ -#ifdef EXPERIMENTAL_TLS_RESUME +#ifndef DISABLE_TLS_RESUME static void tk_init(void); static int tls_exdata_idx = -1; #endif -void -tls_daemon_init(void) +static void +tls_per_lib_daemon_tick(void) { -#ifdef EXPERIMENTAL_TLS_RESUME +#ifndef DISABLE_TLS_RESUME tk_init(); #endif -return; +} + +/* Called once at daemon startup */ + +static void +tls_per_lib_daemon_init(void) +{ +tls_daemon_creds_reload(); } @@ -474,8 +488,270 @@ return host ? FAIL : DEFER; +/************************************************** +* General library initalisation * +**************************************************/ + +static BOOL +lib_rand_init(void * addr) +{ +randstuff r; +if (!RAND_status()) return TRUE; + +gettimeofday(&r.tv, NULL); +r.p = getpid(); +RAND_seed(US (&r), sizeof(r)); +RAND_seed(US big_buffer, big_buffer_size); +if (addr) RAND_seed(US addr, sizeof(addr)); + +return RAND_status(); +} + + +static void +tls_openssl_init(void) +{ +static BOOL once = FALSE; +if (once) return; +once = TRUE; + +#ifdef EXIM_NEED_OPENSSL_INIT +SSL_load_error_strings(); /* basic set up */ +OpenSSL_add_ssl_algorithms(); +#endif + +#if defined(EXIM_HAVE_SHA256) && !defined(OPENSSL_AUTO_SHA256) +/* SHA256 is becoming ever more popular. This makes sure it gets added to the +list of available digests. */ +EVP_add_digest(EVP_sha256()); +#endif + +(void) lib_rand_init(NULL); +(void) tls_openssl_options_parse(openssl_options, &init_options); +} + + + +/************************************************* +* Initialize for DH * +*************************************************/ + +/* If dhparam is set, expand it, and load up the parameters for DH encryption. + +Arguments: + sctx The current SSL CTX (inbound or outbound) + dhparam DH parameter file or fixed parameter identity string + host connected host, if client; NULL if server + errstr error string pointer + +Returns: TRUE if OK (nothing to set up, or setup worked) +*/ + +static BOOL +init_dh(SSL_CTX *sctx, uschar *dhparam, const host_item *host, uschar ** errstr) +{ +BIO *bio; +DH *dh; +uschar *dhexpanded; +const char *pem; +int dh_bitsize; + +if (!expand_check(dhparam, US"tls_dhparam", &dhexpanded, errstr)) + return FALSE; + +if (!dhexpanded || !*dhexpanded) + bio = BIO_new_mem_buf(CS std_dh_prime_default(), -1); +else if (dhexpanded[0] == '/') + { + if (!(bio = BIO_new_file(CS dhexpanded, "r"))) + { + tls_error(string_sprintf("could not read dhparams file %s", dhexpanded), + host, US strerror(errno), errstr); + return FALSE; + } + } +else + { + if (Ustrcmp(dhexpanded, "none") == 0) + { + DEBUG(D_tls) debug_printf("Requested no DH parameters.\n"); + return TRUE; + } + + if (!(pem = std_dh_prime_named(dhexpanded))) + { + tls_error(string_sprintf("Unknown standard DH prime \"%s\"", dhexpanded), + host, US strerror(errno), errstr); + return FALSE; + } + bio = BIO_new_mem_buf(CS pem, -1); + } + +if (!(dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL))) + { + BIO_free(bio); + tls_error(string_sprintf("Could not read tls_dhparams \"%s\"", dhexpanded), + host, NULL, errstr); + return FALSE; + } + +/* note: our default limit of 2236 is not a multiple of 8; the limit comes from + * an NSS limit, and the GnuTLS APIs handle bit-sizes fine, so we went with + * 2236. But older OpenSSL can only report in bytes (octets), not bits. + * If someone wants to dance at the edge, then they can raise the limit or use + * current libraries. */ +#ifdef EXIM_HAVE_OPENSSL_DH_BITS +/* Added in commit 26c79d5641d; `git describe --contains` says OpenSSL_1_1_0-pre1~1022 + * This predates OpenSSL_1_1_0 (before a, b, ...) so is in all 1.1.0 */ +dh_bitsize = DH_bits(dh); +#else +dh_bitsize = 8 * DH_size(dh); +#endif + +/* Even if it is larger, we silently return success rather than cause things + * to fail out, so that a too-large DH will not knock out all TLS; it's a + * debatable choice. */ +if (dh_bitsize > tls_dh_max_bits) + { + DEBUG(D_tls) + debug_printf("dhparams file %d bits, is > tls_dh_max_bits limit of %d\n", + dh_bitsize, tls_dh_max_bits); + } +else + { + SSL_CTX_set_tmp_dh(sctx, dh); + DEBUG(D_tls) + debug_printf("Diffie-Hellman initialized from %s with %d-bit prime\n", + dhexpanded ? dhexpanded : US"default", dh_bitsize); + } + +DH_free(dh); +BIO_free(bio); + +return TRUE; +} + + + + +/************************************************* +* Initialize for ECDH * +*************************************************/ + +/* Load parameters for ECDH encryption. + +For now, we stick to NIST P-256 because: it's simple and easy to configure; +it avoids any patent issues that might bite redistributors; despite events in +the news and concerns over curve choices, we're not cryptographers, we're not +pretending to be, and this is "good enough" to be better than no support, +protecting against most adversaries. Given another year or two, there might +be sufficient clarity about a "right" way forward to let us make an informed +decision, instead of a knee-jerk reaction. + +Longer-term, we should look at supporting both various named curves and +external files generated with "openssl ecparam", much as we do for init_dh(). +We should also support "none" as a value, to explicitly avoid initialisation. + +Patches welcome. + +Arguments: + sctx The current SSL CTX (inbound or outbound) + host connected host, if client; NULL if server + errstr error string pointer + +Returns: TRUE if OK (nothing to set up, or setup worked) +*/ + +static BOOL +init_ecdh(SSL_CTX * sctx, host_item * host, uschar ** errstr) +{ +#ifdef OPENSSL_NO_ECDH +return TRUE; +#else + +EC_KEY * ecdh; +uschar * exp_curve; +int nid; +BOOL rv; + +if (host) /* No ECDH setup for clients, only for servers */ + return TRUE; + +# ifndef EXIM_HAVE_ECDH +DEBUG(D_tls) + debug_printf("No OpenSSL API to define ECDH parameters, skipping\n"); +return TRUE; +# else + +if (!expand_check(tls_eccurve, US"tls_eccurve", &exp_curve, errstr)) + return FALSE; +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 + } + +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), + host, NULL, errstr); + return FALSE; + } + +if (!(ecdh = EC_KEY_new_by_curve_name(nid))) + { + tls_error(US"Unable to create ec curve", host, NULL, errstr); + return FALSE; + } + +/* 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)) + tls_error(string_sprintf("Error enabling '%s' curve", exp_curve), host, NULL, errstr); +else + DEBUG(D_tls) debug_printf("ECDH: enabled '%s' curve\n", exp_curve); + +EC_KEY_free(ecdh); +return !rv; + +# endif /*EXIM_HAVE_ECDH*/ +#endif /*OPENSSL_NO_ECDH*/ +} + + + /************************************************* -* Callback to generate RSA key * +* Expand key and cert file specs * *************************************************/ /* @@ -495,7 +771,6 @@ RSA *rsa_key; BIGNUM *bn = BN_new(); #endif -export = export; /* Shut picky compilers up */ DEBUG(D_tls) debug_printf("Generating %d bit RSA key...\n", keylength); #ifdef EXIM_HAVE_RSA_GENKEY_EX @@ -518,30 +793,143 @@ return rsa_key; -/* Extreme debug -#ifndef DISABLE_OCSP -void -x509_store_dump_cert_s_names(X509_STORE * store) +/* Create and install a selfsigned certificate, for use in server mode */ +/*XXX we could arrange to call this during prelo for a null tls_certificate option. +The normal cache inval + relo will suffice. +Just need a timer for inval. */ + +static int +tls_install_selfsign(SSL_CTX * sctx, uschar ** errstr) { -STACK_OF(X509_OBJECT) * roots= store->objs; -static uschar name[256]; +X509 * x509 = NULL; +EVP_PKEY * pkey; +RSA * rsa; +X509_NAME * name; +uschar * where; -for (int i= 0; i < sk_X509_OBJECT_num(roots); i++) +DEBUG(D_tls) debug_printf("TLS: generating selfsigned server cert\n"); +where = US"allocating pkey"; +if (!(pkey = EVP_PKEY_new())) + goto err; + +where = US"allocating cert"; +if (!(x509 = X509_new())) + goto err; + +where = US"generating pkey"; +if (!(rsa = rsa_callback(NULL, 0, 2048))) + goto err; + +where = US"assigning pkey"; +if (!EVP_PKEY_assign_RSA(pkey, rsa)) + goto err; + +X509_set_version(x509, 2); /* N+1 - version 3 */ +ASN1_INTEGER_set(X509_get_serialNumber(x509), 1); +X509_gmtime_adj(X509_get_notBefore(x509), 0); +X509_gmtime_adj(X509_get_notAfter(x509), (long)2 * 60 * 60); /* 2 hour */ +X509_set_pubkey(x509, pkey); + +name = X509_get_subject_name(x509); +X509_NAME_add_entry_by_txt(name, "C", + MBSTRING_ASC, CUS "UK", -1, -1, 0); +X509_NAME_add_entry_by_txt(name, "O", + MBSTRING_ASC, CUS "Exim Developers", -1, -1, 0); +X509_NAME_add_entry_by_txt(name, "CN", + MBSTRING_ASC, CUS smtp_active_hostname, -1, -1, 0); +X509_set_issuer_name(x509, name); + +where = US"signing cert"; +if (!X509_sign(x509, pkey, EVP_md5())) + goto err; + +where = US"installing selfsign cert"; +if (!SSL_CTX_use_certificate(sctx, x509)) + goto err; + +where = US"installing selfsign key"; +if (!SSL_CTX_use_PrivateKey(sctx, pkey)) + goto err; + +return OK; + +err: + (void) tls_error(where, NULL, NULL, errstr); + if (x509) X509_free(x509); + if (pkey) EVP_PKEY_free(pkey); + return DEFER; +} + + + + + + + +/************************************************* +* Information callback * +*************************************************/ + +/* The SSL library functions call this from time to time to indicate what they +are doing. We copy the string to the debugging output when TLS debugging has +been requested. + +Arguments: + s the SSL connection + where + ret + +Returns: nothing +*/ + +static void +info_callback(SSL *s, int where, int ret) +{ +DEBUG(D_tls) { - 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); - } - } + const uschar * str; + + if (where & SSL_ST_CONNECT) + str = US"SSL_connect"; + else if (where & SSL_ST_ACCEPT) + str = US"SSL_accept"; + else + str = US"SSL info (undefined)"; + + if (where & SSL_CB_LOOP) + debug_printf("%s: %s\n", str, SSL_state_string_long(s)); + else if (where & SSL_CB_ALERT) + debug_printf("SSL3 alert %s:%s:%s\n", + str = where & SSL_CB_READ ? US"read" : US"write", + SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret)); + else if (where & SSL_CB_EXIT) + if (ret == 0) + debug_printf("%s: failed in %s\n", str, SSL_state_string_long(s)); + else if (ret < 0) + debug_printf("%s: error in %s\n", str, SSL_state_string_long(s)); + else if (where & SSL_CB_HANDSHAKE_START) + debug_printf("%s: hshake start: %s\n", str, SSL_state_string_long(s)); + else if (where & SSL_CB_HANDSHAKE_DONE) + debug_printf("%s: hshake done: %s\n", str, SSL_state_string_long(s)); } } + +#ifdef OPENSSL_HAVE_KEYLOG_CB +static void +keylog_callback(const SSL *ssl, const char *line) +{ +char * filename; +FILE * fp; +DEBUG(D_tls) debug_printf("%.200s\n", line); +if (!(filename = getenv("SSLKEYLOGFILE"))) return; +if (!(fp = fopen(filename, "a"))) return; +fprintf(fp, "%s\n", line); +fclose(fp); +} #endif -*/ + + + #ifndef DISABLE_EVENT @@ -553,7 +941,7 @@ uschar * ev; uschar * yield; X509 * old_cert; -ev = tlsp == &tls_out ? client_static_cbinfo->event_action : event_action; +ev = tlsp == &tls_out ? client_static_state->event_action : event_action; if (ev) { DEBUG(D_tls) debug_printf("verify_event: %s %d\n", what, depth); @@ -660,15 +1048,15 @@ 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_cbinfo->u_ocsp.client.verify_store) + 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_cbinfo->u_ocsp.client.verify_store, + if (!X509_STORE_add_cert(client_static_state->u_ocsp.client.verify_store, cert)) ERR_clear_error(); - sk_X509_push(client_static_cbinfo->verify_stack, cert); + sk_X509_push(client_static_state->verify_stack, cert); } #endif #ifndef DISABLE_EVENT @@ -681,7 +1069,7 @@ else const uschar * verify_cert_hostnames; if ( tlsp == &tls_out - && ((verify_cert_hostnames = client_static_cbinfo->verify_cert_hostnames))) + && ((verify_cert_hostnames = client_static_state->verify_cert_hostnames))) /* client, wanting hostname check */ { @@ -801,15 +1189,15 @@ if (preverify_ok == 1) { tls_out.dane_verified = TRUE; #ifndef DISABLE_OCSP - if (client_static_cbinfo->u_ocsp.client.verify_store) + 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_cbinfo->u_ocsp.client.verify_store, + if (!X509_STORE_add_cert(client_static_state->u_ocsp.client.verify_store, cert)) ERR_clear_error(); - sk_X509_push(client_static_cbinfo->verify_stack, cert); + sk_X509_push(client_static_state->verify_stack, cert); } #endif } @@ -827,834 +1215,810 @@ return preverify_ok; #endif /*SUPPORT_DANE*/ +#ifndef DISABLE_OCSP /************************************************* -* Information callback * +* Load OCSP information into state * *************************************************/ +/* Called to load the server OCSP response from the given file into memory, once +caller has determined this is needed. Checks validity. Debugs a message +if invalid. -/* The SSL library functions call this from time to time to indicate what they -are doing. We copy the string to the debugging output when TLS debugging has -been requested. +ASSUMES: single response, for single cert. Arguments: - s the SSL connection - where - ret - -Returns: nothing + state various parts of session state + filename the filename putatively holding an OCSP response + is_pem file is PEM format; otherwise is DER */ static void -info_callback(SSL *s, int where, int ret) +ocsp_load_response(exim_openssl_state_st * state, const uschar * filename, + BOOL is_pem) { +BIO * bio; +OCSP_RESPONSE * resp; +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) + debug_printf("tls_ocsp_file (%s) '%s'\n", is_pem ? "PEM" : "DER", filename); + +if (!(bio = BIO_new_file(CS filename, "rb"))) { - const uschar * str; + DEBUG(D_tls) debug_printf("Failed to open OCSP response file \"%s\"\n", + filename); + return; + } - if (where & SSL_ST_CONNECT) - str = US"SSL_connect"; - else if (where & SSL_ST_ACCEPT) - str = US"SSL_accept"; - else - str = US"SSL info (undefined)"; +if (is_pem) + { + uschar * data, * freep; + char * dummy; + long len; + if (!PEM_read_bio(bio, &dummy, &dummy, &data, &len)) + { + DEBUG(D_tls) debug_printf("Failed to read PEM file \"%s\"\n", + filename); + return; + } + freep = data; + resp = d2i_OCSP_RESPONSE(NULL, CUSS &data, len); + OPENSSL_free(freep); + } +else + resp = d2i_OCSP_RESPONSE_bio(bio, NULL); +BIO_free(bio); - if (where & SSL_CB_LOOP) - debug_printf("%s: %s\n", str, SSL_state_string_long(s)); - else if (where & SSL_CB_ALERT) - debug_printf("SSL3 alert %s:%s:%s\n", - str = where & SSL_CB_READ ? US"read" : US"write", - SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret)); - else if (where & SSL_CB_EXIT) - if (ret == 0) - debug_printf("%s: failed in %s\n", str, SSL_state_string_long(s)); - else if (ret < 0) - debug_printf("%s: error in %s\n", str, SSL_state_string_long(s)); - else if (where & SSL_CB_HANDSHAKE_START) - debug_printf("%s: hshake start: %s\n", str, SSL_state_string_long(s)); - else if (where & SSL_CB_HANDSHAKE_DONE) - debug_printf("%s: hshake done: %s\n", str, SSL_state_string_long(s)); +if (!resp) + { + DEBUG(D_tls) debug_printf("Error reading OCSP response.\n"); + return; } -} -#ifdef OPENSSL_HAVE_KEYLOG_CB -static void -keylog_callback(const SSL *ssl, const char *line) -{ -char * filename; -FILE * fp; -DEBUG(D_tls) debug_printf("%.200s\n", line); -if (!(filename = getenv("SSLKEYLOGFILE"))) return; -if (!(fp = fopen(filename, "a"))) return; -fprintf(fp, "%s\n", line); -fclose(fp); -} +if ((status = OCSP_response_status(resp)) != OCSP_RESPONSE_STATUS_SUCCESSFUL) + { + DEBUG(D_tls) debug_printf("OCSP response not valid: %s (%d)\n", + OCSP_response_status_str(status), status); + goto bad; + } + +#ifdef notdef + { + BIO * bp = BIO_new_fp(debug_file, BIO_NOCLOSE); + OCSP_RESPONSE_print(bp, resp, 0); /* extreme debug: stapling content */ + BIO_free(bp); + } #endif +if (!(basic_response = OCSP_response_get1_basic(resp))) + { + DEBUG(D_tls) + debug_printf("OCSP response parse error: unable to extract basic response.\n"); + goto bad; + } -#ifdef EXPERIMENTAL_TLS_RESUME -/* Manage the keysets used for encrypting the session tickets, on the server. */ +sk = state->verify_stack; +verify_flags = OCSP_NOVERIFY; /* check sigs, but not purpose */ -typedef struct { /* Session ticket encryption key */ - uschar name[16]; +/* May need to expose ability to adjust those flags? +OCSP_NOSIGS OCSP_NOVERIFY OCSP_NOCHAIN OCSP_NOCHECKS OCSP_NOEXPLICIT +OCSP_TRUSTOTHER OCSP_NOINTERN */ - const EVP_CIPHER * aes_cipher; - uschar aes_key[32]; /* size needed depends on cipher. aes_128 implies 128/8 = 16? */ - const EVP_MD * hmac_hash; - uschar hmac_key[16]; - time_t renew; - time_t expire; -} exim_stek; +/* 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. -static exim_stek exim_tk; /* current key */ -static exim_stek exim_tk_old; /* previous key */ +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. -static void -tk_init(void) -{ -time_t t = time(NULL); +We have a stack, loaded in setup_certs() if tls_verify_certificates +was a file (not a directory, or "system"). It is unfortunate we +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) ? ] +We do not free the stack since it could be needed a second time for +SNI handling. -if (exim_tk.name[0]) +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) - +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 (exim_tk.renew >= t) return; - exim_tk_old = exim_tk; + 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); + } + goto bad; } -if (f.running_in_test_harness) ssl_session_timeout = 6; +/* Here's the simplifying assumption: there's only one response, for the +one certificate we use, and nothing for anything else in a chain. If this +proves false, we need to extract a cert id from our issued cert +(tls_certificate) and use that for OCSP_resp_find_status() (which finds the +right cert in the stack and then calls OCSP_single_get0_status()). -DEBUG(D_tls) debug_printf("OpenSSL: %s STEK\n", exim_tk.name[0] ? "rotating" : "creating"); -if (RAND_bytes(exim_tk.aes_key, sizeof(exim_tk.aes_key)) <= 0) return; -if (RAND_bytes(exim_tk.hmac_key, sizeof(exim_tk.hmac_key)) <= 0) return; -if (RAND_bytes(exim_tk.name+1, sizeof(exim_tk.name)-1) <= 0) return; +I'm hoping to avoid reworking a bunch more of how we handle state here. -exim_tk.name[0] = 'E'; -exim_tk.aes_cipher = EVP_aes_256_cbc(); -exim_tk.hmac_hash = EVP_sha256(); -exim_tk.expire = t + ssl_session_timeout; -exim_tk.renew = t + ssl_session_timeout/2; -} +XXX that will change when we add support for (TLS1.3) whole-chain stapling +*/ -static exim_stek * -tk_current(void) -{ -if (!exim_tk.name[0]) return NULL; -return &exim_tk; -} +if (!(single_response = OCSP_resp_get0(basic_response, 0))) + { + DEBUG(D_tls) + debug_printf("Unable to get first response from OCSP basic response.\n"); + goto bad; + } -static exim_stek * -tk_find(const uschar * name) -{ -return memcmp(name, exim_tk.name, sizeof(exim_tk.name)) == 0 ? &exim_tk - : memcmp(name, exim_tk_old.name, sizeof(exim_tk_old.name)) == 0 ? &exim_tk_old - : NULL; -} +status = OCSP_single_get0_status(single_response, &reason, &rev, &thisupd, &nextupd); +if (status != V_OCSP_CERTSTATUS_GOOD) + { + DEBUG(D_tls) debug_printf("OCSP response bad cert status: %s (%d) %s (%d)\n", + OCSP_cert_status_str(status), status, + OCSP_crl_reason_str(reason), reason); + goto bad; + } -/* Callback for session tickets, on server */ -static int -ticket_key_callback(SSL * ssl, uschar key_name[16], - uschar * iv, EVP_CIPHER_CTX * ctx, HMAC_CTX * hctx, int enc) -{ -tls_support * tlsp = server_static_cbinfo->tlsp; -exim_stek * key; +if (!OCSP_check_validity(thisupd, nextupd, EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX_AGE)) + { + DEBUG(D_tls) debug_printf("OCSP status invalid times.\n"); + goto bad; + } -if (enc) +supply_response: + /* Add the resp to the list used by tls_server_stapling_cb() */ { - DEBUG(D_tls) debug_printf("ticket_key_callback: create new session\n"); - tlsp->resumption |= RESUME_CLIENT_REQUESTED; + ocsp_resplist ** op = &state->u_ocsp.server.olist, * oentry; + while (oentry = *op) + op = &oentry->next; + *op = oentry = store_get(sizeof(ocsp_resplist), FALSE); + oentry->next = NULL; + oentry->resp = resp; + } +return; - if (RAND_bytes(iv, EVP_MAX_IV_LENGTH) <= 0) - return -1; /* insufficient random */ +bad: + if (f.running_in_test_harness) + { + extern char ** environ; + if (environ) for (uschar ** p = USS environ; *p; p++) + if (Ustrncmp(*p, "EXIM_TESTHARNESS_DISABLE_OCSPVALIDITYCHECK", 42) == 0) + { + DEBUG(D_tls) debug_printf("Supplying known bad OCSP response\n"); + goto supply_response; + } + } +return; +} - if (!(key = tk_current())) /* current key doesn't exist or isn't valid */ - return 0; /* key couldn't be created */ - memcpy(key_name, key->name, 16); - DEBUG(D_tls) debug_printf("STEK expire " TIME_T_FMT "\n", key->expire - time(NULL)); - /*XXX will want these dependent on the ssl session strength */ - HMAC_Init_ex(hctx, key->hmac_key, sizeof(key->hmac_key), - key->hmac_hash, NULL); - EVP_EncryptInit_ex(ctx, key->aes_cipher, NULL, key->aes_key, iv); +static void +ocsp_free_response_list(exim_openssl_state_st * cbinfo) +{ +for (ocsp_resplist * olist = cbinfo->u_ocsp.server.olist; olist; + olist = olist->next) + OCSP_RESPONSE_free(olist->resp); +cbinfo->u_ocsp.server.olist = NULL; +} +#endif /*!DISABLE_OCSP*/ - DEBUG(D_tls) debug_printf("ticket created\n"); - return 1; - } -else - { - time_t now = time(NULL); - DEBUG(D_tls) debug_printf("ticket_key_callback: retrieve session\n"); - tlsp->resumption |= RESUME_CLIENT_SUGGESTED; - if (!(key = tk_find(key_name)) || key->expire < now) - { - DEBUG(D_tls) - { - debug_printf("ticket not usable (%s)\n", key ? "expired" : "not found"); - if (key) debug_printf("STEK expire " TIME_T_FMT "\n", key->expire - now); - } - return 0; - } - HMAC_Init_ex(hctx, key->hmac_key, sizeof(key->hmac_key), - key->hmac_hash, NULL); - EVP_DecryptInit_ex(ctx, key->aes_cipher, NULL, key->aes_key, iv); - DEBUG(D_tls) debug_printf("ticket usable, STEK expire " TIME_T_FMT "\n", key->expire - now); +static int +tls_add_certfile(SSL_CTX * sctx, exim_openssl_state_st * cbinfo, uschar * file, + uschar ** errstr) +{ +DEBUG(D_tls) debug_printf("tls_certificate file '%s'\n", file); +if (!SSL_CTX_use_certificate_chain_file(sctx, CS file)) + return tls_error(string_sprintf( + "SSL_CTX_use_certificate_chain_file file=%s", file), + cbinfo->host, NULL, errstr); +return 0; +} - /* The ticket lifetime and renewal are the same as the STEK lifetime and - renewal, which is overenthusiastic. A factor of, say, 3x longer STEK would - be better. To do that we'd have to encode ticket lifetime in the name as - we don't yet see the restored session. Could check posthandshake for TLS1.3 - and trigger a new ticket then, but cannot do that for TLS1.2 */ - return key->renew < now ? 2 : 1; - } +static int +tls_add_pkeyfile(SSL_CTX * sctx, exim_openssl_state_st * cbinfo, uschar * file, + uschar ** errstr) +{ +DEBUG(D_tls) debug_printf("tls_privatekey file '%s'\n", file); +if (!SSL_CTX_use_PrivateKey_file(sctx, CS file, SSL_FILETYPE_PEM)) + return tls_error(string_sprintf( + "SSL_CTX_use_PrivateKey_file file=%s", file), cbinfo->host, NULL, errstr); +return 0; } -#endif -/************************************************* -* Initialize for DH * -*************************************************/ -/* If dhparam is set, expand it, and load up the parameters for DH encryption. +/* Called once during tls_init and possibly again during TLS setup, for a +new context, if Server Name Indication was used and tls_sni was seen in +the certificate string. Arguments: - sctx The current SSL CTX (inbound or outbound) - dhparam DH parameter file or fixed parameter identity string - host connected host, if client; NULL if server - errstr error string pointer + sctx the SSL_CTX* to update + state various parts of session state + errstr error string pointer -Returns: TRUE if OK (nothing to set up, or setup worked) +Returns: OK/DEFER/FAIL */ -static BOOL -init_dh(SSL_CTX *sctx, uschar *dhparam, const host_item *host, uschar ** errstr) +static int +tls_expand_session_files(SSL_CTX * sctx, exim_openssl_state_st * state, + uschar ** errstr) { -BIO *bio; -DH *dh; -uschar *dhexpanded; -const char *pem; -int dh_bitsize; - -if (!expand_check(dhparam, US"tls_dhparam", &dhexpanded, errstr)) - return FALSE; +uschar * expanded; -if (!dhexpanded || !*dhexpanded) - bio = BIO_new_mem_buf(CS std_dh_prime_default(), -1); -else if (dhexpanded[0] == '/') +if (!state->certificate) { - if (!(bio = BIO_new_file(CS dhexpanded, "r"))) - { - tls_error(string_sprintf("could not read dhparams file %s", dhexpanded), - host, US strerror(errno), errstr); - return FALSE; - } + if (!state->is_server) /* client */ + return OK; + /* server */ + if (tls_install_selfsign(sctx, errstr) != OK) + return DEFER; } else { - if (Ustrcmp(dhexpanded, "none") == 0) - { - DEBUG(D_tls) debug_printf("Requested no DH parameters.\n"); - return TRUE; - } - - if (!(pem = std_dh_prime_named(dhexpanded))) - { - tls_error(string_sprintf("Unknown standard DH prime \"%s\"", dhexpanded), - host, US strerror(errno), errstr); - return FALSE; - } - bio = BIO_new_mem_buf(CS pem, -1); - } - -if (!(dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL))) - { - BIO_free(bio); - tls_error(string_sprintf("Could not read tls_dhparams \"%s\"", dhexpanded), - host, NULL, errstr); - return FALSE; - } + int err; -/* note: our default limit of 2236 is not a multiple of 8; the limit comes from - * an NSS limit, and the GnuTLS APIs handle bit-sizes fine, so we went with - * 2236. But older OpenSSL can only report in bytes (octets), not bits. - * If someone wants to dance at the edge, then they can raise the limit or use - * current libraries. */ -#ifdef EXIM_HAVE_OPENSSL_DH_BITS -/* Added in commit 26c79d5641d; `git describe --contains` says OpenSSL_1_1_0-pre1~1022 - * This predates OpenSSL_1_1_0 (before a, b, ...) so is in all 1.1.0 */ -dh_bitsize = DH_bits(dh); -#else -dh_bitsize = 8 * DH_size(dh); -#endif + if ( !reexpand_tls_files_for_sni + && ( Ustrstr(state->certificate, US"tls_sni") + || Ustrstr(state->certificate, US"tls_in_sni") + || Ustrstr(state->certificate, US"tls_out_sni") + ) ) + reexpand_tls_files_for_sni = TRUE; -/* Even if it is larger, we silently return success rather than cause things - * to fail out, so that a too-large DH will not knock out all TLS; it's a - * debatable choice. */ -if (dh_bitsize > tls_dh_max_bits) - { - DEBUG(D_tls) - debug_printf("dhparams file %d bits, is > tls_dh_max_bits limit of %d\n", - dh_bitsize, tls_dh_max_bits); - } -else - { - SSL_CTX_set_tmp_dh(sctx, dh); - DEBUG(D_tls) - debug_printf("Diffie-Hellman initialized from %s with %d-bit prime\n", - dhexpanded ? dhexpanded : US"default", dh_bitsize); - } + if (!expand_check(state->certificate, US"tls_certificate", &expanded, errstr)) + return DEFER; -DH_free(dh); -BIO_free(bio); + if (expanded) + if (state->is_server) + { + const uschar * file_list = expanded; + int sep = 0; + uschar * file; +#ifndef DISABLE_OCSP + const uschar * olist = state->u_ocsp.server.file; + int osep = 0; + uschar * ofile; + BOOL fmt_pem = FALSE; -return TRUE; -} + if (olist) + if (!expand_check(olist, US"tls_ocsp_file", USS &olist, errstr)) + return DEFER; + if (olist && !*olist) + olist = NULL; + if ( state->u_ocsp.server.file_expanded && olist + && (Ustrcmp(olist, state->u_ocsp.server.file_expanded) == 0)) + { + DEBUG(D_tls) debug_printf(" - value unchanged, using existing values\n"); + olist = NULL; + } + else + { + ocsp_free_response_list(state); + state->u_ocsp.server.file_expanded = olist; + } +#endif + while (file = string_nextinlist(&file_list, &sep, NULL, 0)) + { + if ((err = tls_add_certfile(sctx, state, file, errstr))) + return err; +#ifndef DISABLE_OCSP + if (olist) + if ((ofile = string_nextinlist(&olist, &osep, NULL, 0))) + { + if (Ustrncmp(ofile, US"PEM ", 4) == 0) + { + fmt_pem = TRUE; + ofile += 4; + } + else if (Ustrncmp(ofile, US"DER ", 4) == 0) + { + fmt_pem = FALSE; + ofile += 4; + } + ocsp_load_response(state, ofile, fmt_pem); + } + else + DEBUG(D_tls) debug_printf("ran out of ocsp file list\n"); +#endif + } + } + else /* would there ever be a need for multiple client certs? */ + if ((err = tls_add_certfile(sctx, state, expanded, errstr))) + return err; -/************************************************* -* Initialize for ECDH * -*************************************************/ + if ( state->privatekey + && !expand_check(state->privatekey, US"tls_privatekey", &expanded, errstr)) + return DEFER; -/* Load parameters for ECDH encryption. + /* 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 + key is in the same file as the certificate. */ -For now, we stick to NIST P-256 because: it's simple and easy to configure; -it avoids any patent issues that might bite redistributors; despite events in -the news and concerns over curve choices, we're not cryptographers, we're not -pretending to be, and this is "good enough" to be better than no support, -protecting against most adversaries. Given another year or two, there might -be sufficient clarity about a "right" way forward to let us make an informed -decision, instead of a knee-jerk reaction. + if (expanded && *expanded) + if (state->is_server) + { + const uschar * file_list = expanded; + int sep = 0; + uschar * file; -Longer-term, we should look at supporting both various named curves and -external files generated with "openssl ecparam", much as we do for init_dh(). -We should also support "none" as a value, to explicitly avoid initialisation. + while (file = string_nextinlist(&file_list, &sep, NULL, 0)) + if ((err = tls_add_pkeyfile(sctx, state, file, errstr))) + return err; + } + else /* would there ever be a need for multiple client certs? */ + if ((err = tls_add_pkeyfile(sctx, state, expanded, errstr))) + return err; + } -Patches welcome. +return OK; +} -Arguments: - sctx The current SSL CTX (inbound or outbound) - host connected host, if client; NULL if server - errstr error string pointer -Returns: TRUE if OK (nothing to set up, or setup worked) -*/ -static BOOL -init_ecdh(SSL_CTX * sctx, host_item * host, uschar ** errstr) -{ -#ifdef OPENSSL_NO_ECDH -return TRUE; -#else -EC_KEY * ecdh; -uschar * exp_curve; -int nid; -BOOL rv; +/************************************************** +* One-time init credentials for server and client * +**************************************************/ -if (host) /* No ECDH setup for clients, only for servers */ - return TRUE; +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); +state->server_cipher_list = ciphers; +return OK; +} -# ifndef EXIM_HAVE_ECDH -DEBUG(D_tls) - debug_printf("No OpenSSL API to define ECDH parameters, skipping\n"); -return TRUE; -# else -if (!expand_check(tls_eccurve, US"tls_eccurve", &exp_curve, errstr)) - return FALSE; -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"; +static int +lib_ctx_new(SSL_CTX ** ctxp, host_item * host, uschar ** errstr) +{ +SSL_CTX * ctx; +#ifdef EXIM_HAVE_OPENSSL_TLS_METHOD +if (!(ctx = SSL_CTX_new(host ? TLS_client_method() : TLS_server_method()))) #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 +if (!(ctx = SSL_CTX_new(host ? SSLv23_client_method() : SSLv23_server_method()))) #endif - } + return tls_error(US"SSL_CTX_new", host, NULL, errstr); -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), - host, NULL, errstr); - return FALSE; - } +/* Set up the information callback, which outputs if debugging is at a suitable +level. */ -if (!(ecdh = EC_KEY_new_by_curve_name(nid))) +DEBUG(D_tls) { - tls_error(US"Unable to create ec curve", host, NULL, errstr); - return FALSE; + SSL_CTX_set_info_callback(ctx, (void (*)())info_callback); +#if defined(EXIM_HAVE_OPESSL_TRACE) && !defined(OPENSSL_NO_SSL_TRACE) + /* this needs a debug build of OpenSSL */ + SSL_CTX_set_msg_callback(ctx, (void (*)())SSL_trace); +#endif +#ifdef OPENSSL_HAVE_KEYLOG_CB + SSL_CTX_set_keylog_callback(ctx, (void (*)())keylog_callback); +#endif } -/* 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)) - tls_error(string_sprintf("Error enabling '%s' curve", exp_curve), host, NULL, errstr); -else - DEBUG(D_tls) debug_printf("ECDH: enabled '%s' curve\n", exp_curve); - -EC_KEY_free(ecdh); -return !rv; - -# endif /*EXIM_HAVE_ECDH*/ -#endif /*OPENSSL_NO_ECDH*/ +/* Automatically re-try reads/writes after renegotiation. */ +(void) SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY); +*ctxp = ctx; +return OK; } +static unsigned +tls_server_creds_init(void) +{ +SSL_CTX * ctx; +uschar * dummy_errstr; +unsigned lifetime = 0; +tls_openssl_init(); -#ifndef DISABLE_OCSP -/************************************************* -* Load OCSP information into state * -*************************************************/ -/* Called to load the server OCSP response from the given file into memory, once -caller has determined this is needed. Checks validity. Debugs a message -if invalid. - -ASSUMES: single response, for single cert. - -Arguments: - sctx the SSL_CTX* to update - cbinfo various parts of session state - filename the filename putatively holding an OCSP response - is_pem file is PEM format; otherwise is DER - -*/ +state_server.lib_state = null_tls_preload; -static void -ocsp_load_response(SSL_CTX * sctx, tls_ext_ctx_cb * cbinfo, - const uschar * filename, BOOL is_pem) -{ -BIO * bio; -OCSP_RESPONSE * resp; -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; +if (lib_ctx_new(&ctx, NULL, &dummy_errstr) != OK) + return 0; +state_server.lib_state.lib_ctx = ctx; -DEBUG(D_tls) - debug_printf("tls_ocsp_file (%s) '%s'\n", is_pem ? "PEM" : "DER", filename); +/* Preload DH params and EC curve */ -if (!(bio = BIO_new_file(CS filename, "rb"))) +if (opt_unset_or_noexpand(tls_dhparam)) { - DEBUG(D_tls) debug_printf("Failed to open OCSP response file \"%s\"\n", - filename); - return; + DEBUG(D_tls) debug_printf("TLS: preloading DH params for server\n"); + if (init_dh(ctx, tls_dhparam, NULL, &dummy_errstr)) + state_server.lib_state.dh = TRUE; } - -if (is_pem) +if (opt_unset_or_noexpand(tls_eccurve)) { - uschar * data, * freep; - char * dummy; - long len; - if (!PEM_read_bio(bio, &dummy, &dummy, &data, &len)) - { - DEBUG(D_tls) debug_printf("Failed to read PEM file \"%s\"\n", - filename); - return; - } -debug_printf("read pem file\n"); - freep = data; - resp = d2i_OCSP_RESPONSE(NULL, CUSS &data, len); - OPENSSL_free(freep); + DEBUG(D_tls) debug_printf("TLS: preloading ECDH curve for server\n"); + if (init_ecdh(ctx, NULL, &dummy_errstr)) + state_server.lib_state.ecdh = TRUE; } -else - resp = d2i_OCSP_RESPONSE_bio(bio, NULL); -BIO_free(bio); -if (!resp) - { - DEBUG(D_tls) debug_printf("Error reading OCSP response.\n"); - return; - } +#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT) +/* If we can, preload the server-side cert, key and ocsp */ -if ((status = OCSP_response_status(resp)) != OCSP_RESPONSE_STATUS_SUCCESSFUL) +if ( opt_set_and_noexpand(tls_certificate) +# ifndef DISABLE_OCSP + && opt_unset_or_noexpand(tls_ocsp_file) +#endif + && opt_unset_or_noexpand(tls_privatekey)) { - DEBUG(D_tls) debug_printf("OCSP response not valid: %s (%d)\n", - OCSP_response_status_str(status), status); - goto bad; - } + /* Set watches on the filenames. The implementation does de-duplication + so we can just blindly do them all. */ -#ifdef notdef - { - BIO * bp = BIO_new_fp(debug_file, BIO_NOCLOSE); - OCSP_RESPONSE_print(bp, resp, 0); /* extreme debug: stapling content */ - BIO_free(bp); - } + 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 -if (!(basic_response = OCSP_response_get1_basic(resp))) - { - DEBUG(D_tls) - debug_printf("OCSP response parse error: unable to extract basic response.\n"); - goto bad; + 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"); -sk = cbinfo->verify_stack; -verify_flags = OCSP_NOVERIFY; /* check sigs, but not purpose */ -/* May need to expose ability to adjust those flags? -OCSP_NOSIGS OCSP_NOVERIFY OCSP_NOCHAIN OCSP_NOCHECKS OCSP_NOEXPLICIT -OCSP_TRUSTOTHER OCSP_NOINTERN */ +/* 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 */ -/* 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. +if ( opt_set_and_noexpand(tls_verify_certificates) + && opt_unset_or_noexpand(tls_crl)) + { + /* Watch the default dir also as they are always included */ -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. + if ( tls_set_watch(CUS X509_get_default_cert_file(), FALSE) + && tls_set_watch(tls_verify_certificates, FALSE) + && tls_set_watch(tls_crl, FALSE)) + { + DEBUG(D_tls) debug_printf("TLS: preloading CA bundle for server\n"); -We have a stack, loaded in setup_certs() if tls_verify_certificates -was a file (not a directory, or "system"). It is unfortunate we -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) ? ] -We do not free the stack since it could be needed a second time for -SNI handling. + if (setup_certs(ctx, tls_verify_certificates, tls_crl, NULL, &dummy_errstr) + == OK) + state_server.lib_state.cabundle = TRUE; + } + } +else + DEBUG(D_tls) debug_printf("TLS: not preloading CA bundle for server\n"); +#endif /* EXIM_HAVE_INOTIFY */ -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) - -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 we can, preload the ciphers control string */ + +if (opt_set_and_noexpand(tls_require_ciphers)) { - 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); - } - goto bad; + DEBUG(D_tls) debug_printf("TLS: preloading cipher list for server\n"); + if (server_load_ciphers(ctx, &state_server, tls_require_ciphers, + &dummy_errstr) == OK) + state_server.lib_state.pri_string = TRUE; } +else + DEBUG(D_tls) debug_printf("TLS: not preloading cipher list for server\n"); +return lifetime; +} -/* Here's the simplifying assumption: there's only one response, for the -one certificate we use, and nothing for anything else in a chain. If this -proves false, we need to extract a cert id from our issued cert -(tls_certificate) and use that for OCSP_resp_find_status() (which finds the -right cert in the stack and then calls OCSP_single_get0_status()). -I'm hoping to avoid reworking a bunch more of how we handle state here. -XXX that will change when we add support for (TLS1.3) whole-chain stapling -*/ -if (!(single_response = OCSP_resp_get0(basic_response, 0))) - { - DEBUG(D_tls) - debug_printf("Unable to get first response from OCSP basic response.\n"); - goto bad; - } +/* Preload whatever creds are static, onto a transport. The client can then +just copy the pointer as it starts up. +Called from the daemon after a cache-invalidate with watch set; called from +a queue-run startup with watch clear. */ + +static void +tls_client_creds_init(transport_instance * t, BOOL watch) +{ +smtp_transport_options_block * ob = t->options_block; +exim_openssl_state_st tpt_dummy_state; +host_item * dummy_host = (host_item *)1; +uschar * dummy_errstr; +SSL_CTX * ctx; + +tls_openssl_init(); -status = OCSP_single_get0_status(single_response, &reason, &rev, &thisupd, &nextupd); -if (status != V_OCSP_CERTSTATUS_GOOD) +ob->tls_preload = null_tls_preload; +if (lib_ctx_new(&ctx, dummy_host, &dummy_errstr) != OK) + return; +ob->tls_preload.lib_ctx = ctx; + +tpt_dummy_state.lib_state = ob->tls_preload; + +if (opt_unset_or_noexpand(tls_dhparam)) { - DEBUG(D_tls) debug_printf("OCSP response bad cert status: %s (%d) %s (%d)\n", - OCSP_cert_status_str(status), status, - OCSP_crl_reason_str(reason), reason); - goto bad; + DEBUG(D_tls) debug_printf("TLS: preloading DH params for transport '%s'\n", t->name); + if (init_dh(ctx, tls_dhparam, NULL, &dummy_errstr)) + ob->tls_preload.dh = TRUE; } - -if (!OCSP_check_validity(thisupd, nextupd, EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX_AGE)) +if (opt_unset_or_noexpand(tls_eccurve)) { - DEBUG(D_tls) debug_printf("OCSP status invalid times.\n"); - goto bad; + DEBUG(D_tls) debug_printf("TLS: preloading ECDH curve for transport '%s'\n", t->name); + if (init_ecdh(ctx, NULL, &dummy_errstr)) + ob->tls_preload.ecdh = TRUE; } -supply_response: - /* Add the resp to the list used by tls_server_stapling_cb() */ +#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT) +if ( opt_set_and_noexpand(ob->tls_certificate) + && opt_unset_or_noexpand(ob->tls_privatekey)) { - ocsp_resplist ** op = &cbinfo->u_ocsp.server.olist, * oentry; - while (oentry = *op) - op = &oentry->next; - *op = oentry = store_get(sizeof(ocsp_resplist), FALSE); - oentry->next = NULL; - oentry->resp = resp; + if ( !watch + || ( tls_set_watch(ob->tls_certificate, FALSE) + && tls_set_watch(ob->tls_privatekey, FALSE) + ) ) + { + uschar * pkey = ob->tls_privatekey; + + DEBUG(D_tls) + debug_printf("TLS: preloading client certs for transport '%s'\n",t->name); + + if ( tls_add_certfile(ctx, &tpt_dummy_state, ob->tls_certificate, + &dummy_errstr) == 0 + && tls_add_pkeyfile(ctx, &tpt_dummy_state, + pkey ? pkey : ob->tls_certificate, + &dummy_errstr) == 0 + ) + ob->tls_preload.conn_certs = TRUE; + } } -return; +else + DEBUG(D_tls) + debug_printf("TLS: not preloading client certs, for transport '%s'\n", t->name); -bad: - if (f.running_in_test_harness) + +if ( opt_set_and_noexpand(ob->tls_verify_certificates) + && opt_unset_or_noexpand(ob->tls_crl)) + { + if ( !watch + || tls_set_watch(CUS X509_get_default_cert_file(), FALSE) + && tls_set_watch(ob->tls_verify_certificates, FALSE) + && tls_set_watch(ob->tls_crl, FALSE) + ) { - extern char ** environ; - if (environ) for (uschar ** p = USS environ; *p; p++) - if (Ustrncmp(*p, "EXIM_TESTHARNESS_DISABLE_OCSPVALIDITYCHECK", 42) == 0) - { - DEBUG(D_tls) debug_printf("Supplying known bad OCSP response\n"); - goto supply_response; - } + DEBUG(D_tls) + debug_printf("TLS: preloading CA bundle for transport '%s'\n", t->name); + + if (setup_certs(ctx, ob->tls_verify_certificates, + ob->tls_crl, dummy_host, &dummy_errstr) == OK) + ob->tls_preload.cabundle = TRUE; } -return; + } +else + DEBUG(D_tls) + debug_printf("TLS: not preloading CA bundle, for transport '%s'\n", t->name); + +#endif /*EXIM_HAVE_INOTIFY*/ } +#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 -ocsp_free_response_list(tls_ext_ctx_cb * cbinfo) +tls_server_creds_invalidate(void) { -for (ocsp_resplist * olist = cbinfo->u_ocsp.server.olist; olist; - olist = olist->next) - OCSP_RESPONSE_free(olist->resp); -cbinfo->u_ocsp.server.olist = NULL; +SSL_CTX_free(state_server.lib_state.lib_ctx); +state_server.lib_state = null_tls_preload; } -#endif /*!DISABLE_OCSP*/ +static void +tls_client_creds_invalidate(transport_instance * t) +{ +smtp_transport_options_block * ob = t->options_block; +SSL_CTX_free(ob->tls_preload.lib_ctx); +ob->tls_preload = null_tls_preload; +} +#else -/* Create and install a selfsigned certificate, for use in server mode */ +static void +tls_server_creds_invalidate(void) +{ return; } -static int -tls_install_selfsign(SSL_CTX * sctx, uschar ** errstr) -{ -X509 * x509 = NULL; -EVP_PKEY * pkey; -RSA * rsa; -X509_NAME * name; -uschar * where; +static void +tls_client_creds_invalidate(transport_instance * t) +{ return; } -where = US"allocating pkey"; -if (!(pkey = EVP_PKEY_new())) - goto err; +#endif /*EXIM_HAVE_INOTIFY*/ -where = US"allocating cert"; -if (!(x509 = X509_new())) - goto err; -where = US"generating pkey"; -if (!(rsa = rsa_callback(NULL, 0, 2048))) - goto err; +/* Extreme debug +#ifndef DISABLE_OCSP +void +x509_store_dump_cert_s_names(X509_STORE * store) +{ +STACK_OF(X509_OBJECT) * roots= store->objs; +static uschar name[256]; -where = US"assigning pkey"; -if (!EVP_PKEY_assign_RSA(pkey, rsa)) - goto err; +for (int i= 0; i < sk_X509_OBJECT_num(roots); i++) + { + X509_OBJECT * tmp_obj= sk_X509_OBJECT_value(roots, i); + if(tmp_obj->type == X509_LU_X509) + { + X509_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); + } + } + } +} +#endif +*/ -X509_set_version(x509, 2); /* N+1 - version 3 */ -ASN1_INTEGER_set(X509_get_serialNumber(x509), 1); -X509_gmtime_adj(X509_get_notBefore(x509), 0); -X509_gmtime_adj(X509_get_notAfter(x509), (long)60 * 60); /* 1 hour */ -X509_set_pubkey(x509, pkey); -name = X509_get_subject_name(x509); -X509_NAME_add_entry_by_txt(name, "C", - MBSTRING_ASC, CUS "UK", -1, -1, 0); -X509_NAME_add_entry_by_txt(name, "O", - MBSTRING_ASC, CUS "Exim Developers", -1, -1, 0); -X509_NAME_add_entry_by_txt(name, "CN", - MBSTRING_ASC, CUS smtp_active_hostname, -1, -1, 0); -X509_set_issuer_name(x509, name); +#ifndef DISABLE_TLS_RESUME +/* Manage the keysets used for encrypting the session tickets, on the server. */ -where = US"signing cert"; -if (!X509_sign(x509, pkey, EVP_md5())) - goto err; +typedef struct { /* Session ticket encryption key */ + uschar name[16]; -where = US"installing selfsign cert"; -if (!SSL_CTX_use_certificate(sctx, x509)) - goto err; + const EVP_CIPHER * aes_cipher; + uschar aes_key[32]; /* size needed depends on cipher. aes_128 implies 128/8 = 16? */ + const EVP_MD * hmac_hash; + uschar hmac_key[16]; + time_t renew; + time_t expire; +} exim_stek; -where = US"installing selfsign key"; -if (!SSL_CTX_use_PrivateKey(sctx, pkey)) - goto err; +static exim_stek exim_tk; /* current key */ +static exim_stek exim_tk_old; /* previous key */ -return OK; +static void +tk_init(void) +{ +time_t t = time(NULL); -err: - (void) tls_error(where, NULL, NULL, errstr); - if (x509) X509_free(x509); - if (pkey) EVP_PKEY_free(pkey); - return DEFER; -} +if (exim_tk.name[0]) + { + if (exim_tk.renew >= t) return; + exim_tk_old = exim_tk; + } +if (f.running_in_test_harness) ssl_session_timeout = 6; +DEBUG(D_tls) debug_printf("OpenSSL: %s STEK\n", exim_tk.name[0] ? "rotating" : "creating"); +if (RAND_bytes(exim_tk.aes_key, sizeof(exim_tk.aes_key)) <= 0) return; +if (RAND_bytes(exim_tk.hmac_key, sizeof(exim_tk.hmac_key)) <= 0) return; +if (RAND_bytes(exim_tk.name+1, sizeof(exim_tk.name)-1) <= 0) return; +exim_tk.name[0] = 'E'; +exim_tk.aes_cipher = EVP_aes_256_cbc(); +exim_tk.hmac_hash = EVP_sha256(); +exim_tk.expire = t + ssl_session_timeout; +exim_tk.renew = t + ssl_session_timeout/2; +} -static int -tls_add_certfile(SSL_CTX * sctx, tls_ext_ctx_cb * cbinfo, uschar * file, - uschar ** errstr) +static exim_stek * +tk_current(void) { -DEBUG(D_tls) debug_printf("tls_certificate file '%s'\n", file); -if (!SSL_CTX_use_certificate_chain_file(sctx, CS file)) - return tls_error(string_sprintf( - "SSL_CTX_use_certificate_chain_file file=%s", file), - cbinfo->host, NULL, errstr); -return 0; +if (!exim_tk.name[0]) return NULL; +return &exim_tk; } -static int -tls_add_pkeyfile(SSL_CTX * sctx, tls_ext_ctx_cb * cbinfo, uschar * file, - uschar ** errstr) +static exim_stek * +tk_find(const uschar * name) { -DEBUG(D_tls) debug_printf("tls_privatekey file '%s'\n", file); -if (!SSL_CTX_use_PrivateKey_file(sctx, CS file, SSL_FILETYPE_PEM)) - return tls_error(string_sprintf( - "SSL_CTX_use_PrivateKey_file file=%s", file), cbinfo->host, NULL, errstr); -return 0; +return memcmp(name, exim_tk.name, sizeof(exim_tk.name)) == 0 ? &exim_tk + : memcmp(name, exim_tk_old.name, sizeof(exim_tk_old.name)) == 0 ? &exim_tk_old + : NULL; } - -/************************************************* -* Expand key and cert file specs * -*************************************************/ - -/* Called once during tls_init and possibly again during TLS setup, for a -new context, if Server Name Indication was used and tls_sni was seen in -the certificate string. - -Arguments: - sctx the SSL_CTX* to update - cbinfo various parts of session state - errstr error string pointer - -Returns: OK/DEFER/FAIL -*/ - +/* Callback for session tickets, on server */ static int -tls_expand_session_files(SSL_CTX * sctx, tls_ext_ctx_cb * cbinfo, - uschar ** errstr) +ticket_key_callback(SSL * ssl, uschar key_name[16], + uschar * iv, EVP_CIPHER_CTX * c_ctx, HMAC_CTX * hctx, int enc) { -uschar * expanded; +tls_support * tlsp = state_server.tlsp; +exim_stek * key; -if (!cbinfo->certificate) - { - if (!cbinfo->is_server) /* client */ - return OK; - /* server */ - if (tls_install_selfsign(sctx, errstr) != OK) - return DEFER; - } -else +if (enc) { - int err; - - if ( !reexpand_tls_files_for_sni - && ( Ustrstr(cbinfo->certificate, US"tls_sni") - || Ustrstr(cbinfo->certificate, US"tls_in_sni") - || Ustrstr(cbinfo->certificate, US"tls_out_sni") - ) ) - reexpand_tls_files_for_sni = TRUE; - - if (!expand_check(cbinfo->certificate, US"tls_certificate", &expanded, errstr)) - return DEFER; - - if (expanded) - if (cbinfo->is_server) - { - const uschar * file_list = expanded; - int sep = 0; - uschar * file; -#ifndef DISABLE_OCSP - const uschar * olist = cbinfo->u_ocsp.server.file; - int osep = 0; - uschar * ofile; - BOOL fmt_pem = FALSE; - - if (olist) - if (!expand_check(olist, US"tls_ocsp_file", USS &olist, errstr)) - return DEFER; - if (olist && !*olist) - olist = NULL; + DEBUG(D_tls) debug_printf("ticket_key_callback: create new session\n"); + tlsp->resumption |= RESUME_CLIENT_REQUESTED; - if ( cbinfo->u_ocsp.server.file_expanded && olist - && (Ustrcmp(olist, cbinfo->u_ocsp.server.file_expanded) == 0)) - { - DEBUG(D_tls) debug_printf(" - value unchanged, using existing values\n"); - olist = NULL; - } - else - { - ocsp_free_response_list(cbinfo); - cbinfo->u_ocsp.server.file_expanded = olist; - } -#endif + if (RAND_bytes(iv, EVP_MAX_IV_LENGTH) <= 0) + return -1; /* insufficient random */ - while (file = string_nextinlist(&file_list, &sep, NULL, 0)) - { - if ((err = tls_add_certfile(sctx, cbinfo, file, errstr))) - return err; + if (!(key = tk_current())) /* current key doesn't exist or isn't valid */ + return 0; /* key couldn't be created */ + memcpy(key_name, key->name, 16); + DEBUG(D_tls) debug_printf("STEK expire " TIME_T_FMT "\n", key->expire - time(NULL)); -#ifndef DISABLE_OCSP - if (olist) - if ((ofile = string_nextinlist(&olist, &osep, NULL, 0))) - { - if (Ustrncmp(ofile, US"PEM ", 4) == 0) - { - fmt_pem = TRUE; - ofile += 4; - } - else if (Ustrncmp(ofile, US"DER ", 4) == 0) - { - fmt_pem = FALSE; - ofile += 4; - } - ocsp_load_response(sctx, cbinfo, ofile, fmt_pem); - } - else - DEBUG(D_tls) debug_printf("ran out of ocsp file list\n"); -#endif - } - } - else /* would there ever be a need for multiple client certs? */ - if ((err = tls_add_certfile(sctx, cbinfo, expanded, errstr))) - return err; + /*XXX will want these dependent on the ssl session strength */ + HMAC_Init_ex(hctx, key->hmac_key, sizeof(key->hmac_key), + key->hmac_hash, NULL); + EVP_EncryptInit_ex(c_ctx, key->aes_cipher, NULL, key->aes_key, iv); - if ( cbinfo->privatekey - && !expand_check(cbinfo->privatekey, US"tls_privatekey", &expanded, errstr)) - return DEFER; + DEBUG(D_tls) debug_printf("ticket created\n"); + return 1; + } +else + { + time_t now = time(NULL); - /* 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 - key is in the same file as the certificate. */ + DEBUG(D_tls) debug_printf("ticket_key_callback: retrieve session\n"); + tlsp->resumption |= RESUME_CLIENT_SUGGESTED; - if (expanded && *expanded) - if (cbinfo->is_server) + if (!(key = tk_find(key_name)) || key->expire < now) + { + DEBUG(D_tls) { - const uschar * file_list = expanded; - int sep = 0; - uschar * file; - - while (file = string_nextinlist(&file_list, &sep, NULL, 0)) - if ((err = tls_add_pkeyfile(sctx, cbinfo, file, errstr))) - return err; + debug_printf("ticket not usable (%s)\n", key ? "expired" : "not found"); + if (key) debug_printf("STEK expire " TIME_T_FMT "\n", key->expire - now); } - else /* would there ever be a need for multiple client certs? */ - if ((err = tls_add_pkeyfile(sctx, cbinfo, expanded, errstr))) - return err; - } + return 0; + } -return OK; + HMAC_Init_ex(hctx, key->hmac_key, sizeof(key->hmac_key), + key->hmac_hash, NULL); + 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); + + /* The ticket lifetime and renewal are the same as the STEK lifetime and + renewal, which is overenthusiastic. A factor of, say, 3x longer STEK would + be better. To do that we'd have to encode ticket lifetime in the name as + we don't yet see the restored session. Could check posthandshake for TLS1.3 + and trigger a new ticket then, but cannot do that for TLS1.2 */ + return key->renew < now ? 2 : 1; + } } +#endif + + +static void +setup_cert_verify(SSL_CTX * ctx, BOOL optional, + int (*cert_vfy_cb)(int, X509_STORE_CTX *)) +{ +/* If verification is optional, don't fail if no certificate */ +SSL_CTX_set_verify(ctx, + SSL_VERIFY_PEER | (optional ? 0 : SSL_VERIFY_FAIL_IF_NO_PEER_CERT), + cert_vfy_cb); +} /************************************************* @@ -1682,7 +2046,7 @@ static int tls_servername_cb(SSL *s, int *ad ARG_UNUSED, void *arg) { const char *servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name); -tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg; +exim_openssl_state_st *state = (exim_openssl_state_st *) arg; int rc; int old_pool = store_pool; uschar * dummy_errstr; @@ -1705,51 +2069,54 @@ if (!reexpand_tls_files_for_sni) not confident that memcpy wouldn't break some internal reference counting. Especially since there's a references struct member, which would be off. */ -#ifdef EXIM_HAVE_OPENSSL_TLS_METHOD -if (!(server_sni = SSL_CTX_new(TLS_server_method()))) -#else -if (!(server_sni = SSL_CTX_new(SSLv23_server_method()))) -#endif - { - ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring)); - DEBUG(D_tls) debug_printf("SSL_CTX_new() failed: %s\n", ssl_errstring); +if (lib_ctx_new(&server_sni, NULL, &dummy_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_set_info_callback(server_sni, SSL_CTX_get_info_callback(server_ctx)); -SSL_CTX_set_mode(server_sni, SSL_CTX_get_mode(server_ctx)); -SSL_CTX_set_options(server_sni, SSL_CTX_get_options(server_ctx)); -SSL_CTX_set_timeout(server_sni, SSL_CTX_get_timeout(server_ctx)); -SSL_CTX_set_tlsext_servername_callback(server_sni, tls_servername_cb); -SSL_CTX_set_tlsext_servername_arg(server_sni, cbinfo); + { + 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)); + SSL_CTX_set_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, cbinfo->dhparam, NULL, &dummy_errstr) +if ( !init_dh(server_sni, state->dhparam, NULL, &dummy_errstr) || !init_ecdh(server_sni, NULL, &dummy_errstr) ) goto bad; -if ( cbinfo->server_cipher_list - && !SSL_CTX_set_cipher_list(server_sni, CS cbinfo->server_cipher_list)) +if ( state->server_cipher_list + && !SSL_CTX_set_cipher_list(server_sni, CS state->server_cipher_list)) goto bad; #ifndef DISABLE_OCSP -if (cbinfo->u_ocsp.server.file) +if (state->u_ocsp.server.file) { SSL_CTX_set_tlsext_status_cb(server_sni, tls_server_stapling_cb); - SSL_CTX_set_tlsext_status_arg(server_sni, cbinfo); + SSL_CTX_set_tlsext_status_arg(server_sni, state); } #endif -if ((rc = setup_certs(server_sni, tls_verify_certificates, tls_crl, NULL, FALSE, - verify_callback_server, &dummy_errstr)) != OK) - goto bad; + { + 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) + goto bad; + + if (expcerts && *expcerts) + 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, cbinfo, &dummy_errstr)) != OK) +if ((rc = tls_expand_session_files(server_sni, state, &dummy_errstr)) != OK) goto bad; DEBUG(D_tls) debug_printf("Switching SSL context.\n"); @@ -1780,8 +2147,8 @@ project. static int tls_server_stapling_cb(SSL *s, void *arg) { -const tls_ext_ctx_cb * cbinfo = (tls_ext_ctx_cb *) arg; -ocsp_resplist * olist = cbinfo->u_ocsp.server.olist; +const exim_openssl_state_st * state = arg; +ocsp_resplist * olist = state->u_ocsp.server.olist; uschar * response_der; /*XXX blob */ int response_der_len; @@ -1855,7 +2222,8 @@ response_der_len = i2d_OCSP_RESPONSE(olist->resp, &response_der); if (response_der_len <= 0) return SSL_TLSEXT_ERR_NOACK; -SSL_set_tlsext_status_ocsp_resp(server_ssl, response_der, response_der_len); +SSL_set_tlsext_status_ocsp_resp(state_server.lib_state.lib_ssl, + response_der, response_der_len); tls_in.ocsp = OCSP_VFIED; return SSL_TLSEXT_ERR_OK; } @@ -1872,7 +2240,7 @@ BIO_puts(bp, "\n"); static int tls_client_stapling_cb(SSL *s, void *arg) { -tls_ext_ctx_cb * cbinfo = arg; +exim_openssl_state_st * cbinfo = arg; const unsigned char * p; int len; OCSP_RESPONSE * rsp; @@ -2032,95 +2400,64 @@ return i; /************************************************* * Initialize for TLS * *************************************************/ - -static void -tls_openssl_init(void) -{ -#ifdef EXIM_NEED_OPENSSL_INIT -SSL_load_error_strings(); /* basic set up */ -OpenSSL_add_ssl_algorithms(); -#endif - -#if defined(EXIM_HAVE_SHA256) && !defined(OPENSSL_AUTO_SHA256) -/* SHA256 is becoming ever more popular. This makes sure it gets added to the -list of available digests. */ -EVP_add_digest(EVP_sha256()); -#endif -} - - - /* Called from both server and client code, to do preliminary initialization of the library. We allocate and return a context structure. Arguments: - ctxp returned SSL context host connected host, if client; NULL if server - dhparam DH parameter file - certificate certificate file - privatekey private key + ob transport options block, if client; NULL if server ocsp_file file of stapling info (server); flag for require ocsp (client) addr address if client; NULL if server (for some randomness) - cbp place to put allocated callback context + caller_state place to put pointer to allocated state-struct errstr error string pointer Returns: OK/DEFER/FAIL */ static int -tls_init(SSL_CTX **ctxp, host_item *host, uschar *dhparam, uschar *certificate, - uschar *privatekey, +tls_init(host_item * host, smtp_transport_options_block * ob, #ifndef DISABLE_OCSP uschar *ocsp_file, #endif - address_item *addr, tls_ext_ctx_cb ** cbp, + address_item *addr, exim_openssl_state_st ** caller_state, tls_support * tlsp, uschar ** errstr) { SSL_CTX * ctx; -long init_options; +exim_openssl_state_st * state; int rc; -tls_ext_ctx_cb * cbinfo; -cbinfo = store_malloc(sizeof(tls_ext_ctx_cb)); -cbinfo->tlsp = tlsp; -cbinfo->certificate = certificate; -cbinfo->privatekey = privatekey; -cbinfo->is_server = host==NULL; -#ifndef DISABLE_OCSP -cbinfo->verify_stack = NULL; -if (!host) +if (host) /* client */ { - cbinfo->u_ocsp.server.file = ocsp_file; - cbinfo->u_ocsp.server.file_expanded = NULL; - cbinfo->u_ocsp.server.olist = NULL; + state = store_malloc(sizeof(exim_openssl_state_st)); + memset(state, 0, sizeof(*state)); + state->certificate = ob->tls_certificate; + state->privatekey = ob->tls_privatekey; + state->is_server = FALSE; + state->dhparam = NULL; + state->lib_state = ob->tls_preload; + } +else /* server */ + { + state = &state_server; + state->certificate = tls_certificate; + state->privatekey = tls_privatekey; + state->is_server = TRUE; + state->dhparam = tls_dhparam; + state->lib_state = state_server.lib_state; } -else - cbinfo->u_ocsp.client.verify_store = NULL; -#endif -cbinfo->dhparam = dhparam; -cbinfo->server_cipher_list = NULL; -cbinfo->host = host; -#ifndef DISABLE_EVENT -cbinfo->event_action = NULL; -#endif -tls_openssl_init(); +state->tlsp = tlsp; +state->host = host; -/* Create a context. -The OpenSSL docs in 1.0.1b have not been updated to clarify TLS variant -negotiation in the different methods; as far as I can tell, the only -*_{server,client}_method which allows negotiation is SSLv23, which exists even -when OpenSSL is built without SSLv2 support. -By disabling with openssl_options, we can let admins re-enable with the -existing knob. */ +if (!state->lib_state.pri_string) + state->server_cipher_list = NULL; -#ifdef EXIM_HAVE_OPENSSL_TLS_METHOD -if (!(ctx = SSL_CTX_new(host ? TLS_client_method() : TLS_server_method()))) -#else -if (!(ctx = SSL_CTX_new(host ? SSLv23_client_method() : SSLv23_server_method()))) +#ifndef DISABLE_EVENT +state->event_action = NULL; #endif - return tls_error(US"SSL_CTX_new", host, NULL, errstr); + +tls_openssl_init(); /* It turns out that we need to seed the random number generator this early in order to get the full complement of ciphers to work. It took me roughly a day @@ -2128,40 +2465,14 @@ of work to discover this by experiment. On systems that have /dev/urandom, SSL may automatically seed itself from there. Otherwise, we have to make something up as best we can. Double check -afterwards. */ - -if (!RAND_status()) - { - randstuff r; - gettimeofday(&r.tv, NULL); - r.p = getpid(); - - RAND_seed(US (&r), sizeof(r)); - RAND_seed(US big_buffer, big_buffer_size); - if (addr != NULL) RAND_seed(US addr, sizeof(addr)); - - if (!RAND_status()) - return tls_error(US"RAND_status", host, - US"unable to seed random number generator", errstr); - } - -/* Set up the information callback, which outputs if debugging is at a suitable -level. */ +afterwards. -DEBUG(D_tls) - { - SSL_CTX_set_info_callback(ctx, (void (*)())info_callback); -#if defined(EXIM_HAVE_OPESSL_TRACE) && !defined(OPENSSL_NO_SSL_TRACE) - /* this needs a debug build of OpenSSL */ - SSL_CTX_set_msg_callback(ctx, (void (*)())SSL_trace); -#endif -#ifdef OPENSSL_HAVE_KEYLOG_CB - SSL_CTX_set_keylog_callback(ctx, (void (*)())keylog_callback); -#endif - } +Although we likely called this before, at daemon startup, this is a chance +to mix in further variable info (time, pid) if needed. */ -/* Automatically re-try reads/writes after renegotiation. */ -(void) SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY); +if (!lib_rand_init(addr)) + return tls_error(US"RAND_status", host, + US"unable to seed random number generator", errstr); /* Apply administrator-supplied work-arounds. Historically we applied just one requested option, @@ -2172,15 +2483,31 @@ grandfathered in the first one as the default value for "openssl_options". No OpenSSL version number checks: the options we accept depend upon the availability of the option value macros from OpenSSL. */ -if (!tls_openssl_options_parse(openssl_options, &init_options)) - return tls_error(US"openssl_options parsing failed", host, NULL, errstr); +if (!init_options) + if (!tls_openssl_options_parse(openssl_options, &init_options)) + return tls_error(US"openssl_options parsing failed", host, NULL, errstr); + +/* Create a context. +The OpenSSL docs in 1.0.1b have not been updated to clarify TLS variant +negotiation in the different methods; as far as I can tell, the only +*_{server,client}_method which allows negotiation is SSLv23, which exists even +when OpenSSL is built without SSLv2 support. +By disabling with openssl_options, we can let admins re-enable with the +existing knob. */ + +if (!(ctx = state->lib_state.lib_ctx)) + { + if ((rc = lib_ctx_new(&ctx, host, errstr)) != OK) + return rc; + state->lib_state.lib_ctx = ctx; + } -#ifdef EXPERIMENTAL_TLS_RESUME +#ifndef DISABLE_TLS_RESUME tlsp->resumption = RESUME_SUPPORTED; #endif if (init_options) { -#ifdef EXPERIMENTAL_TLS_RESUME +#ifndef DISABLE_TLS_RESUME /* Should the server offer session resumption? */ if (!host && verify_check_host(&tls_resumption_hosts) == OK) { @@ -2212,21 +2539,41 @@ will never be used because we use a new context every time. */ /* Initialize with DH parameters if supplied */ /* Initialize ECDH temp key parameter selection */ -if ( !init_dh(ctx, dhparam, host, errstr) - || !init_ecdh(ctx, host, errstr) - ) - return DEFER; +if (state->lib_state.dh) + { DEBUG(D_tls) debug_printf("TLS: DH params were preloaded\n"); } +else + if (!init_dh(ctx, state->dhparam, host, errstr)) return DEFER; + +if (state->lib_state.ecdh) + { DEBUG(D_tls) debug_printf("TLS: ECDH curve was preloaded\n"); } +else + if (!init_ecdh(ctx, host, errstr)) return DEFER; /* Set up certificate and key (and perhaps OCSP info) */ -if ((rc = tls_expand_session_files(ctx, cbinfo, errstr)) != OK) - return rc; +if (state->lib_state.conn_certs) + { + DEBUG(D_tls) + debug_printf("TLS: %s certs were preloaded\n", host ? "client":"server"); + } +else + { +#ifndef DISABLE_OCSP + if (!host) + { + state->u_ocsp.server.file = ocsp_file; + state->u_ocsp.server.file_expanded = NULL; + state->u_ocsp.server.olist = NULL; + } +#endif + if ((rc = tls_expand_session_files(ctx, state, errstr)) != OK) return rc; + } /* If we need to handle SNI or OCSP, do so */ #ifdef EXIM_HAVE_OPENSSL_TLSEXT # ifndef DISABLE_OCSP - if (!(cbinfo->verify_stack = sk_X509_new_null())) + if (!(state->verify_stack = sk_X509_new_null())) { DEBUG(D_tls) debug_printf("failed to create stack for stapling verify\n"); return FAIL; @@ -2240,33 +2587,33 @@ if (!host) /* server */ the option exists, not what the current expansion might be, as SNI might change the certificate and OCSP file in use between now and the time the callback is invoked. */ - if (cbinfo->u_ocsp.server.file) + if (state->u_ocsp.server.file) { SSL_CTX_set_tlsext_status_cb(ctx, tls_server_stapling_cb); - SSL_CTX_set_tlsext_status_arg(ctx, cbinfo); + SSL_CTX_set_tlsext_status_arg(ctx, state); } # endif /* We always do this, so that $tls_sni is available even if not used in tls_certificate */ SSL_CTX_set_tlsext_servername_callback(ctx, tls_servername_cb); - SSL_CTX_set_tlsext_servername_arg(ctx, cbinfo); + SSL_CTX_set_tlsext_servername_arg(ctx, state); } # ifndef DISABLE_OCSP else /* client */ if(ocsp_file) /* wanting stapling */ { - if (!(cbinfo->u_ocsp.client.verify_store = X509_STORE_new())) + if (!(state->u_ocsp.client.verify_store = X509_STORE_new())) { 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, cbinfo); + SSL_CTX_set_tlsext_status_arg(ctx, state); } # endif #endif -cbinfo->verify_cert_hostnames = NULL; +state->verify_cert_hostnames = NULL; #ifdef EXIM_HAVE_EPHEM_RSA_KEX /* Set up the RSA callback */ @@ -2279,8 +2626,7 @@ The period appears to be also used for (server-generated) session tickets */ SSL_CTX_set_timeout(ctx, ssl_session_timeout); DEBUG(D_tls) debug_printf("Initialized TLS\n"); -*cbp = cbinfo; -*ctxp = ctx; +*caller_state = state; return OK; } @@ -2430,20 +2776,17 @@ repeated after a Server Name Indication. Arguments: sctx SSL_CTX* to initialise - certs certs file or NULL + certs certs file, expanded crl CRL file or NULL host NULL in a server; the remote host in a client - optional TRUE if called from a server for a host in tls_try_verify_hosts; - otherwise passed as FALSE - cert_vfy_cb Callback function for certificate verification errstr error string pointer Returns: OK/DEFER/FAIL */ static int -setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional, - int (*cert_vfy_cb)(int, X509_STORE_CTX *), uschar ** errstr) +setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, + uschar ** errstr) { uschar *expcerts, *expcrl; @@ -2459,7 +2802,7 @@ if (expcerts && *expcerts) if (!SSL_CTX_set_default_verify_paths(sctx)) return tls_error(US"SSL_CTX_set_default_verify_paths", host, NULL, errstr); - if (Ustrcmp(expcerts, "system") != 0) + if (Ustrcmp(expcerts, "system") != 0 && Ustrncmp(expcerts, "system,", 7) != 0) { struct stat statbuf; @@ -2487,8 +2830,8 @@ This is inconsistent with the need to verify the OCSP proof of the server cert. if ( !host && statbuf.st_size > 0 - && server_static_cbinfo->u_ocsp.server.file - && !chain_from_pem_file(file, server_static_cbinfo->verify_stack) + && state_server.u_ocsp.server.file + && !chain_from_pem_file(file, state_server.verify_stack) ) { log_write(0, LOG_MAIN|LOG_PANIC, @@ -2505,7 +2848,8 @@ This is inconsistent with the need to verify the OCSP proof of the server cert. if ( (!file || statbuf.st_size > 0) && !SSL_CTX_load_verify_locations(sctx, CS file, CS dir)) - return tls_error(US"SSL_CTX_load_verify_locations", host, NULL, errstr); + return tls_error(US"SSL_CTX_load_verify_locations", + host, NULL, errstr); /* On the server load the list of CAs for which we will accept certs, for sending to the client. This is only for the one-file @@ -2520,11 +2864,15 @@ This is inconsistent with the need to verify the OCSP proof of the server cert. if (file) { STACK_OF(X509_NAME) * names = SSL_load_client_CA_file(CS file); + int i = sk_X509_NAME_num(names); if (!host) SSL_CTX_set_client_CA_list(sctx, names); - DEBUG(D_tls) debug_printf("Added %d certificate authorities.\n", - sk_X509_NAME_num(names)); + DEBUG(D_tls) debug_printf("Added %d additional certificate authorit%s\n", + i, i>1 ? "ies":"y"); } + else + DEBUG(D_tls) + debug_printf("Added dir for additional certificate authorities\n"); } } @@ -2580,12 +2928,6 @@ This is inconsistent with the need to verify the OCSP proof of the server cert. } #endif /* OPENSSL_VERSION_NUMBER > 0x00907000L */ - - /* If verification is optional, don't fail if no certificate */ - - SSL_CTX_set_verify(sctx, - SSL_VERIFY_PEER | (optional ? 0 : SSL_VERIFY_FAIL_IF_NO_PEER_CERT), - cert_vfy_cb); } return OK; @@ -2596,13 +2938,11 @@ return OK; /************************************************* * Start a TLS session in a server * *************************************************/ - /* This is called when Exim is running as a server, after having received the STARTTLS command. It must respond to that command, and then negotiate a TLS session. Arguments: - require_ciphers allowed ciphers errstr pointer to error message Returns: OK on success @@ -2612,11 +2952,13 @@ Returns: OK on success */ int -tls_server_start(const uschar * require_ciphers, uschar ** errstr) +tls_server_start(uschar ** errstr) { int rc; uschar * expciphers; -tls_ext_ctx_cb * cbinfo; +exim_openssl_state_st * dummy_statep; +SSL_CTX * ctx; +SSL * ssl; static uschar peerdn[256]; /* Check for previous activation */ @@ -2631,16 +2973,13 @@ if (tls_in.active.sock >= 0) /* Initialize the SSL library. If it fails, it will already have logged the error. */ -rc = tls_init(&server_ctx, NULL, tls_dhparam, tls_certificate, tls_privatekey, +rc = tls_init(NULL, NULL, #ifndef DISABLE_OCSP tls_ocsp_file, #endif - NULL, &server_static_cbinfo, &tls_in, errstr); + NULL, &dummy_statep, &tls_in, errstr); if (rc != OK) return rc; -cbinfo = server_static_cbinfo; - -if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers, errstr)) - return FAIL; +ctx = state_server.lib_state.lib_ctx; /* In OpenSSL, cipher components are separated by hyphens. In GnuTLS, they were historically separated by underscores. So that I can use either form in my @@ -2651,13 +2990,16 @@ for TLS 1.3 . Since we do not call it at present we get the default list: TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256 */ -if (expciphers) +if (state_server.lib_state.pri_string) + { DEBUG(D_tls) debug_printf("TLS: cipher list was preloaded\n"); } +else { - for (uschar * s = expciphers; *s; s++ ) if (*s == '_') *s = '-'; - DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers); - if (!SSL_CTX_set_cipher_list(server_ctx, CS expciphers)) - return tls_error(US"SSL_CTX_set_cipher_list", NULL, NULL, errstr); - cbinfo->server_cipher_list = expciphers; + 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 this is a host for which certificate verification is mandatory or @@ -2670,37 +3012,48 @@ tls_in.dane_verified = FALSE; server_verify_callback_called = FALSE; if (verify_check_host(&tls_verify_hosts) == OK) - { - rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL, - FALSE, verify_callback_server, errstr); - if (rc != OK) return rc; server_verify_optional = FALSE; - } else if (verify_check_host(&tls_try_verify_hosts) == OK) - { - rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL, - TRUE, verify_callback_server, errstr); - if (rc != OK) return rc; server_verify_optional = TRUE; +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); + + if (state_server.lib_state.cabundle) + { DEBUG(D_tls) debug_printf("TLS: CA bundle for server was preloaded\n"); } + else + if ((rc = setup_certs(ctx, expcerts, tls_crl, NULL, errstr)) != OK) + return rc; + + if (expcerts && *expcerts) + setup_cert_verify(ctx, server_verify_optional, verify_callback_server); } +skip_certs: ; -#ifdef EXPERIMENTAL_TLS_RESUME -SSL_CTX_set_tlsext_ticket_key_cb(server_ctx, ticket_key_callback); +#ifndef DISABLE_TLS_RESUME +SSL_CTX_set_tlsext_ticket_key_cb(ctx, ticket_key_callback); /* despite working, appears to always return failure, so ignoring */ #endif #ifdef OPENSSL_HAVE_NUM_TICKETS -# ifdef EXPERIMENTAL_TLS_RESUME -SSL_CTX_set_num_tickets(server_ctx, tls_in.host_resumable ? 1 : 0); +# ifndef DISABLE_TLS_RESUME +SSL_CTX_set_num_tickets(ctx, tls_in.host_resumable ? 1 : 0); # else -SSL_CTX_set_num_tickets(server_ctx, 0); /* send no TLS1.3 stateful-tickets */ +SSL_CTX_set_num_tickets(ctx, 0); /* send no TLS1.3 stateful-tickets */ # endif #endif /* Prepare for new connection */ -if (!(server_ssl = SSL_new(server_ctx))) +if (!(ssl = SSL_new(ctx))) return tls_error(US"SSL_new", NULL, NULL, errstr); +state_server.lib_state.lib_ssl = ssl; /* Warning: we used to SSL_clear(ssl) here, it was removed. * @@ -2721,7 +3074,7 @@ make them disconnect. We need to have an explicit fflush() here, to force out the response. Other smtp_printf() calls do not need it, because in non-TLS mode, the fflush() happens when smtp_getc() is called. */ -SSL_set_session_id_context(server_ssl, sid_ctx, Ustrlen(sid_ctx)); +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); @@ -2731,20 +3084,21 @@ if (!tls_in.on_connect) /* Now negotiate the TLS session. We put our own timer on it, since it seems that the OpenSSL library doesn't. */ -SSL_set_wfd(server_ssl, fileno(smtp_out)); -SSL_set_rfd(server_ssl, fileno(smtp_in)); -SSL_set_accept_state(server_ssl); +SSL_set_wfd(ssl, fileno(smtp_out)); +SSL_set_rfd(ssl, fileno(smtp_in)); +SSL_set_accept_state(ssl); DEBUG(D_tls) debug_printf("Calling SSL_accept\n"); +ERR_clear_error(); sigalrm_seen = FALSE; if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout); -rc = SSL_accept(server_ssl); +rc = SSL_accept(ssl); ALARM_CLR(0); if (rc <= 0) { - int error = SSL_get_error(server_ssl, rc); + int error = SSL_get_error(ssl, rc); switch(error) { case SSL_ERROR_NONE: @@ -2754,8 +3108,8 @@ if (rc <= 0) 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); - if (SSL_get_shutdown(server_ssl) == SSL_RECEIVED_SHUTDOWN) - SSL_shutdown(server_ssl); + if (SSL_get_shutdown(ssl) == SSL_RECEIVED_SHUTDOWN) + SSL_shutdown(ssl); tls_close(NULL, TLS_NO_SHUTDOWN); return FAIL; @@ -2770,7 +3124,7 @@ 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(server_ssl)); + s = string_sprintf("%s (%s)", s, SSL_get_version(ssl)); (void) tls_error(s, NULL, sigalrm_seen ? US"timed out" : NULL, errstr); return FAIL; } @@ -2786,7 +3140,10 @@ if (rc <= 0) } DEBUG(D_tls) debug_printf(" - syscall %s\n", strerror(errno)); } - (void) tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : NULL, errstr); + (void) tls_error(US"SSL_accept", NULL, + sigalrm_seen ? US"timed out" + : ERR_peek_error() ? NULL : string_sprintf("ret %d", error), + errstr); return FAIL; } } @@ -2795,8 +3152,8 @@ DEBUG(D_tls) debug_printf("SSL_accept was successful\n"); ERR_clear_error(); /* Even success can leave errors in the stack. Seen with anon-authentication ciphersuite negotiated. */ -#ifdef EXPERIMENTAL_TLS_RESUME -if (SSL_session_reused(server_ssl)) +#ifndef DISABLE_TLS_RESUME +if (SSL_session_reused(ssl)) { tls_in.resumption |= RESUME_USED; DEBUG(D_tls) debug_printf("Session reused\n"); @@ -2807,31 +3164,31 @@ if (SSL_session_reused(server_ssl)) adjust the input functions to read via TLS, and initialize things. */ #ifdef SSL_get_extms_support -tls_in.ext_master_secret = SSL_get_extms_support(server_ssl) == 1; +tls_in.ext_master_secret = SSL_get_extms_support(ssl) == 1; #endif -peer_cert(server_ssl, &tls_in, peerdn, sizeof(peerdn)); +peer_cert(ssl, &tls_in, peerdn, sizeof(peerdn)); -tls_in.ver = tlsver_name(server_ssl); -tls_in.cipher = construct_cipher_name(server_ssl, tls_in.ver, &tls_in.bits); -tls_in.cipher_stdname = cipher_stdname_ssl(server_ssl); +tls_in.ver = tlsver_name(ssl); +tls_in.cipher = construct_cipher_name(ssl, tls_in.ver, &tls_in.bits); +tls_in.cipher_stdname = cipher_stdname_ssl(ssl); DEBUG(D_tls) { uschar buf[2048]; - if (SSL_get_shared_ciphers(server_ssl, CS buf, sizeof(buf))) + 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(server_ssl)); + SSL_SESSION_print_keylog(bp, SSL_get_session(ssl)); BIO_free(bp); } #endif #ifdef EXIM_HAVE_SESSION_TICKET { - SSL_SESSION * ss = SSL_get_session(server_ssl); + SSL_SESSION * ss = SSL_get_session(ssl); if (SSL_SESSION_has_ticket(ss)) /* 1.1.0 */ debug_printf("The session has a ticket, life %lu seconds\n", SSL_SESSION_get_ticket_lifetime_hint(ss)); @@ -2841,7 +3198,7 @@ DEBUG(D_tls) /* Record the certificate we presented */ { - X509 * crt = SSL_get_certificate(server_ssl); + X509 * crt = SSL_get_certificate(ssl); tls_in.ourcert = crt ? X509_dup(crt) : NULL; } @@ -2849,10 +3206,10 @@ DEBUG(D_tls) See description in https://paquier.xyz/postgresql-2/channel-binding-openssl/ */ { uschar c, * s; - size_t len = SSL_get_peer_finished(server_ssl, &c, 0); + size_t len = SSL_get_peer_finished(ssl, &c, 0); int old_pool = store_pool; - SSL_get_peer_finished(server_ssl, s = store_get((int)len, FALSE), len); + 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; @@ -2886,16 +3243,21 @@ return OK; static int tls_client_basic_ctx_init(SSL_CTX * ctx, - host_item * host, smtp_transport_options_block * ob, tls_ext_ctx_cb * cbinfo, + host_item * host, smtp_transport_options_block * ob, exim_openssl_state_st * state, uschar ** errstr) { int rc; -/* stick to the old behaviour for compatibility if tls_verify_certificates is - set but both tls_verify_hosts and tls_try_verify_hosts is not set. Check only - the specified host patterns if one of them is defined */ -if ( ( !ob->tls_verify_hosts - && (!ob->tls_try_verify_hosts || !*ob->tls_try_verify_hosts) +/* Back-compatible old behaviour if tls_verify_certificates is set but both +tls_verify_hosts and tls_try_verify_hosts are not set. Check only the specified +host patterns if one of them is set with content. */ + +if ( ( ( !ob->tls_verify_hosts || !ob->tls_verify_hosts + || Ustrcmp(ob->tls_try_verify_hosts, ":") == 0 + ) + && ( !ob->tls_try_verify_hosts || !*ob->tls_try_verify_hosts + || Ustrcmp(ob->tls_try_verify_hosts, ":") == 0 + ) ) || verify_check_given_host(CUSS &ob->tls_verify_hosts, host) == OK ) @@ -2905,21 +3267,33 @@ else if (verify_check_given_host(CUSS &ob->tls_try_verify_hosts, host) == OK) else return OK; -if ((rc = setup_certs(ctx, ob->tls_verify_certificates, - ob->tls_crl, host, client_verify_optional, verify_callback_client, - errstr)) != OK) - return rc; + { + 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); + + if (state->lib_state.cabundle) + { DEBUG(D_tls) debug_printf("TLS: CA bundle was preloaded\n"); } + else + if ((rc = setup_certs(ctx, expcerts, ob->tls_crl, host, errstr)) != OK) + return rc; + + if (expcerts && *expcerts) + setup_cert_verify(ctx, client_verify_optional, verify_callback_client); + } if (verify_check_given_host(CUSS &ob->tls_verify_cert_hostnames, host) == OK) { - cbinfo->verify_cert_hostnames = + state->verify_cert_hostnames = #ifdef SUPPORT_I18N - string_domain_utf8_to_alabel(host->name, NULL); + string_domain_utf8_to_alabel(host->certname, NULL); #else - host->name; + host->certname; #endif DEBUG(D_tls) debug_printf("Cert hostname to check: \"%s\"\n", - cbinfo->verify_cert_hostnames); + state->verify_cert_hostnames); } return OK; } @@ -2982,7 +3356,7 @@ return DEFER; -#ifdef EXPERIMENTAL_TLS_RESUME +#ifndef DISABLE_TLS_RESUME /* On the client, get any stashed session for the given IP from hints db and apply it to the ssl-connection for attempted resumption. */ @@ -3054,7 +3428,7 @@ if (tlsp->host_resumable) static int tls_save_session_cb(SSL * ssl, SSL_SESSION * ss) { -tls_ext_ctx_cb * cbinfo = SSL_get_ex_data(ssl, tls_exdata_idx); +exim_openssl_state_st * cbinfo = SSL_get_ex_data(ssl, tls_exdata_idx); tls_support * tlsp; DEBUG(D_tls) debug_printf("tls_save_session_cb\n"); @@ -3120,12 +3494,12 @@ if (tlsp->host_resumable) SSL_clear_options(ssl, SSL_OP_NO_TICKET); tls_exdata_idx = SSL_get_ex_new_index(0, 0, 0, 0, 0); - if (!SSL_set_ex_data(ssl, tls_exdata_idx, client_static_cbinfo)) + if (!SSL_set_ex_data(ssl, tls_exdata_idx, client_static_state)) { tls_error(US"set ex_data", host, NULL, errstr); return FALSE; } - debug_printf("tls_exdata_idx %d cbinfo %p\n", tls_exdata_idx, client_static_cbinfo); + debug_printf("tls_exdata_idx %d cbinfo %p\n", tls_exdata_idx, client_static_state); } tlsp->resumption = RESUME_SUPPORTED; @@ -3144,7 +3518,7 @@ if (SSL_session_reused(exim_client_ctx->ssl)) tlsp->resumption |= RESUME_USED; } } -#endif /* EXPERIMENTAL_TLS_RESUME */ +#endif /* !DISABLE_TLS_RESUME */ /************************************************* @@ -3196,6 +3570,7 @@ 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' @@ -3221,14 +3596,15 @@ tlsp->tlsa_usage = 0; } #endif -rc = tls_init(&exim_client_ctx->ctx, host, NULL, - ob->tls_certificate, ob->tls_privatekey, +rc = tls_init(host, ob, #ifndef DISABLE_OCSP (void *)(long)request_ocsp, #endif - cookie, &client_static_cbinfo, tlsp, errstr); + cookie, &client_static_state, tlsp, errstr); if (rc != OK) return FALSE; +exim_client_ctx->ctx = client_static_state->lib_state.lib_ctx; + tlsp->certificate_verified = FALSE; client_verify_callback_called = FALSE; @@ -3289,11 +3665,11 @@ else #endif - if (tls_client_basic_ctx_init(exim_client_ctx->ctx, host, ob, - client_static_cbinfo, errstr) != OK) - return FALSE; +if (tls_client_basic_ctx_init(exim_client_ctx->ctx, host, ob, + client_static_state, errstr) != OK) + return FALSE; -#ifdef EXPERIMENTAL_TLS_RESUME +#ifndef DISABLE_TLS_RESUME tls_client_ctx_resume_prehandshake(exim_client_ctx, tlsp, ob, host); #endif @@ -3359,19 +3735,19 @@ if (request_ocsp) if (request_ocsp) { SSL_set_tlsext_status_type(exim_client_ctx->ssl, TLSEXT_STATUSTYPE_ocsp); - client_static_cbinfo->u_ocsp.client.verify_required = require_ocsp; + client_static_state->u_ocsp.client.verify_required = require_ocsp; tlsp->ocsp = OCSP_NOT_RESP; } #endif -#ifdef EXPERIMENTAL_TLS_RESUME +#ifndef DISABLE_TLS_RESUME if (!tls_client_ssl_resume_prehandshake(exim_client_ctx->ssl, tlsp, host, errstr)) return FALSE; #endif #ifndef DISABLE_EVENT -client_static_cbinfo->event_action = tb ? tb->event_action : NULL; +client_static_state->event_action = tb ? tb->event_action : NULL; #endif /* There doesn't seem to be a built-in timeout on connection. */ @@ -3405,7 +3781,7 @@ DEBUG(D_tls) #endif } -#ifdef EXPERIMENTAL_TLS_RESUME +#ifndef DISABLE_TLS_RESUME tls_client_resume_posthandshake(exim_client_ctx, tlsp); #endif @@ -3451,16 +3827,18 @@ return TRUE; static BOOL tls_refill(unsigned lim) { +SSL * ssl = state_server.lib_state.lib_ssl; int error; int inbytes; -DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", server_ssl, +DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", ssl, ssl_xfer_buffer, ssl_xfer_buffer_size); +ERR_clear_error(); if (smtp_receive_timeout > 0) ALARM(smtp_receive_timeout); -inbytes = SSL_read(server_ssl, CS ssl_xfer_buffer, +inbytes = SSL_read(ssl, CS ssl_xfer_buffer, MIN(ssl_xfer_buffer_size, lim)); -error = SSL_get_error(server_ssl, inbytes); +error = SSL_get_error(ssl, inbytes); if (smtp_receive_timeout > 0) ALARM_CLR(0); if (had_command_timeout) /* set by signal handler */ @@ -3484,8 +3862,8 @@ switch(error) case SSL_ERROR_ZERO_RETURN: DEBUG(D_tls) debug_printf("Got SSL_ERROR_ZERO_RETURN\n"); - if (SSL_get_shutdown(server_ssl) == SSL_RECEIVED_SHUTDOWN) - SSL_shutdown(server_ssl); + if (SSL_get_shutdown(ssl) == SSL_RECEIVED_SHUTDOWN) + SSL_shutdown(ssl); tls_close(NULL, TLS_NO_SHUTDOWN); return FALSE; @@ -3563,7 +3941,7 @@ return buf; void -tls_get_cache() +tls_get_cache(void) { #ifndef DISABLE_DKIM int n = ssl_xfer_buffer_hwm - ssl_xfer_buffer_lwm; @@ -3576,7 +3954,8 @@ if (n > 0) BOOL tls_could_read(void) { -return ssl_xfer_buffer_lwm < ssl_xfer_buffer_hwm || SSL_pending(server_ssl) > 0; +return ssl_xfer_buffer_lwm < ssl_xfer_buffer_hwm + || SSL_pending(state_server.lib_state.lib_ssl) > 0; } @@ -3599,13 +3978,15 @@ Only used by the client-side TLS. int tls_read(void * ct_ctx, uschar *buff, size_t len) { -SSL * ssl = ct_ctx ? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl : server_ssl; +SSL * ssl = ct_ctx ? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl + : state_server.lib_state.lib_ssl; int inbytes; int error; DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", ssl, buff, (unsigned int)len); +ERR_clear_error(); inbytes = SSL_read(ssl, CS buff, len); error = SSL_get_error(ssl, inbytes); @@ -3648,7 +4029,8 @@ tls_write(void * ct_ctx, const uschar * buff, size_t len, BOOL more) size_t olen = len; int outbytes, error; SSL * ssl = ct_ctx - ? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl : server_ssl; + ? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl + : state_server.lib_state.lib_ssl; static gstring * server_corked = NULL; gstring ** corkedp = ct_ctx ? &((exim_openssl_client_tls_ctx *)ct_ctx)->corked : &server_corked; @@ -3667,20 +4049,16 @@ context for the stashed information. */ a store reset there, so use POOL_PERM. */ /* + if CHUNKING, cmds EHLO,MAIL,RCPT(s),BDAT */ -if ((more || corked)) +if (more || corked) { if (!len) buff = US &error; /* dummy just so that string_catn is ok */ -#ifndef DISABLE_PIPE_CONNECT int save_pool = store_pool; store_pool = POOL_PERM; -#endif corked = string_catn(corked, buff, len); -#ifndef DISABLE_PIPE_CONNECT store_pool = save_pool; -#endif if (more) { @@ -3695,6 +4073,7 @@ if ((more || corked)) for (int left = len; left > 0;) { DEBUG(D_tls) debug_printf("SSL_write(%p, %p, %d)\n", ssl, buff, left); + ERR_clear_error(); outbytes = SSL_write(ssl, CS buff, left); error = SSL_get_error(ssl, outbytes); DEBUG(D_tls) debug_printf("outbytes=%d error=%d\n", outbytes, error); @@ -3715,9 +4094,16 @@ for (int left = len; left > 0;) return -1; case SSL_ERROR_SYSCALL: - log_write(0, LOG_MAIN, "SSL_write: (from %s) syscall: %s", - sender_fullhost ? sender_fullhost : US"", - strerror(errno)); + if (ct_ctx || errno != ECONNRESET || !f.smtp_in_quit) + log_write(0, LOG_MAIN, "SSL_write: (from %s) syscall: %s", + sender_fullhost ? sender_fullhost : US"", + strerror(errno)); + else if (LOGGING(protocol_detail)) + log_write(0, LOG_MAIN, "[%s] after QUIT, client reset TCP before" + " SMTP response and TLS close\n", sender_host_address); + else + DEBUG(D_tls) debug_printf("[%s] SSL_write: after QUIT," + " client reset TCP before TLS close\n", sender_host_address); return -1; default: @@ -3730,6 +4116,32 @@ return olen; +/* +Arguments: + ct_ctx client TLS context pointer, or NULL for the one global server context +*/ + +void +tls_shutdown_wr(void * ct_ctx) +{ +exim_openssl_client_tls_ctx * o_ctx = ct_ctx; +SSL ** sslp = o_ctx ? &o_ctx->ssl : (SSL **) &state_server.lib_state.lib_ssl; +int * fdp = o_ctx ? &tls_out.active.sock : &tls_in.active.sock; +int rc; + +if (*fdp < 0) return; /* TLS was not active */ + +tls_write(ct_ctx, NULL, 0, FALSE); /* flush write buffer */ + +HDEBUG(D_transport|D_tls|D_acl|D_v) debug_printf_indent(" SMTP(TLS shutdown)>>\n"); +rc = SSL_shutdown(*sslp); +if (rc < 0) DEBUG(D_tls) + { + ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring)); + debug_printf("SSL_shutdown: %s\n", ssl_errstring); + } +} + /************************************************* * Close down a TLS session * *************************************************/ @@ -3740,7 +4152,8 @@ would tamper with the SSL session in the parent process). Arguments: ct_ctx client TLS context pointer, or NULL for the one global server context - shutdown 1 if TLS close-alert is to be sent, + do_shutdown 0 no data-flush or TLS close-alert + 1 if TLS close-alert is to be sent, 2 if also response to be waited for Returns: nothing @@ -3749,23 +4162,24 @@ Used by both server-side and client-side TLS. */ void -tls_close(void * ct_ctx, int shutdown) +tls_close(void * ct_ctx, int do_shutdown) { exim_openssl_client_tls_ctx * o_ctx = ct_ctx; -SSL_CTX **ctxp = o_ctx ? &o_ctx->ctx : &server_ctx; -SSL **sslp = o_ctx ? &o_ctx->ssl : &server_ssl; -int *fdp = o_ctx ? &tls_out.active.sock : &tls_in.active.sock; +SSL ** sslp = o_ctx ? &o_ctx->ssl : (SSL **) &state_server.lib_state.lib_ssl; +int * fdp = o_ctx ? &tls_out.active.sock : &tls_in.active.sock; if (*fdp < 0) return; /* TLS was not active */ -if (shutdown) +if (do_shutdown) { int rc; DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS%s\n", - shutdown > 1 ? " (with response-wait)" : ""); + do_shutdown > 1 ? " (with response-wait)" : ""); + + tls_write(ct_ctx, NULL, 0, FALSE); /* flush write buffer */ if ( (rc = SSL_shutdown(*sslp)) == 0 /* send "close notify" alert */ - && shutdown > 1) + && do_shutdown > 1) { ALARM(2); rc = SSL_shutdown(*sslp); /* wait for response */ @@ -3782,8 +4196,8 @@ if (shutdown) if (!o_ctx) /* server side */ { #ifndef DISABLE_OCSP - sk_X509_pop_free(server_static_cbinfo->verify_stack, X509_free); - server_static_cbinfo->verify_stack = NULL; + sk_X509_pop_free(state_server.verify_stack, X509_free); + state_server.verify_stack = NULL; #endif receive_getc = smtp_getc; @@ -3798,9 +4212,7 @@ if (!o_ctx) /* server side */ /* Leave bits, peercert, cipher, peerdn, certificate_verified set, for logging */ } -SSL_CTX_free(*ctxp); SSL_free(*sslp); -*ctxp = NULL; *sslp = NULL; *fdp = -1; } @@ -3842,28 +4254,20 @@ while (*s != 0) { if (*s == '_') *s = '-'; s++; } err = NULL; -#ifdef EXIM_HAVE_OPENSSL_TLS_METHOD -if (!(ctx = SSL_CTX_new(TLS_server_method()))) -#else -if (!(ctx = SSL_CTX_new(SSLv23_server_method()))) -#endif +if (lib_ctx_new(&ctx, NULL, &err) == OK) { - ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring)); - return string_sprintf("SSL_CTX_new() failed: %s", ssl_errstring); - } + DEBUG(D_tls) + debug_printf("tls_require_ciphers expands to \"%s\"\n", expciphers); -DEBUG(D_tls) - debug_printf("tls_require_ciphers expands to \"%s\"\n", expciphers); + if (!SSL_CTX_set_cipher_list(ctx, CS expciphers)) + { + ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring)); + err = string_sprintf("SSL_CTX_set_cipher_list(%s) failed: %s", + expciphers, ssl_errstring); + } -if (!SSL_CTX_set_cipher_list(ctx, CS expciphers)) - { - ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring)); - err = string_sprintf("SSL_CTX_set_cipher_list(%s) failed: %s", - expciphers, ssl_errstring); + SSL_CTX_free(ctx); } - -SSL_CTX_free(ctx); - return err; }