* Exim - an Internet mail transport agent *
*************************************************/
+/* Copyright (c) The Exim Maintainers 2020 - 2023 */
/* Copyright (c) University of Cambridge 1995 - 2019 */
-/* Copyright (c) The Exim Maintainers 2020 */
/* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
/* Portions Copyright (c) The OpenSSL Project 1999 */
#if OPENSSL_VERSION_NUMBER >= 0x10100000L
# define EXIM_HAVE_OCSP_RESP_COUNT
# define OPENSSL_AUTO_SHA256
+# define OPENSSL_MIN_PROTO_VERSION
#else
# define EXIM_HAVE_EPHEM_RSA_KEX
# define EXIM_HAVE_RAND_PSEUDO
# define EXIM_HAVE_OPENSSL_KEYLOG
# define EXIM_HAVE_OPENSSL_CIPHER_GET_ID
# define EXIM_HAVE_SESSION_TICKET
-# define EXIM_HAVE_OPESSL_TRACE
-# define EXIM_HAVE_OPESSL_GET0_SERIAL
+# define EXIM_HAVE_OPENSSL_TRACE
+# define EXIM_HAVE_OPENSSL_GET0_SERIAL
+# define EXIM_HAVE_OPENSSL_OCSP_RESP_GET0_CERTS
+# define EXIM_HAVE_SSL_GET0_VERIFIED_CHAIN
# ifndef DISABLE_OCSP
# define EXIM_HAVE_OCSP
# endif
+# define EXIM_HAVE_ALPN /* fail ret from hshake-cb is ignored by LibreSSL */
# else
# define EXIM_NEED_OPENSSL_INIT
# endif
# endif
#endif
+#if LIBRESSL_VERSION_NUMBER >= 0x3040000fL
+# define EXIM_HAVE_OPENSSL_CIPHER_GET_ID
+#endif
+
+#if !defined(LIBRESSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER >= 0x030000000L)
+# define EXIM_HAVE_EXPORT_CHNL_BNGNG
+# define EXIM_HAVE_OPENSSL_X509_STORE_GET1_ALL_CERTS
+#endif
+
#if !defined(LIBRESSL_VERSION_NUMBER) \
|| LIBRESSL_VERSION_NUMBER >= 0x20010000L
# if !defined(OPENSSL_NO_ECDH)
# define OPENSSL_HAVE_KEYLOG_CB
# define OPENSSL_HAVE_NUM_TICKETS
# define EXIM_HAVE_OPENSSL_CIPHER_STD_NAME
+# define EXIM_HAVE_EXP_CHNL_BNGNG
+# define EXIM_HAVE_OPENSSL_OCSP_RESP_GET0_SIGNER
+# define EXIM_HAVE_OPENSSL_SET1_GROUPS
# else
# define OPENSSL_BAD_SRVR_OURCERT
# endif
#endif
+#if !defined(LIBRESSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER >= 0x010002000L)
+# define EXIM_HAVE_EXPORT_CHNL_BNGNG
+#endif
+
#if !defined(EXIM_HAVE_OPENSSL_TLSEXT) && !defined(DISABLE_OCSP)
# warning "OpenSSL library version too old; define DISABLE_OCSP in Makefile"
# define DISABLE_OCSP
#endif
-#ifdef EXPERIMENTAL_TLS_RESUME
+#ifndef DISABLE_TLS_RESUME
# if OPENSSL_VERSION_NUMBER < 0x0101010L
# error OpenSSL version too old for session-resumption
# endif
# endif
#endif
+#define TESTSUITE_TICKET_LIFE 10 /* seconds */
/*************************************************
* OpenSSL option parse *
*************************************************/
{ 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 },
#ifndef MACRO_PREDEF
static int exim_openssl_options_size = nelem(exim_openssl_options);
+static long init_options = 0;
#endif
#ifdef MACRO_PREDEF
builtin_macro_create(buf);
}
-# ifdef EXPERIMENTAL_TLS_RESUME
+# ifndef DISABLE_TLS_RESUME
builtin_macro_create_var(US"_RESUME_DECODE", RESUME_DECODE_STRING );
# endif
# ifdef SSL_OP_NO_TLSv1_3
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
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;
#endif
+#ifdef EXIM_HAVE_ALPN
+static BOOL server_seen_alpn = FALSE;
+#endif
static char ssl_errstring[256];
OCSP_RESPONSE * resp;
} ocsp_resplist;
-typedef struct tls_ext_ctx_cb {
- tls_support * tlsp;
- uschar *certificate;
- uschar *privatekey;
- BOOL is_server;
+typedef struct exim_openssl_state {
+ exim_tlslib_state lib_state;
+#define lib_ctx libdata0
+#define lib_ssl libdata1
+
+ tls_support * tlsp;
+ uschar * certificate;
+ uschar * privatekey;
+ BOOL is_server;
#ifndef DISABLE_OCSP
- STACK_OF(X509) *verify_stack; /* chain for verifying the proof */
union {
struct {
uschar *file;
const uschar *file_expanded;
ocsp_resplist *olist;
+ STACK_OF(X509) *verify_stack; /* chain for verifying the proof */
} server;
struct {
X509_STORE *verify_store; /* non-null if status requested */
+ uschar *verify_errstr; /* only if _required */
BOOL verify_required;
} client;
} u_ocsp;
#endif
- uschar *dhparam;
+ uschar * dhparam;
/* these are cached from first expand */
- uschar *server_cipher_list;
+ uschar * server_cipher_list;
/* only passed down to tls_error: */
- host_item *host;
+ host_item * host;
const uschar * verify_cert_hostnames;
#ifndef DISABLE_EVENT
- uschar * event_action;
+ uschar * event_action;
#endif
-} tls_ext_ctx_cb;
+} exim_openssl_state_st;
/* should figure out a cleanup of API to handle state preserved per
implementation, for various reasons, which can be void * in the APIs.
For now, we hack around it. */
-tls_ext_ctx_cb *client_static_cbinfo = NULL; /*XXX should not use static; multiple concurrent clients! */
-tls_ext_ctx_cb *server_static_cbinfo = NULL;
+exim_openssl_state_st *client_static_state = NULL; /*XXX should not use static; multiple concurrent clients! */
+exim_openssl_state_st state_server = {.is_server = TRUE};
static int
-setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional,
- int (*cert_vfy_cb)(int, X509_STORE_CTX *), uschar ** errstr );
+setup_certs(SSL_CTX * sctx, uschar ** certs, uschar * crl, host_item * host,
+ uschar ** errstr);
/* Callbacks */
-#ifdef EXIM_HAVE_OPENSSL_TLSEXT
-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);
+static void x509_stack_dump_cert_s_names(const STACK_OF(X509) * sk);
+static void x509_store_dump_cert_s_names(X509_STORE * store);
#endif
/* Daemon-called, before every connection, key create/rotate */
-#ifdef EXPERIMENTAL_TLS_RESUME
+#ifndef DISABLE_TLS_RESUME
static void tk_init(void);
static int tls_exdata_idx = -1;
#endif
-void
-tls_daemon_init(void)
+static void
+tls_per_lib_daemon_tick(void)
{
-#ifdef EXPERIMENTAL_TLS_RESUME
+#ifndef DISABLE_TLS_RESUME
tk_init();
#endif
-return;
+}
+
+/* Called once at daemon startup */
+
+static void
+tls_per_lib_daemon_init(void)
+{
+tls_daemon_creds_reload();
}
-/*************************************************
-* Callback to generate RSA key *
-*************************************************/
+/**************************************************
+* General library initalisation *
+**************************************************/
-/*
-Arguments:
- s SSL connection (not used)
- export not used
- keylength keylength
+static BOOL
+lib_rand_init(void * addr)
+{
+randstuff r;
+if (!RAND_status()) return TRUE;
-Returns: pointer to generated key
-*/
+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));
-static RSA *
-rsa_callback(SSL *s, int export, int keylength)
+return RAND_status();
+}
+
+
+static void
+tls_openssl_init(void)
{
-RSA *rsa_key;
-#ifdef EXIM_HAVE_RSA_GENKEY_EX
-BIGNUM *bn = BN_new();
-#endif
+static BOOL once = FALSE;
+if (once) return;
+once = TRUE;
-export = export; /* Shut picky compilers up */
-DEBUG(D_tls) debug_printf("Generating %d bit RSA key...\n", keylength);
+#ifdef EXIM_NEED_OPENSSL_INIT
+SSL_load_error_strings(); /* basic set up */
+OpenSSL_add_ssl_algorithms();
+#endif
-#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
-if (!(rsa_key = RSA_generate_key(keylength, RSA_F4, NULL, NULL)))
+#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
- {
- ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
- log_write(0, LOG_MAIN|LOG_PANIC, "TLS error (RSA_generate_key): %s",
- ssl_errstring);
- return NULL;
- }
-return rsa_key;
+(void) lib_rand_init(NULL);
+(void) tls_openssl_options_parse(openssl_options, &init_options);
}
-/* Extreme debug
-#ifndef DISABLE_OCSP
-void
-x509_store_dump_cert_s_names(X509_STORE * store)
+/*************************************************
+* Initialize for DH *
+*************************************************/
+
+/* 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
+ errstr error string pointer
+
+Returns: TRUE if OK (nothing to set up, or setup worked)
+*/
+
+static BOOL
+init_dh(SSL_CTX * sctx, uschar * dhparam, uschar ** errstr)
{
-STACK_OF(X509_OBJECT) * roots= store->objs;
-static uschar name[256];
+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))
+ return FALSE;
-for (int i= 0; i < sk_X509_OBJECT_num(roots); i++)
+if (!dhexpanded || !*dhexpanded)
+ bio = BIO_new_mem_buf(CS std_dh_prime_default(), -1);
+else if (dhexpanded[0] == '/')
{
- X509_OBJECT * tmp_obj= sk_X509_OBJECT_value(roots, i);
- if(tmp_obj->type == X509_LU_X509)
+ if (!(bio = BIO_new_file(CS dhexpanded, "r")))
{
- 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);
- }
+ tls_error(string_sprintf("could not read dhparams file %s", dhexpanded),
+ NULL, 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),
+ NULL, US strerror(errno), errstr);
+ return FALSE;
+ }
+ bio = BIO_new_mem_buf(CS pem, -1);
+ }
+
+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),
+ 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. */
-#ifndef DISABLE_EVENT
-static int
-verify_event(tls_support * tlsp, X509 * cert, int depth, const uschar * dn,
- BOOL *calledp, const BOOL *optionalp, const uschar * what)
-{
-uschar * ev;
-uschar * yield;
-X509 * old_cert;
+#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 */
+dh_bitsize = DH_bits(dh);
+# else
+dh_bitsize = 8 * DH_size(dh);
+# endif
+#else /* 3.0.0 + */
+dh_bitsize = EVP_PKEY_get_bits(pkey);
+#endif
-ev = tlsp == &tls_out ? client_static_cbinfo->event_action : event_action;
-if (ev)
+/* 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("verify_event: %s %d\n", what, depth);
- 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 (
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+ SSL_CTX_set_tmp_dh(sctx, dh)
+#else
+ SSL_CTX_set0_tmp_dh_pkey(sctx, pkey)
+#endif
+ == 0)
{
- log_write(0, LOG_MAIN, "[%s] %s verify denied by event-action: "
- "depth=%d cert=%s: %s",
- tlsp == &tls_out ? deliver_host_address : sender_host_address,
- what, depth, dn, yield);
- *calledp = TRUE;
- if (!*optionalp)
- {
- if (old_cert) tlsp->peercert = old_cert; /* restore 1st failing cert */
- return 1; /* reject (leaving peercert set) */
- }
- DEBUG(D_tls) debug_printf("Event-action verify failure overridden "
- "(host in tls_try_verify_hosts)\n");
- tlsp->verify_override = TRUE;
+ 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
}
- X509_free(tlsp->peercert);
- tlsp->peercert = old_cert;
+ else
+ DEBUG(D_tls)
+ debug_printf(" Diffie-Hellman initialized from %s with %d-bit prime\n",
+ dhexpanded ? dhexpanded : US"default", dh_bitsize);
}
-return 0;
-}
+else
+ DEBUG(D_tls)
+ 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);
#endif
+/* The EVP_PKEY ownership stays with the ctx; do not free it */
+
+BIO_free(bio);
+return TRUE;
+}
+
+
+
/*************************************************
-* Callback for verification *
+* Initialize for ECDH *
*************************************************/
-/* The SSL library does certificate verification if set up to do so. This
-callback has the current yes/no state is in "state". If verification succeeded,
-we set the certificate-verified flag. If verification failed, what happens
-depends on whether the client is required to present a verifiable certificate
-or not.
+/* "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 verification is optional, we change the state to yes, but still log the
-verification error. For some reason (it really would help to have proper
-documentation of OpenSSL), this callback function then gets called again, this
-time with state = 1. We must take care not to set the private verified flag on
-the second time through.
+*/
-Note: this function is not called if the client fails to present a certificate
-when asked. We get here only if a certificate has been received. Handling of
-optional verification for this case is done when requesting SSL to verify, by
-setting SSL_VERIFY_FAIL_IF_NO_PEER_CERT in the non-optional case.
+static uschar *
+init_ecdh_auto(SSL_CTX * sctx)
+{
+#if OPENSSL_VERSION_NUMBER < 0x10002000L
+DEBUG(D_tls) debug_printf(
+ " ECDH OpenSSL < 1.0.2: temp key parameter settings: overriding \"auto\" with \"prime256v1\"\n");
+return US"prime256v1";
-May be called multiple times for different issues with a certificate, even
-for a given "depth" in the certificate chain.
+#else
+# if defined SSL_CTRL_SET_ECDH_AUTO
+
+DEBUG(D_tls) debug_printf(
+ " ECDH OpenSSL 1.0.2+: temp key parameter settings: autoselection\n");
+SSL_CTX_set_ecdh_auto(sctx, 1);
+return NULL;
+
+# else
+
+DEBUG(D_tls) debug_printf(
+ " ECDH OpenSSL 1.1.0+: temp key parameter settings: library default selection\n");
+return NULL;
+
+# endif
+#endif
+}
+
+/* Load parameters for ECDH encryption. Server only.
+
+For now, we stick to NIST P-256 because: it's simple and easy to configure;
+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:
- preverify_ok current yes/no state as 1/0
- x509ctx certificate information.
- tlsp per-direction (client vs. server) support data
- calledp has-been-called flag
- optionalp verification-is-optional flag
+ sctx The current SSL CTX (inbound or outbound)
+ errstr error string pointer
-Returns: 0 if verification should fail, otherwise 1
+Returns: TRUE if OK (nothing to set up, or setup worked)
*/
-static int
-verify_callback(int preverify_ok, X509_STORE_CTX * x509ctx,
- tls_support * tlsp, BOOL * calledp, BOOL * optionalp)
+static BOOL
+init_ecdh(SSL_CTX * sctx, uschar ** errstr)
{
-X509 * cert = X509_STORE_CTX_get_current_cert(x509ctx);
-int depth = X509_STORE_CTX_get_error_depth(x509ctx);
-uschar dn[256];
+#ifdef OPENSSL_NO_ECDH
+return TRUE;
+#else
-if (!X509_NAME_oneline(X509_get_subject_name(cert), CS dn, sizeof(dn)))
- {
- DEBUG(D_tls) debug_printf("X509_NAME_oneline() error\n");
- log_write(0, LOG_MAIN, "[%s] SSL verify error: internal error",
+# ifndef EXIM_HAVE_ECDH
+DEBUG(D_tls)
+ debug_printf(" No OpenSSL API to define ECDH parameters, skipping\n");
+return TRUE;
+# else
+
+uschar * exp_curve;
+int ngroups, rc, sep;
+const uschar * curves_list, * curve;
+# ifdef EXIM_HAVE_OPENSSL_SET1_GROUPS
+int nids[16];
+# else
+int nids[1];
+# endif
+
+if (!expand_check(tls_eccurve, US"tls_eccurve", &exp_curve, errstr))
+ return FALSE;
+
+/* Is the option deliberately empty? */
+
+if (!exp_curve || !*exp_curve)
+ return TRUE;
+
+/* Limit the list to hardwired array size. Drop out if any element is "suto". */
+
+curves_list = exp_curve;
+sep = 0;
+for (ngroups = 0;
+ ngroups < nelem(nids)
+ && (curve = string_nextinlist(&curves_list, &sep, NULL, 0));
+ )
+ if (Ustrcmp(curve, "auto") == 0)
+ {
+ DEBUG(D_tls) if (ngroups > 0)
+ debug_printf(" tls_eccurve 'auto' item takes precedence\n");
+ if ((exp_curve = init_ecdh_auto(sctx))) break; /* have a curve name to set */
+ return TRUE; /* all done */
+ }
+ else
+ ngroups++;
+
+/* Translate to NIDs */
+
+curves_list = exp_curve;
+for (ngroups = 0; curve = string_nextinlist(&curves_list, &sep, NULL, 0);
+ ngroups++)
+ if ( (nids[ngroups] = OBJ_sn2nid (CCS curve)) == NID_undef
+# ifdef EXIM_HAVE_OPENSSL_EC_NIST2NID
+ && (nids[ngroups] = EC_curve_nist2nid(CCS curve)) == NID_undef
+# endif
+ )
+ {
+ uschar * s = string_sprintf("Unknown curve name in tls_eccurve '%s'", curve);
+ DEBUG(D_tls) debug_printf("TLS error: %s\n", s);
+ if (errstr) *errstr = s;
+ return FALSE;
+ }
+
+# ifdef EXIM_HAVE_OPENSSL_SET1_GROUPS
+/* Set the groups */
+
+if ((rc = SSL_CTX_set1_groups(sctx, nids, ngroups)) == 0)
+ tls_error(string_sprintf("Error enabling '%s' group(s)", exp_curve), NULL, NULL, errstr);
+else
+ DEBUG(D_tls) debug_printf(" ECDH: enabled '%s' group(s)\n", exp_curve);
+
+# else /* Cannot handle a list; only 1 element nids array */
+ {
+ EC_KEY * ecdh;
+ if (!(ecdh = EC_KEY_new_by_curve_name(nids[0])))
+ {
+ 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 ((rc = SSL_CTX_set_tmp_ecdh(sctx, ecdh)) == 0)
+ tls_error(string_sprintf("Error enabling '%s' curve", exp_curve), NULL, NULL, errstr);
+ else
+ DEBUG(D_tls) debug_printf(" ECDH: enabled '%s' curve\n", exp_curve);
+ EC_KEY_free(ecdh);
+ }
+# endif /*!EXIM_HAVE_OPENSSL_SET1_GROUPS*/
+
+return !!rc;
+
+# endif /*EXIM_HAVE_ECDH*/
+#endif /*OPENSSL_NO_ECDH*/
+}
+
+
+
+/*************************************************
+* Expand key and cert file specs *
+*************************************************/
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+/*
+Arguments:
+ s SSL connection (not used)
+ export not used
+ keylength keylength
+
+Returns: pointer to generated key
+*/
+
+static RSA *
+rsa_callback(SSL *s, int export, int keylength)
+{
+RSA *rsa_key;
+#ifdef EXIM_HAVE_RSA_GENKEY_EX
+BIGNUM *bn = BN_new();
+#endif
+
+DEBUG(D_tls) debug_printf("Generating %d bit RSA key...\n", keylength);
+
+# 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
+if (!(rsa_key = RSA_generate_key(keylength, RSA_F4, NULL, NULL)))
+# endif
+
+ {
+ ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
+ log_write(0, LOG_MAIN|LOG_PANIC, "TLS error (RSA_generate_key): %s",
+ ssl_errstring);
+ return NULL;
+ }
+return rsa_key;
+}
+#endif /* pre-3.0.0 */
+
+
+
+/* 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)
+{
+X509 * x509 = NULL;
+EVP_PKEY * pkey;
+X509_NAME * name;
+uschar * where;
+
+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 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;
+ }
+#else
+pkey = EVP_RSA_gen(2048);
+#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)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(const SSL * s, int where, int ret)
+{
+DEBUG(D_tls)
+ {
+ gstring * g = NULL;
+
+ if (where & SSL_ST_CONNECT) g = string_append_listele(g, ',', US"SSL_connect");
+ if (where & SSL_ST_ACCEPT) g = string_append_listele(g, ',', US"SSL_accept");
+ if (where & SSL_CB_LOOP) g = string_append_listele(g, ',', US"state_chg");
+ if (where & SSL_CB_EXIT) g = string_append_listele(g, ',', US"hshake_exit");
+ if (where & SSL_CB_READ) g = string_append_listele(g, ',', US"read");
+ if (where & SSL_CB_WRITE) g = string_append_listele(g, ',', US"write");
+ if (where & SSL_CB_ALERT) g = string_append_listele(g, ',', US"alert");
+ if (where & SSL_CB_HANDSHAKE_START) g = string_append_listele(g, ',', US"hshake_start");
+ if (where & SSL_CB_HANDSHAKE_DONE) g = string_append_listele(g, ',', US"hshake_done");
+
+ if (where & SSL_CB_LOOP)
+ debug_printf("SSL %s: %s\n", g->s, SSL_state_string_long(s));
+ else if (where & SSL_CB_ALERT)
+ debug_printf("SSL %s %s:%s\n", g->s,
+ SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret));
+ else if (where & SSL_CB_EXIT)
+ {
+ if (ret <= 0)
+ debug_printf("SSL %s: %s in %s\n", g->s,
+ ret == 0 ? "failed" : "error", SSL_state_string_long(s));
+ }
+ else if (where & (SSL_CB_HANDSHAKE_START | SSL_CB_HANDSHAKE_DONE))
+ debug_printf("SSL %s: %s\n", g->s, 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
+static int
+verify_event(tls_support * tlsp, X509 * cert, int depth, const uschar * dn,
+ BOOL *calledp, const BOOL *optionalp, const uschar * what)
+{
+uschar * ev;
+uschar * yield;
+X509 * old_cert;
+
+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);
+ 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), &errno)))
+ {
+ log_write(0, LOG_MAIN, "[%s] %s verify denied by event-action: "
+ "depth=%d cert=%s: %s",
+ tlsp == &tls_out ? deliver_host_address : sender_host_address,
+ what, depth, dn, yield);
+ *calledp = TRUE;
+ if (!*optionalp)
+ {
+ if (old_cert) tlsp->peercert = old_cert; /* restore 1st failing cert */
+ return 1; /* reject (leaving peercert set) */
+ }
+ DEBUG(D_tls) debug_printf("Event-action verify failure overridden "
+ "(host in tls_try_verify_hosts)\n");
+ tlsp->verify_override = TRUE;
+ }
+ X509_free(tlsp->peercert);
+ tlsp->peercert = old_cert;
+ }
+return 0;
+}
+#endif
+
+/*************************************************
+* Callback for verification *
+*************************************************/
+
+/* The SSL library does certificate verification if set up to do so. This
+callback has the current yes/no state is in "state". If verification succeeded,
+we set the certificate-verified flag. If verification failed, what happens
+depends on whether the client is required to present a verifiable certificate
+or not.
+
+If verification is optional, we change the state to yes, but still log the
+verification error. For some reason (it really would help to have proper
+documentation of OpenSSL), this callback function then gets called again, this
+time with state = 1. We must take care not to set the private verified flag on
+the second time through.
+
+Note: this function is not called if the client fails to present a certificate
+when asked. We get here only if a certificate has been received. Handling of
+optional verification for this case is done when requesting SSL to verify, by
+setting SSL_VERIFY_FAIL_IF_NO_PEER_CERT in the non-optional case.
+
+May be called multiple times for different issues with a certificate, even
+for a given "depth" in the certificate chain.
+
+Arguments:
+ preverify_ok current yes/no state as 1/0
+ x509ctx certificate information.
+ tlsp per-direction (client vs. server) support data
+ calledp has-been-called flag
+ optionalp verification-is-optional flag
+
+Returns: 0 if verification should fail, otherwise 1
+*/
+
+static int
+verify_callback(int preverify_ok, X509_STORE_CTX * x509ctx,
+ tls_support * tlsp, BOOL * calledp, BOOL * optionalp)
+{
+X509 * cert = X509_STORE_CTX_get_current_cert(x509ctx);
+int depth = X509_STORE_CTX_get_error_depth(x509ctx);
+uschar dn[256];
+
+if (!X509_NAME_oneline(X509_get_subject_name(cert), CS dn, sizeof(dn)))
+ {
+ DEBUG(D_tls) debug_printf("X509_NAME_oneline() error\n");
+ log_write(0, LOG_MAIN, "[%s] SSL verify error: internal error",
tlsp == &tls_out ? deliver_host_address : sender_host_address);
return 0;
}
else if (depth != 0)
{
DEBUG(D_tls) debug_printf("SSL verify ok: depth=%d SN=%s\n", depth, dn);
-#ifndef DISABLE_OCSP
- if (tlsp == &tls_out && client_static_cbinfo->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,
- cert))
- ERR_clear_error();
- sk_X509_push(client_static_cbinfo->verify_stack, cert);
- }
-#endif
#ifndef DISABLE_EVENT
if (verify_event(tlsp, cert, depth, dn, calledp, optionalp, US"SSL"))
return 0; /* reject, with peercert set */
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 */
{
uschar * name;
int rc;
while ((name = string_nextinlist(&list, &sep, NULL, 0)))
+ {
+ DEBUG(D_tls|D_lookup) debug_printf_indent("%s suitable for cert, per OpenSSL?", name);
if ((rc = X509_check_host(cert, CCS name, 0,
X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS
| X509_CHECK_FLAG_SINGLE_LABEL_SUBDOMAINS,
tlsp == &tls_out ? deliver_host_address : sender_host_address);
name = NULL;
}
+ DEBUG(D_tls|D_lookup) debug_printf_indent(" yes\n");
break;
}
+ else DEBUG(D_tls|D_lookup) debug_printf_indent(" no\n");
+ }
if (!name)
#else
if (!tls_is_name_for_cert(verify_cert_hostnames, cert))
#endif
if (preverify_ok == 1)
- {
tls_out.dane_verified = TRUE;
-#ifndef DISABLE_OCSP
- if (client_static_cbinfo->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,
- cert))
- ERR_clear_error();
- sk_X509_push(client_static_cbinfo->verify_stack, cert);
- }
-#endif
- }
else
{
int err = X509_STORE_CTX_get_error(x509ctx);
#endif /*SUPPORT_DANE*/
+#ifndef DISABLE_OCSP
+static void
+time_print(BIO * bp, const char * str, ASN1_GENERALIZEDTIME * time)
+{
+BIO_printf(bp, "\t%s: ", str);
+ASN1_GENERALIZEDTIME_print(bp, time);
+BIO_puts(bp, "\n");
+}
+
/*************************************************
-* 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;
+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")))
{
- const uschar * str;
+ log_write(0, LOG_MAIN|LOG_PANIC,
+ "Failed to open OCSP response file \"%s\": %.100s",
+ filename, ERR_reason_error_string(ERR_get_error()));
+ return;
+ }
- if (where & SSL_ST_CONNECT)
- str = US"SSL_connect";
- else if (where & SSL_ST_ACCEPT)
- str = US"SSL_accept";
- else
- str = US"SSL info (undefined)";
+if (is_pem)
+ {
+ uschar * data, * freep;
+ char * dummy;
+ long len;
+ if (!PEM_read_bio(bio, &dummy, &dummy, &data, &len))
+ {
+ 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 (where & SSL_CB_LOOP)
- debug_printf("%s: %s\n", str, SSL_state_string_long(s));
- else if (where & SSL_CB_ALERT)
- debug_printf("SSL3 alert %s:%s:%s\n",
- str = where & SSL_CB_READ ? US"read" : US"write",
- SSL_alert_type_string_long(ret), SSL_alert_desc_string_long(ret));
- else if (where & SSL_CB_EXIT)
- if (ret == 0)
- debug_printf("%s: failed in %s\n", str, SSL_state_string_long(s));
- else if (ret < 0)
- debug_printf("%s: error in %s\n", str, SSL_state_string_long(s));
- else if (where & SSL_CB_HANDSHAKE_START)
- debug_printf("%s: hshake start: %s\n", str, SSL_state_string_long(s));
- else if (where & SSL_CB_HANDSHAKE_DONE)
- debug_printf("%s: hshake done: %s\n", str, SSL_state_string_long(s));
+if (!resp)
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC, "Error reading OCSP response from \"%s\": %s",
+ 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);
-}
+if ((status = OCSP_response_status(resp)) != OCSP_RESPONSE_STATUS_SUCCESSFUL)
+ {
+ DEBUG(D_tls) debug_printf("OCSP response not valid: %s (%d)\n",
+ OCSP_response_status_str(status), status);
+ goto bad;
+ }
+
+#ifdef notdef
+ {
+ BIO * bp = BIO_new_fp(debug_file, BIO_NOCLOSE);
+ OCSP_RESPONSE_print(bp, resp, 0); /* extreme debug: stapling content */
+ BIO_free(bp);
+ }
#endif
+if (!(basic_response = OCSP_response_get1_basic(resp)))
+ {
+ DEBUG(D_tls)
+ debug_printf("OCSP response parse error: unable to extract basic response.\n");
+ goto bad;
+ }
-#ifdef EXPERIMENTAL_TLS_RESUME
-/* Manage the keysets used for encrypting the session tickets, on the server. */
+sk = state->u_ocsp.server.verify_stack; /* set by setup_certs() / chain_from_pem_file() */
-typedef struct { /* Session ticket encryption key */
- uschar name[16];
+/* May need to expose ability to adjust those flags?
+OCSP_NOSIGS OCSP_NOVERIFY OCSP_NOCHAIN OCSP_NOCHECKS OCSP_NOEXPLICIT
+OCSP_TRUSTOTHER OCSP_NOINTERN */
- const EVP_CIPHER * aes_cipher;
- uschar aes_key[32]; /* size needed depends on cipher. aes_128 implies 128/8 = 16? */
- const EVP_MD * hmac_hash;
- uschar hmac_key[16];
- time_t renew;
- time_t expire;
-} exim_stek;
+/* This does a partial verify (only the signer link, not the whole chain-to-CA)
+on the OCSP proof before we load it for serving up; possibly overkill -
+just date-checks might be nice enough.
+
+OCSP_basic_verify takes a "store" arg, but does not
+use it for the chain verification, 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) ? ]
+[ 3.0.0 - sk = X509_STORE_get1_all_certs(store) ]
+We do not free the stack since it could be needed a second time for
+SNI handling.
+
+Separately we might try to replace using OCSP_basic_verify() - which seems to not
+be a public interface into the OpenSSL library (there's no manual entry) -
+(in 3.0.0 + it is public)
+But what with? We also use OCSP_basic_verify in the client stapling callback.
+And there we NEED it; we must verify that status... unless the
+library does it for us anyway? */
+
+if ((i = OCSP_basic_verify(basic_response, sk, NULL, OCSP_NOVERIFY)) < 0)
+ {
+ DEBUG(D_tls)
+ {
+ ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
+ debug_printf("OCSP response has bad signature: %s\n", US ssl_errstring);
+ }
+ goto bad;
+ }
+
+/* Here's the simplifying assumption: there's only one response, for the
+one certificate we use, and nothing for anything else in a chain. If this
+proves false, we need to extract a cert id from our issued cert
+(tls_certificate) and use that for OCSP_resp_find_status() (which finds the
+right cert in the stack and then calls OCSP_single_get0_status()).
+
+I'm hoping to avoid reworking a bunch more of how we handle state here.
+
+XXX that will change when we add support for (TLS1.3) whole-chain stapling
+*/
+
+if (!(single_response = OCSP_resp_get0(basic_response, 0)))
+ {
+ DEBUG(D_tls)
+ debug_printf("Unable to get first response from OCSP basic response.\n");
+ goto bad;
+ }
+
+status = OCSP_single_get0_status(single_response, &reason, &rev, &thisupd, &nextupd);
+if (status != V_OCSP_CERTSTATUS_GOOD)
+ {
+ DEBUG(D_tls) debug_printf("OCSP response bad cert status: %s (%d) %s (%d)\n",
+ OCSP_cert_status_str(status), status,
+ OCSP_crl_reason_str(reason), reason);
+ goto bad;
+ }
+
+if (!OCSP_check_validity(thisupd, nextupd, EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX_AGE))
+ {
+ DEBUG(D_tls)
+ {
+ BIO * bp = BIO_new(BIO_s_mem());
+ uschar * s = NULL;
+ int len;
+ time_print(bp, "This OCSP Update", thisupd);
+ if (nextupd) time_print(bp, "Next OCSP Update", nextupd);
+ if ((len = (int) BIO_get_mem_data(bp, CSS &s)) > 0) debug_printf("%.*s", len, s);
+ debug_printf("OCSP status invalid times.\n");
+ }
+ goto bad;
+ }
+
+supply_response:
+ /* Add the resp to the list used by tls_server_stapling_cb() */
+ {
+ ocsp_resplist ** op = &state->u_ocsp.server.olist, * oentry;
+ while (oentry = *op)
+ op = &oentry->next;
+ *op = oentry = store_get(sizeof(ocsp_resplist), GET_UNTAINTED);
+ oentry->next = NULL;
+ oentry->resp = resp;
+ }
+return;
+
+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;
+}
-static exim_stek exim_tk; /* current key */
-static exim_stek exim_tk_old; /* previous key */
static void
-tk_init(void)
+ocsp_free_response_list(exim_openssl_state_st * state)
{
-time_t t = time(NULL);
+for (ocsp_resplist * olist = state->u_ocsp.server.olist; olist;
+ olist = olist->next)
+ OCSP_RESPONSE_free(olist->resp);
+state->u_ocsp.server.olist = NULL;
+}
+#endif /*!DISABLE_OCSP*/
-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)
+static int
+tls_add_certfile(SSL_CTX * sctx, exim_openssl_state_st * cbinfo, uschar * file,
+ uschar ** errstr)
{
-if (!exim_tk.name[0]) return NULL;
-return &exim_tk;
+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;
}
-static exim_stek *
-tk_find(const uschar * name)
+static int
+tls_add_pkeyfile(SSL_CTX * sctx, exim_openssl_state_st * cbinfo, uschar * file,
+ uschar ** errstr)
{
-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;
+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;
}
-/* Callback for session tickets, on server */
-static int
-ticket_key_callback(SSL * ssl, uschar key_name[16],
- uschar * iv, EVP_CIPHER_CTX * ctx, HMAC_CTX * hctx, int enc)
-{
-tls_support * tlsp = server_static_cbinfo->tlsp;
-exim_stek * key;
-if (enc)
- {
- DEBUG(D_tls) debug_printf("ticket_key_callback: create new session\n");
- tlsp->resumption |= RESUME_CLIENT_REQUESTED;
- if (RAND_bytes(iv, EVP_MAX_IV_LENGTH) <= 0)
- return -1; /* insufficient random */
- 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));
+/* 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.
- /*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);
+Arguments:
+ sctx the SSL_CTX* to update
+ state various parts of session state
+ errstr error string pointer
- DEBUG(D_tls) debug_printf("ticket created\n");
- return 1;
+Returns: OK/DEFER/FAIL
+*/
+
+static int
+tls_expand_session_files(SSL_CTX * sctx, exim_openssl_state_st * state,
+ uschar ** errstr)
+{
+uschar * expanded;
+
+if (!state->certificate)
+ {
+ if (!state->is_server) /* client */
+ return OK;
+ /* server */
+ if (tls_install_selfsign(sctx, errstr) != OK)
+ return DEFER;
}
else
{
- time_t now = time(NULL);
+ int err;
- DEBUG(D_tls) debug_printf("ticket_key_callback: retrieve session\n");
- tlsp->resumption |= RESUME_CLIENT_SUGGESTED;
+ 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;
- if (!(key = tk_find(key_name)) || key->expire < now)
+ if ( !expand_check(state->certificate, US"tls_certificate", &expanded, errstr)
+ || f.expand_string_forcedfail)
{
- 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;
+ if (f.expand_string_forcedfail)
+ *errstr = US"expansion of tls_certificate failed";
+ return DEFER;
}
- 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);
+ 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;
- DEBUG(D_tls) debug_printf("ticket usable, STEK expire " TIME_T_FMT "\n", key->expire - now);
+ if (olist)
+ if (!expand_check(olist, US"tls_ocsp_file", USS &olist, errstr))
+ return DEFER;
+ if (olist && !*olist)
+ olist = NULL;
- /* 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;
- }
-}
+ /* If doing a re-expand after SNI, avoid reloading the OCSP
+ responses when the list of filenames has not changed.
+ The creds-invali on content change wipes file_expanded, so that
+ always reloads here. */
+
+ if ( state->u_ocsp.server.file_expanded && olist
+ && (Ustrcmp(olist, state->u_ocsp.server.file_expanded) == 0))
+ {
+ DEBUG(D_tls) debug_printf(" - value unchanged, using existing values\n");
+ olist = NULL;
+ }
+ else
+ {
+ ocsp_free_response_list(state);
+ state->u_ocsp.server.file_expanded = olist;
+ }
#endif
+ while (file = string_nextinlist(&file_list, &sep, NULL, 0))
+ {
+ if ((err = tls_add_certfile(sctx, state, file, errstr)))
+ return err;
+#ifndef DISABLE_OCSP
+ if (olist)
+ if ((ofile = string_nextinlist(&olist, &osep, NULL, 0)))
+ {
+ if (Ustrncmp(ofile, US"PEM ", 4) == 0)
+ {
+ fmt_pem = TRUE;
+ ofile += 4;
+ }
+ else if (Ustrncmp(ofile, US"DER ", 4) == 0)
+ {
+ fmt_pem = FALSE;
+ ofile += 4;
+ }
+ ocsp_load_response(state, ofile, fmt_pem);
+ }
+ else
+ DEBUG(D_tls) debug_printf("ran out of ocsp file list\n");
+#endif
+ }
+ }
+ else /* would there ever be a need for multiple client certs? */
+ if ((err = tls_add_certfile(sctx, state, expanded, errstr)))
+ return err;
-/*************************************************
-* Initialize for DH *
-*************************************************/
+ if ( state->privatekey
+ && !expand_check(state->privatekey, US"tls_privatekey", &expanded, errstr)
+ || f.expand_string_forcedfail)
+ {
+ if (f.expand_string_forcedfail)
+ *errstr = US"expansion of tls_privatekey failed";
+ return DEFER;
+ }
-/* If dhparam is set, expand it, and load up the parameters for DH encryption.
+ /* If expansion was forced to fail, key_expanded will be NULL. If the result
+ of the expansion is an empty string, ignore it also, and assume the private
+ key is in the same file as the certificate. */
-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
+ if (expanded && *expanded)
+ if (state->is_server)
+ {
+ const uschar * file_list = expanded;
+ int sep = 0;
+ uschar * file;
-Returns: TRUE if OK (nothing to set up, or setup worked)
-*/
+ 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;
+ }
-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;
+return OK;
+}
-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;
- }
+/**************************************************
+* One-time init credentials for server and client *
+**************************************************/
-/* 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
+static void
+normalise_ciphers(uschar ** ciphers, const uschar * pre_expansion_ciphers)
+{
+uschar * s = *ciphers;
-/* Even if it is larger, we silently return success rather than cause things
- * to fail out, so that a too-large DH will not knock out all TLS; it's a
- * debatable choice. */
-if (dh_bitsize > tls_dh_max_bits)
- {
- DEBUG(D_tls)
- debug_printf("dhparams file %d bits, is > tls_dh_max_bits limit of %d\n",
- dh_bitsize, tls_dh_max_bits);
- }
-else
- {
- SSL_CTX_set_tmp_dh(sctx, dh);
- DEBUG(D_tls)
- debug_printf("Diffie-Hellman initialized from %s with %d-bit prime\n",
- dhexpanded ? dhexpanded : US"default", dh_bitsize);
- }
+if (!s || !Ustrchr(s, '_')) return; /* no change needed */
-DH_free(dh);
-BIO_free(bio);
+if (s == pre_expansion_ciphers)
+ s = string_copy(s); /* get writable copy */
-return TRUE;
+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)
+{
+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;
+}
-/*************************************************
-* 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.
+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);
-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.
+/* Set up the information callback, which outputs if debugging is at a suitable
+level. */
-Patches welcome.
+DEBUG(D_tls)
+ {
+ SSL_CTX_set_info_callback(ctx, info_callback);
+#if defined(EXIM_HAVE_OPENSSL_TRACE) && !defined(OPENSSL_NO_SSL_TRACE)
+ /* this needs a debug build of OpenSSL */
+ SSL_CTX_set_msg_callback(ctx, SSL_trace);
+#endif
+#ifdef OPENSSL_HAVE_KEYLOG_CB
+ SSL_CTX_set_keylog_callback(ctx, keylog_callback);
+#endif
+ }
-Arguments:
- sctx The current SSL CTX (inbound or outbound)
- host connected host, if client; NULL if server
- errstr error string pointer
+/* Automatically re-try reads/writes after renegotiation. */
+(void) SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);
+*ctxp = ctx;
+return OK;
+}
-Returns: TRUE if OK (nothing to set up, or setup worked)
-*/
-static BOOL
-init_ecdh(SSL_CTX * sctx, host_item * host, uschar ** errstr)
+static unsigned
+tls_server_creds_init(void)
{
-#ifdef OPENSSL_NO_ECDH
-return TRUE;
-#else
+SSL_CTX * ctx;
+uschar * dummy_errstr;
+unsigned lifetime = 0;
-EC_KEY * ecdh;
-uschar * exp_curve;
-int nid;
-BOOL rv;
+tls_openssl_init();
-if (host) /* No ECDH setup for clients, only for servers */
- return TRUE;
+state_server.lib_state = null_tls_preload;
-# ifndef EXIM_HAVE_ECDH
-DEBUG(D_tls)
- debug_printf("No OpenSSL API to define ECDH parameters, skipping\n");
-return TRUE;
-# else
+if (lib_ctx_new(&ctx, NULL, &dummy_errstr) != OK)
+ return 0;
+state_server.lib_state.lib_ctx = ctx;
-if (!expand_check(tls_eccurve, US"tls_eccurve", &exp_curve, errstr))
- return FALSE;
-if (!exp_curve || !*exp_curve)
- return TRUE;
+/* Preload DH params and EC curve */
-/* "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 (opt_unset_or_noexpand(tls_dhparam))
{
-#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("TLS: preloading DH params '%s' for server\n", tls_dhparam);
+ if (init_dh(ctx, tls_dhparam, &dummy_errstr))
+ state_server.lib_state.dh = TRUE;
}
-
-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
- )
+else
+ DEBUG(D_tls) debug_printf("TLS: not preloading DH params for server\n");
+if (opt_unset_or_noexpand(tls_eccurve))
{
- tls_error(string_sprintf("Unknown curve name tls_eccurve '%s'", exp_curve),
- host, NULL, errstr);
- return FALSE;
+ DEBUG(D_tls) debug_printf("TLS: preloading ECDH curve '%s' for server\n", tls_eccurve);
+ if (init_ecdh(ctx, &dummy_errstr))
+ state_server.lib_state.ecdh = TRUE;
}
+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 Authorities for checking client certs against.
+Actual choice to do verify is made (tls_{,try_}verify_hosts)
+at TLS conn startup.
+Do this before the server ocsp so that its info can verify the ocsp. */
-if (!(ecdh = EC_KEY_new_by_curve_name(nid)))
+if ( opt_set_and_noexpand(tls_verify_certificates)
+ && opt_unset_or_noexpand(tls_crl))
{
- tls_error(US"Unable to create ec curve", host, NULL, errstr);
- return FALSE;
- }
+ /* Watch the default dir also as they are always included */
-/* The "tmp" in the name here refers to setting a temporary key
-not to the stability of the interface. */
+ if ( tls_set_watch(CUS X509_get_default_cert_file(), FALSE)
+ && tls_set_watch(tls_verify_certificates, FALSE)
+ && tls_set_watch(tls_crl, FALSE))
+ {
+ uschar * v_certs = tls_verify_certificates;
+ DEBUG(D_tls) debug_printf("TLS: preloading CA bundle for server\n");
+
+ if (setup_certs(ctx, &v_certs, tls_crl, NULL, &dummy_errstr) == OK)
+ state_server.lib_state.cabundle = TRUE;
+
+ /* 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))
+ {
+ /* 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))
+ {
+ state_server.certificate = tls_certificate;
+ state_server.privatekey = tls_privatekey;
+#ifndef DISABLE_OCSP
+ state_server.u_ocsp.server.file = tls_ocsp_file;
+# endif
-if ((rv = SSL_CTX_set_tmp_ecdh(sctx, ecdh) == 0))
- tls_error(string_sprintf("Error enabling '%s' curve", exp_curve), host, NULL, errstr);
+ DEBUG(D_tls) debug_printf("TLS: preloading server certs\n");
+ if (tls_expand_session_files(ctx, &state_server, &dummy_errstr) == OK)
+ state_server.lib_state.conn_certs = TRUE;
+ }
+ }
+ else if ( !tls_certificate && !tls_privatekey
+# ifndef DISABLE_OCSP
+ && !tls_ocsp_file
+# endif
+ )
+ { /* Generate & preload a selfsigned cert. No files to watch. */
+ if (tls_expand_session_files(ctx, &state_server, &dummy_errstr) == OK)
+ {
+ state_server.lib_state.conn_certs = TRUE;
+ lifetime = f.running_in_test_harness ? 2 : 60 * 60; /* 1 hour */
+ }
+ }
+ else
+ DEBUG(D_tls) debug_printf("TLS: not preloading server certs\n");
+ }
+ }
else
- DEBUG(D_tls) debug_printf("ECDH: enabled '%s' curve\n", exp_curve);
+ DEBUG(D_tls) debug_printf("TLS: not preloading CA bundle for server\n");
-EC_KEY_free(ecdh);
-return !rv;
-# endif /*EXIM_HAVE_ECDH*/
-#endif /*OPENSSL_NO_ECDH*/
-}
+#endif /* EXIM_HAVE_INOTIFY */
+/* If we can, preload the ciphers control string */
+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;
+ }
+else
+ DEBUG(D_tls) debug_printf("TLS: not preloading cipher list for server\n");
+return lifetime;
+}
-#ifndef DISABLE_OCSP
-/*************************************************
-* Load OCSP information into state *
-*************************************************/
-/* Called to load the server OCSP response from the given file into memory, once
-caller has determined this is needed. Checks validity. Debugs a message
-if invalid.
-ASSUMES: single response, for single cert.
-Arguments:
- sctx the SSL_CTX* to update
- cbinfo various parts of session state
- filename the filename putatively holding an OCSP response
- is_pem file is PEM format; otherwise is DER
-*/
+/* Preload whatever creds are static, onto a transport. The client can then
+just copy the pointer as it starts up.
+Called from the daemon after a cache-invalidate with watch set; called from
+a queue-run startup with watch clear. */
static void
-ocsp_load_response(SSL_CTX * sctx, tls_ext_ctx_cb * cbinfo,
- const uschar * filename, BOOL is_pem)
+tls_client_creds_init(transport_instance * t, BOOL watch)
{
-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;
+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;
-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")))
- {
- DEBUG(D_tls) debug_printf("Failed to open OCSP response file \"%s\"\n",
- filename);
+ob->tls_preload = null_tls_preload;
+if (lib_ctx_new(&ctx, dummy_host, &dummy_errstr) != OK)
return;
- }
+ob->tls_preload.lib_ctx = ctx;
-if (is_pem)
+tpt_dummy_state.lib_state = ob->tls_preload;
+
+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
+if ( opt_set_and_noexpand(ob->tls_certificate)
+ && opt_unset_or_noexpand(ob->tls_privatekey))
{
- uschar * data, * freep;
- char * dummy;
- long len;
- if (!PEM_read_bio(bio, &dummy, &dummy, &data, &len))
+ if ( !watch
+ || ( tls_set_watch(ob->tls_certificate, FALSE)
+ && tls_set_watch(ob->tls_privatekey, FALSE)
+ ) )
{
- DEBUG(D_tls) debug_printf("Failed to read PEM file \"%s\"\n",
- filename);
- return;
+ 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;
}
-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 client certs, for transport '%s'\n", t->name);
-if (!resp)
- {
- DEBUG(D_tls) debug_printf("Error reading OCSP response.\n");
- return;
- }
-if ((status = OCSP_response_status(resp)) != OCSP_RESPONSE_STATUS_SUCCESSFUL)
+if ( opt_set_and_noexpand(ob->tls_verify_certificates)
+ && opt_unset_or_noexpand(ob->tls_crl))
{
- DEBUG(D_tls) debug_printf("OCSP response not valid: %s (%d)\n",
- OCSP_response_status_str(status), status);
- goto bad;
- }
+ 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)
+ )
+ {
+ uschar * v_certs = ob->tls_verify_certificates;
+ DEBUG(D_tls)
+ debug_printf("TLS: preloading CA bundle for transport '%s'\n", t->name);
-#ifdef notdef
- {
- BIO * bp = BIO_new_fp(debug_file, BIO_NOCLOSE);
- OCSP_RESPONSE_print(bp, resp, 0); /* extreme debug: stapling content */
- BIO_free(bp);
+ if (setup_certs(ctx, &v_certs,
+ ob->tls_crl, dummy_host, &dummy_errstr) == OK)
+ ob->tls_preload.cabundle = TRUE;
+ }
}
-#endif
-
-if (!(basic_response = OCSP_response_get1_basic(resp)))
- {
+else
DEBUG(D_tls)
- debug_printf("OCSP response parse error: unable to extract basic response.\n");
- goto bad;
- }
+ debug_printf("TLS: not preloading CA bundle, for transport '%s'\n", t->name);
-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 */
+#endif /*EXIM_HAVE_INOTIFY*/
+}
-/* 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.
-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 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. */
-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.
+static void
+tls_server_creds_invalidate(void)
+{
+SSL_CTX_free(state_server.lib_state.lib_ctx);
+state_server.lib_state = null_tls_preload;
+#ifndef DISABLE_OCSP
+state_server.u_ocsp.server.file_expanded = NULL;
+#endif
+}
-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;
- }
+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;
+}
-/* 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()).
+#else
-I'm hoping to avoid reworking a bunch more of how we handle state here.
+static void
+tls_server_creds_invalidate(void)
+{ return; }
-XXX that will change when we add support for (TLS1.3) whole-chain stapling
-*/
+static void
+tls_client_creds_invalidate(transport_instance * t)
+{ return; }
-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;
- }
+#endif /*EXIM_HAVE_INOTIFY*/
-status = OCSP_single_get0_status(single_response, &reason, &rev, &thisupd, &nextupd);
-if (status != V_OCSP_CERTSTATUS_GOOD)
- {
- DEBUG(D_tls) debug_printf("OCSP response bad cert status: %s (%d) %s (%d)\n",
- OCSP_cert_status_str(status), status,
- OCSP_crl_reason_str(reason), reason);
- goto bad;
- }
-if (!OCSP_check_validity(thisupd, nextupd, EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX_AGE))
+/* Extreme debug
+ * */
+#ifndef DISABLE_OCSP
+static void
+debug_print_sn(const X509 * cert)
+{
+X509_NAME * sn = X509_get_subject_name((X509 *)cert);
+static uschar name[256];
+if (X509_NAME_oneline(sn, CS name, sizeof(name)))
{
- DEBUG(D_tls) debug_printf("OCSP status invalid times.\n");
- goto bad;
+ name[sizeof(name)-1] = '\0';
+ debug_printf(" %s\n", name);
}
+}
-supply_response:
- /* Add the resp to the list used by tls_server_stapling_cb() */
+static void
+x509_stack_dump_cert_s_names(const STACK_OF(X509) * sk)
+{
+if (!sk)
+ debug_printf(" (null)\n");
+else
{
- 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;
- }
-return;
-
-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;
+ int idx = sk_X509_num(sk);
+ if (!idx)
+ debug_printf(" (empty)\n");
+ else
+ while (--idx >= 0) debug_print_sn(sk_X509_value(sk, idx));
+ }
}
-
static void
-ocsp_free_response_list(tls_ext_ctx_cb * cbinfo)
+x509_store_dump_cert_s_names(X509_STORE * store)
{
-for (ocsp_resplist * olist = cbinfo->u_ocsp.server.olist; olist;
- olist = olist->next)
- OCSP_RESPONSE_free(olist->resp);
-cbinfo->u_ocsp.server.olist = NULL;
+# ifdef EXIM_HAVE_OPENSSL_X509_STORE_GET1_ALL_CERTS
+if (!store)
+ debug_printf(" (no store)\n");
+else
+ {
+ STACK_OF(X509) * sk = X509_STORE_get1_all_certs(store);
+ x509_stack_dump_cert_s_names(sk);
+ sk_X509_pop_free(sk, X509_free);
+ }
+# endif
}
#endif /*!DISABLE_OCSP*/
+/*
+*/
+#ifndef DISABLE_TLS_RESUME
+/* Manage the keysets used for encrypting the session tickets, on the server. */
+typedef struct { /* Session ticket encryption key */
+ uschar name[16];
-/* Create and install a selfsigned certificate, for use in server mode */
-
-static int
-tls_install_selfsign(SSL_CTX * sctx, uschar ** errstr)
-{
-X509 * x509 = NULL;
-EVP_PKEY * pkey;
-RSA * rsa;
-X509_NAME * name;
-uschar * where;
-
-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)60 * 60); /* 1 hour */
-X509_set_pubkey(x509, pkey);
+ 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_stek;
-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);
+static exim_stek exim_tk; /* current key */
+static exim_stek exim_tk_old; /* previous key */
-where = US"signing cert";
-if (!X509_sign(x509, pkey, EVP_md5()))
- goto err;
+static void
+tk_init(void)
+{
+time_t t = time(NULL);
-where = US"installing selfsign cert";
-if (!SSL_CTX_use_certificate(sctx, x509))
- goto err;
+if (exim_tk.name[0])
+ {
+ if (exim_tk.renew >= t) return;
+ exim_tk_old = exim_tk;
+ }
-where = US"installing selfsign key";
-if (!SSL_CTX_use_PrivateKey(sctx, pkey))
- goto err;
+if (f.running_in_test_harness) ssl_session_timeout = TESTSUITE_TICKET_LIFE;
-return OK;
+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;
-err:
- (void) tls_error(where, NULL, NULL, errstr);
- if (x509) X509_free(x509);
- if (pkey) EVP_PKEY_free(pkey);
- return DEFER;
+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;
}
+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;
+}
static int
-tls_add_certfile(SSL_CTX * sctx, tls_ext_ctx_cb * cbinfo, uschar * file,
- uschar ** errstr)
+tk_hmac_init(
+# if OPENSSL_VERSION_NUMBER < 0x30000000L
+ HMAC_CTX * hctx,
+#else
+ EVP_MAC_CTX * hctx,
+#endif
+ exim_stek * key
+ )
{
-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;
+/*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
-tls_add_pkeyfile(SSL_CTX * sctx, tls_ext_ctx_cb * cbinfo, uschar * file,
- uschar ** errstr)
+ticket_key_callback(SSL * ssl, uschar key_name[16],
+ uschar * iv, EVP_CIPHER_CTX * c_ctx,
+# if OPENSSL_VERSION_NUMBER < 0x30000000L
+ HMAC_CTX * hctx,
+#else
+ EVP_MAC_CTX * hctx,
+#endif
+ int enc)
{
-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;
-}
-
-
-/*************************************************
-* Expand key and cert file specs *
-*************************************************/
+tls_support * tlsp = state_server.tlsp;
+exim_stek * key;
-/* 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;
+ if (tk_hmac_init(hctx, key) == 0) return 0;
+ 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
- }
+ debug_printf("ticket not usable (%s)\n", key ? "expired" : "not found");
+ if (key) debug_printf("STEK expire " TIME_T_FMT "\n", key->expire - now);
}
- else /* would there ever be a need for multiple client certs? */
- if ((err = tls_add_certfile(sctx, cbinfo, expanded, errstr)))
- return err;
-
- if ( cbinfo->privatekey
- && !expand_check(cbinfo->privatekey, US"tls_privatekey", &expanded, errstr))
- return DEFER;
+ 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. */
+ if (tk_hmac_init(hctx, key) == 0) return 0;
+ 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 /* !DISABLE_TLS_RESUME */
+
+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);
+}
/*************************************************
#ifdef EXIM_HAVE_OPENSSL_TLSEXT
static int
-tls_servername_cb(SSL *s, int *ad ARG_UNUSED, void *arg)
+tls_servername_cb(SSL * s, int * ad ARG_UNUSED, void * arg)
{
-const char *servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name);
-tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg;
+const char * servername = SSL_get_servername(s, TLSEXT_NAMETYPE_host_name);
+exim_openssl_state_st * state = (exim_openssl_state_st *) arg;
int rc;
int old_pool = store_pool;
-uschar * dummy_errstr;
+uschar * errstr;
if (!servername)
return SSL_TLSEXT_ERR_OK;
/* 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)
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, &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));
+#ifdef OPENSSL_MIN_PROTO_VERSION
+ SSL_CTX_set_min_proto_version(server_sni, SSL3_VERSION);
+#endif
+ SSL_CTX_set_options(server_sni, SSL_CTX_get_options(ctx));
+ SSL_CTX_clear_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)
- || !init_ecdh(server_sni, NULL, &dummy_errstr)
+if ( !init_dh(server_sni, state->dhparam, &errstr)
+ || !init_ecdh(server_sni, &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 * v_certs = tls_verify_certificates;
+ if ((rc = setup_certs(server_sni, &v_certs, tls_crl, NULL,
+ &errstr)) != OK)
+ goto bad;
+
+ if (v_certs && *v_certs)
+ 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, &errstr)) != OK)
goto bad;
DEBUG(D_tls) debug_printf("Switching SSL context.\n");
SSL_set_SSL_CTX(s, server_sni);
return SSL_TLSEXT_ERR_OK;
-bad: return SSL_TLSEXT_ERR_ALERT_FATAL;
+bad:
+ log_write(0, LOG_MAIN|LOG_PANIC, "%s", errstr);
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
}
#endif /* EXIM_HAVE_OPENSSL_TLSEXT */
+#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)
+{
+gstring * g = NULL;
+
+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 client, or name did not match our list. */
+
+/* This will be fatal to the TLS conn; would be nice to kill TCP also.
+Maybe as an option in future; for now leave control to the config (must-tls). */
+
+for (int pos = 0, siz; pos < inlen; pos += siz+1)
+ {
+ siz = in[pos];
+ if (pos + 1 + siz > inlen) siz = inlen - pos - 1;
+ g = string_append_listele_n(g, ':', in + pos + 1, siz);
+ }
+log_write(0, LOG_MAIN, "TLS ALPN (%Y) rejected", g);
+gstring_release_unused(g);
+return SSL_TLSEXT_ERR_ALERT_FATAL;
+}
+#endif /* EXIM_HAVE_ALPN */
+
+
+
#ifndef DISABLE_OCSP
/*************************************************
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;
if (!olist)
return SSL_TLSEXT_ERR_NOACK;
-#ifdef EXIM_HAVE_OPESSL_GET0_SERIAL
+#ifdef EXIM_HAVE_OPENSSL_GET0_SERIAL
{
const X509 * cert_sent = SSL_get_certificate(s);
const ASN1_INTEGER * cert_serial = X509_get0_serialNumber(cert_sent);
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)
{
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;
}
static void
-time_print(BIO * bp, const char * str, ASN1_GENERALIZEDTIME * time)
+add_chain_to_store(X509_STORE * store, STACK_OF(X509) * sk,
+ const char * debug_text)
{
-BIO_printf(bp, "\t%s: ", str);
-ASN1_GENERALIZEDTIME_print(bp, time);
-BIO_puts(bp, "\n");
+int idx;
+
+DEBUG(D_tls)
+ {
+ debug_printf("chain for %s:\n", debug_text);
+ x509_stack_dump_cert_s_names(sk);
+ }
+if (sk)
+ if ((idx = sk_X509_num(sk)) > 0)
+ while (--idx >= 0)
+ X509_STORE_add_cert(store, sk_X509_value(sk, idx));
+
}
static int
-tls_client_stapling_cb(SSL *s, void *arg)
+tls_client_stapling_cb(SSL * ssl, void * arg)
{
-tls_ext_ctx_cb * cbinfo = arg;
+exim_openssl_state_st * cbinfo = arg;
const unsigned char * p;
int len;
OCSP_RESPONSE * rsp;
int i;
DEBUG(D_tls) debug_printf("Received TLS status callback (OCSP stapling):\n");
-len = SSL_get_tlsext_status_ocsp_resp(s, &p);
+len = SSL_get_tlsext_status_ocsp_resp(ssl, &p);
if(!p)
- {
- /* Expect this when we requested ocsp but got none */
+ { /* Expect this when we requested ocsp but got none */
+ if (SSL_session_reused(ssl) && tls_out.ocsp == OCSP_VFIED)
+ {
+ DEBUG(D_tls) debug_printf(" null, but resumed; ocsp vfy stored with session is good\n");
+ return 1;
+ }
+
if (cbinfo->u_ocsp.client.verify_required && LOGGING(tls_cipher))
log_write(0, LOG_MAIN, "Required TLS certificate status not received");
else
DEBUG(D_tls) debug_printf(" null\n");
- return cbinfo->u_ocsp.client.verify_required ? 0 : 1;
- }
+
+ if (!cbinfo->u_ocsp.client.verify_required)
+ return 1;
+ cbinfo->u_ocsp.client.verify_errstr =
+ US"(SSL_connect) Required TLS certificate status not received";
+ return 0;
+ }
if (!(rsp = d2i_OCSP_RESPONSE(NULL, &p, len)))
{
*/
{
BIO * bp = NULL;
+ X509_STORE * verify_store = NULL;
+ BOOL have_verified_OCSP_signer = FALSE;
#ifndef EXIM_HAVE_OCSP_RESP_COUNT
STACK_OF(OCSP_SINGLERESP) * sresp = bs->tbsResponseData->responses;
#endif
- DEBUG(D_tls) bp = BIO_new_fp(debug_file, BIO_NOCLOSE);
+ DEBUG(D_tls) bp = BIO_new(BIO_s_mem());
+
+ /* Use the CA & chain that verified the server cert to verify the stapled info */
+ /*XXX could we do an event here, for observability of ocsp? What reasonable data could we give access to? */
+ /* Dates would be a start. Do we need another opaque variable type, as for certs, plus an extract expansion? */
+
+ {
+ /* If this routine is not available, we've avoided [in tls_client_start()]
+ asking for certificate-status under DANE, so this callback won't run for
+ that combination. It still will for non-DANE. */
+
+#if defined(EXIM_HAVE_OPENSSL_OCSP_RESP_GET0_SIGNER) && defined(SUPPORT_DANE)
+ X509 * signer;
+
+ if ( tls_out.dane_verified
+ && (have_verified_OCSP_signer =
+ OCSP_resp_get0_signer(bs, &signer, SSL_get0_verified_chain(ssl)) == 1))
+ {
+ DEBUG(D_tls)
+ debug_printf("signer for OCSP basicres is in the verified chain;"
+ " shortcut its verification\n");
+ }
+ else
+#endif
+ {
+ STACK_OF(X509) * verified_chain;
+
+ verify_store = X509_STORE_new();
+
+ SSL_get0_chain_certs(ssl, &verified_chain);
+ add_chain_to_store(verify_store, verified_chain,
+ "'current cert' per SSL_get0_chain_certs()");
+#ifdef EXIM_HAVE_SSL_GET0_VERIFIED_CHAIN
+ verified_chain = SSL_get0_verified_chain(ssl);
+ add_chain_to_store(verify_store, verified_chain,
+ "SSL_get0_verified_chain()");
+#endif
+ }
+ }
+
+ DEBUG(D_tls)
+ {
+ debug_printf("Untrusted intermediate cert stack (from SSL_get_peer_cert_chain()):\n");
+ x509_stack_dump_cert_s_names(SSL_get_peer_cert_chain(ssl));
+
+ debug_printf("will use this CA store for verifying basicresp:\n");
+ x509_store_dump_cert_s_names(verify_store);
+
+ /* OCSP_RESPONSE_print(bp, rsp, 0); extreme debug: stapling content */
+
+ debug_printf("certs contained in basicresp:\n");
+ x509_stack_dump_cert_s_names(
+#ifdef EXIM_HAVE_OPENSSL_OCSP_RESP_GET0_CERTS
+ OCSP_resp_get0_certs(bs)
+#else
+ bs->certs
+#endif
+ );
+
+#ifdef EXIM_HAVE_OPENSSL_X509_STORE_GET1_ALL_CERTS
+/* could do via X509_STORE_get0_objects(); not worth it just for debug info */
+ {
+ X509 * signer;
+ if (OCSP_resp_get0_signer(bs, &signer, X509_STORE_get1_all_certs(verify_store)) == 1)
+ {
+ debug_printf("found signer for basicres:\n");
+ debug_print_sn(signer);
+ }
+ else
+ {
+ debug_printf("failed to find signer for basicres:\n");
+ ERR_print_errors(bp);
+ }
+ }
+#endif
+
+ }
+
+ ERR_clear_error();
+
+ /* Under DANE the trust-anchor (at least in TA mode) is indicated by the TLSA
+ record in DNS, and probably is not the root of the chain of certificates. So
+ accept a partial chain for that case (and hope that anchor is visible for
+ verifying the OCSP stapling).
+ XXX for EE mode it won't even be that. Does that make OCSP useless for EE?
- /*OCSP_RESPONSE_print(bp, rsp, 0); extreme debug: stapling content */
+ Worse, for LetsEncrypt-mode (ocsp signer is leaf-signer) under DANE, the
+ data used within OpenSSL for the signer has nil pointers for signing
+ algorithms - and a crash results. Avoid this by shortcutting verification,
+ having determined that the OCSP signer is in the (DANE-)validated set.
+ */
- /* Use the chain that verified the server cert to verify the stapled info */
- /* DEBUG(D_tls) x509_store_dump_cert_s_names(cbinfo->u_ocsp.client.verify_store); */
+#ifndef OCSP_PARTIAL_CHAIN /* defined for 3.0.0 onwards */
+# define OCSP_PARTIAL_CHAIN 0
+#endif
- if ((i = OCSP_basic_verify(bs, cbinfo->verify_stack,
- cbinfo->u_ocsp.client.verify_store, OCSP_NOEXPLICIT)) <= 0)
+ if ((i = OCSP_basic_verify(bs, SSL_get_peer_cert_chain(ssl),
+ verify_store,
+#ifdef SUPPORT_DANE
+ tls_out.dane_verified
+ ? have_verified_OCSP_signer
+ ? OCSP_NOVERIFY | OCSP_NOEXPLICIT
+ : OCSP_PARTIAL_CHAIN | OCSP_NOEXPLICIT
+ :
+#endif
+ OCSP_NOEXPLICIT)) <= 0)
+ {
+ DEBUG(D_tls) debug_printf("OCSP_basic_verify() fail: returned %d\n", i);
if (ERR_peek_error())
{
tls_out.ocsp = OCSP_FAILED;
- if (LOGGING(tls_cipher)) 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);
+ if (LOGGING(tls_cipher))
+ {
+ static uschar peerdn[256];
+ const uschar * errstr;;
+
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+ ERR_peek_error_all(NULL, NULL, NULL, CCSS &errstr, NULL);
+ if (!errstr)
+#endif
+ errstr = CUS ERR_reason_error_string(ERR_peek_error());
+
+ X509_NAME_oneline(X509_get_subject_name(SSL_get_peer_certificate(ssl)),
+ CS peerdn, sizeof(peerdn));
+ log_write(0, LOG_MAIN,
+ "[%s] %s Received TLS cert (DN: '%.*s') status response, "
+ "itself unverifiable: %s",
+ deliver_host_address, deliver_host,
+ (int)sizeof(peerdn), peerdn, errstr);
+ }
+ DEBUG(D_tls)
+ {
+ BIO_printf(bp, "OCSP response verify failure\n");
+ ERR_print_errors(bp);
+ {
+ uschar * s = NULL;
+ int len = (int) BIO_get_mem_data(bp, CSS &s);
+ if (len > 0) debug_printf("%.*s", len, s);
+ BIO_reset(bp);
+ }
+ OCSP_RESPONSE_print(bp, rsp, 0);
+ }
goto failed;
}
else
DEBUG(D_tls) debug_printf("no explicit trust for OCSP signing"
" in the root CA certificate; ignoring\n");
+ }
DEBUG(D_tls) debug_printf("OCSP response well-formed and signed OK\n");
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");
+ cbinfo->u_ocsp.client.verify_errstr =
+ US"(SSL_connect) Server certificate status is out-of-date";
+ log_write(0, LOG_MAIN, "OCSP dates invalid");
goto failed;
}
case V_OCSP_CERTSTATUS_GOOD:
continue; /* the idx loop */
case V_OCSP_CERTSTATUS_REVOKED:
+ cbinfo->u_ocsp.client.verify_errstr =
+ US"(SSL_connect) Server certificate revoked";
log_write(0, LOG_MAIN, "Server certificate revoked%s%s",
reason != -1 ? "; reason: " : "",
reason != -1 ? OCSP_crl_reason_str(reason) : "");
DEBUG(D_tls) time_print(bp, "Revocation Time", rev);
break;
default:
+ cbinfo->u_ocsp.client.verify_errstr =
+ US"(SSL_connect) Server certificate has unknown status";
log_write(0, LOG_MAIN,
"Server certificate status unknown, in OCSP stapling");
break;
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 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,
- tls_support * tlsp,
- uschar ** errstr)
+ 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
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,
No OpenSSL version number checks: the options we accept depend upon the
availability of the option value macros from OpenSSL. */
-if (!tls_openssl_options_parse(openssl_options, &init_options))
- return tls_error(US"openssl_options parsing failed", host, NULL, errstr);
+if (!init_options)
+ if (!tls_openssl_options_parse(openssl_options, &init_options))
+ return tls_error(US"openssl_options parsing failed", host, NULL, errstr);
+
+/* Create a context.
+The OpenSSL docs in 1.0.1b have not been updated to clarify TLS variant
+negotiation in the different methods; as far as I can tell, the only
+*_{server,client}_method which allows negotiation is SSLv23, which exists even
+when OpenSSL is built without SSLv2 support.
+By disabling with openssl_options, we can let admins re-enable with the
+existing knob. */
+
+if (!(ctx = state->lib_state.lib_ctx))
+ {
+ if ((rc = lib_ctx_new(&ctx, host, errstr)) != OK)
+ return rc;
+ state->lib_state.lib_ctx = ctx;
+ }
-#ifdef EXPERIMENTAL_TLS_RESUME
+#ifndef DISABLE_TLS_RESUME
tlsp->resumption = RESUME_SUPPORTED;
#endif
if (init_options)
{
-#ifdef EXPERIMENTAL_TLS_RESUME
+#ifndef DISABLE_TLS_RESUME
/* Should the server offer session resumption? */
if (!host && verify_check_host(&tls_resumption_hosts) == OK)
{
}
#endif
- DEBUG(D_tls) debug_printf("setting SSL CTX options: %#lx\n", init_options);
- if (!(SSL_CTX_set_options(ctx, init_options)))
- return tls_error(string_sprintf(
+#ifdef OPENSSL_MIN_PROTO_VERSION
+ SSL_CTX_set_min_proto_version(ctx, SSL3_VERSION);
+#endif
+ DEBUG(D_tls) debug_printf("setting SSL CTX options: %016lx\n", init_options);
+ SSL_CTX_set_options(ctx, init_options);
+ {
+ uint64_t readback = SSL_CTX_clear_options(ctx, ~init_options);
+ if (readback != init_options)
+ return tls_error(string_sprintf(
"SSL_CTX_set_option(%#lx)", init_options), host, NULL, errstr);
+ }
}
else
DEBUG(D_tls) debug_printf("no SSL CTX options to set\n");
/* 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 (!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, 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) /* server */
+ {
+ 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 (!host && !(state->u_ocsp.server.verify_stack = sk_X509_new_null()))
{
DEBUG(D_tls) debug_printf("failed to create stack for stapling verify\n");
return FAIL;
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
+ 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 */
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
+#endif /*EXIM_HAVE_OPENSSL_TLSEXT*/
-cbinfo->verify_cert_hostnames = NULL;
+state->verify_cert_hostnames = NULL;
#ifdef EXIM_HAVE_EPHEM_RSA_KEX
/* Set up the RSA callback */
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;
}
*************************************************/
#ifndef DISABLE_OCSP
-/* Load certs from file, return TRUE on success */
+/* In the server, load certs from file, return TRUE on success */
static BOOL
-chain_from_pem_file(const uschar * file, STACK_OF(X509) * 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
Arguments:
sctx SSL_CTX* to initialise
- certs certs file or NULL
+ certsp certs file, returned 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 ** certsp, uschar * crl, host_item * host,
+ uschar ** errstr)
{
-uschar *expcerts, *expcrl;
+uschar * expcerts, * expcrl;
-if (!expand_check(certs, US"tls_verify_certificates", &expcerts, errstr))
+if (!expand_check(*certsp, US"tls_verify_certificates", &expcerts, errstr))
return DEFER;
DEBUG(D_tls) debug_printf("tls_verify_certificates: %s\n", expcerts);
+*certsp = expcerts;
if (expcerts && *expcerts)
{
/* Tell the library to use its compiled-in location for the system default
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;
{ file = NULL; dir = expcerts; }
else
{
+ STACK_OF(X509) * verify_stack =
+#ifndef DISABLE_OCSP
+ !host ? state_server.u_ocsp.server.verify_stack :
+#endif
+ NULL;
+ STACK_OF(X509) ** vp = &verify_stack;
+
file = expcerts; dir = NULL;
#ifndef DISABLE_OCSP
- /* In the server if we will be offering an OCSP proof, load chain from
+ /* In the server if we will be offering an OCSP proof; load chain from
file for verifying the OCSP proof at load time. */
/*XXX Glitch! The file here is tls_verify_certs: the chain for verifying the client cert.
This is inconsistent with the need to verify the OCSP proof of the server cert.
*/
-
+/* *debug_printf("file for checking server ocsp stapling is: %s\n", file); */
if ( !host
&& statbuf.st_size > 0
- && 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,
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
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");
}
}
}
#endif /* OPENSSL_VERSION_NUMBER > 0x00907000L */
+ }
- /* If verification is optional, don't fail if no certificate */
+return OK;
+}
- SSL_CTX_set_verify(sctx,
- SSL_VERIFY_PEER | (optional ? 0 : SSL_VERIFY_FAIL_IF_NO_PEER_CERT),
- cert_vfy_cb);
+
+
+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
+}
+
+
+/* Channel-binding info for authenticators
+See description in https://paquier.xyz/postgresql-2/channel-binding-openssl/
+for pre-TLS1.3
+*/
+
+static void
+tls_get_channel_binding(SSL * ssl, tls_support * tlsp, const void * taintval)
+{
+uschar c, * s;
+size_t len;
+
+#ifdef EXIM_HAVE_EXPORT_CHNL_BNGNG
+if (SSL_version(ssl) > TLS1_2_VERSION)
+ {
+ /* It's not documented by OpenSSL how big the output buffer must be.
+ The OpenSSL testcases use 80 bytes but don't say why. The GnuTLS impl only
+ serves out 32B. RFC 9266 says it is 32B.
+ Interop fails unless we use the same each end. */
+ len = 32;
+
+ tlsp->channelbind_exporter = TRUE;
+ taintval = GET_UNTAINTED;
+ if (SSL_export_keying_material(ssl,
+ s = store_get((int)len, taintval), len,
+ "EXPORTER-Channel-Binding", (size_t) 24,
+ NULL, 0, 0) != 1)
+ len = 0;
+ }
+else
+#endif
+ {
+ len = SSL_get_peer_finished(ssl, &c, 0);
+ len = SSL_get_peer_finished(ssl, s = store_get((int)len, taintval), len);
}
-return OK;
+if (len > 0)
+ {
+ int old_pool = store_pool;
+ store_pool = POOL_PERM;
+ tlsp->channelbinding = b64encode_taint(CUS s, (int)len, taintval);
+ store_pool = old_pool;
+ DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage %p %p\n", tlsp->channelbinding, tlsp);
+ }
}
-
/*************************************************
* 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
*/
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 */
if (tls_in.active.sock >= 0)
{
tls_error(US"STARTTLS received after TLS started", NULL, US"", errstr);
- smtp_printf("554 Already in TLS\r\n", FALSE);
+ smtp_printf("554 Already in TLS\r\n", SP_NO_MORE);
return FAIL;
}
/* 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
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)
+ {
+ 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
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 * v_certs = tls_verify_certificates;
-#ifdef EXPERIMENTAL_TLS_RESUME
-SSL_CTX_set_tlsext_ticket_key_cb(server_ctx, ticket_key_callback);
+ if (state_server.lib_state.cabundle)
+ {
+ DEBUG(D_tls) debug_printf("TLS: CA bundle for server was preloaded\n");
+ setup_cert_verify(ctx, server_verify_optional, verify_callback_server);
+ }
+ else
+ {
+ if ((rc = setup_certs(ctx, &v_certs, tls_crl, NULL, errstr)) != OK)
+ return rc;
+ if (v_certs && *v_certs)
+ 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
-# ifdef EXPERIMENTAL_TLS_RESUME
-SSL_CTX_set_num_tickets(server_ctx, tls_in.host_resumable ? 1 : 0);
+# ifndef DISABLE_TLS_RESUME
+SSL_CTX_set_num_tickets(ctx, tls_in.host_resumable ? 1 : 0);
# else
-SSL_CTX_set_num_tickets(server_ctx, 0); /* send no TLS1.3 stateful-tickets */
+SSL_CTX_set_num_tickets(ctx, 0); /* send no TLS1.3 stateful-tickets */
# endif
#endif
/* Prepare for new connection */
-if (!(server_ssl = SSL_new(server_ctx)))
+if (!(ssl = SSL_new(ctx)))
return tls_error(US"SSL_new", NULL, NULL, errstr);
+state_server.lib_state.lib_ssl = ssl;
/* Warning: we used to SSL_clear(ssl) here, it was removed.
*
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);
+ smtp_printf("220 TLS go ahead\r\n", SP_NO_MORE);
fflush(smtp_out);
}
/* 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:
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);
-
- if (SSL_get_shutdown(server_ssl) == SSL_RECEIVED_SHUTDOWN)
- SSL_shutdown(server_ssl);
+#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);
tls_close(NULL, TLS_NO_SHUTDOWN);
return FAIL;
/* 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)", 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));
}
- (void) tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : NULL, errstr);
+ (void) tls_error(US"SSL_accept", NULL,
+ sigalrm_seen ? US"timed out"
+ : ERR_peek_error() ? NULL : string_sprintf("ret %d", error),
+ errstr);
+#ifndef DISABLE_EVENT
+ (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL);
+#endif
return FAIL;
}
}
ERR_clear_error(); /* Even success can leave errors in the stack. Seen with
anon-authentication ciphersuite negotiated. */
-#ifdef EXPERIMENTAL_TLS_RESUME
-if (SSL_session_reused(server_ssl))
+#ifndef DISABLE_TLS_RESUME
+if (SSL_session_reused(ssl))
{
tls_in.resumption |= RESUME_USED;
DEBUG(D_tls) debug_printf("Session reused\n");
}
#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. */
#ifdef SSL_get_extms_support
-tls_in.ext_master_secret = SSL_get_extms_support(server_ssl) == 1;
+/*XXX what does this return for tls1.3 ? */
+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));
- BIO_free(bp);
- }
-#endif
+ tls_dump_keylog(ssl);
#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));
/* 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;
}
-/* Channel-binding info for authenticators
-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);
- int old_pool = store_pool;
-
- SSL_get_peer_finished(server_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;
- DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage %p\n", tls_in.channelbinding);
- }
+tls_get_channel_binding(ssl, &tls_in, GET_UNTAINTED);
/* Only used by the server-side tls (tls_in), including tls_getc.
Client-side (tls_out) reads (seem to?) go via
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 */
static int
tls_client_basic_ctx_init(SSL_CTX * ctx,
- host_item * host, smtp_transport_options_block * ob, tls_ext_ctx_cb * cbinfo,
+ host_item * host, smtp_transport_options_block * ob, exim_openssl_state_st * state,
uschar ** errstr)
{
int rc;
-/* stick to the old behaviour for compatibility if tls_verify_certificates is
- set but both tls_verify_hosts and tls_try_verify_hosts is not set. Check only
- the specified host patterns if one of them is defined */
-if ( ( !ob->tls_verify_hosts
- && (!ob->tls_try_verify_hosts || !*ob->tls_try_verify_hosts)
+/* Back-compatible old behaviour if tls_verify_certificates is set but both
+tls_verify_hosts and tls_try_verify_hosts are not set. Check only the specified
+host patterns if one of them is set with content. */
+
+if ( ( ( !ob->tls_verify_hosts || !ob->tls_verify_hosts
+ || Ustrcmp(ob->tls_try_verify_hosts, ":") == 0
+ )
+ && ( !ob->tls_try_verify_hosts || !*ob->tls_try_verify_hosts
+ || Ustrcmp(ob->tls_try_verify_hosts, ":") == 0
+ )
)
|| verify_check_given_host(CUSS &ob->tls_verify_hosts, host) == OK
)
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 * v_certs = ob->tls_verify_certificates;
+
+ if (state->lib_state.cabundle)
+ {
+ DEBUG(D_tls) debug_printf("TLS: CA bundle for tpt was preloaded\n");
+ setup_cert_verify(ctx, client_verify_optional, verify_callback_client);
+ }
+ else
+ {
+ if ((rc = setup_certs(ctx, &v_certs, ob->tls_crl, host, errstr)) != OK)
+ return rc;
+ if (v_certs && *v_certs)
+ setup_cert_verify(ctx, client_verify_optional, verify_callback_client);
+ }
+ }
if (verify_check_given_host(CUSS &ob->tls_verify_cert_hostnames, host) == OK)
{
- cbinfo->verify_cert_hostnames =
+ state->verify_cert_hostnames =
#ifdef SUPPORT_I18N
- string_domain_utf8_to_alabel(host->name, NULL);
+ string_domain_utf8_to_alabel(host->certname, NULL);
#else
- host->name;
+ host->certname;
#endif
DEBUG(D_tls) debug_printf("Cert hostname to check: \"%s\"\n",
- cbinfo->verify_cert_hostnames);
+ state->verify_cert_hostnames);
}
return OK;
}
-#ifdef EXPERIMENTAL_TLS_RESUME
+#ifndef DISABLE_TLS_RESUME
/* On the client, get any stashed session for the given IP from hints db
and apply it to the ssl-connection for attempted resumption. */
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)
{
dbdata_tls_session * dt;
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;
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 ? TESTSUITE_TICKET_LIFE : ssl_session_timeout;
#endif
- else if (!SSL_set_session(ssl, ss))
- {
- DEBUG(D_tls)
+ time_t now = time(NULL), expires = lifetime + dt->time_stamp;
+ if (expires < now)
+ {
+ DEBUG(D_tls) debug_printf("session expired (by " TIME_T_FMT "s from %lus)\n", now - expires, lifetime);
+ dbfn_delete(dbm_file, tlsp->resume_index);
+ }
+ else if (SSL_set_session(ssl, ss))
+ {
+ DEBUG(D_tls) debug_printf("good session (" TIME_T_FMT "s left of %lus)\n", expires - now, lifetime);
+ tlsp->resumption |= RESUME_CLIENT_SUGGESTED;
+ tlsp->verify_override = dt->verify_override;
+ tlsp->ocsp = dt->ocsp;
+ }
+ 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");
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");
{
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
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;
/* Pick up a previous session, saved on an old ticket */
-tls_retrieve_session(tlsp, ssl, host->address);
+tls_retrieve_session(tlsp, ssl);
return TRUE;
}
tlsp->resumption |= RESUME_USED;
}
}
-#endif /* EXPERIMENTAL_TLS_RESUME */
+#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.
+
+Overwite the passed-in list with the expanded version.
+*/
+
+static BOOL
+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))
+ return FALSE;
+*tls_alpn = exp_alpn;
+
+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 */
/*************************************************
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;
# endif
request_ocsp =
verify_check_given_host(CUSS &ob->hosts_request_ocsp, host) == OK;
+
+# if defined(SUPPORT_DANE) && !defined(EXIM_HAVE_OPENSSL_OCSP_RESP_GET0_SIGNER)
+ if (conn_args->dane && (require_ocsp || request_ocsp))
+ {
+ DEBUG(D_tls) debug_printf("OpenSSL version to early to combine OCSP"
+ " and DANE; disabling OCSP\n");
+ require_ocsp = request_ocsp = FALSE;
+ }
+# endif
}
#endif
-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;
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))
{
tls_error(US"context init", host, NULL, errstr);
return FALSE;
}
+ DEBUG(D_tls) debug_printf("since dane-mode conn, not loading the usual CA bundle\n");
}
else
#endif
- if (tls_client_basic_ctx_init(exim_client_ctx->ctx, host, ob,
- client_static_cbinfo, errstr) != OK)
- return FALSE;
-
-#ifdef EXPERIMENTAL_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);
+if (tls_client_basic_ctx_init(exim_client_ctx->ctx, host, ob,
+ client_static_state, errstr) != OK)
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
}
}
+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_CTX_set_alpn_protos(exim_client_ctx->ctx, 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
+
+#ifndef DISABLE_TLS_RESUME
+/*XXX have_lbserver: another cmdline arg possibly, for continued-conn, but use
+will be very low. */
+
+if (!conn_args->have_lbserver) /* wanted for tls_client_resmption_key() */
+ { DEBUG(D_tls) debug_printf("resumption not supported on continued-connection\n"); }
+else 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)
if (request_ocsp)
{
SSL_set_tlsext_status_type(exim_client_ctx->ssl, TLSEXT_STATUSTYPE_ocsp);
- client_static_cbinfo->u_ocsp.client.verify_required = require_ocsp;
+ client_static_state->u_ocsp.client.verify_required = require_ocsp;
tlsp->ocsp = OCSP_NOT_RESP;
}
#endif
-#ifdef EXPERIMENTAL_TLS_RESUME
+#ifndef DISABLE_TLS_RESUME
if (!tls_client_ssl_resume_prehandshake(exim_client_ctx->ssl, tlsp, host,
errstr))
return FALSE;
#endif
#ifndef DISABLE_EVENT
-client_static_cbinfo->event_action = tb ? tb->event_action : NULL;
+client_static_state->event_action = tb ? tb->event_action : NULL;
#endif
/* There doesn't seem to be a built-in timeout on connection. */
if (rc <= 0)
{
- tls_error(US"SSL_connect", host, sigalrm_seen ? US"timed out" : NULL, errstr);
+#ifndef DISABLE_OCSP
+ if (client_static_state->u_ocsp.client.verify_errstr)
+ { if (errstr) *errstr = client_static_state->u_ocsp.client.verify_errstr; }
+ else
+#endif
+ tls_error(US"SSL_connect", host, sigalrm_seen ? US"timed out" : NULL, errstr);
return FALSE;
}
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);
}
-#ifdef EXPERIMENTAL_TLS_RESUME
+#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
}
/*XXX will this work with continued-TLS? */
-/* Channel-binding info for authenticators */
- {
- uschar c, * s;
- 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);
- store_pool = POOL_PERM;
- tlsp->channelbinding = b64encode_taint(CUS s, (int)len, TRUE);
- store_pool = old_pool;
- DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage %p %p\n", tlsp->channelbinding, tlsp);
- }
+tls_get_channel_binding(exim_client_ctx->ssl, tlsp, GET_TAINTED);
tlsp->active.sock = cctx->sock;
tlsp->active.tls_ctx = exim_client_ctx;
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 */
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;
/* Handle genuine errors */
case SSL_ERROR_SSL:
+ {
+ uschar * conn_info = smtp_get_connection_info();
+ if (Ustrncmp(conn_info, US"SMTP ", 5) == 0) conn_info += 5;
+ /* I'd like to get separated H= here, but too hard for now */
ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
- log_write(0, LOG_MAIN, "TLS error (SSL_read): %s", ssl_errstring);
+ log_write(0, LOG_MAIN, "TLS error (SSL_read): on %s %s", conn_info, ssl_errstring);
ssl_xfer_error = TRUE;
return FALSE;
+ }
default:
DEBUG(D_tls) debug_printf("Got SSL error %d\n", error);
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)
{
void
-tls_get_cache()
+tls_get_cache(unsigned lim)
{
#ifndef DISABLE_DKIM
int n = ssl_xfer_buffer_hwm - ssl_xfer_buffer_lwm;
+if (n > lim)
+ n = lim;
if (n > 0)
dkim_exim_verify_feed(ssl_xfer_buffer+ssl_xfer_buffer_lwm, n);
#endif
BOOL
-tls_could_read(void)
+tls_could_getc(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;
}
int
tls_read(void * ct_ctx, uschar *buff, size_t len)
{
-SSL * ssl = ct_ctx ? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl : server_ssl;
+SSL * ssl = ct_ctx ? ((exim_openssl_client_tls_ctx *)ct_ctx)->ssl
+ : state_server.lib_state.lib_ssl;
int inbytes;
int error;
DEBUG(D_tls) debug_printf("Calling SSL_read(%p, %p, %u)\n", ssl,
buff, (unsigned int)len);
+ERR_clear_error();
inbytes = SSL_read(ssl, CS buff, len);
error = SSL_get_error(ssl, inbytes);
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;
a store reset there, so use POOL_PERM. */
/* + if CHUNKING, cmds EHLO,MAIL,RCPT(s),BDAT */
-if ((more || corked))
+if (more || corked)
{
if (!len) buff = US &error; /* dummy just so that string_catn is ok */
-#ifndef DISABLE_PIPE_CONNECT
int save_pool = store_pool;
store_pool = POOL_PERM;
-#endif
corked = string_catn(corked, buff, len);
-#ifndef DISABLE_PIPE_CONNECT
store_pool = save_pool;
-#endif
if (more)
{
for (int left = len; left > 0;)
{
DEBUG(D_tls) debug_printf("SSL_write(%p, %p, %d)\n", ssl, buff, left);
+ ERR_clear_error();
outbytes = SSL_write(ssl, CS buff, left);
error = SSL_get_error(ssl, outbytes);
DEBUG(D_tls) debug_printf("outbytes=%d error=%d\n", outbytes, error);
return -1;
case SSL_ERROR_SYSCALL:
- log_write(0, LOG_MAIN, "SSL_write: (from %s) syscall: %s",
- sender_fullhost ? sender_fullhost : US"<unknown>",
- strerror(errno));
+ if (ct_ctx || errno != ECONNRESET || !f.smtp_in_quit)
+ log_write(0, LOG_MAIN, "SSL_write: (from %s) syscall: %s",
+ sender_fullhost ? sender_fullhost : US"<unknown>",
+ strerror(errno));
+ else if (LOGGING(protocol_detail))
+ log_write(0, LOG_MAIN, "[%s] after QUIT, client reset TCP before"
+ " SMTP response and TLS close\n", sender_host_address);
+ else
+ DEBUG(D_tls) debug_printf("[%s] SSL_write: after QUIT,"
+ " client reset TCP before TLS close\n", sender_host_address);
return -1;
default:
+/*
+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 *
*************************************************/
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
*/
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 > 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);
}
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.u_ocsp.server.verify_stack, X509_free);
+ state_server.u_ocsp.server.verify_stack = NULL;
#endif
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 */
}
-SSL_CTX_free(*ctxp);
SSL_free(*sslp);
-*ctxp = NULL;
*sslp = NULL;
*fdp = -1;
}
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;
-
-#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;
}
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. */
}
{
long result, item;
uschar * exp, * end;
-uschar keep_c;
BOOL adding, item_parsed;
/* Server: send no (<= TLS1.2) session tickets */
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);