X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/4cfb21b860882e76c3d3147b01708b347d0f9a0d..28d2eab1414ef8d20ff0fde7026aa52fd01ef795:/src/src/tls-openssl.c diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index 6ce20f143..9692033f0 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -48,6 +48,7 @@ functions from the OpenSSL library. */ #if OPENSSL_VERSION_NUMBER >= 0x10100000L # define EXIM_HAVE_OCSP_RESP_COUNT # define OPENSSL_AUTO_SHA256 +# define EXIM_HAVE_ALPN #else # define EXIM_HAVE_EPHEM_RSA_KEX # define EXIM_HAVE_RAND_PSEUDO @@ -274,6 +275,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 @@ -350,8 +352,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; @@ -371,7 +374,11 @@ typedef struct ocsp_resp { OCSP_RESPONSE * resp; } ocsp_resplist; -typedef struct tls_ext_ctx_cb { +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; @@ -399,22 +406,19 @@ typedef struct tls_ext_ctx_cb { #ifndef DISABLE_EVENT 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 -static int tls_servername_cb(SSL *s, int *ad ARG_UNUSED, void *arg); -#endif #ifndef DISABLE_OCSP static int tls_server_stapling_cb(SSL *s, void *arg); #endif @@ -427,13 +431,20 @@ 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) { #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(); } @@ -475,8 +486,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 * *************************************************/ /* @@ -518,30 +791,145 @@ 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) + 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) { - 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); - } + 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,815 @@ 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) - { - const uschar * str; + debug_printf("tls_ocsp_file (%s) '%s'\n", is_pem ? "PEM" : "DER", filename); - 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 (!filename || !*filename) return; - 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)); +ERR_clear_error(); +if (!(bio = BIO_new_file(CS filename, "rb"))) + { + log_write(0, LOG_MAIN|LOG_PANIC, + "Failed to open OCSP response file \"%s\": %.100s", + filename, ERR_reason_error_string(ERR_get_error())); + 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); -} -#endif +if (is_pem) + { + uschar * data, * freep; + char * dummy; + long len; + if (!PEM_read_bio(bio, &dummy, &dummy, &data, &len)) + { + log_write(0, LOG_MAIN|LOG_PANIC, "Failed to read PEM file \"%s\": %.100s", + filename, ERR_reason_error_string(ERR_get_error())); + 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 (!resp) + { + log_write(0, LOG_MAIN|LOG_PANIC, "Error reading OCSP response from \"%s\": %s", + filename, ERR_reason_error_string(ERR_get_error())); + return; + } -#ifndef DISABLE_TLS_RESUME -/* Manage the keysets used for encrypting the session tickets, on the server. */ +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; + } -typedef struct { /* Session ticket encryption key */ - uschar name[16]; +#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 - 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; +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; + } -static exim_stek exim_tk; /* current key */ -static exim_stek exim_tk_old; /* previous key */ +sk = state->verify_stack; +verify_flags = OCSP_NOVERIFY; /* check sigs, but not purpose */ -static void -tk_init(void) -{ -time_t t = time(NULL); - -if (exim_tk.name[0]) - { - if (exim_tk.renew >= t) return; - exim_tk_old = exim_tk; - } - -if (f.running_in_test_harness) ssl_session_timeout = 6; - -DEBUG(D_tls) debug_printf("OpenSSL: %s STEK\n", exim_tk.name[0] ? "rotating" : "creating"); -if (RAND_bytes(exim_tk.aes_key, sizeof(exim_tk.aes_key)) <= 0) return; -if (RAND_bytes(exim_tk.hmac_key, sizeof(exim_tk.hmac_key)) <= 0) return; -if (RAND_bytes(exim_tk.name+1, sizeof(exim_tk.name)-1) <= 0) return; - -exim_tk.name[0] = 'E'; -exim_tk.aes_cipher = EVP_aes_256_cbc(); -exim_tk.hmac_hash = EVP_sha256(); -exim_tk.expire = t + ssl_session_timeout; -exim_tk.renew = t + ssl_session_timeout/2; -} - -static exim_stek * -tk_current(void) -{ -if (!exim_tk.name[0]) return NULL; -return &exim_tk; -} - -static exim_stek * -tk_find(const uschar * name) -{ -return memcmp(name, exim_tk.name, sizeof(exim_tk.name)) == 0 ? &exim_tk - : memcmp(name, exim_tk_old.name, sizeof(exim_tk_old.name)) == 0 ? &exim_tk_old - : NULL; -} - -/* Callback for session tickets, on server */ -static int -ticket_key_callback(SSL * ssl, uschar key_name[16], - uschar * iv, EVP_CIPHER_CTX * ctx, HMAC_CTX * hctx, int enc) -{ -tls_support * tlsp = server_static_cbinfo->tlsp; -exim_stek * key; +/* May need to expose ability to adjust those flags? +OCSP_NOSIGS OCSP_NOVERIFY OCSP_NOCHAIN OCSP_NOCHECKS OCSP_NOEXPLICIT +OCSP_TRUSTOTHER OCSP_NOINTERN */ -if (enc) - { - DEBUG(D_tls) debug_printf("ticket_key_callback: create new session\n"); - tlsp->resumption |= RESUME_CLIENT_REQUESTED; +/* 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 (RAND_bytes(iv, EVP_MAX_IV_LENGTH) <= 0) - return -1; /* insufficient random */ +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 (!(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)); +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. - /*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); +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? */ - DEBUG(D_tls) debug_printf("ticket created\n"); - return 1; - } -else +if ((i = OCSP_basic_verify(basic_response, sk, NULL, verify_flags)) < 0) { - 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(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; + ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring)); + debug_printf("OCSP response verify failure: %s\n", US ssl_errstring); } - - HMAC_Init_ex(hctx, key->hmac_key, sizeof(key->hmac_key), - key->hmac_hash, NULL); - EVP_DecryptInit_ex(ctx, key->aes_cipher, NULL, key->aes_key, iv); - - DEBUG(D_tls) debug_printf("ticket usable, STEK expire " TIME_T_FMT "\n", key->expire - now); - - /* The ticket lifetime and renewal are the same as the STEK lifetime and - renewal, which is overenthusiastic. A factor of, say, 3x longer STEK would - be better. To do that we'd have to encode ticket lifetime in the name as - we don't yet see the restored session. Could check posthandshake for TLS1.3 - and trigger a new ticket then, but cannot do that for TLS1.2 */ - return key->renew < now ? 2 : 1; + goto bad; } -} -#endif - - - -/************************************************* -* Initialize for DH * -*************************************************/ -/* If dhparam is set, expand it, and load up the parameters for DH encryption. +/* 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()). -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 +I'm hoping to avoid reworking a bunch more of how we handle state here. -Returns: TRUE if OK (nothing to set up, or setup worked) +XXX that will change when we add support for (TLS1.3) whole-chain stapling */ -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 (!(single_response = OCSP_resp_get0(basic_response, 0))) { - 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); + DEBUG(D_tls) + debug_printf("Unable to get first response from OCSP basic response.\n"); + goto bad; } -if (!(dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL))) +status = OCSP_single_get0_status(single_response, &reason, &rev, &thisupd, &nextupd); +if (status != V_OCSP_CERTSTATUS_GOOD) { - BIO_free(bio); - tls_error(string_sprintf("Could not read tls_dhparams \"%s\"", dhexpanded), - host, NULL, errstr); - return FALSE; + 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; } -/* 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) +if (!OCSP_check_validity(thisupd, nextupd, EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX_AGE)) { - DEBUG(D_tls) - debug_printf("dhparams file %d bits, is > tls_dh_max_bits limit of %d\n", - dh_bitsize, tls_dh_max_bits); + DEBUG(D_tls) debug_printf("OCSP status invalid times.\n"); + goto bad; } -else + +supply_response: + /* Add the resp to the list used by tls_server_stapling_cb() */ { - 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); + 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; -DH_free(dh); -BIO_free(bio); +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; +} -return TRUE; + +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*/ -/************************************************* -* Initialize for ECDH * -*************************************************/ -/* Load parameters for ECDH encryption. +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; +} -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. +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; +} -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. + + +/* 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) - 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_ecdh(SSL_CTX * sctx, host_item * host, uschar ** errstr) +static int +tls_expand_session_files(SSL_CTX * sctx, exim_openssl_state_st * state, + uschar ** errstr) { -#ifdef OPENSSL_NO_ECDH -return TRUE; -#else +uschar * expanded; -EC_KEY * ecdh; -uschar * exp_curve; -int nid; -BOOL rv; +if (!state->certificate) + { + if (!state->is_server) /* client */ + return OK; + /* server */ + if (tls_install_selfsign(sctx, errstr) != OK) + return DEFER; + } +else + { + int err; -if (host) /* No ECDH setup for clients, only for servers */ - return TRUE; + 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; -# ifndef EXIM_HAVE_ECDH -DEBUG(D_tls) - debug_printf("No OpenSSL API to define ECDH parameters, skipping\n"); -return TRUE; -# else + if (!expand_check(state->certificate, US"tls_certificate", &expanded, errstr)) + return DEFER; -if (!expand_check(tls_eccurve, US"tls_eccurve", &exp_curve, errstr)) - return FALSE; -if (!exp_curve || !*exp_curve) - return TRUE; + 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; -/* "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 + 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 - } -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; - } + 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; + + if ( state->privatekey + && !expand_check(state->privatekey, US"tls_privatekey", &expanded, errstr)) + return DEFER; -if (!(ecdh = EC_KEY_new_by_curve_name(nid))) - { - tls_error(US"Unable to create ec curve", host, NULL, errstr); - return FALSE; + /* 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. */ + + if (expanded && *expanded) + if (state->is_server) + { + const uschar * file_list = expanded; + int sep = 0; + uschar * file; + + while (file = string_nextinlist(&file_list, &sep, NULL, 0)) + if ((err = tls_add_pkeyfile(sctx, 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; } -/* The "tmp" in the name here refers to setting a temporary key -not to the stability of the interface. */ +return OK; +} -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*/ + +/************************************************** +* One-time init credentials for server and client * +**************************************************/ + +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; } +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 (!(ctx = SSL_CTX_new(host ? SSLv23_client_method() : SSLv23_server_method()))) +#endif + return tls_error(US"SSL_CTX_new", host, NULL, errstr); -#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. +/* Set up the information callback, which outputs if debugging is at a suitable +level. */ -ASSUMES: single response, for single cert. +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 + } -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 +/* Automatically re-try reads/writes after renegotiation. */ +(void) SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY); +*ctxp = ctx; +return OK; +} -*/ -static void -ocsp_load_response(SSL_CTX * sctx, tls_ext_ctx_cb * cbinfo, - const uschar * filename, BOOL is_pem) +static unsigned +tls_server_creds_init(void) { -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; +SSL_CTX * ctx; +uschar * dummy_errstr; +unsigned lifetime = 0; -DEBUG(D_tls) - debug_printf("tls_ocsp_file (%s) '%s'\n", is_pem ? "PEM" : "DER", filename); +tls_openssl_init(); -if (!(bio = BIO_new_file(CS filename, "rb"))) +state_server.lib_state = null_tls_preload; + +if (lib_ctx_new(&ctx, NULL, &dummy_errstr) != OK) + return 0; +state_server.lib_state.lib_ctx = ctx; + +/* Preload DH params and EC curve */ + +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 (opt_unset_or_noexpand(tls_eccurve)) + { + 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; } -if (is_pem) +#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT) +/* If we can, preload the server-side cert, key and ocsp */ + +if ( opt_set_and_noexpand(tls_certificate) +# ifndef DISABLE_OCSP + && opt_unset_or_noexpand(tls_ocsp_file) +#endif + && opt_unset_or_noexpand(tls_privatekey)) { - uschar * data, * freep; - char * dummy; - long len; - if (!PEM_read_bio(bio, &dummy, &dummy, &data, &len)) + /* Set watches on the filenames. The implementation does de-duplication + so we can just blindly do them all. */ + + if ( tls_set_watch(tls_certificate, TRUE) +# ifndef DISABLE_OCSP + && tls_set_watch(tls_ocsp_file, TRUE) +#endif + && tls_set_watch(tls_privatekey, TRUE)) { - DEBUG(D_tls) debug_printf("Failed to read PEM file \"%s\"\n", - filename); - return; + state_server.certificate = tls_certificate; + state_server.privatekey = tls_privatekey; +#ifndef DISABLE_OCSP + state_server.u_ocsp.server.file = tls_ocsp_file; +#endif + + DEBUG(D_tls) debug_printf("TLS: preloading server certs\n"); + if (tls_expand_session_files(ctx, &state_server, &dummy_errstr) == OK) + state_server.lib_state.conn_certs = TRUE; + } + } +else if ( !tls_certificate && !tls_privatekey +# ifndef DISABLE_OCSP + && !tls_ocsp_file +#endif + ) + { /* Generate & preload a selfsigned cert. No files to watch. */ + if (tls_expand_session_files(ctx, &state_server, &dummy_errstr) == OK) + { + state_server.lib_state.conn_certs = TRUE; + lifetime = f.running_in_test_harness ? 2 : 60 * 60; /* 1 hour */ } -debug_printf("read pem file\n"); - freep = data; - resp = d2i_OCSP_RESPONSE(NULL, CUSS &data, len); - OPENSSL_free(freep); } else - resp = d2i_OCSP_RESPONSE_bio(bio, NULL); -BIO_free(bio); + DEBUG(D_tls) debug_printf("TLS: not preloading server certs\n"); -if (!resp) - { - DEBUG(D_tls) debug_printf("Error reading OCSP response.\n"); - return; - } -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; - } +/* 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 */ -#ifdef notdef +if ( opt_set_and_noexpand(tls_verify_certificates) + && opt_unset_or_noexpand(tls_crl)) { - BIO * bp = BIO_new_fp(debug_file, BIO_NOCLOSE); - OCSP_RESPONSE_print(bp, resp, 0); /* extreme debug: stapling content */ - BIO_free(bp); - } -#endif + /* Watch the default dir also as they are always included */ -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; + 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"); + + 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 */ -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 ciphers control string */ -/* 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_require_ciphers)) + { + 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; +} -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. -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. -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) - { - 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; - } +/* 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. */ -/* 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()). +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; -I'm hoping to avoid reworking a bunch more of how we handle state here. +tls_openssl_init(); -XXX that will change when we add support for (TLS1.3) whole-chain stapling -*/ +ob->tls_preload = null_tls_preload; +if (lib_ctx_new(&ctx, dummy_host, &dummy_errstr) != OK) + return; +ob->tls_preload.lib_ctx = ctx; -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; - } +tpt_dummy_state.lib_state = ob->tls_preload; -status = OCSP_single_get0_status(single_response, &reason, &rev, &thisupd, &nextupd); -if (status != V_OCSP_CERTSTATUS_GOOD) +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) - { - 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; - } + +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) + ) + { + 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; } +/* Callback for session tickets, on server */ +static int +ticket_key_callback(SSL * ssl, uschar key_name[16], + uschar * iv, EVP_CIPHER_CTX * c_ctx, HMAC_CTX * hctx, int enc) +{ +tls_support * tlsp = state_server.tlsp; +exim_stek * key; -/************************************************* -* 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. +if (enc) + { + DEBUG(D_tls) debug_printf("ticket_key_callback: create new session\n"); + tlsp->resumption |= RESUME_CLIENT_REQUESTED; -Arguments: - sctx the SSL_CTX* to update - cbinfo various parts of session state - errstr error string pointer + if (RAND_bytes(iv, EVP_MAX_IV_LENGTH) <= 0) + return -1; /* insufficient random */ -Returns: OK/DEFER/FAIL -*/ + 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)); -static int -tls_expand_session_files(SSL_CTX * sctx, tls_ext_ctx_cb * cbinfo, - uschar ** errstr) -{ -uschar * expanded; + /*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->certificate) - { - if (!cbinfo->is_server) /* client */ - return OK; - /* server */ - if (tls_install_selfsign(sctx, errstr) != OK) - return DEFER; + DEBUG(D_tls) debug_printf("ticket created\n"); + return 1; } else { - 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; + time_t now = time(NULL); - if (!expand_check(cbinfo->certificate, US"tls_certificate", &expanded, errstr)) - return DEFER; + DEBUG(D_tls) debug_printf("ticket_key_callback: retrieve session\n"); + tlsp->resumption |= RESUME_CLIENT_SUGGESTED; - if (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; -#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; - - if ( cbinfo->u_ocsp.server.file_expanded && olist - && (Ustrcmp(olist, cbinfo->u_ocsp.server.file_expanded) == 0)) - { - DEBUG(D_tls) debug_printf(" - value unchanged, using existing values\n"); - olist = NULL; - } - else - { - ocsp_free_response_list(cbinfo); - cbinfo->u_ocsp.server.file_expanded = olist; - } -#endif - - while (file = string_nextinlist(&file_list, &sep, NULL, 0)) - { - if ((err = tls_add_certfile(sctx, cbinfo, file, errstr))) - return err; - -#ifndef DISABLE_OCSP - if (olist) - if ((ofile = string_nextinlist(&olist, &osep, NULL, 0))) - { - 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; - - if ( cbinfo->privatekey - && !expand_check(cbinfo->privatekey, US"tls_privatekey", &expanded, errstr)) - return DEFER; + 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; + } - /* 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. */ + 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); - if (expanded && *expanded) - if (cbinfo->is_server) - { - const uschar * file_list = expanded; - int sep = 0; - uschar * file; + DEBUG(D_tls) debug_printf("ticket usable, STEK expire " TIME_T_FMT "\n", key->expire - now); - while (file = string_nextinlist(&file_list, &sep, NULL, 0)) - if ((err = tls_add_pkeyfile(sctx, cbinfo, file, errstr))) - return err; - } - else /* would there ever be a need for multiple client certs? */ - if ((err = tls_add_pkeyfile(sctx, cbinfo, expanded, errstr))) - return err; + /* 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; } - -return OK; } +#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 +2051,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 +2074,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"); @@ -1763,6 +2135,53 @@ bad: return SSL_TLSEXT_ERR_ALERT_FATAL; +#ifdef EXIM_HAVE_ALPN +/************************************************* +* Callback to handle ALPN * +*************************************************/ + +/* SSL_CTX_set_alpn_select_cb() */ +/* Called on server when client offers ALPN, after the SNI callback. +If set and not e?smtp then we dump the connection */ + +static int +tls_server_alpn_cb(SSL *ssl, const uschar ** out, uschar * outlen, + const uschar * in, unsigned int inlen, void * arg) +{ +const exim_openssl_state_st * state = arg; + +DEBUG(D_tls) + { + debug_printf("Received TLS ALPN offer:"); + for (int pos = 0, siz; pos < inlen; pos += siz+1) + { + siz = in[pos]; + if (pos + 1 + siz > inlen) siz = inlen - pos - 1; + debug_printf(" '%.*s'", siz, in + pos + 1); + } + debug_printf("\n"); + } + +/* Look for an acceptable ALPN */ +if ( inlen > 1 /* at least one name */ + && in[0]+1 == inlen /* filling the vector, so exactly one name */ + && ( Ustrncmp(in+1, "smtp", in[0]) == 0 + || Ustrncmp(in+1, "esmtp", in[0]) == 0 + ) ) + { + *out = in; /* we checked for exactly one, so can just point to it */ + *outlen = inlen; + return SSL_TLSEXT_ERR_OK; /* use ALPN */ + } + +/* Reject unacceptable ALPN */ +/* This will be fatal to the TLS conn; would be nice to kill TCP also */ +return SSL_TLSEXT_ERR_ALERT_FATAL; +} +#endif /* EXIM_HAVE_ALPN */ + + + #ifndef DISABLE_OCSP /************************************************* @@ -1780,8 +2199,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 +2274,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 +2292,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 +2452,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 +2517,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,8 +2535,24 @@ 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; + } #ifndef DISABLE_TLS_RESUME tlsp->resumption = RESUME_SUPPORTED; @@ -2212,21 +2591,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 +2639,36 @@ 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); +# ifdef EXIM_HAVE_ALPN + SSL_CTX_set_alpn_select_cb(ctx, tls_server_alpn_cb, state); +# endif } # 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 +2681,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; } @@ -2407,18 +2808,23 @@ if (tlsp->peercert) /* Load certs from file, return TRUE on success */ static BOOL -chain_from_pem_file(const uschar * file, STACK_OF(X509) * verify_stack) +chain_from_pem_file(const uschar * file, STACK_OF(X509) ** vp) { BIO * bp; +STACK_OF(X509) * verify_stack = *vp; X509 * x; -while (sk_X509_num(verify_stack) > 0) - X509_free(sk_X509_pop(verify_stack)); +if (verify_stack) + while (sk_X509_num(verify_stack) > 0) + X509_free(sk_X509_pop(verify_stack)); +else + verify_stack = sk_X509_new_null(); if (!(bp = BIO_new_file(CS file, "r"))) return FALSE; -while ((x = PEM_read_bio_X509(bp, NULL, 0, NULL))) +for (X509 * x; x = PEM_read_bio_X509(bp, NULL, 0, NULL); ) sk_X509_push(verify_stack, x); BIO_free(bp); +*vp = verify_stack; return TRUE; } #endif @@ -2430,20 +2836,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 +2862,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; @@ -2476,6 +2879,13 @@ if (expcerts && *expcerts) { file = NULL; dir = expcerts; } else { + STACK_OF(X509) * verify_stack = +#ifndef DISABLE_OCSP + !host ? state_server.verify_stack : +#endif + NULL; + STACK_OF(X509) ** vp = &verify_stack; + file = expcerts; dir = NULL; #ifndef DISABLE_OCSP /* In the server if we will be offering an OCSP proof, load chain from @@ -2484,11 +2894,10 @@ if (expcerts && *expcerts) /*XXX Glitch! The file here is tls_verify_certs: the chain for verifying the client cert. This is inconsistent with the need to verify the OCSP proof of the server cert. */ - if ( !host && statbuf.st_size > 0 - && server_static_cbinfo->u_ocsp.server.file - && !chain_from_pem_file(file, server_static_cbinfo->verify_stack) + && state_server.u_ocsp.server.file + && !chain_from_pem_file(file, vp) ) { log_write(0, LOG_MAIN|LOG_PANIC, @@ -2505,7 +2914,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 +2930,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 +2994,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 +3004,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 +3018,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 +3039,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 +3056,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 +3078,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: ; #ifndef DISABLE_TLS_RESUME -SSL_CTX_set_tlsext_ticket_key_cb(server_ctx, ticket_key_callback); +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 # ifndef DISABLE_TLS_RESUME -SSL_CTX_set_num_tickets(server_ctx, tls_in.host_resumable ? 1 : 0); +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 +3140,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,21 +3150,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: @@ -2755,8 +3174,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; @@ -2764,15 +3183,15 @@ if (rc <= 0) /* Handle genuine errors */ case SSL_ERROR_SSL: { - uschar * s = US"SSL_accept"; + uschar * s = NULL; int r = ERR_GET_REASON(ERR_peek_error()); if ( r == SSL_R_WRONG_VERSION_NUMBER #ifdef SSL_R_VERSION_TOO_LOW || 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)); - (void) tls_error(s, NULL, sigalrm_seen ? US"timed out" : NULL, errstr); + s = string_sprintf("%s (%s)", s, SSL_get_version(ssl)); + (void) tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : s, errstr); return FAIL; } @@ -2800,7 +3219,7 @@ ERR_clear_error(); /* Even success can leave errors in the stack. Seen with anon-authentication ciphersuite negotiated. */ #ifndef DISABLE_TLS_RESUME -if (SSL_session_reused(server_ssl)) +if (SSL_session_reused(ssl)) { tls_in.resumption |= RESUME_USED; DEBUG(D_tls) debug_printf("Session reused\n"); @@ -2811,31 +3230,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)); @@ -2845,7 +3264,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; } @@ -2853,10 +3272,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; @@ -2890,7 +3309,7 @@ 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; @@ -2914,21 +3333,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->certname, NULL); #else host->certname; #endif DEBUG(D_tls) debug_printf("Cert hostname to check: \"%s\"\n", - cbinfo->verify_cert_hostnames); + state->verify_cert_hostnames); } return OK; } @@ -3025,29 +3456,35 @@ if (tlsp->host_resumable) debug_printf("decoding session: %s\n", ssl_errstring); } } -#ifdef EXIM_HAVE_SESSION_TICKET - else if ( SSL_SESSION_get_ticket_lifetime_hint(ss) + dt->time_stamp - < time(NULL)) + else { - DEBUG(D_tls) debug_printf("session expired\n"); - dbfn_delete(dbm_file, key); - } + unsigned long lifetime = +#ifdef EXIM_HAVE_SESSION_TICKET + SSL_SESSION_get_ticket_lifetime_hint(ss); +#else /* Use, fairly arbitrilarily, what we as server would */ + f.running_in_test_harness ? 6 : ssl_session_timeout; #endif - else if (!SSL_set_session(ssl, ss)) - { - DEBUG(D_tls) + if (lifetime + dt->time_stamp < time(NULL)) { - ERR_error_string_n(ERR_get_error(), - ssl_errstring, sizeof(ssl_errstring)); - debug_printf("applying session to ssl: %s\n", ssl_errstring); + DEBUG(D_tls) debug_printf("session expired\n"); + dbfn_delete(dbm_file, key); + } + else if (!SSL_set_session(ssl, ss)) + { + DEBUG(D_tls) + { + ERR_error_string_n(ERR_get_error(), + ssl_errstring, sizeof(ssl_errstring)); + debug_printf("applying session to ssl: %s\n", ssl_errstring); + } + } + else + { + DEBUG(D_tls) debug_printf("good session\n"); + tlsp->resumption |= RESUME_CLIENT_SUGGESTED; + tlsp->verify_override = dt->verify_override; + tlsp->ocsp = dt->ocsp; } - } - else - { - DEBUG(D_tls) debug_printf("good session\n"); - tlsp->resumption |= RESUME_CLIENT_SUGGESTED; - tlsp->verify_override = dt->verify_override; - tlsp->ocsp = dt->ocsp; } } else @@ -3063,7 +3500,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"); @@ -3129,12 +3566,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; @@ -3231,14 +3668,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; @@ -3299,9 +3737,9 @@ 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; #ifndef DISABLE_TLS_RESUME tls_client_ctx_resume_prehandshake(exim_client_ctx, tlsp, ob, host); @@ -3369,7 +3807,7 @@ 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 @@ -3381,7 +3819,7 @@ if (!tls_client_ssl_resume_prehandshake(exim_client_ctx->ssl, tlsp, host, #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. */ @@ -3461,17 +3899,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 */ @@ -3495,8 +3934,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; @@ -3574,7 +4013,7 @@ return buf; void -tls_get_cache() +tls_get_cache(void) { #ifndef DISABLE_DKIM int n = ssl_xfer_buffer_hwm - ssl_xfer_buffer_lwm; @@ -3587,7 +4026,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; } @@ -3610,7 +4050,8 @@ 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; @@ -3660,7 +4101,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; @@ -3683,16 +4125,12 @@ 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) { @@ -3750,6 +4188,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 * *************************************************/ @@ -3760,7 +4224,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 @@ -3769,23 +4234,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 */ @@ -3802,8 +4268,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; @@ -3818,9 +4284,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; } @@ -3862,28 +4326,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; }