/* Copyright (c) The Exim Maintainers 2020 - 2022 */
/* 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 */
# 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
#endif
+#define TESTSUITE_TICKET_LIFE 10 /* seconds */
/*************************************************
* OpenSSL option parse *
*************************************************/
}
else
DEBUG(D_tls)
- debug_printf("Diffie-Hellman initialized from %s with %d-bit prime\n",
+ debug_printf(" Diffie-Hellman initialized from %s with %d-bit prime\n",
dhexpanded ? dhexpanded : US"default", dh_bitsize);
}
else
DEBUG(D_tls)
- debug_printf("dhparams '%s' %d bits, is > tls_dh_max_bits limit of %d\n",
+ 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
* Initialize for ECDH *
*************************************************/
+/* "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
+
+*/
+
+static uschar *
+init_ecdh_auto(SSL_CTX * sctx)
+{
+#if OPENSSL_VERSION_NUMBER < 0x10002000L
+DEBUG(D_tls) debug_printf(
+ " ECDH OpenSSL < 1.0.2: temp key parameter settings: overriding \"auto\" with \"prime256v1\"\n");
+return US"prime256v1";
+
+#else
+# if defined SSL_CTRL_SET_ECDH_AUTO
+
+DEBUG(D_tls) debug_printf(
+ " ECDH OpenSSL 1.0.2+: temp key parameter settings: autoselection\n");
+SSL_CTX_set_ecdh_auto(sctx, 1);
+return NULL;
+
+# else
+
+DEBUG(D_tls) debug_printf(
+ " ECDH OpenSSL 1.1.0+: temp key parameter settings: library default selection\n");
+return NULL;
+
+# endif
+#endif
+}
+
/* Load parameters for ECDH encryption. Server only.
For now, we stick to NIST P-256 because: it's simple and easy to configure;
return TRUE;
#else
-uschar * exp_curve;
-int nid;
-BOOL rv;
-
# ifndef EXIM_HAVE_ECDH
DEBUG(D_tls)
- debug_printf("No OpenSSL API to define ECDH parameters, skipping\n");
+ 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;
-/* "auto" needs to be handled carefully.
- * OpenSSL < 1.0.2: we do not select anything, but fallback to prime256v1
- * OpenSSL < 1.1.0: we have to call SSL_CTX_set_ecdh_auto
- * (openssl/ssl.h defines SSL_CTRL_SET_ECDH_AUTO)
- * OpenSSL >= 1.1.0: we do not set anything, the libray does autoselection
- * https://github.com/openssl/openssl/commit/fe6ef2472db933f01b59cad82aa925736935984b
- */
-if (Ustrcmp(exp_curve, "auto") == 0)
- {
-#if OPENSSL_VERSION_NUMBER < 0x10002000L
- DEBUG(D_tls) debug_printf(
- "ECDH OpenSSL < 1.0.2: temp key parameter settings: overriding \"auto\" with \"prime256v1\"\n");
- exp_curve = US"prime256v1";
-#else
-# if defined SSL_CTRL_SET_ECDH_AUTO
- DEBUG(D_tls) debug_printf(
- "ECDH OpenSSL 1.0.2+: temp key parameter settings: autoselection\n");
- SSL_CTX_set_ecdh_auto(sctx, 1);
- return TRUE;
-# else
- DEBUG(D_tls) debug_printf(
- "ECDH OpenSSL 1.1.0+: temp key parameter settings: default selection\n");
- return TRUE;
-# endif
-#endif
- }
+/* Limit the list to hardwired array size. Drop out if any element is "suto". */
-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),
- NULL, NULL, errstr);
- return FALSE;
- }
+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++;
-# if OPENSSL_VERSION_NUMBER < 0x30000000L
+/* 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(nid)))
+ 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 ((rv = SSL_CTX_set_tmp_ecdh(sctx, ecdh) == 0))
+ 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);
+ DEBUG(D_tls) debug_printf(" ECDH: enabled '%s' curve\n", exp_curve);
EC_KEY_free(ecdh);
}
+# endif /*!EXIM_HAVE_OPENSSL_SET1_GROUPS*/
-#else /* v 3.0.0 + */
-
-if ((rv = SSL_CTX_set1_groups(sctx, &nid, 1)) == 0)
- tls_error(string_sprintf("Error enabling '%s' group", exp_curve), NULL, NULL, errstr);
-else
- DEBUG(D_tls) debug_printf("ECDH: enabled '%s' group\n", exp_curve);
-
-#endif
-
-return !rv;
+return !!rc;
# endif /*EXIM_HAVE_ECDH*/
#endif /*OPENSSL_NO_ECDH*/
) )
reexpand_tls_files_for_sni = TRUE;
- if (!expand_check(state->certificate, US"tls_certificate", &expanded, errstr))
+ if ( !expand_check(state->certificate, US"tls_certificate", &expanded, errstr)
+ || f.expand_string_forcedfail)
+ {
+ if (f.expand_string_forcedfail)
+ *errstr = US"expansion of tls_certificate failed";
return DEFER;
+ }
if (expanded)
if (state->is_server)
if ((err = tls_add_certfile(sctx, state, expanded, errstr)))
return err;
- if ( state->privatekey
- && !expand_check(state->privatekey, US"tls_privatekey", &expanded, errstr))
+ if ( state->privatekey
+ && !expand_check(state->privatekey, US"tls_privatekey", &expanded, errstr)
+ || f.expand_string_forcedfail)
+ {
+ if (f.expand_string_forcedfail)
+ *errstr = US"expansion of tls_privatekey failed";
return DEFER;
+ }
/* If 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
if (opt_unset_or_noexpand(tls_dhparam))
{
- DEBUG(D_tls) debug_printf("TLS: preloading DH params for server\n");
+ DEBUG(D_tls) debug_printf("TLS: preloading DH params '%s' for server\n", tls_dhparam);
if (init_dh(ctx, tls_dhparam, &dummy_errstr))
state_server.lib_state.dh = TRUE;
}
DEBUG(D_tls) debug_printf("TLS: not preloading DH params for server\n");
if (opt_unset_or_noexpand(tls_eccurve))
{
- DEBUG(D_tls) debug_printf("TLS: preloading ECDH curve for server\n");
+ 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;
}
exim_tk_old = exim_tk;
}
-if (f.running_in_test_harness) ssl_session_timeout = 6;
+if (f.running_in_test_harness) ssl_session_timeout = TESTSUITE_TICKET_LIFE;
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;
#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);
-exim_openssl_state_st *state = (exim_openssl_state_st *) 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;
not confident that memcpy wouldn't break some internal reference counting.
Especially since there's a references struct member, which would be off. */
-if (lib_ctx_new(&server_sni, NULL, &dummy_errstr) != OK)
+if (lib_ctx_new(&server_sni, NULL, &errstr) != OK)
goto bad;
/* Not sure how many of these are actually needed, since SSL object
SSL_CTX_set_tlsext_servername_arg(server_sni, state);
}
-if ( !init_dh(server_sni, state->dhparam, &dummy_errstr)
- || !init_ecdh(server_sni, &dummy_errstr)
+if ( !init_dh(server_sni, state->dhparam, &errstr)
+ || !init_ecdh(server_sni, &errstr)
)
goto bad;
{
uschar * v_certs = tls_verify_certificates;
if ((rc = setup_certs(server_sni, &v_certs, tls_crl, NULL,
- &dummy_errstr)) != OK)
+ &errstr)) != OK)
goto bad;
if (v_certs && *v_certs)
/* do this after setup_certs, because this can require the certs for verifying
OCSP information. */
-if ((rc = tls_expand_session_files(server_sni, state, &dummy_errstr)) != OK)
+if ((rc = tls_expand_session_files(server_sni, state, &errstr)) != OK)
goto bad;
DEBUG(D_tls) debug_printf("Switching SSL context.\n");
SSL_set_SSL_CTX(s, server_sni);
return SSL_TLSEXT_ERR_OK;
-bad: return SSL_TLSEXT_ERR_ALERT_FATAL;
+bad:
+ log_write(0, LOG_MAIN|LOG_PANIC, "%s", errstr);
+ return SSL_TLSEXT_ERR_ALERT_FATAL;
}
#endif /* EXIM_HAVE_OPENSSL_TLSEXT */
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)
{
}
}
-/* More than one name from clilent, or name did not match our list. */
+/* More than one name from client, or name did not match our list. */
/* This will be fatal to the TLS conn; would be nice to kill TCP also.
Maybe as an option in future; for now leave control to the config (must-tls). */
-DEBUG(D_tls) debug_printf("TLS ALPN rejected\n");
+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 (%s) rejected", string_from_gstring(g));
+gstring_release_unused(g);
return SSL_TLSEXT_ERR_ALERT_FATAL;
}
#endif /* EXIM_HAVE_ALPN */
DEBUG(D_tls) bp = BIO_new(BIO_s_mem());
/* Use the CA & chain that verified the server cert to verify the stapled info */
+ /*XXX could we do an event here, for observability of ocsp? What reasonable data could we give access to? */
+ /* Dates would be a start. Do we need another opaque variable type, as for certs, plus an extract expansion? */
{
/* If this routine is not available, we've avoided [in tls_client_start()]
#ifdef EXIM_HAVE_SESSION_TICKET
SSL_SESSION_get_ticket_lifetime_hint(ss);
#else /* Use, fairly arbitrilarily, what we as server would */
- f.running_in_test_harness ? 6 : ssl_session_timeout;
+ f.running_in_test_harness ? TESTSUITE_TICKET_LIFE : ssl_session_timeout;
#endif
- if (lifetime + dt->time_stamp < time(NULL))
+ time_t now = time(NULL), expires = lifetime + dt->time_stamp;
+ if (expires < now)
{
- DEBUG(D_tls) debug_printf("session expired\n");
+ 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\n");
+ 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;
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_state);
+ /* debug_printf("tls_exdata_idx %d cbinfo %p\n", tls_exdata_idx, client_static_state); */
}
tlsp->resumption = RESUME_SUPPORTED;