* Exim - an Internet mail transport agent *
*************************************************/
+/* Copyright (c) The Exim Maintainers 2020 - 2023 */
/* Copyright (c) University of Cambridge 1995 - 2019 */
/* 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 *
*************************************************/
to apply.
This list is current as of:
- ==> 1.0.1b <==
-Plus SSL_OP_SAFARI_ECDHE_ECDSA_BUG from 2013-June patch/discussion on openssl-dev
-Plus SSL_OP_NO_TLSv1_3 for 1.1.2-dev
+ ==> 1.1.1c <==
+
+XXX could we autobuild this list, as with predefined-macros?
+Seems just parsing ssl.h for SSL_OP_.* would be enough (except to exclude DTLS).
+Also allow a numeric literal?
*/
static exim_openssl_option exim_openssl_options[] = {
/* KEEP SORTED ALPHABETICALLY! */
#ifdef SSL_OP_ALL
{ US"all", (long) SSL_OP_ALL },
#endif
+#ifdef SSL_OP_ALLOW_NO_DHE_KEX
+ { US"allow_no_dhe_kex", SSL_OP_ALLOW_NO_DHE_KEX },
+#endif
#ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION
{ US"allow_unsafe_legacy_renegotiation", SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION },
#endif
#ifdef SSL_OP_CIPHER_SERVER_PREFERENCE
{ US"cipher_server_preference", SSL_OP_CIPHER_SERVER_PREFERENCE },
#endif
+#ifdef SSL_OP_CRYPTOPRO_TLSEXT_BUG
+ { US"cryptopro_tlsext_bug", SSL_OP_CRYPTOPRO_TLSEXT_BUG },
+#endif
#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
{ US"dont_insert_empty_fragments", SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS },
#endif
+#ifdef SSL_OP_ENABLE_MIDDLEBOX_COMPAT
+ { US"enable_middlebox_compat", SSL_OP_ENABLE_MIDDLEBOX_COMPAT },
+#endif
#ifdef SSL_OP_EPHEMERAL_RSA
{ US"ephemeral_rsa", SSL_OP_EPHEMERAL_RSA },
#endif
#ifdef SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG
{ US"netscape_reuse_cipher_change_bug", SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG },
#endif
+#ifdef SSL_OP_NO_ANTI_REPLAY
+ { US"no_anti_replay", SSL_OP_NO_ANTI_REPLAY },
+#endif
#ifdef SSL_OP_NO_COMPRESSION
{ US"no_compression", SSL_OP_NO_COMPRESSION },
#endif
+#ifdef SSL_OP_NO_ENCRYPT_THEN_MAC
+ { US"no_encrypt_then_mac", SSL_OP_NO_ENCRYPT_THEN_MAC },
+#endif
+#ifdef SSL_OP_NO_RENEGOTIATION
+ { US"no_renegotiation", SSL_OP_NO_RENEGOTIATION },
+#endif
#ifdef SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION
{ US"no_session_resumption_on_renegotiation", SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION },
#endif
{ 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 },
#ifdef SSL_OP_NO_TLSv1_3
{ US"no_tlsv1_3", SSL_OP_NO_TLSv1_3 },
#endif
+#ifdef SSL_OP_PRIORITIZE_CHACHA
+ { US"prioritize_chacha", SSL_OP_PRIORITIZE_CHACHA },
+#endif
#ifdef SSL_OP_SAFARI_ECDHE_ECDSA_BUG
{ US"safari_ecdhe_ecdsa_bug", SSL_OP_SAFARI_ECDHE_ECDSA_BUG },
#endif
#ifdef SSL_OP_TLS_ROLLBACK_BUG
{ US"tls_rollback_bug", SSL_OP_TLS_ROLLBACK_BUG },
#endif
+#ifdef SSL_OP_TLSEXT_PADDING
+ { US"tlsext_padding", SSL_OP_TLSEXT_PADDING },
+#endif
};
#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_TLS1_3");
# endif
+# ifdef OPENSSL_BAD_SRVR_OURCERT
+builtin_macro_create(US"_TLS_BAD_MULTICERT_IN_OURCERT");
+# endif
+# ifdef EXIM_HAVE_OCSP
+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;
-for (int i= 0; i < sk_X509_OBJECT_num(roots); i++)
+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] == '/')
{
- 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. */
+#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
-#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;
+/* 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. */
-ev = tlsp == &tls_out ? client_static_cbinfo->event_action : event_action;
-if (ev)
+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 */
-/*************************************************
-* Callback for verification *
-*************************************************/
+BIO_free(bio);
+return TRUE;
+}
-/* 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.
+/*************************************************
+* Initialize for ECDH *
+*************************************************/
-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
+/* "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
-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)
+static uschar *
+init_ecdh_auto(SSL_CTX * sctx)
{
-X509 * cert = X509_STORE_CTX_get_current_cert(x509ctx);
-int depth = X509_STORE_CTX_get_error_depth(x509ctx);
-uschar dn[256];
+#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";
-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;
- }
-dn[sizeof(dn)-1] = '\0';
+#else
+# if defined SSL_CTRL_SET_ECDH_AUTO
-tlsp->verify_override = FALSE;
-if (preverify_ok == 0)
- {
- uschar * extra = verify_mode ? string_sprintf(" (during %c-verify for [%s])",
- *verify_mode, sender_host_address)
- : US"";
- log_write(0, LOG_MAIN, "[%s] SSL verify error%s: depth=%d error=%s cert=%s",
- tlsp == &tls_out ? deliver_host_address : sender_host_address,
- extra, depth,
- X509_verify_cert_error_string(X509_STORE_CTX_get_error(x509ctx)), dn);
- *calledp = TRUE;
- if (!*optionalp)
- {
- if (!tlsp->peercert)
+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:
+ sctx The current SSL CTX (inbound or outbound)
+ errstr error string pointer
+
+Returns: TRUE if OK (nothing to set up, or setup worked)
+*/
+
+static BOOL
+init_ecdh(SSL_CTX * sctx, uschar ** errstr)
+{
+#ifdef OPENSSL_NO_ECDH
+return TRUE;
+#else
+
+# 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;
+ }
+dn[sizeof(dn)-1] = '\0';
+
+tlsp->verify_override = FALSE;
+if (preverify_ok == 0)
+ {
+ uschar * extra = verify_mode ? string_sprintf(" (during %c-verify for [%s])",
+ *verify_mode, sender_host_address)
+ : US"";
+ log_write(0, LOG_MAIN, "[%s] SSL verify error%s: depth=%d error=%s cert=%s",
+ tlsp == &tls_out ? deliver_host_address : sender_host_address,
+ extra, depth,
+ X509_verify_cert_error_string(X509_STORE_CTX_get_error(x509ctx)), dn);
+ *calledp = TRUE;
+ if (!*optionalp)
+ {
+ if (!tlsp->peercert)
tlsp->peercert = X509_dup(cert); /* record failing cert */
return 0; /* reject */
}
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)
-{
-DEBUG(D_tls) debug_printf("%.200s\n", line);
-}
+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.
-static exim_stek exim_tk; /* current key */
-static exim_stek exim_tk_old; /* previous key */
+OCSP_basic_verify takes a "store" arg, but does not
+use it for the chain verification, when OCSP_NOVERIFY is set.
+The content from the wire "basic_response" and a cert-stack "sk" are all
+that is used.
-static void
-tk_init(void)
-{
-time_t t = time(NULL);
+We have a stack, loaded in setup_certs() if tls_verify_certificates
+was a file (not a directory, or "system"). It is unfortunate we
+cannot used the connection context store, as that would neatly
+handle the "system" case too, but there seems to be no library
+function for getting a stack from a store.
+[ In OpenSSL 1.1 - ? X509_STORE_CTX_get0_chain(ctx) ? ]
+[ 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.
-if (exim_tk.name[0])
+Separately we might try to replace using OCSP_basic_verify() - which seems to not
+be a public interface into the OpenSSL library (there's no manual entry) -
+(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)
{
- if (exim_tk.renew >= t) return;
- exim_tk_old = exim_tk;
+ DEBUG(D_tls)
+ {
+ ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
+ debug_printf("OCSP response has bad signature: %s\n", US ssl_errstring);
+ }
+ goto bad;
}
-if (f.running_in_test_harness) ssl_session_timeout = 6;
+/* Here's the simplifying assumption: there's only one response, for the
+one certificate we use, and nothing for anything else in a chain. If this
+proves false, we need to extract a cert id from our issued cert
+(tls_certificate) and use that for OCSP_resp_find_status() (which finds the
+right cert in the stack and then calls OCSP_single_get0_status()).
-DEBUG(D_tls) debug_printf("OpenSSL: %s STEK\n", exim_tk.name[0] ? "rotating" : "creating");
-if (RAND_bytes(exim_tk.aes_key, sizeof(exim_tk.aes_key)) <= 0) return;
-if (RAND_bytes(exim_tk.hmac_key, sizeof(exim_tk.hmac_key)) <= 0) return;
-if (RAND_bytes(exim_tk.name+1, sizeof(exim_tk.name)-1) <= 0) return;
+I'm hoping to avoid reworking a bunch more of how we handle state here.
-exim_tk.name[0] = 'E';
-exim_tk.aes_cipher = EVP_aes_256_cbc();
-exim_tk.hmac_hash = EVP_sha256();
-exim_tk.expire = t + ssl_session_timeout;
-exim_tk.renew = t + ssl_session_timeout/2;
-}
+XXX that will change when we add support for (TLS1.3) whole-chain stapling
+*/
-static exim_stek *
-tk_current(void)
-{
-if (!exim_tk.name[0]) return NULL;
-return &exim_tk;
-}
+if (!(single_response = OCSP_resp_get0(basic_response, 0)))
+ {
+ DEBUG(D_tls)
+ debug_printf("Unable to get first response from OCSP basic response.\n");
+ goto bad;
+ }
-static exim_stek *
-tk_find(const uschar * name)
-{
-return memcmp(name, exim_tk.name, sizeof(exim_tk.name)) == 0 ? &exim_tk
- : memcmp(name, exim_tk_old.name, sizeof(exim_tk_old.name)) == 0 ? &exim_tk_old
- : NULL;
-}
+status = OCSP_single_get0_status(single_response, &reason, &rev, &thisupd, &nextupd);
+if (status != V_OCSP_CERTSTATUS_GOOD)
+ {
+ DEBUG(D_tls) debug_printf("OCSP response bad cert status: %s (%d) %s (%d)\n",
+ OCSP_cert_status_str(status), status,
+ OCSP_crl_reason_str(reason), reason);
+ goto bad;
+ }
-/* Callback for session tickets, on server */
-static int
-ticket_key_callback(SSL * ssl, uschar key_name[16],
- uschar * iv, EVP_CIPHER_CTX * ctx, HMAC_CTX * hctx, int enc)
-{
-tls_support * tlsp = server_static_cbinfo->tlsp;
-exim_stek * key;
+if (!OCSP_check_validity(thisupd, nextupd, EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX_AGE))
+ {
+ DEBUG(D_tls)
+ {
+ 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;
+ }
-if (enc)
+supply_response:
+ /* Add the resp to the list used by tls_server_stapling_cb() */
{
- DEBUG(D_tls) debug_printf("ticket_key_callback: create new session\n");
- tlsp->resumption |= RESUME_CLIENT_REQUESTED;
+ ocsp_resplist ** op = &state->u_ocsp.server.olist, * oentry;
+ while (oentry = *op)
+ op = &oentry->next;
+ *op = oentry = store_get(sizeof(ocsp_resplist), GET_UNTAINTED);
+ oentry->next = NULL;
+ oentry->resp = resp;
+ }
+return;
- if (RAND_bytes(iv, EVP_MAX_IV_LENGTH) <= 0)
- return -1; /* insufficient random */
+bad:
+ if (f.running_in_test_harness)
+ {
+ extern char ** environ;
+ if (environ) for (uschar ** p = USS environ; *p; p++)
+ if (Ustrncmp(*p, "EXIM_TESTHARNESS_DISABLE_OCSPVALIDITYCHECK", 42) == 0)
+ {
+ DEBUG(D_tls) debug_printf("Supplying known bad OCSP response\n");
+ goto supply_response;
+ }
+ }
+return;
+}
- if (!(key = tk_current())) /* current key doesn't exist or isn't valid */
- return 0; /* key couldn't be created */
- memcpy(key_name, key->name, 16);
- DEBUG(D_tls) debug_printf("STEK expire " TIME_T_FMT "\n", key->expire - time(NULL));
- /*XXX will want these dependent on the ssl session strength */
- HMAC_Init_ex(hctx, key->hmac_key, sizeof(key->hmac_key),
- key->hmac_hash, NULL);
- EVP_EncryptInit_ex(ctx, key->aes_cipher, NULL, key->aes_key, iv);
+static void
+ocsp_free_response_list(exim_openssl_state_st * state)
+{
+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*/
- DEBUG(D_tls) debug_printf("ticket created\n");
- return 1;
- }
-else
- {
- time_t now = time(NULL);
- DEBUG(D_tls) debug_printf("ticket_key_callback: retrieve session\n");
- tlsp->resumption |= RESUME_CLIENT_SUGGESTED;
- if (!(key = tk_find(key_name)) || key->expire < now)
- {
- DEBUG(D_tls)
- {
- debug_printf("ticket not usable (%s)\n", key ? "expired" : "not found");
- if (key) debug_printf("STEK expire " TIME_T_FMT "\n", key->expire - now);
- }
- return 0;
- }
- HMAC_Init_ex(hctx, key->hmac_key, sizeof(key->hmac_key),
- key->hmac_hash, NULL);
- EVP_DecryptInit_ex(ctx, key->aes_cipher, NULL, key->aes_key, iv);
- DEBUG(D_tls) debug_printf("ticket usable, STEK expire " TIME_T_FMT "\n", key->expire - now);
+static int
+tls_add_certfile(SSL_CTX * sctx, exim_openssl_state_st * cbinfo, uschar * file,
+ uschar ** errstr)
+{
+DEBUG(D_tls) debug_printf("tls_certificate file '%s'\n", file);
+if (!SSL_CTX_use_certificate_chain_file(sctx, CS file))
+ return tls_error(string_sprintf(
+ "SSL_CTX_use_certificate_chain_file file=%s", file),
+ cbinfo->host, NULL, errstr);
+return 0;
+}
- /* The ticket lifetime and renewal are the same as the STEK lifetime and
- renewal, which is overenthusiastic. A factor of, say, 3x longer STEK would
- be better. To do that we'd have to encode ticket lifetime in the name as
- we don't yet see the restored session. Could check posthandshake for TLS1.3
- and trigger a new ticket then, but cannot do that for TLS1.2 */
- return key->renew < now ? 2 : 1;
- }
+static int
+tls_add_pkeyfile(SSL_CTX * sctx, exim_openssl_state_st * cbinfo, uschar * file,
+ uschar ** errstr)
+{
+DEBUG(D_tls) debug_printf("tls_privatekey file '%s'\n", file);
+if (!SSL_CTX_use_PrivateKey_file(sctx, CS file, SSL_FILETYPE_PEM))
+ return tls_error(string_sprintf(
+ "SSL_CTX_use_PrivateKey_file file=%s", file), cbinfo->host, NULL, errstr);
+return 0;
}
-#endif
-/*************************************************
-* Initialize for DH *
-*************************************************/
-/* If dhparam is set, expand it, and load up the parameters for DH encryption.
+/* Called once during tls_init and possibly again during TLS setup, for a
+new context, if Server Name Indication was used and tls_sni was seen in
+the certificate string.
Arguments:
- sctx The current SSL CTX (inbound or outbound)
- dhparam DH parameter file or fixed parameter identity string
- host connected host, if client; NULL if server
- errstr error string pointer
+ sctx the SSL_CTX* to update
+ state various parts of session state
+ errstr error string pointer
-Returns: TRUE if OK (nothing to set up, or setup worked)
+Returns: OK/DEFER/FAIL
*/
-static BOOL
-init_dh(SSL_CTX *sctx, uschar *dhparam, const host_item *host, uschar ** errstr)
+static int
+tls_expand_session_files(SSL_CTX * sctx, exim_openssl_state_st * state,
+ uschar ** errstr)
{
-BIO *bio;
-DH *dh;
-uschar *dhexpanded;
-const char *pem;
-int dh_bitsize;
-
-if (!expand_check(dhparam, US"tls_dhparam", &dhexpanded, errstr))
- return FALSE;
+uschar * expanded;
-if (!dhexpanded || !*dhexpanded)
- bio = BIO_new_mem_buf(CS std_dh_prime_default(), -1);
-else if (dhexpanded[0] == '/')
+if (!state->certificate)
{
- if (!(bio = BIO_new_file(CS dhexpanded, "r")))
- {
- tls_error(string_sprintf("could not read dhparams file %s", dhexpanded),
- host, US strerror(errno), errstr);
- return FALSE;
- }
+ if (!state->is_server) /* client */
+ return OK;
+ /* server */
+ if (tls_install_selfsign(sctx, errstr) != OK)
+ return DEFER;
}
else
{
- if (Ustrcmp(dhexpanded, "none") == 0)
+ int err;
+
+ 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 ( !expand_check(state->certificate, US"tls_certificate", &expanded, errstr)
+ || f.expand_string_forcedfail)
{
- DEBUG(D_tls) debug_printf("Requested no DH parameters.\n");
- return TRUE;
+ if (f.expand_string_forcedfail)
+ *errstr = US"expansion of tls_certificate failed";
+ return DEFER;
}
- if (!(pem = std_dh_prime_named(dhexpanded)))
+ 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;
+
+ if (olist)
+ if (!expand_check(olist, US"tls_ocsp_file", USS &olist, errstr))
+ return DEFER;
+ if (olist && !*olist)
+ olist = NULL;
+
+ /* 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;
+
+ if ( state->privatekey
+ && !expand_check(state->privatekey, US"tls_privatekey", &expanded, errstr)
+ || f.expand_string_forcedfail)
{
- tls_error(string_sprintf("Unknown standard DH prime \"%s\"", dhexpanded),
- host, US strerror(errno), errstr);
- return FALSE;
+ if (f.expand_string_forcedfail)
+ *errstr = US"expansion of tls_privatekey failed";
+ return DEFER;
}
- 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;
- }
+ /* 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. */
-/* note: our default limit of 2236 is not a multiple of 8; the limit comes from
- * an NSS limit, and the GnuTLS APIs handle bit-sizes fine, so we went with
- * 2236. But older OpenSSL can only report in bytes (octets), not bits.
- * If someone wants to dance at the edge, then they can raise the limit or use
- * current libraries. */
-#ifdef EXIM_HAVE_OPENSSL_DH_BITS
-/* Added in commit 26c79d5641d; `git describe --contains` says OpenSSL_1_1_0-pre1~1022
- * This predates OpenSSL_1_1_0 (before a, b, ...) so is in all 1.1.0 */
-dh_bitsize = DH_bits(dh);
-#else
-dh_bitsize = 8 * DH_size(dh);
-#endif
+ if (expanded && *expanded)
+ if (state->is_server)
+ {
+ const uschar * file_list = expanded;
+ int sep = 0;
+ uschar * file;
-/* 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);
+ 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;
}
-DH_free(dh);
-BIO_free(bio);
-
-return TRUE;
+return OK;
}
-/*************************************************
-* Initialize for ECDH *
-*************************************************/
+/**************************************************
+* One-time init credentials for server and client *
+**************************************************/
-/* Load parameters for ECDH encryption.
+static void
+normalise_ciphers(uschar ** ciphers, const uschar * pre_expansion_ciphers)
+{
+uschar * s = *ciphers;
-For now, we stick to NIST P-256 because: it's simple and easy to configure;
-it avoids any patent issues that might bite redistributors; despite events in
-the news and concerns over curve choices, we're not cryptographers, we're not
-pretending to be, and this is "good enough" to be better than no support,
-protecting against most adversaries. Given another year or two, there might
-be sufficient clarity about a "right" way forward to let us make an informed
-decision, instead of a knee-jerk reaction.
+if (!s || !Ustrchr(s, '_')) return; /* no change needed */
-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.
+if (s == pre_expansion_ciphers)
+ s = string_copy(s); /* get writable copy */
-Patches welcome.
+for (uschar * t = s; *t; t++) if (*t == '_') *t = '-';
+*ciphers = s;
+}
-Arguments:
- sctx The current SSL CTX (inbound or outbound)
- host connected host, if client; NULL if server
- errstr error string pointer
+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;
+}
-Returns: TRUE if OK (nothing to set up, or setup worked)
-*/
-static BOOL
-init_ecdh(SSL_CTX * sctx, host_item * host, uschar ** errstr)
+
+static int
+lib_ctx_new(SSL_CTX ** ctxp, host_item * host, uschar ** errstr)
{
-#ifdef OPENSSL_NO_ECDH
-return TRUE;
+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);
-EC_KEY * ecdh;
-uschar * exp_curve;
-int nid;
-BOOL rv;
-
-if (host) /* No ECDH setup for clients, only for servers */
- return TRUE;
+/* Set up the information callback, which outputs if debugging is at a suitable
+level. */
-# ifndef EXIM_HAVE_ECDH
DEBUG(D_tls)
- debug_printf("No OpenSSL API to define ECDH parameters, skipping\n");
-return TRUE;
-# else
-
-if (!expand_check(tls_eccurve, US"tls_eccurve", &exp_curve, errstr))
- return FALSE;
-if (!exp_curve || !*exp_curve)
- return TRUE;
-
-/* "auto" needs to be handled carefully.
- * OpenSSL < 1.0.2: we do not select anything, but fallback to prime256v1
- * OpenSSL < 1.1.0: we have to call SSL_CTX_set_ecdh_auto
- * (openssl/ssl.h defines SSL_CTRL_SET_ECDH_AUTO)
- * OpenSSL >= 1.1.0: we do not set anything, the libray does autoselection
- * https://github.com/openssl/openssl/commit/fe6ef2472db933f01b59cad82aa925736935984b
- */
-if (Ustrcmp(exp_curve, "auto") == 0)
{
-#if OPENSSL_VERSION_NUMBER < 0x10002000L
- DEBUG(D_tls) debug_printf(
- "ECDH OpenSSL < 1.0.2: temp key parameter settings: overriding \"auto\" with \"prime256v1\"\n");
- exp_curve = US"prime256v1";
-#else
-# if defined SSL_CTRL_SET_ECDH_AUTO
- DEBUG(D_tls) debug_printf(
- "ECDH OpenSSL 1.0.2+ temp key parameter settings: autoselection\n");
- SSL_CTX_set_ecdh_auto(sctx, 1);
- return TRUE;
-# else
- DEBUG(D_tls) debug_printf(
- "ECDH OpenSSL 1.1.0+ temp key parameter settings: default selection\n");
- return TRUE;
-# endif
+ 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
}
-DEBUG(D_tls) debug_printf("ECDH: curve '%s'\n", exp_curve);
-if ( (nid = OBJ_sn2nid (CCS exp_curve)) == NID_undef
-# ifdef EXIM_HAVE_OPENSSL_EC_NIST2NID
- && (nid = EC_curve_nist2nid(CCS exp_curve)) == NID_undef
-# endif
- )
- {
- tls_error(string_sprintf("Unknown curve name tls_eccurve '%s'", exp_curve),
- host, NULL, errstr);
- return FALSE;
- }
+/* Automatically re-try reads/writes after renegotiation. */
+(void) SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);
+*ctxp = ctx;
+return OK;
+}
-if (!(ecdh = EC_KEY_new_by_curve_name(nid)))
- {
- tls_error(US"Unable to create ec curve", host, NULL, errstr);
- return FALSE;
- }
-/* The "tmp" in the name here refers to setting a temporary key
-not to the stability of the interface. */
+static unsigned
+tls_server_creds_init(void)
+{
+SSL_CTX * ctx;
+uschar * dummy_errstr;
+unsigned lifetime = 0;
-if ((rv = SSL_CTX_set_tmp_ecdh(sctx, ecdh) == 0))
- tls_error(string_sprintf("Error enabling '%s' curve", exp_curve), host, NULL, errstr);
-else
- DEBUG(D_tls) debug_printf("ECDH: enabled '%s' curve\n", exp_curve);
+tls_openssl_init();
-EC_KEY_free(ecdh);
-return !rv;
+state_server.lib_state = null_tls_preload;
-# endif /*EXIM_HAVE_ECDH*/
-#endif /*OPENSSL_NO_ECDH*/
-}
+if (lib_ctx_new(&ctx, NULL, &dummy_errstr) != OK)
+ return 0;
+state_server.lib_state.lib_ctx = ctx;
+/* Preload DH params and EC curve */
+if (opt_unset_or_noexpand(tls_dhparam))
+ {
+ DEBUG(D_tls) debug_printf("TLS: preloading DH params '%s' for server\n", tls_dhparam);
+ if (init_dh(ctx, tls_dhparam, &dummy_errstr))
+ state_server.lib_state.dh = TRUE;
+ }
+else
+ DEBUG(D_tls) debug_printf("TLS: not preloading DH params for server\n");
+if (opt_unset_or_noexpand(tls_eccurve))
+ {
+ DEBUG(D_tls) debug_printf("TLS: preloading ECDH curve '%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. */
-#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.
+if ( opt_set_and_noexpand(tls_verify_certificates)
+ && opt_unset_or_noexpand(tls_crl))
+ {
+ /* Watch the default dir also as they are always included */
-ASSUMES: single response, for single cert.
+ 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");
-Arguments:
- sctx the SSL_CTX* to update
- cbinfo various parts of session state
- filename the filename putatively holding an OCSP response
+ 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 */
-static void
-ocsp_load_response(SSL_CTX * sctx, tls_ext_ctx_cb * cbinfo,
- const uschar * filename)
-{
-BIO * bio;
-OCSP_RESPONSE * resp;
-OCSP_BASICRESP * basic_response;
-OCSP_SINGLERESP * single_response;
-ASN1_GENERALIZEDTIME * rev, * thisupd, * nextupd;
-STACK_OF(X509) * sk;
-unsigned long verify_flags;
-int status, reason, i;
+ if ( 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. */
-DEBUG(D_tls) debug_printf("tls_ocsp_file '%s'\n", filename);
+ 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 (!(bio = BIO_new_file(CS filename, "rb")))
- {
- DEBUG(D_tls) debug_printf("Failed to open OCSP response file \"%s\"\n",
- filename);
- return;
+ 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("TLS: not preloading CA bundle for server\n");
-resp = d2i_OCSP_RESPONSE_bio(bio, NULL);
-BIO_free(bio);
-if (!resp)
- {
- DEBUG(D_tls) debug_printf("Error reading OCSP response.\n");
- return;
- }
-if ((status = OCSP_response_status(resp)) != OCSP_RESPONSE_STATUS_SUCCESSFUL)
- {
- DEBUG(D_tls) debug_printf("OCSP response not valid: %s (%d)\n",
- OCSP_response_status_str(status), status);
- goto bad;
- }
+#endif /* EXIM_HAVE_INOTIFY */
-#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)))
+/* If we can, preload the ciphers control string */
+
+if (opt_set_and_noexpand(tls_require_ciphers))
{
- DEBUG(D_tls)
- debug_printf("OCSP response parse error: unable to extract basic response.\n");
- goto bad;
+ 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;
+}
-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 */
-/* 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.
+/* 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. */
-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_client_creds_init(transport_instance * t, BOOL watch)
+{
+smtp_transport_options_block * ob = t->options_block;
+exim_openssl_state_st tpt_dummy_state;
+host_item * dummy_host = (host_item *)1;
+uschar * dummy_errstr;
+SSL_CTX * ctx;
-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? */
+tls_openssl_init();
+
+ob->tls_preload = null_tls_preload;
+if (lib_ctx_new(&ctx, dummy_host, &dummy_errstr) != OK)
+ return;
+ob->tls_preload.lib_ctx = ctx;
-if ((i = OCSP_basic_verify(basic_response, sk, NULL, verify_flags)) < 0)
+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))
{
- DEBUG(D_tls)
+ if ( !watch
+ || ( tls_set_watch(ob->tls_certificate, FALSE)
+ && tls_set_watch(ob->tls_privatekey, FALSE)
+ ) )
{
- ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
- debug_printf("OCSP response verify failure: %s\n", US ssl_errstring);
+ 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;
}
- goto bad;
}
+else
+ DEBUG(D_tls)
+ debug_printf("TLS: not preloading client certs, for transport '%s'\n", t->name);
-/* 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)))
+if ( opt_set_and_noexpand(ob->tls_verify_certificates)
+ && opt_unset_or_noexpand(ob->tls_crl))
{
- DEBUG(D_tls)
- debug_printf("Unable to get first response from OCSP basic response.\n");
- 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);
-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 (setup_certs(ctx, &v_certs,
+ ob->tls_crl, dummy_host, &dummy_errstr) == OK)
+ ob->tls_preload.cabundle = TRUE;
+ }
}
+else
+ DEBUG(D_tls)
+ debug_printf("TLS: not preloading CA bundle, for transport '%s'\n", t->name);
-if (!OCSP_check_validity(thisupd, nextupd, EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX_AGE))
- {
- DEBUG(D_tls) debug_printf("OCSP status invalid times.\n");
- goto bad;
- }
+#endif /*EXIM_HAVE_INOTIFY*/
+}
-supply_response:
- /* Add the resp to the list used by tls_server_stapling_cb() */
- {
- 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;
+#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
+/* Invalidate the creds cached, by dropping the current ones.
+Call when we notice one of the source files has changed. */
+
+static void
+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
}
static void
-ocsp_free_response_list(tls_ext_ctx_cb * cbinfo)
+tls_client_creds_invalidate(transport_instance * t)
{
-for (ocsp_resplist * olist = cbinfo->u_ocsp.server.olist; olist;
- olist = olist->next)
- OCSP_RESPONSE_free(olist->resp);
-cbinfo->u_ocsp.server.olist = NULL;
+smtp_transport_options_block * ob = t->options_block;
+SSL_CTX_free(ob->tls_preload.lib_ctx);
+ob->tls_preload = null_tls_preload;
}
-#endif /*!DISABLE_OCSP*/
+#else
+
+static void
+tls_server_creds_invalidate(void)
+{ return; }
+static void
+tls_client_creds_invalidate(transport_instance * t)
+{ return; }
+#endif /*EXIM_HAVE_INOTIFY*/
-/* Create and install a selfsigned certificate, for use in server mode */
-static int
-tls_install_selfsign(SSL_CTX * sctx, uschar ** errstr)
+/* Extreme debug
+ * */
+#ifndef DISABLE_OCSP
+static void
+debug_print_sn(const X509 * cert)
{
-X509 * x509 = NULL;
-EVP_PKEY * pkey;
-RSA * rsa;
-X509_NAME * name;
-uschar * where;
+X509_NAME * sn = X509_get_subject_name((X509 *)cert);
+static uschar name[256];
+if (X509_NAME_oneline(sn, CS name, sizeof(name)))
+ {
+ name[sizeof(name)-1] = '\0';
+ debug_printf(" %s\n", name);
+ }
+}
-where = US"allocating pkey";
-if (!(pkey = EVP_PKEY_new()))
- goto err;
+static void
+x509_stack_dump_cert_s_names(const STACK_OF(X509) * sk)
+{
+if (!sk)
+ debug_printf(" (null)\n");
+else
+ {
+ int idx = sk_X509_num(sk);
+ if (!idx)
+ debug_printf(" (empty)\n");
+ else
+ while (--idx >= 0) debug_print_sn(sk_X509_value(sk, idx));
+ }
+}
-where = US"allocating cert";
-if (!(x509 = X509_new()))
- goto err;
+static void
+x509_store_dump_cert_s_names(X509_STORE * store)
+{
+# 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*/
+/*
+*/
-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;
+#ifndef DISABLE_TLS_RESUME
+/* Manage the keysets used for encrypting the session tickets, on the server. */
-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);
+typedef struct { /* Session ticket encryption key */
+ uschar name[16];
-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);
+ 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;
-where = US"signing cert";
-if (!X509_sign(x509, pkey, EVP_md5()))
- goto err;
+static exim_stek exim_tk; /* current key */
+static exim_stek exim_tk_old; /* previous key */
-where = US"installing selfsign cert";
-if (!SSL_CTX_use_certificate(sctx, x509))
- goto err;
+static void
+tk_init(void)
+{
+time_t t = time(NULL);
-where = US"installing selfsign key";
-if (!SSL_CTX_use_PrivateKey(sctx, pkey))
- goto err;
+if (exim_tk.name[0])
+ {
+ if (exim_tk.renew >= t) return;
+ exim_tk_old = exim_tk;
+ }
-return OK;
+if (f.running_in_test_harness) ssl_session_timeout = TESTSUITE_TICKET_LIFE;
-err:
- (void) tls_error(where, NULL, NULL, errstr);
- if (x509) X509_free(x509);
- if (pkey) EVP_PKEY_free(pkey);
- return DEFER;
+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();
+# 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;
-
- 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)))
- ocsp_load_response(sctx, cbinfo, ofile);
- 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, state->dhparam, &errstr)
+ || !init_ecdh(server_sni, &errstr)
+ )
+ goto bad;
+
+if ( state->server_cipher_list
+ && !SSL_CTX_set_cipher_list(server_sni, CS state->server_cipher_list))
+ goto bad;
+
+#ifndef DISABLE_OCSP
+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, state);
+ }
+#endif
+
+ {
+ 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, 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:
+ 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 ( !init_dh(server_sni, cbinfo->dhparam, NULL, &dummy_errstr)
- || !init_ecdh(server_sni, NULL, &dummy_errstr)
+if ( inlen > 1 /* at least one name */
+ && in[0]+1 == inlen /* filling the vector, so exactly one name */
)
- goto bad;
-
-if ( cbinfo->server_cipher_list
- && !SSL_CTX_set_cipher_list(server_sni, CS cbinfo->server_cipher_list))
- goto bad;
-
-#ifndef DISABLE_OCSP
-if (cbinfo->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);
+ 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 */
+ }
}
-#endif
-
-if ((rc = setup_certs(server_sni, tls_verify_certificates, tls_crl, NULL, FALSE,
- verify_callback_server, &dummy_errstr)) != OK)
- goto bad;
-/* 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)
- goto bad;
+/* More than one name from client, or name did not match our list. */
-DEBUG(D_tls) debug_printf("Switching SSL context.\n");
-SSL_set_SSL_CTX(s, server_sni);
-return SSL_TLSEXT_ERR_OK;
+/* 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). */
-bad: return SSL_TLSEXT_ERR_ALERT_FATAL;
+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_OPENSSL_TLSEXT */
-
+#endif /* EXIM_HAVE_ALPN */
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;
OCSP_BASICRESP * bs;
int i;
-DEBUG(D_tls) debug_printf("Received TLS status response (OCSP stapling):");
-len = SSL_get_tlsext_status_ocsp_resp(s, &p);
+DEBUG(D_tls) debug_printf("Received TLS status callback (OCSP stapling):\n");
+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, "Received TLS status callback, null content");
+ 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;
- int status, reason;
- ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd;
+ 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(BIO_s_mem());
- DEBUG(D_tls) bp = BIO_new_fp(debug_file, BIO_NOCLOSE);
+ /* 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? */
- /*OCSP_RESPONSE_print(bp, rsp, 0); extreme debug: stapling content */
+ {
+ /* 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. */
- /* 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); */
+#if defined(EXIM_HAVE_OPENSSL_OCSP_RESP_GET0_SIGNER) && defined(SUPPORT_DANE)
+ X509 * signer;
- if ((i = OCSP_basic_verify(bs, cbinfo->verify_stack,
- cbinfo->u_ocsp.client.verify_store, 0)) <= 0)
+ if ( tls_out.dane_verified
+ && (have_verified_OCSP_signer =
+ OCSP_resp_get0_signer(bs, &signer, SSL_get0_verified_chain(ssl)) == 1))
{
- 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);
- goto failed;
+ 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
+
}
- BIO_printf(bp, "OCSP response well-formed and signed OK\n");
+ 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?
+
+ 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.
+ */
+
+#ifndef OCSP_PARTIAL_CHAIN /* defined for 3.0.0 onwards */
+# define OCSP_PARTIAL_CHAIN 0
+#endif
+
+ 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))
+ {
+ 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");
/*XXX So we have a good stapled OCSP status. How do we know
it is for the cert of interest? OpenSSL 1.1.0 has a routine
For now, carry on blindly accepting the resp. */
- {
- OCSP_SINGLERESP * single;
-
+ for (int idx =
#ifdef EXIM_HAVE_OCSP_RESP_COUNT
- if (OCSP_resp_count(bs) != 1)
+ OCSP_resp_count(bs) - 1;
#else
- STACK_OF(OCSP_SINGLERESP) * sresp = bs->tbsResponseData->responses;
- if (sk_OCSP_SINGLERESP_num(sresp) != 1)
+ sk_OCSP_SINGLERESP_num(sresp) - 1;
#endif
- {
- tls_out.ocsp = OCSP_FAILED;
- log_write(0, LOG_MAIN, "OCSP stapling "
- "with multiple responses not handled");
- goto failed;
- }
- single = OCSP_resp_get0(bs, 0);
+ idx >= 0; idx--)
+ {
+ OCSP_SINGLERESP * single = OCSP_resp_get0(bs, idx);
+ int status, reason;
+ ASN1_GENERALIZEDTIME * rev, * thisupd, * nextupd;
+
+ /*XXX so I can see putting a loop in here to handle a rsp with >1 singleresp
+ - but what happens with a GnuTLS-style input?
+
+ we could do with a debug label for each singleresp
+ - it has a certID with a serialNumber, but I see no API to get that
+ */
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);
- 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");
- }
- else
- {
+ 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);
+ 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;
+ }
+
DEBUG(D_tls) BIO_printf(bp, "Certificate status: %s\n",
OCSP_cert_status_str(status));
switch(status)
{
case V_OCSP_CERTSTATUS_GOOD:
- tls_out.ocsp = OCSP_VFIED;
- i = 1;
- goto good;
+ continue; /* the idx loop */
case V_OCSP_CERTSTATUS_REVOKED:
- tls_out.ocsp = OCSP_FAILED;
+ 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:
- tls_out.ocsp = OCSP_FAILED;
+ 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;
}
+
+ goto failed;
}
+
+ i = 1;
+ tls_out.ocsp = OCSP_VFIED;
+ goto good;
+
failed:
+ tls_out.ocsp = OCSP_FAILED;
i = cbinfo->u_ocsp.client.verify_required ? 0 : 1;
good:
+ {
+ uschar * s = NULL;
+ int len = (int) BIO_get_mem_data(bp, CSS &s);
+ if (len > 0) debug_printf("%.*s", len, s);
+ }
BIO_free(bp);
}
/*************************************************
* Initialize 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;
}
for resumption next to the TLS session, and used here. */
if (!tlsp->verify_override)
- tlsp->certificate_verified = SSL_get_verify_result(ssl) == X509_V_OK;
+ tlsp->certificate_verified =
+#ifdef SUPPORT_DANE
+ tlsp->dane_verified ||
+#endif
+ SSL_get_verify_result(ssl) == X509_V_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,
#endif
}
- /* If a certificate file is empty, the next function fails with an
+ /* If a certificate file is empty, the load function fails with an
unhelpful error message. If we skip it, we get the correct behaviour (no
certificates are recognized, but the error message is still misleading (it
says no certificate was supplied). But this is better. */
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);
- /* Load the list of CAs for which we will accept certs, for sending
- to the client. This is only for the one-file tls_verify_certificates
- variant.
+ /* 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
+ tls_verify_certificates variant.
If a list isn't loaded into the server, but some verify locations are set,
the server end appears to make a wildcard request for client certs.
Meanwhile, the client library as default behaviour *ignores* the list
if (file)
{
STACK_OF(X509_NAME) * names = SSL_load_client_CA_file(CS file);
+ int i = sk_X509_NAME_num(names);
- SSL_CTX_set_client_CA_list(sctx, names);
- DEBUG(D_tls) debug_printf("Added %d certificate authorities.\n",
- sk_X509_NAME_num(names));
+ if (!host) SSL_CTX_set_client_CA_list(sctx, 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 */
+ }
+
+return OK;
+}
- /* If verification is optional, don't fail if no certificate */
- SSL_CTX_set_verify(sctx,
- SSL_VERIFY_PEER | (optional ? 0 : SSL_VERIFY_FAIL_IF_NO_PEER_CERT),
- cert_vfy_cb);
+
+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;
+
+ 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: ;
-#ifdef EXPERIMENTAL_TLS_RESUME
-SSL_CTX_set_tlsext_ticket_key_cb(server_ctx, ticket_key_callback);
+#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:
- (void) tls_error(US"SSL_accept", NULL, sigalrm_seen ? US"timed out" : NULL, errstr);
+ {
+ 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)", 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;
+ }
default:
DEBUG(D_tls) debug_printf("Got SSL error %d\n", error);
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. */
-peer_cert(server_ssl, &tls_in, peerdn, sizeof(peerdn));
+#ifdef SSL_get_extms_support
+/*XXX what does this return for tls1.3 ? */
+tls_in.ext_master_secret = SSL_get_extms_support(ssl) == 1;
+#endif
+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\n");
- }
+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);
- if ((dbm_file = dbfn_open(US"tls", O_RDONLY, &dbblock, FALSE, FALSE)))
+ 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
peer_cert(exim_client_ctx->ssl, tlsp, peerdn, sizeof(peerdn));
tlsp->ver = tlsver_name(exim_client_ctx->ssl);
}
/*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\n");
- }
+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)" : "");
- if ( (rc = SSL_shutdown(*sslp)) == 0 /* send "close notify" alert */
- && shutdown > 1)
+ tls_write(ct_ctx, NULL, 0, FALSE); /* flush write buffer */
+
+ 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. */
}
tls_openssl_options_parse(uschar *option_spec, long *results)
{
long result, item;
-uschar *end;
-uschar keep_c;
+uschar * exp, * end;
BOOL adding, item_parsed;
/* Server: send no (<= TLS1.2) session tickets */
result = SSL_OP_NO_TICKET;
/* Prior to 4.80 we or'd in SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; removed
- * from default because it increases BEAST susceptibility. */
+from default because it increases BEAST susceptibility. */
#ifdef SSL_OP_NO_SSLv2
result |= SSL_OP_NO_SSLv2;
#endif
#ifdef SSL_OP_SINGLE_DH_USE
result |= SSL_OP_SINGLE_DH_USE;
#endif
+#ifdef SSL_OP_NO_RENEGOTIATION
+result |= SSL_OP_NO_RENEGOTIATION;
+#endif
if (!option_spec)
{
return TRUE;
}
-for (uschar * s = option_spec; *s; /**/)
+if (!expand_check(option_spec, US"openssl_options", &exp, &end))
+ return FALSE;
+
+for (uschar * s = exp; *s; /**/)
{
while (isspace(*s)) ++s;
if (*s == '\0')
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);
return FALSE;
}
- DEBUG(D_tls) debug_printf("openssl option, %s %8lx: %lx (%s)\n",
+ DEBUG(D_tls) debug_printf("openssl option, %s %08lx: %08lx (%s)\n",
adding ? "adding to " : "removing from", result, item, s);
if (adding)
result |= item;