* Exim - an Internet mail transport agent *
*************************************************/
+/* Copyright (c) The Exim Maintainers 2020 - 2022 */
/* Copyright (c) University of Cambridge 1995 - 2019 */
-/* Copyright (c) The Exim Maintainers 2020 */
/* See the file NOTICE for conditions of use and distribution. */
/* Portions Copyright (c) The OpenSSL Project 1999 */
# 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)
{ 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
*************************************************/
/* 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))
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;
}
}
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;
}
* 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
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");
)
{
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. */
-/* 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);
+ }
-if ((rv = SSL_CTX_set_tmp_ecdh(sctx, ecdh) == 0))
- tls_error(string_sprintf("Error enabling '%s' curve", exp_curve), host, NULL, errstr);
+#else /* v 3.0.0 + */
+
+if ((rv = SSL_CTX_set1_groups(sctx, &nid, 1)) == 0)
+ tls_error(string_sprintf("Error enabling '%s' group", exp_curve), NULL, NULL, errstr);
else
- DEBUG(D_tls) debug_printf("ECDH: enabled '%s' 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*/
* Expand key and cert file specs *
*************************************************/
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
/*
Arguments:
s SSL connection (not used)
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));
}
return rsa_key;
}
+#endif /* pre-3.0.0 */
{
X509 * x509 = NULL;
EVP_PKEY * pkey;
-RSA * rsa;
X509_NAME * name;
uschar * where;
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);
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",
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;
}
* 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);
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 */
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;
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))
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;
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;
}
: 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;
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");
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);
return key->renew < now ? 2 : 1;
}
}
-#endif
+#endif /* !DISABLE_TLS_RESUME */
/* 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)
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;
for (uschar * name; name = string_nextinlist(&list, &sep, NULL, 0); )
if (Ustrncmp(in+1, name, in[0]) == 0)
{
- *out = in; /* we checked for exactly one, so can just point to it */
+ *out = in+1; /* we checked for exactly one, so can just point to it */
*outlen = inlen;
return SSL_TLSEXT_ERR_OK; /* use ALPN */
}
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)
{
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
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 */
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
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;
}
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);
}
/* 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) */
+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 *
*************************************************/
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
else
goto skip_certs;
- {
+ {
uschar * expcerts;
if (!expand_check(tls_verify_certificates, US"tls_verify_certificates",
&expcerts, errstr))
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);
case SSL_ERROR_ZERO_RETURN:
DEBUG(D_tls) debug_printf("Got SSL_ERROR_ZERO_RETURN\n");
(void) tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : NULL, errstr);
-
+#ifndef DISABLE_EVENT
+ (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL);
+#endif
if (SSL_get_shutdown(ssl) == SSL_RECEIVED_SHUTDOWN)
- SSL_shutdown(ssl);
+ SSL_shutdown(ssl);
tls_close(NULL, TLS_NO_SHUTDOWN);
return FAIL;
|| r == SSL_R_VERSION_TOO_LOW
#endif
|| r == SSL_R_UNKNOWN_PROTOCOL || r == SSL_R_UNSUPPORTED_PROTOCOL)
- s = string_sprintf("%s (%s)", s, SSL_get_version(ssl));
+ s = string_sprintf("(%s)", SSL_get_version(ssl));
(void) tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : s, errstr);
+#ifndef DISABLE_EVENT
+ (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL);
+#endif
return FAIL;
}
if (!errno)
{
*errstr = US"SSL_accept: TCP connection closed by peer";
+#ifndef DISABLE_EVENT
+ (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL);
+#endif
return FAIL;
}
DEBUG(D_tls) debug_printf(" - syscall %s\n", strerror(errno));
sigalrm_seen ? US"timed out"
: ERR_peek_error() ? NULL : string_sprintf("ret %d", error),
errstr);
+#ifndef DISABLE_EVENT
+ (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL);
+#endif
return FAIL;
}
}
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
{
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);
}
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 */
else
return OK;
- {
+ {
uschar * expcerts;
if (!expand_check(ob->tls_verify_certificates, US"tls_verify_certificates",
&expcerts, errstr))
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)
{
and apply it to the ssl-connection for attempted resumption. */
static void
-tls_retrieve_session(tls_support * tlsp, SSL * ssl, const uschar * key)
+tls_retrieve_session(tls_support * tlsp, SSL * ssl)
{
-tlsp->resumption |= RESUME_SUPPORTED;
if (tlsp->host_resumable)
{
+ const uschar * key = tlsp->resume_index;
dbdata_tls_session * dt;
int len;
open_db dbblock, * dbm_file;
tlsp->resumption |= RESUME_CLIENT_REQUESTED;
- DEBUG(D_tls) debug_printf("checking for resumable session for %s\n", key);
+ DEBUG(D_tls)
+ debug_printf("checking for resumable session for %s\n", tlsp->resume_index);
if ((dbm_file = dbfn_open(US"tls", O_RDWR, &dbblock, FALSE, FALSE)))
{
- /* key for the db is the IP */
- if ((dt = dbfn_read_with_length(dbm_file, key, &len)))
+ if ((dt = dbfn_read_with_length(dbm_file, tlsp->resume_index, &len)))
{
SSL_SESSION * ss = NULL;
const uschar * sess_asn1 = dt->session;
if (lifetime + dt->time_stamp < time(NULL))
{
DEBUG(D_tls) debug_printf("session expired\n");
- dbfn_delete(dbm_file, key);
+ dbfn_delete(dbm_file, tlsp->resume_index);
}
- 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
+ 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
{
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;
if ((dbm_file = dbfn_open(US"tls", O_RDWR, &dbblock, FALSE, FALSE)))
{
- const uschar * key = cbinfo->host->address;
- dbfn_delete(dbm_file, key);
- dbfn_write(dbm_file, key, dt, dlen);
+ dbfn_write(dbm_file, tlsp->resume_index, dt, dlen);
dbfn_close(dbm_file);
DEBUG(D_tls) debug_printf("wrote session (len %u) to db\n",
(unsigned)dlen);
}
+/* Construct a key for session DB lookup, and setup the SSL_CTX for resumption */
+
static void
tls_client_ctx_resume_prehandshake(
- exim_openssl_client_tls_ctx * exim_client_ctx, tls_support * tlsp,
- smtp_transport_options_block * ob, host_item * host)
+ exim_openssl_client_tls_ctx * exim_client_ctx, smtp_connect_args * conn_args,
+ tls_support * tlsp, smtp_transport_options_block * ob)
{
-/* Should the client request a session resumption ticket? */
-if (verify_check_given_host(CUSS &ob->tls_resumption_hosts, host) == OK)
- {
- tlsp->host_resumable = TRUE;
+tlsp->host_resumable = TRUE;
+tls_client_resmption_key(tlsp, conn_args, ob);
- SSL_CTX_set_session_cache_mode(exim_client_ctx->ctx,
- SSL_SESS_CACHE_CLIENT
- | SSL_SESS_CACHE_NO_INTERNAL | SSL_SESS_CACHE_NO_AUTO_CLEAR);
- SSL_CTX_sess_set_new_cb(exim_client_ctx->ctx, tls_save_session_cb);
- }
+SSL_CTX_set_session_cache_mode(exim_client_ctx->ctx,
+ SSL_SESS_CACHE_CLIENT
+ | SSL_SESS_CACHE_NO_INTERNAL | SSL_SESS_CACHE_NO_AUTO_CLEAR);
+SSL_CTX_sess_set_new_cb(exim_client_ctx->ctx, tls_save_session_cb);
}
static BOOL
tlsp->resumption = RESUME_SUPPORTED;
/* Pick up a previous session, saved on an old ticket */
-tls_retrieve_session(tlsp, ssl, host->address);
+tls_retrieve_session(tlsp, ssl);
return TRUE;
}
#ifdef EXIM_HAVE_ALPN
/* Expand and convert an Exim list to an ALPN list. False return for fail.
NULL plist return for silent no-ALPN.
+
+Overwite the passed-in list with the expanded version.
*/
static BOOL
-tls_alpn_plist(const uschar * tls_alpn, const uschar ** plist, unsigned * plen,
+tls_alpn_plist(uschar ** tls_alpn, const uschar ** plist, unsigned * plen,
uschar ** errstr)
{
uschar * exp_alpn;
-if (!expand_check(tls_alpn, US"tls_alpn", &exp_alpn, errstr))
+if (!expand_check(*tls_alpn, US"tls_alpn", &exp_alpn, errstr))
return FALSE;
+*tls_alpn = exp_alpn;
if (!exp_alpn)
{
but it's little extra code complexity in the client. */
const uschar * list = exp_alpn;
- uschar * p = store_get(Ustrlen(exp_alpn), is_tainted(exp_alpn)), * s, * t;
+ uschar * p = store_get(Ustrlen(exp_alpn), exp_alpn), * s, * t;
int sep = 0;
uschar len;
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;
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))
{
client_static_state, errstr) != OK)
return FALSE;
-#ifndef DISABLE_TLS_RESUME
-tls_client_ctx_resume_prehandshake(exim_client_ctx, tlsp, ob, host);
-#endif
-
-
-if (!(exim_client_ctx->ssl = SSL_new(exim_client_ctx->ctx)))
- {
- tls_error(US"SSL_new", host, NULL, errstr);
- return FALSE;
- }
-SSL_set_session_id_context(exim_client_ctx->ssl, sid_ctx, Ustrlen(sid_ctx));
-
-SSL_set_fd(exim_client_ctx->ssl, cctx->sock);
-SSL_set_connect_state(exim_client_ctx->ssl);
-
if (ob->tls_sni)
{
if (!expand_check(ob->tls_sni, US"tls_sni", &tlsp->sni, errstr))
return FALSE;
if (!tlsp->sni)
- {
- DEBUG(D_tls) debug_printf("Setting TLS SNI forced to fail, not sending\n");
- }
+ { DEBUG(D_tls) debug_printf("Setting TLS SNI forced to fail, not sending\n"); }
else if (!Ustrlen(tlsp->sni))
tlsp->sni = NULL;
else
{
-#ifdef EXIM_HAVE_OPENSSL_TLSEXT
- DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tlsp->sni);
- SSL_set_tlsext_host_name(exim_client_ctx->ssl, tlsp->sni);
-#else
+#ifndef EXIM_HAVE_OPENSSL_TLSEXT
log_write(0, LOG_MAIN, "SNI unusable with this OpenSSL library version; ignoring \"%s\"\n",
tlsp->sni);
+ tlsp->sni = NULL;
#endif
}
}
const uschar * plist;
unsigned plen;
- if (!tls_alpn_plist(ob->tls_alpn, &plist, &plen, errstr))
+ if (!tls_alpn_plist(&ob->tls_alpn, &plist, &plen, errstr))
return FALSE;
if (plist)
- if (SSL_set_alpn_protos(exim_client_ctx->ssl, plist, plen) != 0)
+ if (SSL_CTX_set_alpn_protos(exim_client_ctx->ctx, plist, plen) != 0)
{
tls_error(US"alpn init", host, NULL, errstr);
return FALSE;
ob->tls_alpn);
#endif
+#ifndef DISABLE_TLS_RESUME
+if (verify_check_given_host(CUSS &ob->tls_resumption_hosts, host) == OK)
+ tls_client_ctx_resume_prehandshake(exim_client_ctx, conn_args, tlsp, ob);
+#endif
+
+
+if (!(exim_client_ctx->ssl = SSL_new(exim_client_ctx->ctx)))
+ {
+ tls_error(US"SSL_new", host, NULL, errstr);
+ return FALSE;
+ }
+SSL_set_session_id_context(exim_client_ctx->ssl, sid_ctx, Ustrlen(sid_ctx));
+SSL_set_fd(exim_client_ctx->ssl, cctx->sock);
+SSL_set_connect_state(exim_client_ctx->ssl);
+
+#ifdef EXIM_HAVE_OPENSSL_TLSEXT
+if (tlsp->sni)
+ {
+ DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tlsp->sni);
+ SSL_set_tlsext_host_name(exim_client_ctx->ssl, tlsp->sni);
+ }
+#endif
+
#ifdef SUPPORT_DANE
if (conn_args->dane)
if (dane_tlsa_load(exim_client_ctx->ssl, host, &conn_args->tlsa_dnsa, errstr) != OK)
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
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);
}
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)
{
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;
if (*fdp < 0) return; /* TLS was not active */
-if (do_shutdown)
+if (do_shutdown > TLS_NO_SHUTDOWN)
{
int rc;
DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS%s\n",
- do_shutdown > 1 ? " (with response-wait)" : "");
+ do_shutdown > TLS_SHUTDOWN_NOWAIT ? " (with response-wait)" : "");
tls_write(ct_ctx, NULL, 0, FALSE); /* flush write buffer */
- if ( (rc = SSL_shutdown(*sslp)) == 0 /* send "close notify" alert */
- && do_shutdown > 1)
+ if ( ( do_shutdown >= TLS_SHUTDOWN_WONLY
+ || (rc = SSL_shutdown(*sslp)) == 0 /* send "close notify" alert */
+ )
+ && do_shutdown > TLS_SHUTDOWN_NOWAIT
+ )
{
+#ifdef EXIM_TCP_CORK
+ (void) setsockopt(*fdp, IPPROTO_TCP, EXIM_TCP_CORK, US &off, sizeof(off));
+#endif
ALARM(2);
- rc = SSL_shutdown(*sslp); /* wait for response */
+ rc = SSL_shutdown(*sslp); /* wait for response */
ALARM_CLR(0);
}
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 */
uschar *
tls_validate_require_cipher(void)
{
-SSL_CTX *ctx;
-uschar *s, *expciphers, *err;
+SSL_CTX * ctx;
+uschar * expciphers, * err;
tls_openssl_init();
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)
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. */
}