X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/97cfe3942f67200f77f6ae9b302409075e4e5792..d95313eb794f13bf43af3f0cbcc31491a5091fd2:/src/src/tls-openssl.c diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index 0d653a445..ab3b636a3 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -3,7 +3,7 @@ *************************************************/ /* Copyright (c) University of Cambridge 1995 - 2019 */ -/* Copyright (c) The Exim Maintainers 2020 */ +/* Copyright (c) The Exim Maintainers 2020 - 2021 */ /* See the file NOTICE for conditions of use and distribution. */ /* Portions Copyright (c) The OpenSSL Project 1999 */ @@ -80,6 +80,7 @@ change this guard and punt the issue for a while longer. */ # ifndef DISABLE_OCSP # define EXIM_HAVE_OCSP # endif +# define EXIM_HAVE_ALPN /* fail ret from hshake-cb is ignored by LibreSSL */ # else # define EXIM_NEED_OPENSSL_INIT # endif @@ -89,6 +90,10 @@ change this guard and punt the issue for a while longer. */ # endif #endif +#if LIBRESSL_VERSION_NUMBER >= 0x3040000fL +# define EXIM_HAVE_OPENSSL_CIPHER_GET_ID +#endif + #if !defined(LIBRESSL_VERSION_NUMBER) \ || LIBRESSL_VERSION_NUMBER >= 0x20010000L # if !defined(OPENSSL_NO_ECDH) @@ -227,12 +232,16 @@ static exim_openssl_option exim_openssl_options[] = { { US"no_tlsv1", SSL_OP_NO_TLSv1 }, #endif #ifdef SSL_OP_NO_TLSv1_1 -#if SSL_OP_NO_TLSv1_1 == 0x00000400L +# if OPENSSL_VERSION_NUMBER < 0x30000000L +# if SSL_OP_NO_TLSv1_1 == 0x00000400L /* Error in chosen value in 1.0.1a; see first item in CHANGES for 1.0.1b */ -#warning OpenSSL 1.0.1a uses a bad value for SSL_OP_NO_TLSv1_1, ignoring -#else +# warning OpenSSL 1.0.1a uses a bad value for SSL_OP_NO_TLSv1_1, ignoring +# define NO_SSL_OP_NO_TLSv1_1 +# endif +# endif +# ifndef NO_SSL_OP_NO_TLSv1_1 { US"no_tlsv1_1", SSL_OP_NO_TLSv1_1 }, -#endif +# endif #endif #ifdef SSL_OP_NO_TLSv1_2 { US"no_tlsv1_2", SSL_OP_NO_TLSv1_2 }, @@ -306,6 +315,9 @@ builtin_macro_create(US"_TLS_BAD_MULTICERT_IN_OURCERT"); builtin_macro_create(US"_HAVE_TLS_OCSP"); builtin_macro_create(US"_HAVE_TLS_OCSP_LIST"); # endif +# ifdef EXIM_HAVE_ALPN +builtin_macro_create(US"_HAVE_TLS_ALPN"); +# endif } #else @@ -358,6 +370,9 @@ typedef struct { #ifdef EXIM_HAVE_OPENSSL_TLSEXT static SSL_CTX *server_sni = NULL; #endif +#ifdef EXIM_HAVE_ALPN +static BOOL server_seen_alpn = FALSE; +#endif static char ssl_errstring[256]; @@ -418,9 +433,6 @@ 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 @@ -537,23 +549,27 @@ EVP_add_digest(EVP_sha256()); *************************************************/ /* If dhparam is set, expand it, and load up the parameters for DH encryption. +Server only. 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) +init_dh(SSL_CTX * sctx, uschar * dhparam, uschar ** errstr) { -BIO *bio; -DH *dh; -uschar *dhexpanded; -const char *pem; +BIO * bio; +#if OPENSSL_VERSION_NUMBER < 0x30000000L +DH * dh; +#else +EVP_PKEY * pkey; +#endif +uschar * dhexpanded; +const char * pem; int dh_bitsize; if (!expand_check(dhparam, US"tls_dhparam", &dhexpanded, errstr)) @@ -566,7 +582,7 @@ 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); + NULL, US strerror(errno), errstr); return FALSE; } } @@ -581,53 +597,80 @@ else if (!(pem = std_dh_prime_named(dhexpanded))) { tls_error(string_sprintf("Unknown standard DH prime \"%s\"", dhexpanded), - host, US strerror(errno), errstr); + NULL, US strerror(errno), errstr); return FALSE; } bio = BIO_new_mem_buf(CS pem, -1); } -if (!(dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL))) +if (!( +#if OPENSSL_VERSION_NUMBER < 0x30000000L + dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL) +#else + pkey = PEM_read_bio_Parameters_ex(bio, NULL, NULL, NULL) +#endif + ) ) { BIO_free(bio); tls_error(string_sprintf("Could not read tls_dhparams \"%s\"", dhexpanded), - host, NULL, errstr); + NULL, 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 +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. */ + +#if OPENSSL_VERSION_NUMBER < 0x30000000L +# 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 */ +This predates OpenSSL_1_1_0 (before a, b, ...) so is in all 1.1.0 */ dh_bitsize = DH_bits(dh); -#else +# else dh_bitsize = 8 * DH_size(dh); +# endif +#else /* 3.0.0 + */ +dh_bitsize = EVP_PKEY_get_bits(pkey); #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) +/* 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. Likewise for a failing attempt to set one. */ + +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); + if ( +#if OPENSSL_VERSION_NUMBER < 0x30000000L + SSL_CTX_set_tmp_dh(sctx, dh) +#else + SSL_CTX_set0_tmp_dh_pkey(sctx, pkey) +#endif + == 0) + { + ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring)); + log_write(0, LOG_MAIN|LOG_PANIC, "TLS error (D-H param setting '%s'): %s", + dhexpanded ? dhexpanded : US"default", ssl_errstring); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + /* EVP_PKEY_free(pkey); crashes */ +#endif + } + else + DEBUG(D_tls) + debug_printf("Diffie-Hellman initialized from %s with %d-bit prime\n", + dhexpanded ? dhexpanded : US"default", dh_bitsize); } 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); - } + debug_printf("dhparams '%s' %d bits, is > tls_dh_max_bits limit of %d\n", + dhexpanded ? dhexpanded : US"default", dh_bitsize, tls_dh_max_bits); +#if OPENSSL_VERSION_NUMBER < 0x30000000L DH_free(dh); -BIO_free(bio); +#endif +/* The EVP_PKEY ownership stays with the ctx; do not free it */ +BIO_free(bio); return TRUE; } @@ -638,7 +681,7 @@ return TRUE; * Initialize for ECDH * *************************************************/ -/* Load parameters for ECDH encryption. +/* Load parameters for ECDH encryption. Server only. 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 @@ -656,27 +699,22 @@ 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) +init_ecdh(SSL_CTX * sctx, 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"); @@ -723,25 +761,38 @@ if ( (nid = OBJ_sn2nid (CCS exp_curve)) == NID_undef ) { tls_error(string_sprintf("Unknown curve name tls_eccurve '%s'", exp_curve), - host, NULL, errstr); + NULL, 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; - } +# if OPENSSL_VERSION_NUMBER < 0x30000000L + { + EC_KEY * ecdh; + if (!(ecdh = EC_KEY_new_by_curve_name(nid))) + { + tls_error(US"Unable to create ec curve", NULL, 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), NULL, NULL, errstr); + else + DEBUG(D_tls) debug_printf("ECDH: enabled '%s' curve\n", exp_curve); + EC_KEY_free(ecdh); + } -/* The "tmp" in the name here refers to setting a temporary key -not to the stability of the interface. */ +#else /* v 3.0.0 + */ -if ((rv = SSL_CTX_set_tmp_ecdh(sctx, ecdh) == 0)) - tls_error(string_sprintf("Error enabling '%s' curve", exp_curve), host, NULL, errstr); +if ((rv = SSL_CTX_set1_groups(sctx, &nid, 1)) == 0) + tls_error(string_sprintf("Error enabling '%s' group", exp_curve), NULL, NULL, errstr); else - DEBUG(D_tls) debug_printf("ECDH: enabled '%s' curve\n", exp_curve); + DEBUG(D_tls) debug_printf("ECDH: enabled '%s' group\n", exp_curve); + +#endif -EC_KEY_free(ecdh); return !rv; # endif /*EXIM_HAVE_ECDH*/ @@ -754,6 +805,7 @@ return !rv; * Expand key and cert file specs * *************************************************/ +#if OPENSSL_VERSION_NUMBER < 0x30000000L /* Arguments: s SSL connection (not used) @@ -773,14 +825,14 @@ BIGNUM *bn = BN_new(); DEBUG(D_tls) debug_printf("Generating %d bit RSA key...\n", keylength); -#ifdef EXIM_HAVE_RSA_GENKEY_EX +# ifdef EXIM_HAVE_RSA_GENKEY_EX if ( !BN_set_word(bn, (unsigned long)RSA_F4) || !(rsa_key = RSA_new()) || !RSA_generate_key_ex(rsa_key, keylength, bn, NULL) ) -#else +# else if (!(rsa_key = RSA_generate_key(keylength, RSA_F4, NULL, NULL))) -#endif +# endif { ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring)); @@ -790,6 +842,7 @@ if (!(rsa_key = RSA_generate_key(keylength, RSA_F4, NULL, NULL))) } return rsa_key; } +#endif /* pre-3.0.0 */ @@ -803,7 +856,6 @@ tls_install_selfsign(SSL_CTX * sctx, uschar ** errstr) { X509 * x509 = NULL; EVP_PKEY * pkey; -RSA * rsa; X509_NAME * name; uschar * where; @@ -817,12 +869,19 @@ if (!(x509 = X509_new())) goto err; where = US"generating pkey"; -if (!(rsa = rsa_callback(NULL, 0, 2048))) - goto err; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + { + RSA * rsa; + if (!(rsa = rsa_callback(NULL, 0, 2048))) + goto err; -where = US"assigning pkey"; -if (!EVP_PKEY_assign_RSA(pkey, rsa)) - goto err; + where = US"assigning pkey"; + if (!EVP_PKEY_assign_RSA(pkey, rsa)) + goto err; + } +#else +pkey = EVP_RSA_gen(2048); +#endif X509_set_version(x509, 2); /* N+1 - version 3 */ ASN1_INTEGER_set(X509_get_serialNumber(x509), 1); @@ -903,10 +962,12 @@ DEBUG(D_tls) 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)); + { + 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) @@ -948,7 +1009,7 @@ if (ev) old_cert = tlsp->peercert; tlsp->peercert = X509_dup(cert); /* NB we do not bother setting peerdn */ - if ((yield = event_raise(ev, US"tls:cert", string_sprintf("%d", depth)))) + if ((yield = event_raise(ev, US"tls:cert", string_sprintf("%d", depth), &errno))) { log_write(0, LOG_MAIN, "[%s] %s verify denied by event-action: " "depth=%d cert=%s: %s", @@ -1247,10 +1308,14 @@ int status, reason, i; DEBUG(D_tls) debug_printf("tls_ocsp_file (%s) '%s'\n", is_pem ? "PEM" : "DER", filename); +if (!filename || !*filename) return; + +ERR_clear_error(); if (!(bio = BIO_new_file(CS filename, "rb"))) { - DEBUG(D_tls) debug_printf("Failed to open OCSP response file \"%s\"\n", - filename); + log_write(0, LOG_MAIN|LOG_PANIC, + "Failed to open OCSP response file \"%s\": %.100s", + filename, ERR_reason_error_string(ERR_get_error())); return; } @@ -1261,8 +1326,8 @@ if (is_pem) long len; if (!PEM_read_bio(bio, &dummy, &dummy, &data, &len)) { - DEBUG(D_tls) debug_printf("Failed to read PEM file \"%s\"\n", - filename); + 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; @@ -1275,7 +1340,8 @@ BIO_free(bio); if (!resp) { - DEBUG(D_tls) debug_printf("Error reading OCSP response.\n"); + log_write(0, LOG_MAIN|LOG_PANIC, "Error reading OCSP response from \"%s\": %s", + filename, ERR_reason_error_string(ERR_get_error())); return; } @@ -1380,7 +1446,7 @@ supply_response: ocsp_resplist ** op = &state->u_ocsp.server.olist, * oentry; while (oentry = *op) op = &oentry->next; - *op = oentry = store_get(sizeof(ocsp_resplist), FALSE); + *op = oentry = store_get(sizeof(ocsp_resplist), GET_UNTAINTED); oentry->next = NULL; oentry->resp = resp; } @@ -1576,11 +1642,24 @@ return OK; * One-time init credentials for server and client * **************************************************/ +static void +normalise_ciphers(uschar ** ciphers, const uschar * pre_expansion_ciphers) +{ +uschar * s = *ciphers; + +if (!s || !Ustrchr(s, '_')) return; /* no change needed */ + +if (s == pre_expansion_ciphers) + s = string_copy(s); /* get writable copy */ + +for (uschar * t = s; *t; t++) if (*t == '_') *t = '-'; +*ciphers = s; +} + static int server_load_ciphers(SSL_CTX * ctx, exim_openssl_state_st * state, uschar * ciphers, uschar ** errstr) { -for (uschar * s = ciphers; *s; s++ ) if (*s == '_') *s = '-'; DEBUG(D_tls) debug_printf("required ciphers: %s\n", ciphers); if (!SSL_CTX_set_cipher_list(ctx, CS ciphers)) return tls_error(US"SSL_CTX_set_cipher_list", NULL, NULL, errstr); @@ -1643,15 +1722,19 @@ state_server.lib_state.lib_ctx = ctx; if (opt_unset_or_noexpand(tls_dhparam)) { DEBUG(D_tls) debug_printf("TLS: preloading DH params for server\n"); - if (init_dh(ctx, tls_dhparam, NULL, &dummy_errstr)) + if (init_dh(ctx, tls_dhparam, &dummy_errstr)) state_server.lib_state.dh = TRUE; } +else + DEBUG(D_tls) debug_printf("TLS: not preloading DH params for server\n"); if (opt_unset_or_noexpand(tls_eccurve)) { DEBUG(D_tls) debug_printf("TLS: preloading ECDH curve for server\n"); - if (init_ecdh(ctx, NULL, &dummy_errstr)) + if (init_ecdh(ctx, &dummy_errstr)) state_server.lib_state.ecdh = TRUE; } +else + DEBUG(D_tls) debug_printf("TLS: not preloading ECDH curve for server\n"); #if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT) /* If we can, preload the server-side cert, key and ocsp */ @@ -1728,6 +1811,7 @@ else if (opt_set_and_noexpand(tls_require_ciphers)) { DEBUG(D_tls) debug_printf("TLS: preloading cipher list for server\n"); + normalise_ciphers(&tls_require_ciphers, tls_require_ciphers); if (server_load_ciphers(ctx, &state_server, tls_require_ciphers, &dummy_errstr) == OK) state_server.lib_state.pri_string = TRUE; @@ -1763,19 +1847,6 @@ ob->tls_preload.lib_ctx = ctx; tpt_dummy_state.lib_state = ob->tls_preload; -if (opt_unset_or_noexpand(tls_dhparam)) - { - DEBUG(D_tls) debug_printf("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 (opt_unset_or_noexpand(tls_eccurve)) - { - 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; - } - #if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT) if ( opt_set_and_noexpand(ob->tls_certificate) && opt_unset_or_noexpand(ob->tls_privatekey)) @@ -1896,7 +1967,11 @@ typedef struct { /* Session ticket encryption key */ const EVP_CIPHER * aes_cipher; uschar aes_key[32]; /* size needed depends on cipher. aes_128 implies 128/8 = 16? */ +# if OPENSSL_VERSION_NUMBER < 0x30000000L const EVP_MD * hmac_hash; +# else + const uschar * hmac_hashname; +# endif uschar hmac_key[16]; time_t renew; time_t expire; @@ -1925,7 +2000,11 @@ if (RAND_bytes(exim_tk.name+1, sizeof(exim_tk.name)-1) <= 0) return; exim_tk.name[0] = 'E'; exim_tk.aes_cipher = EVP_aes_256_cbc(); +# if OPENSSL_VERSION_NUMBER < 0x30000000L exim_tk.hmac_hash = EVP_sha256(); +# else +exim_tk.hmac_hashname = US "sha256"; +# endif exim_tk.expire = t + ssl_session_timeout; exim_tk.renew = t + ssl_session_timeout/2; } @@ -1945,10 +2024,49 @@ return memcmp(name, exim_tk.name, sizeof(exim_tk.name)) == 0 ? &exim_tk : NULL; } + +static int +tk_hmac_init( +# if OPENSSL_VERSION_NUMBER < 0x30000000L + HMAC_CTX * hctx, +#else + EVP_MAC_CTX * hctx, +#endif + exim_stek * key + ) +{ +/*XXX will want these dependent on the ssl session strength */ +# if OPENSSL_VERSION_NUMBER < 0x30000000L + HMAC_Init_ex(hctx, key->hmac_key, sizeof(key->hmac_key), + key->hmac_hash, NULL); +#else + { + OSSL_PARAM params[3]; + uschar * hk = string_copy(key->hmac_hashname); /* need nonconst */ + params[0] = OSSL_PARAM_construct_octet_string("key", key->hmac_key, sizeof(key->hmac_key)); + params[1] = OSSL_PARAM_construct_utf8_string("digest", CS hk, 0); + params[2] = OSSL_PARAM_construct_end(); + if (EVP_MAC_CTX_set_params(hctx, params) == 0) + { + DEBUG(D_tls) debug_printf("EVP_MAC_CTX_set_params: %s\n", + ERR_reason_error_string(ERR_get_error())); + return 0; /* error in mac initialisation */ + } +} +#endif +return 1; +} + /* Callback for session tickets, on server */ static int ticket_key_callback(SSL * ssl, uschar key_name[16], - uschar * iv, EVP_CIPHER_CTX * c_ctx, HMAC_CTX * hctx, int enc) + uschar * iv, EVP_CIPHER_CTX * c_ctx, +# if OPENSSL_VERSION_NUMBER < 0x30000000L + HMAC_CTX * hctx, +#else + EVP_MAC_CTX * hctx, +#endif + int enc) { tls_support * tlsp = state_server.tlsp; exim_stek * key; @@ -1966,9 +2084,7 @@ if (enc) memcpy(key_name, key->name, 16); DEBUG(D_tls) debug_printf("STEK expire " TIME_T_FMT "\n", key->expire - time(NULL)); - /*XXX will want these dependent on the ssl session strength */ - HMAC_Init_ex(hctx, key->hmac_key, sizeof(key->hmac_key), - key->hmac_hash, NULL); + if (tk_hmac_init(hctx, key) == 0) return 0; EVP_EncryptInit_ex(c_ctx, key->aes_cipher, NULL, key->aes_key, iv); DEBUG(D_tls) debug_printf("ticket created\n"); @@ -1991,8 +2107,7 @@ else return 0; } - HMAC_Init_ex(hctx, key->hmac_key, sizeof(key->hmac_key), - key->hmac_hash, NULL); + if (tk_hmac_init(hctx, key) == 0) return 0; EVP_DecryptInit_ex(c_ctx, key->aes_cipher, NULL, key->aes_key, iv); DEBUG(D_tls) debug_printf("ticket usable, STEK expire " TIME_T_FMT "\n", key->expire - now); @@ -2005,7 +2120,7 @@ else return key->renew < now ? 2 : 1; } } -#endif +#endif /* !DISABLE_TLS_RESUME */ @@ -2059,7 +2174,7 @@ DEBUG(D_tls) debug_printf("Received TLS SNI \"%s\"%s\n", servername, /* Make the extension value available for expansion */ store_pool = POOL_PERM; -tls_in.sni = string_copy_taint(US servername, TRUE); +tls_in.sni = string_copy_taint(US servername, GET_TAINTED); store_pool = old_pool; if (!reexpand_tls_files_for_sni) @@ -2085,8 +2200,8 @@ already exists. Might even need this selfsame callback, for reneg? */ SSL_CTX_set_tlsext_servername_arg(server_sni, state); } -if ( !init_dh(server_sni, state->dhparam, NULL, &dummy_errstr) - || !init_ecdh(server_sni, NULL, &dummy_errstr) +if ( !init_dh(server_sni, state->dhparam, &dummy_errstr) + || !init_ecdh(server_sni, &dummy_errstr) ) goto bad; @@ -2130,6 +2245,61 @@ bad: return SSL_TLSEXT_ERR_ALERT_FATAL; +#ifdef EXIM_HAVE_ALPN +/************************************************* +* Callback to handle ALPN * +*************************************************/ + +/* Called on server if tls_alpn nonblank after expansion, +when client offers ALPN, after the SNI callback. +If set and not matching the list 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) +{ +server_seen_alpn = TRUE; +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(". Our list: '%s'\n", tls_alpn); + } + +/* Look for an acceptable ALPN */ + +if ( inlen > 1 /* at least one name */ + && in[0]+1 == inlen /* filling the vector, so exactly one name */ + ) + { + const uschar * list = tls_alpn; + int sep = 0; + for (uschar * name; name = string_nextinlist(&list, &sep, NULL, 0); ) + if (Ustrncmp(in+1, name, in[0]) == 0) + { + *out = in+1; /* we checked for exactly one, so can just point to it */ + *outlen = inlen; + return SSL_TLSEXT_ERR_OK; /* use ALPN */ + } + } + +/* More than one name from clilent, or name did not match our list. */ + +/* This will be fatal to the TLS conn; would be nice to kill TCP also. +Maybe as an option in future; for now leave control to the config (must-tls). */ + +DEBUG(D_tls) debug_printf("TLS ALPN rejected\n"); +return SSL_TLSEXT_ERR_ALERT_FATAL; +} +#endif /* EXIM_HAVE_ALPN */ + + + #ifndef DISABLE_OCSP /************************************************* @@ -2165,9 +2335,6 @@ if (!olist) const X509 * cert_sent = SSL_get_certificate(s); const ASN1_INTEGER * cert_serial = X509_get0_serialNumber(cert_sent); const BIGNUM * cert_bn = ASN1_INTEGER_to_BN(cert_serial, NULL); - const X509_NAME * cert_issuer = X509_get_issuer_name(cert_sent); - uschar * chash; - uint chash_len; for (; olist; olist = olist->next) { @@ -2250,8 +2417,12 @@ int i; DEBUG(D_tls) debug_printf("Received TLS status callback (OCSP stapling):\n"); len = SSL_get_tlsext_status_ocsp_resp(s, &p); if(!p) - { - /* Expect this when we requested ocsp but got none */ + { /* Expect this when we requested ocsp but got none */ + if (SSL_session_reused(s) && tls_out.ocsp == OCSP_VFIED) + { + DEBUG(D_tls) debug_printf(" null, but resumed; ocsp vfy stored with session is good\n"); + return 1; + } if (cbinfo->u_ocsp.client.verify_required && LOGGING(tls_cipher)) log_write(0, LOG_MAIN, "Required TLS certificate status not received"); else @@ -2293,7 +2464,7 @@ if (!(bs = OCSP_response_get1_basic(rsp))) STACK_OF(OCSP_SINGLERESP) * sresp = bs->tbsResponseData->responses; #endif - DEBUG(D_tls) bp = BIO_new_fp(debug_file, BIO_NOCLOSE); + DEBUG(D_tls) bp = BIO_new(BIO_s_mem()); /*OCSP_RESPONSE_print(bp, rsp, 0); extreme debug: stapling content */ @@ -2308,9 +2479,12 @@ if (!(bs = OCSP_response_get1_basic(rsp))) if (LOGGING(tls_cipher)) log_write(0, LOG_MAIN, "Received TLS cert status response, itself unverifiable: %s", ERR_reason_error_string(ERR_peek_error())); - BIO_printf(bp, "OCSP response verify failure\n"); - ERR_print_errors(bp); - OCSP_RESPONSE_print(bp, rsp, 0); + DEBUG(D_tls) + { + BIO_printf(bp, "OCSP response verify failure\n"); + ERR_print_errors(bp); + OCSP_RESPONSE_print(bp, rsp, 0); + } goto failed; } else @@ -2348,14 +2522,17 @@ if (!(bs = OCSP_response_get1_basic(rsp))) status = OCSP_single_get0_status(single, &reason, &rev, &thisupd, &nextupd); - DEBUG(D_tls) time_print(bp, "This OCSP Update", thisupd); - DEBUG(D_tls) if(nextupd) time_print(bp, "Next OCSP Update", nextupd); + DEBUG(D_tls) + { + time_print(bp, "This OCSP Update", thisupd); + if (nextupd) time_print(bp, "Next OCSP Update", nextupd); + } if (!OCSP_check_validity(thisupd, nextupd, EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX_AGE)) { tls_out.ocsp = OCSP_FAILED; DEBUG(D_tls) ERR_print_errors(bp); - log_write(0, LOG_MAIN, "Server OSCP dates invalid"); + log_write(0, LOG_MAIN, "OCSP dates invalid"); goto failed; } @@ -2388,6 +2565,11 @@ if (!(bs = OCSP_response_get1_basic(rsp))) tls_out.ocsp = OCSP_FAILED; i = cbinfo->u_ocsp.client.verify_required ? 0 : 1; good: + { + uschar * s = NULL; + int len = (int) BIO_get_mem_data(bp, CSS &s); + if (len > 0) debug_printf("%.*s", len, s); + } BIO_free(bp); } @@ -2539,15 +2721,18 @@ 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 (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 (!host) + { + if (state->lib_state.dh) + { DEBUG(D_tls) debug_printf("TLS: DH params were preloaded\n"); } + else + if (!init_dh(ctx, state->dhparam, 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; + if (state->lib_state.ecdh) + { DEBUG(D_tls) debug_printf("TLS: ECDH curve was preloaded\n"); } + else + if (!init_ecdh(ctx, errstr)) return DEFER; + } /* Set up certificate and key (and perhaps OCSP info) */ @@ -2597,6 +2782,21 @@ if (!host) /* server */ tls_certificate */ SSL_CTX_set_tlsext_servername_callback(ctx, tls_servername_cb); SSL_CTX_set_tlsext_servername_arg(ctx, state); + +# ifdef EXIM_HAVE_ALPN + if (tls_alpn && *tls_alpn) + { + uschar * exp_alpn; + if ( expand_check(tls_alpn, US"tls_alpn", &exp_alpn, errstr) + && *exp_alpn && !isblank(*exp_alpn)) + { + tls_alpn = exp_alpn; /* subprocess so ok to overwrite */ + SSL_CTX_set_alpn_select_cb(ctx, tls_server_alpn_cb, state); + } + else + tls_alpn = NULL; + } +# endif } # ifndef DISABLE_OCSP else /* client */ @@ -2753,18 +2953,22 @@ 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; -X509 * x; +STACK_OF(X509) * verify_stack = *vp; -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 @@ -2819,6 +3023,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 @@ -2827,11 +3038,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 && state_server.u_ocsp.server.file - && !chain_from_pem_file(file, state_server.verify_stack) + && !chain_from_pem_file(file, vp) ) { log_write(0, LOG_MAIN|LOG_PANIC, @@ -2935,6 +3145,21 @@ return OK; +static void +tls_dump_keylog(SSL * ssl) +{ +#ifdef EXIM_HAVE_OPENSSL_KEYLOG + BIO * bp = BIO_new(BIO_s_mem()); + uschar * s = NULL; + int len; + SSL_SESSION_print_keylog(bp, SSL_get_session(ssl)); + len = (int) BIO_get_mem_data(bp, CSS &s); + if (len > 0) debug_printf("%.*s", len, s); + BIO_free(bp); +#endif +} + + /************************************************* * Start a TLS session in a server * *************************************************/ @@ -2997,9 +3222,12 @@ else if (!expand_check(tls_require_ciphers, US"tls_require_ciphers", &expciphers, errstr)) return FAIL; - if ( expciphers - && (rc = server_load_ciphers(ctx, &state_server, expciphers, errstr)) != OK) - return rc; + if (expciphers) + { + normalise_ciphers(&expciphers, tls_require_ciphers); + if ((rc = server_load_ciphers(ctx, &state_server, expciphers, errstr)) != OK) + return rc; + } } /* If this is a host for which certificate verification is mandatory or @@ -3018,7 +3246,7 @@ else if (verify_check_host(&tls_try_verify_hosts) == OK) else goto skip_certs; - { + { uschar * expcerts; if (!expand_check(tls_verify_certificates, US"tls_verify_certificates", &expcerts, errstr)) @@ -3033,13 +3261,19 @@ else if (expcerts && *expcerts) setup_cert_verify(ctx, server_verify_optional, verify_callback_server); - } + } skip_certs: ; #ifndef DISABLE_TLS_RESUME +# if OPENSSL_VERSION_NUMBER < 0x30000000L SSL_CTX_set_tlsext_ticket_key_cb(ctx, ticket_key_callback); /* despite working, appears to always return failure, so ignoring */ +# else +SSL_CTX_set_tlsext_ticket_key_evp_cb(ctx, ticket_key_callback); +/* despite working, appears to always return failure, so ignoring */ +# endif #endif + #ifdef OPENSSL_HAVE_NUM_TICKETS # ifndef DISABLE_TLS_RESUME SSL_CTX_set_num_tickets(ctx, tls_in.host_resumable ? 1 : 0); @@ -3107,9 +3341,10 @@ if (rc <= 0) case SSL_ERROR_ZERO_RETURN: DEBUG(D_tls) debug_printf("Got SSL_ERROR_ZERO_RETURN\n"); (void) tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : NULL, errstr); + (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL); if (SSL_get_shutdown(ssl) == SSL_RECEIVED_SHUTDOWN) - SSL_shutdown(ssl); + SSL_shutdown(ssl); tls_close(NULL, TLS_NO_SHUTDOWN); return FAIL; @@ -3117,15 +3352,16 @@ 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(ssl)); - (void) tls_error(s, NULL, sigalrm_seen ? US"timed out" : NULL, errstr); + s = string_sprintf("(%s)", SSL_get_version(ssl)); + (void) tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : s, errstr); + (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL); return FAIL; } @@ -3136,6 +3372,7 @@ if (rc <= 0) if (!errno) { *errstr = US"SSL_accept: TCP connection closed by peer"; + (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL); return FAIL; } DEBUG(D_tls) debug_printf(" - syscall %s\n", strerror(errno)); @@ -3144,6 +3381,7 @@ if (rc <= 0) sigalrm_seen ? US"timed out" : ERR_peek_error() ? NULL : string_sprintf("ret %d", error), errstr); + (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL); return FAIL; } } @@ -3160,6 +3398,33 @@ if (SSL_session_reused(ssl)) } #endif +#ifdef EXIM_HAVE_ALPN +/* If require-alpn, check server_seen_alpn here. Else abort TLS */ +if (!tls_alpn || !*tls_alpn) + { DEBUG(D_tls) debug_printf("TLS: was not watching for ALPN\n"); } +else if (!server_seen_alpn) + if (verify_check_host(&hosts_require_alpn) == OK) + { + /* We'd like to send a definitive Alert but OpenSSL provides no facility */ + SSL_shutdown(ssl); + tls_error(US"handshake", NULL, US"ALPN required but not negotiated", errstr); + return FAIL; + } + else + { DEBUG(D_tls) debug_printf("TLS: no ALPN presented in handshake\n"); } +else DEBUG(D_tls) + { + const uschar * name; + unsigned len; + SSL_get0_alpn_selected(ssl, &name, &len); + if (len && name) + debug_printf("ALPN negotiated: '%.*s'\n", (int)*name, name+1); + else + debug_printf("ALPN: no protocol negotiated\n"); + } +#endif + + /* TLS has been set up. Record data for the connection, adjust the input functions to read via TLS, and initialize things. */ @@ -3178,13 +3443,7 @@ DEBUG(D_tls) if (SSL_get_shared_ciphers(ssl, CS buf, sizeof(buf))) debug_printf("Shared ciphers: %s\n", buf); -#ifdef EXIM_HAVE_OPENSSL_KEYLOG - { - BIO * bp = BIO_new_fp(debug_file, BIO_NOCLOSE); - SSL_SESSION_print_keylog(bp, SSL_get_session(ssl)); - BIO_free(bp); - } -#endif + tls_dump_keylog(ssl); #ifdef EXIM_HAVE_SESSION_TICKET { @@ -3209,9 +3468,9 @@ See description in https://paquier.xyz/postgresql-2/channel-binding-openssl/ */ size_t len = SSL_get_peer_finished(ssl, &c, 0); int old_pool = store_pool; - SSL_get_peer_finished(ssl, s = store_get((int)len, FALSE), len); + SSL_get_peer_finished(ssl, s = store_get((int)len, GET_UNTAINTED), len); store_pool = POOL_PERM; - tls_in.channelbinding = b64encode_taint(CUS s, (int)len, FALSE); + tls_in.channelbinding = b64encode_taint(CUS s, (int)len, GET_UNTAINTED); store_pool = old_pool; DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage %p\n", tls_in.channelbinding); } @@ -3228,10 +3487,10 @@ ssl_xfer_eof = ssl_xfer_error = FALSE; receive_getc = tls_getc; receive_getbuf = tls_getbuf; receive_get_cache = tls_get_cache; +receive_hasc = tls_hasc; receive_ungetc = tls_ungetc; receive_feof = tls_feof; receive_ferror = tls_ferror; -receive_smtp_buffered = tls_smtp_buffered; tls_in.active.sock = fileno(smtp_out); tls_in.active.tls_ctx = NULL; /* not using explicit ctx for server-side */ @@ -3267,7 +3526,7 @@ else if (verify_check_given_host(CUSS &ob->tls_try_verify_hosts, host) == OK) else return OK; - { + { uschar * expcerts; if (!expand_check(ob->tls_verify_certificates, US"tls_verify_certificates", &expcerts, errstr)) @@ -3282,7 +3541,7 @@ else 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) { @@ -3390,30 +3649,33 @@ 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)) + { + DEBUG(D_tls) debug_printf("session expired\n"); + dbfn_delete(dbm_file, key); + } + else if (SSL_set_session(ssl, ss)) + { + 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) { ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring)); debug_printf("applying session to ssl: %s\n", ssl_errstring); } } - else - { - DEBUG(D_tls) debug_printf("good session\n"); - tlsp->resumption |= RESUME_CLIENT_SUGGESTED; - tlsp->verify_override = dt->verify_override; - tlsp->ocsp = dt->ocsp; - } } else DEBUG(D_tls) debug_printf("no session record\n"); @@ -3441,7 +3703,7 @@ if (SSL_SESSION_is_resumable(ss)) /* 1.1.1 */ { int len = i2d_SSL_SESSION(ss, NULL); int dlen = sizeof(dbdata_tls_session) + len; - dbdata_tls_session * dt = store_get(dlen, TRUE); + dbdata_tls_session * dt = store_get(dlen, GET_TAINTED); uschar * s = dt->session; open_db dbblock, * dbm_file; @@ -3521,6 +3783,47 @@ if (SSL_session_reused(exim_client_ctx->ssl)) #endif /* !DISABLE_TLS_RESUME */ +#ifdef EXIM_HAVE_ALPN +/* Expand and convert an Exim list to an ALPN list. False return for fail. +NULL plist return for silent no-ALPN. +*/ + +static BOOL +tls_alpn_plist(const uschar * tls_alpn, const uschar ** plist, unsigned * plen, + uschar ** errstr) +{ +uschar * exp_alpn; + +if (!expand_check(tls_alpn, US"tls_alpn", &exp_alpn, errstr)) + return FALSE; + +if (!exp_alpn) + { + DEBUG(D_tls) debug_printf("Setting TLS ALPN forced to fail, not sending\n"); + *plist = NULL; + } +else + { + /* The server implementation only accepts exactly one protocol name + but it's little extra code complexity in the client. */ + + const uschar * list = exp_alpn; + uschar * p = store_get(Ustrlen(exp_alpn), exp_alpn), * s, * t; + int sep = 0; + uschar len; + + for (t = p; s = string_nextinlist(&list, &sep, NULL, 0); t += len) + { + *t++ = len = (uschar) Ustrlen(s); + memcpy(t, s, len); + } + *plist = (*plen = t - p) ? p : NULL; + } +return TRUE; +} +#endif /* EXIM_HAVE_ALPN */ + + /************************************************* * Start a TLS session in a client * *************************************************/ @@ -3559,7 +3862,7 @@ BOOL require_ocsp = FALSE; rc = store_pool; store_pool = POOL_PERM; -exim_client_ctx = store_get(sizeof(exim_openssl_client_tls_ctx), FALSE); +exim_client_ctx = store_get(sizeof(exim_openssl_client_tls_ctx), GET_UNTAINTED); exim_client_ctx->corked = NULL; store_pool = rc; @@ -3620,21 +3923,25 @@ if (conn_args->dane) return FALSE; if (expciphers && *expciphers == '\0') expciphers = NULL; + + normalise_ciphers(&expciphers, ob->dane_require_tls_ciphers); } #endif -if (!expciphers && - !expand_check(ob->tls_require_ciphers, US"tls_require_ciphers", +if (!expciphers) + { + if (!expand_check(ob->tls_require_ciphers, US"tls_require_ciphers", &expciphers, errstr)) - return FALSE; + return FALSE; -/* In OpenSSL, cipher components are separated by hyphens. In GnuTLS, they -are separated by underscores. So that I can use either form in my tests, and -also for general convenience, we turn underscores into hyphens here. */ + /* In OpenSSL, cipher components are separated by hyphens. In GnuTLS, they + are separated by underscores. So that I can use either form in my tests, and + also for general convenience, we turn underscores into hyphens here. */ + + normalise_ciphers(&expciphers, ob->tls_require_ciphers); + } if (expciphers) { - uschar *s = expciphers; - while (*s) { if (*s == '_') *s = '-'; s++; } DEBUG(D_tls) debug_printf("required ciphers: %s\n", expciphers); if (!SSL_CTX_set_cipher_list(exim_client_ctx->ctx, CS expciphers)) { @@ -3706,6 +4013,28 @@ if (ob->tls_sni) } } +if (ob->tls_alpn) +#ifdef EXIM_HAVE_ALPN + { + const uschar * plist; + unsigned plen; + + if (!tls_alpn_plist(ob->tls_alpn, &plist, &plen, errstr)) + return FALSE; + if (plist) + if (SSL_set_alpn_protos(exim_client_ctx->ssl, plist, plen) != 0) + { + tls_error(US"alpn init", host, NULL, errstr); + return FALSE; + } + else + DEBUG(D_tls) debug_printf("Setting TLS ALPN '%s'\n", ob->tls_alpn); + } +#else + log_write(0, LOG_MAIN, "ALPN unusable with this OpenSSL library version; ignoring \"%s\"\n", + ob->tls_alpn); +#endif + #ifdef SUPPORT_DANE if (conn_args->dane) if (dane_tlsa_load(exim_client_ctx->ssl, host, &conn_args->tlsa_dnsa, errstr) != OK) @@ -3772,19 +4101,31 @@ if (rc <= 0) DEBUG(D_tls) { debug_printf("SSL_connect succeeded\n"); -#ifdef EXIM_HAVE_OPENSSL_KEYLOG - { - BIO * bp = BIO_new_fp(debug_file, BIO_NOCLOSE); - SSL_SESSION_print_keylog(bp, SSL_get_session(exim_client_ctx->ssl)); - BIO_free(bp); - } -#endif + tls_dump_keylog(exim_client_ctx->ssl); } #ifndef DISABLE_TLS_RESUME tls_client_resume_posthandshake(exim_client_ctx, tlsp); #endif +#ifdef EXIM_HAVE_ALPN +if (ob->tls_alpn) /* We requested. See what was negotiated. */ + { + const uschar * name; + unsigned len; + + SSL_get0_alpn_selected(exim_client_ctx->ssl, &name, &len); + if (len > 0) + { DEBUG(D_tls) debug_printf("ALPN negotiated %u: '%.*s'\n", len, (int)*name, name+1); } + else if (verify_check_given_host(CUSS &ob->hosts_require_alpn, host) == OK) + { + /* Would like to send a relevant fatal Alert, but OpenSSL has no API */ + tls_error(US"handshake", host, US"ALPN required but not negotiated", errstr); + return FALSE; + } + } +#endif + #ifdef SSL_get_extms_support tlsp->ext_master_secret = SSL_get_extms_support(exim_client_ctx->ssl) == 1; #endif @@ -3807,9 +4148,9 @@ tlsp->cipher_stdname = cipher_stdname_ssl(exim_client_ctx->ssl); size_t len = SSL_get_finished(exim_client_ctx->ssl, &c, 0); int old_pool = store_pool; - SSL_get_finished(exim_client_ctx->ssl, s = store_get((int)len, TRUE), len); + SSL_get_finished(exim_client_ctx->ssl, s = store_get((int)len, GET_TAINTED), len); store_pool = POOL_PERM; - tlsp->channelbinding = b64encode_taint(CUS s, (int)len, TRUE); + tlsp->channelbinding = b64encode_taint(CUS s, (int)len, GET_TAINTED); store_pool = old_pool; DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage %p %p\n", tlsp->channelbinding, tlsp); } @@ -3917,6 +4258,12 @@ if (ssl_xfer_buffer_lwm >= ssl_xfer_buffer_hwm) return ssl_xfer_buffer[ssl_xfer_buffer_lwm++]; } +BOOL +tls_hasc(void) +{ +return ssl_xfer_buffer_lwm < ssl_xfer_buffer_hwm; +} + uschar * tls_getbuf(unsigned * len) { @@ -3941,10 +4288,13 @@ return buf; void -tls_get_cache() +tls_get_cache(unsigned lim) { #ifndef DISABLE_DKIM int n = ssl_xfer_buffer_hwm - ssl_xfer_buffer_lwm; +debug_printf("tls_get_cache\n"); +if (n > lim) + n = lim; if (n > 0) dkim_exim_verify_feed(ssl_xfer_buffer+ssl_xfer_buffer_lwm, n); #endif @@ -3952,10 +4302,10 @@ if (n > 0) BOOL -tls_could_read(void) +tls_could_getc(void) { return ssl_xfer_buffer_lwm < ssl_xfer_buffer_hwm - || SSL_pending(state_server.lib_state.lib_ssl) > 0; + || SSL_pending(state_server.lib_state.lib_ssl) > 0; } @@ -4053,16 +4403,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) { @@ -4120,6 +4466,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 * *************************************************/ @@ -4130,7 +4502,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 @@ -4139,27 +4512,33 @@ 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 **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; +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 */ -tls_write(ct_ctx, NULL, 0, FALSE); /* flush write buffer */ - -if (shutdown) +if (do_shutdown > TLS_NO_SHUTDOWN) { int rc; DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS%s\n", - shutdown > 1 ? " (with response-wait)" : ""); + do_shutdown > TLS_SHUTDOWN_NOWAIT ? " (with response-wait)" : ""); + + tls_write(ct_ctx, NULL, 0, FALSE); /* flush write buffer */ - if ( (rc = SSL_shutdown(*sslp)) == 0 /* send "close notify" alert */ - && shutdown > 1) + if ( ( do_shutdown >= TLS_SHUTDOWN_WONLY + || (rc = SSL_shutdown(*sslp)) == 0 /* send "close notify" alert */ + ) + && do_shutdown > TLS_SHUTDOWN_NOWAIT + ) { +#ifdef EXIM_TCP_CORK + (void) setsockopt(*fdp, IPPROTO_TCP, EXIM_TCP_CORK, US &off, sizeof(off)); +#endif ALARM(2); - rc = SSL_shutdown(*sslp); /* wait for response */ + rc = SSL_shutdown(*sslp); /* wait for response */ ALARM_CLR(0); } @@ -4180,10 +4559,10 @@ if (!o_ctx) /* server side */ receive_getc = smtp_getc; receive_getbuf = smtp_getbuf; receive_get_cache = smtp_get_cache; + receive_hasc = smtp_hasc; receive_ungetc = smtp_ungetc; receive_feof = smtp_feof; receive_ferror = smtp_ferror; - receive_smtp_buffered = smtp_buffered; tls_in.active.tls_ctx = NULL; tls_in.sni = NULL; /* Leave bits, peercert, cipher, peerdn, certificate_verified set, for logging */ @@ -4210,8 +4589,8 @@ Returns: NULL on success, or error message uschar * tls_validate_require_cipher(void) { -SSL_CTX *ctx; -uschar *s, *expciphers, *err; +SSL_CTX * ctx; +uschar * expciphers, * err; tls_openssl_init(); @@ -4225,12 +4604,9 @@ if (!expand_check(tls_require_ciphers, US"tls_require_ciphers", &expciphers, if (!(expciphers && *expciphers)) return NULL; -/* normalisation ripped from above */ -s = expciphers; -while (*s != 0) { if (*s == '_') *s = '-'; s++; } +normalise_ciphers(&expciphers, tls_require_ciphers); err = NULL; - if (lib_ctx_new(&ctx, NULL, &err) == OK) { DEBUG(D_tls) @@ -4266,21 +4642,22 @@ number/string, and the version date remains unchanged. The _build_ date will change, so we can more usefully assist with version diagnosis by also reporting the build date. -Arguments: a FILE* to print the results to -Returns: nothing +Arguments: string to append to +Returns: string */ -void -tls_version_report(FILE *f) +gstring * +tls_version_report(gstring * g) { -fprintf(f, "Library version: OpenSSL: Compile: %s\n" - " Runtime: %s\n" - " : %s\n", - OPENSSL_VERSION_TEXT, - SSLeay_version(SSLEAY_VERSION), - SSLeay_version(SSLEAY_BUILT_ON)); -/* third line is 38 characters for the %s and the line is 73 chars long; -the OpenSSL output includes a "built on: " prefix already. */ +return string_fmt_append(g, + "Library version: OpenSSL: Compile: %s\n" + " Runtime: %s\n" + " : %s\n", + OPENSSL_VERSION_TEXT, + SSLeay_version(SSLEAY_VERSION), + SSLeay_version(SSLEAY_BUILT_ON)); + /* third line is 38 characters for the %s and the line is 73 chars long; + the OpenSSL output includes a "built on: " prefix already. */ } @@ -4434,7 +4811,6 @@ tls_openssl_options_parse(uschar *option_spec, long *results) { long result, item; uschar * exp, * end; -uschar keep_c; BOOL adding, item_parsed; /* Server: send no (<= TLS1.2) session tickets */ @@ -4476,11 +4852,8 @@ for (uschar * s = exp; *s; /**/) return FALSE; } adding = *s++ == '+'; - for (end = s; (*end != '\0') && !isspace(*end); ++end) /**/ ; - keep_c = *end; - *end = '\0'; - item_parsed = tls_openssl_one_option_parse(s, &item); - *end = keep_c; + for (end = s; *end && !isspace(*end); ) end++; + item_parsed = tls_openssl_one_option_parse(string_copyn(s, end-s), &item); if (!item_parsed) { DEBUG(D_tls) debug_printf("openssl option setting unrecognised: \"%s\"\n", s);