/* Copyright (c) University of Cambridge 1995 - 2018 */
/* Copyright (c) Phil Pennock 2012 */
/* See the file NOTICE for conditions of use and distribution. */
+/* SPDX-License-Identifier: GPL-2.0-or-later */
/* This file provides TLS/SSL support for Exim using the GnuTLS library,
one of the available supported implementations. This file is #included into
# endif
#endif
+#if GNUTLS_VERSION_NUMBER >= 0x030702
+# define HAVE_GNUTLS_EXPORTER
+#endif
+
#ifndef DISABLE_OCSP
# include <gnutls/ocsp.h>
#endif
the connected host if setting up a client
errstr pointer to returned error string
-Returns: OK/DEFER/FAIL
+Returns: DEFER/FAIL
*/
static int
}
+/* Returns: DEFER/FAIL */
static int
tls_error_gnu(exim_gnutls_state_st * state, const uschar *prefix, int err,
uschar ** errstr)
{
return tls_error(prefix,
state && err == GNUTLS_E_FATAL_ALERT_RECEIVED
- ? US gnutls_alert_get_name(gnutls_alert_get(state->session))
+ ? string_sprintf("rxd alert: %s",
+ US gnutls_alert_get_name(gnutls_alert_get(state->session)))
: US gnutls_strerror(err),
state ? state->host : NULL,
errstr);
#ifdef HAVE_GNUTLS_SESSION_CHANNEL_BINDING
{
gnutls_datum_t channel = {.data = NULL, .size = 0};
- uschar * buf;
int rc;
-# ifdef HAVE_GNUTLS_PRF_RFC5705
+# ifdef HAVE_GNUTLS_EXPORTER
+ if (gnutls_protocol_get_version(state->session) >= GNUTLS_TLS1_3)
+ {
+ rc = gnutls_session_channel_binding(state->session, GNUTLS_CB_TLS_EXPORTER, &channel);
+ tlsp->channelbind_exporter = TRUE;
+ }
+ else
+# elif defined(HAVE_GNUTLS_PRF_RFC5705)
/* Older libraries may not have GNUTLS_TLS1_3 defined! */
if (gnutls_protocol_get_version(state->session) > GNUTLS_TLS1_2)
{
- buf = store_get(32, state->host ? GET_TAINTED : GET_UNTAINTED);
+ uschar * buf = store_get(32, state->host ? GET_TAINTED : GET_UNTAINTED);
rc = gnutls_prf_rfc5705(state->session,
(size_t)24, "EXPORTER-Channel-Binding", (size_t)0, "",
32, CS buf);
{
int old_pool = store_pool;
/* Declare the taintedness of the binding info. On server, untainted; on
- client, tainted - being the Finish msg from the server. */
+ client, tainted if we used the Finish msg from the server. */
store_pool = POOL_PERM;
tlsp->channelbinding = b64encode_taint(CUS channel.data, (int)channel.size,
- state->host ? GET_TAINTED : GET_UNTAINTED);
+ !tlsp->channelbind_exporter && state->host ? GET_TAINTED : GET_UNTAINTED);
store_pool = old_pool;
DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage\n");
}
/* The format of "data" here doesn't seem to be documented, but appears
to be a 2-byte field with a (redundant, given the "size" arg) total length
then a sequence of one-byte size then string (not nul-term) names. The
- latter is as described in OpenSSL documentation. */
+ latter is as described in OpenSSL documentation.
+ Note that we do not get called for a match_fail, making it hard to log
+ a single bad ALPN being offered (the common case). */
+ {
+ gstring * g = NULL;
DEBUG(D_tls) debug_printf("Seen ALPN extension from client (s=%u):", size);
for (const uschar * s = data+2; s-data < size-1; s += *s + 1)
{
server_seen_alpn++;
+ g = string_append_listele_n(g, ':', s+1, *s);
DEBUG(D_tls) debug_printf(" '%.*s'", (int)*s, s+1);
}
DEBUG(D_tls) debug_printf("\n");
if (server_seen_alpn > 1)
{
+ log_write(0, LOG_MAIN, "TLS ALPN (%s) rejected", string_from_gstring(g));
DEBUG(D_tls) debug_printf("TLS: too many ALPNs presented in handshake\n");
return GNUTLS_E_NO_APPLICATION_PROTOCOL;
}
break;
+ }
#endif
}
return 0;
unsigned when, unsigned int incoming, const gnutls_datum_t * msg)
{
/* Call fn for each extension seen. 3.6.3 onwards */
-return gnutls_ext_raw_parse(NULL, tls_server_clienthello_ext, msg,
+int rc = gnutls_ext_raw_parse(NULL, tls_server_clienthello_ext, msg,
GNUTLS_EXT_RAW_FLAG_TLS_CLIENT_HELLO);
+return rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE ? 0 : rc;
}
debug_printf("TLS: basic cred init, %s\n", server ? "server" : "client");
}
+/* Returns OK/DEFER/FAIL */
static int
creds_load_server_certs(exim_gnutls_state_st * state, const uschar * cert,
const uschar * pkey, const uschar * ocsp, uschar ** errstr)
if (!(kfile = string_nextinlist(&klist, &ksep, NULL, 0)))
return tls_error(US"cert/key setup: out of keys", NULL, NULL, errstr);
- else if ((rc = tls_add_certfile(state, NULL, cfile, kfile, errstr)) > 0)
+ else if ((rc = tls_add_certfile(state, NULL, cfile, kfile, errstr)) > OK)
return rc;
else
{
}
#endif /* DISABLE_OCSP */
}
-return 0;
+return OK;
}
static int
int rc = tls_add_certfile(state, host, cert, pkey, errstr);
if (rc > 0) return rc;
DEBUG(D_tls) debug_printf("TLS: cert/key registered\n");
-return 0;
+return OK;
}
static int
/* Preload whatever creds are static, onto a transport. The client can then
just copy the pointer as it starts up. */
+/*XXX this is not called for a cmdline send. But one needing to use >1 conn would benefit,
+and there seems little downside. */
+
static void
tls_client_creds_init(transport_instance * t, BOOL watch)
{
if (!state->lib_state.conn_certs)
{
- if (!Expand_check_tlsvar(tls_certificate, errstr))
+ if ( !Expand_check_tlsvar(tls_certificate, errstr)
+ || f.expand_string_forcedfail)
+ {
+ if (f.expand_string_forcedfail)
+ *errstr = US"expansion of tls_certificate failed";
return DEFER;
+ }
/* certificate is mandatory in server, optional in client */
else
DEBUG(D_tls) debug_printf("TLS: no client certificate specified; okay\n");
- if (state->tls_privatekey && !Expand_check_tlsvar(tls_privatekey, errstr))
+ if ( state->tls_privatekey && !Expand_check_tlsvar(tls_privatekey, errstr)
+ || f.expand_string_forcedfail
+ )
+ {
+ if (f.expand_string_forcedfail)
+ *errstr = US"expansion of tls_privatekey failed";
return DEFER;
+ }
/* tls_privatekey is optional, defaulting to same file as certificate */
tls_ocsp_file,
#endif
errstr)
- ) ) return rc;
+ ) )
+ {
+ DEBUG(D_tls) debug_printf("load-cert: '%s'\n", *errstr);
+ return rc;
+ }
}
}
else
{
/* If the setup of certs/etc failed before handshake, TLS would not have
been offered. The best we can do now is abort. */
- return GNUTLS_E_APPLICATION_ERROR_MIN;
+ DEBUG(D_tls) debug_printf("expansion for SNI-dependent session files failed\n");
+ return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE;
}
rc = tls_set_remaining_x509(state, &dummy_errstr);
-if (rc != OK) return GNUTLS_E_APPLICATION_ERROR_MIN;
+if (rc != OK) return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE;
return 0;
}
if ((cert_list = gnutls_certificate_get_peers(session, &cert_list_size)))
while (cert_list_size--)
- {
- if ((rc = import_cert(&cert_list[cert_list_size], &crt)) != GNUTLS_E_SUCCESS)
{
- DEBUG(D_tls) debug_printf("TLS: peer cert problem: depth %d: %s\n",
- cert_list_size, gnutls_strerror(rc));
- break;
- }
+ if ((rc = import_cert(&cert_list[cert_list_size], &crt)) != GNUTLS_E_SUCCESS)
+ {
+ DEBUG(D_tls) debug_printf("TLS: peer cert problem: depth %d: %s\n",
+ cert_list_size, gnutls_strerror(rc));
+ break;
+ }
- state->tlsp->peercert = crt;
- if ((yield = event_raise(state->event_action,
- US"tls:cert", string_sprintf("%d", cert_list_size), &errno)))
- {
- log_write(0, LOG_MAIN,
- "SSL verify denied by event-action: depth=%d: %s",
- cert_list_size, yield);
- return 1; /* reject */
+ state->tlsp->peercert = crt;
+ if ((yield = event_raise(state->event_action,
+ US"tls:cert", string_sprintf("%d", cert_list_size), &errno)))
+ {
+ log_write(0, LOG_MAIN,
+ "SSL verify denied by event-action: depth=%d: %s",
+ cert_list_size, yield);
+ return 1; /* reject */
+ }
+ state->tlsp->peercert = NULL;
}
- state->tlsp->peercert = NULL;
- }
return 0;
}
#endif
(void) gnutls_alert_send_appropriate(state->session, rc);
gnutls_deinit(state->session);
- gnutls_certificate_free_credentials(state->lib_state.x509_cred);
- state->lib_state = null_tls_preload;
millisleep(500);
shutdown(state->fd_out, SHUT_WR);
for (int i = 1024; fgetc(smtp_in) != EOF && i > 0; ) i--; /* drain skt */
smtp_connect_args * conn_args, smtp_transport_options_block * ob)
{
tlsp->resumption = RESUME_SUPPORTED;
-if (verify_check_given_host(CUSS &ob->tls_resumption_hosts, conn_args->host) == OK)
+
+if (!conn_args->have_lbserver)
+ { DEBUG(D_tls) debug_printf("resumption not supported on continued-connection\n"); }
+else if (verify_check_given_host(CUSS &ob->tls_resumption_hosts, conn_args->host) == OK)
{
dbdata_tls_session * dt;
int len, rc;
}
gnutls_deinit(state->session);
-gnutls_certificate_free_credentials(state->lib_state.x509_cred);
-state->lib_state = null_tls_preload;
-
tlsp->active.sock = -1;
tlsp->active.tls_ctx = NULL;
/* Leave bits, peercert, cipher, peerdn, certificate_verified set, for logging */