*************************************************/
/* Copyright (c) University of Cambridge 1995 - 2018 */
+/* Copyright (c) The Exim Maintainers 2020 */
/* See the file NOTICE for conditions of use and distribution. */
/* Copyright (c) Phil Pennock 2012 */
# warning "GnuTLS library version too old; tls:cert event unsupported"
# define DISABLE_EVENT
#endif
+#if GNUTLS_VERSION_NUMBER >= 0x030000
+# define SUPPORT_SELFSIGN /* Uncertain what version is first usable but 2.12.23 is not */
+#endif
#if GNUTLS_VERSION_NUMBER >= 0x030306
# define SUPPORT_CA_DIR
#else
# define GNUTLS_AUTO_GLOBAL_INIT
# define GNUTLS_AUTO_PKCS11_MANUAL
#endif
+#if (GNUTLS_VERSION_NUMBER >= 0x030404) \
+ || (GNUTLS_VERSION_NUMBER >= 0x030311) && (GNUTLS_VERSION_NUMBER & 0xffff00 == 0x030300)
+# ifndef DISABLE_OCSP
+# define EXIM_HAVE_OCSP
+# endif
+#endif
#if GNUTLS_VERSION_NUMBER >= 0x030500
# define SUPPORT_GNUTLS_KEYLOG
#endif
# endif
#endif
-#ifdef EXPERIMENTAL_TLS_RESUME
-# if GNUTLS_VERSION_NUMBER < 0x030603
-# error GNUTLS version too early for session-resumption
+#ifndef DISABLE_TLS_RESUME
+# if GNUTLS_VERSION_NUMBER >= 0x030603
+# define EXIM_HAVE_TLS_RESUME
+# else
+# warning "GnuTLS library version too old; resumption unsupported"
# endif
#endif
void
options_tls(void)
{
-# ifdef EXPERIMENTAL_TLS_RESUME
+# ifndef DISABLE_TLS_RESUME
builtin_macro_create_var(US"_RESUME_DECODE", RESUME_DECODE_STRING );
# endif
# ifdef EXIM_HAVE_TLS1_3
builtin_macro_create(US"_HAVE_TLS1_3");
# endif
+# ifdef EXIM_HAVE_OCSP
+builtin_macro_create(US"_HAVE_TLS_OCSP");
+# endif
+# ifdef SUPPORT_SRV_OCSP_STACK
+builtin_macro_create(US"_HAVE_TLS_OCSP_LIST");
+# endif
}
#else
be set to point to content in one of these instances, as appropriate for
the stage of the process lifetime.
-Not handled here: global tls_channelbinding_b64.
+Not handled here: global tlsp->tls_channelbinding.
*/
typedef struct exim_gnutls_state {
enum peer_verify_requirement verify_requirement;
int fd_in;
int fd_out;
- BOOL peer_cert_verified;
- BOOL peer_dane_verified;
- BOOL trigger_sni_changes;
- BOOL have_set_peerdn;
+
+ BOOL peer_cert_verified:1;
+ BOOL peer_dane_verified:1;
+ BOOL trigger_sni_changes:1;
+ BOOL have_set_peerdn:1;
+ BOOL xfer_eof:1; /*XXX never gets set! */
+ BOOL xfer_error:1;
+#ifdef SUPPORT_CORK
+ BOOL corked:1;
+#endif
+
const struct host_item *host; /* NULL if server */
gnutls_x509_crt_t peercert;
uschar *peerdn;
uschar *xfer_buffer;
int xfer_buffer_lwm;
int xfer_buffer_hwm;
- BOOL xfer_eof; /*XXX never gets set! */
- BOOL xfer_error;
} exim_gnutls_state_st;
static const exim_gnutls_state_st exim_gnutls_state_init = {
static BOOL exim_testharness_disable_ocsp_validity_check = FALSE;
#endif
-#ifdef EXPERIMENTAL_TLS_RESUME
+#ifdef EXIM_HAVE_TLS_RESUME
static gnutls_datum_t server_sessticket_key;
#endif
static int exim_sni_handling_cb(gnutls_session_t session);
-#ifdef EXPERIMENTAL_TLS_RESUME
+#ifdef EXIM_HAVE_TLS_RESUME
static int
tls_server_ticket_cb(gnutls_session_t sess, u_int htype, unsigned when,
unsigned incoming, const gnutls_datum_t * msg);
void
tls_daemon_init(void)
{
-#ifdef EXPERIMENTAL_TLS_RESUME
+#ifdef EXIM_HAVE_TLS_RESUME
/* We are dependent on the GnuTLS implementation of the Session Ticket
encryption; both the strength and the key rotation period. We hope that
the strength at least matches that of the ciphersuite (but GnuTLS does not
tls_active fd
tls_bits strength indicator
tls_certificate_verified bool indicator
- tls_channelbinding_b64 for some SASL mechanisms
+ tls_channelbinding for some SASL mechanisms
+ tls_ver a string
tls_cipher a string
tls_peercert pointer to library internal
tls_peerdn a string
tlsp->dane_verified = state->peer_dane_verified;
#endif
-/* note that tls_channelbinding_b64 is not saved to the spool file, since it's
+/* note that tls_channelbinding is not saved to the spool file, since it's
only available for use for authenticators while this TLS session is running. */
-tls_channelbinding_b64 = NULL;
+tlsp->channelbinding = NULL;
#ifdef HAVE_GNUTLS_SESSION_CHANNEL_BINDING
channel.data = NULL;
channel.size = 0;
{ DEBUG(D_tls) debug_printf("Channel binding error: %s\n", gnutls_strerror(rc)); }
else
{
+ /* Declare the taintedness of the binding info. On server, untainted; on
+ client, tainted - being the Finish msg from the server. */
+
old_pool = store_pool;
store_pool = POOL_PERM;
- tls_channelbinding_b64 = b64encode(CUS channel.data, (int)channel.size);
+ tlsp->channelbinding = b64encode_taint(CUS channel.data, (int)channel.size,
+ !!state->host);
store_pool = old_pool;
- DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage.\n");
+ DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage\n");
}
#endif
const uschar * where;
int rc;
+#ifndef SUPPORT_SELFSIGN
+where = US"library too old";
+rc = GNUTLS_E_NO_CERTIFICATE_FOUND;
+if (TRUE) goto err;
+#endif
+
where = US"initialising pkey";
if ((rc = gnutls_x509_privkey_init(&pkey))) goto err;
where = US"initialising cert";
if ((rc = gnutls_x509_crt_init(&cert))) goto err;
-where = US"generating pkey";
+where = US"generating pkey"; /* Hangs on 2.12.23 */
if ((rc = gnutls_x509_privkey_generate(pkey, GNUTLS_PK_RSA,
#ifdef SUPPORT_PARAM_TO_PK_BITS
# ifndef GNUTLS_SEC_PARAM_MEDIUM
"Handshake Protocol: Certificate" record.
So we need to spot the Certificate handshake message, parse it and spot any status_request extension(s)
-This is different to tls1.2 - where it is a separate record (wireshake term) / handshake message (gnutls term).
+This is different to tls1.2 - where it is a separate record (wireshark term) / handshake message (gnutls term).
*/
-#if defined(EXPERIMENTAL_TLS_RESUME) || defined(SUPPORT_GNUTLS_EXT_RAW_PARSE)
+#if defined(EXIM_HAVE_TLS_RESUME) || defined(SUPPORT_GNUTLS_EXT_RAW_PARSE)
/* Callback for certificate-status, on server. We sent stapled OCSP. */
static int
tls_server_certstatus_cb(gnutls_session_t session, unsigned int htype,
# endif
case GNUTLS_HANDSHAKE_CERTIFICATE_STATUS:
return tls_server_certstatus_cb(sess, htype, when, incoming, msg);
-# ifdef EXPERIMENTAL_TLS_RESUME
+# ifdef EXIM_HAVE_TLS_RESUME
case GNUTLS_HANDSHAKE_NEW_SESSION_TICKET:
return tls_server_ticket_cb(sess, htype, when, incoming, msg);
# endif
/* debug_printf("peer_status: gnutls_session_get_desc %s\n", s); */
for (s++; (c = *s) && c != ')'; s++) g = string_catn(g, s, 1);
+
+ tlsp->ver = string_copyn(g->s, g->ptr);
+ for (uschar * p = US tlsp->ver; *p; p++)
+ if (*p == '-') { *p = '\0'; break; } /* TLS1.0-PKIX -> TLS1.0 */
+
g = string_catn(g, US":", 1);
if (*s) s++; /* now on _ between groups */
while ((c = *s))
{
- for (*++s && ++s; (c = *s) && c != ')'; s++) g = string_catn(g, c == '-' ? US"_" : s, 1);
+ for (*++s && ++s; (c = *s) && c != ')'; s++)
+ g = string_catn(g, c == '-' ? US"_" : s, 1);
/* now on ) closing group */
if ((c = *s) && *++s == '-') g = string_catn(g, US"__", 2);
/* now on _ between groups */
releases did return "TLS 1.0"; play it safe, just in case. */
for (uschar * p = state->ciphersuite; *p; p++) if (isspace(*p)) *p = '-';
+ tlsp->ver = string_copyn(state->ciphersuite,
+ Ustrchr(state->ciphersuite, ':') - state->ciphersuite);
#endif
/* debug_printf("peer_status: ciphersuite %s\n", state->ciphersuite); */
const char ** dd;
int * ddl;
- for(nrec = 0; state->dane_data_len[nrec]; ) nrec++;
+ for (nrec = 0; state->dane_data_len[nrec]; ) nrec++;
nrec++;
dd = store_get(nrec * sizeof(uschar *), FALSE);
#ifdef SUPPORT_GNUTLS_SESS_DESC
debug_printf("%s\n", gnutls_session_get_desc(state->session));
#endif
-#ifdef SUPPORT_GNUTLS_KEYLOG
+#ifdef SUPPORT_GNUTLS_KEYLOG
# ifdef EXIM_HAVE_TLS1_3
if (gnutls_protocol_get_version(state->session) < GNUTLS_TLS1_3)
-#else
+# else
if (TRUE)
-#endif
+# endif
{
gnutls_datum_t c, s;
gstring * gc, * gs;
- /* we only want the client random and the master secret */
+ /* For TLS1.2 we only want the client random and the master secret */
gnutls_session_get_random(state->session, &c, &s);
gnutls_session_get_master_secret(state->session, &s);
gc = ddump(&c);
}
else
debug_printf("To get keying info for TLS1.3 is hard:\n"
- " set environment variable SSLKEYLOGFILE to a filename writable by uid exim\n"
- " add SSLKEYLOGFILE to keep_environment in the exim config\n"
- " run exim as root\n"
- " if using sudo, add SSLKEYLOGFILE to env_keep in /etc/sudoers\n"
- " (works for TLS1.2 also, and saves cut-paste into file)\n");
+ " Set environment variable SSLKEYLOGFILE to a filename relative to the spool directory,\n"
+ " and make sure it is writable by the Exim runtime user.\n"
+ " Add SSLKEYLOGFILE to keep_environment in the exim config.\n"
+ " Start Exim as root.\n"
+ " If using sudo, add SSLKEYLOGFILE to env_keep in /etc/sudoers\n"
+ " (works for TLS1.2 also, and saves cut-paste into file).\n"
+ " Trying to use add_environment for this will not work\n");
#endif
}
-#ifdef EXPERIMENTAL_TLS_RESUME
+#ifdef EXIM_HAVE_TLS_RESUME
static int
tls_server_ticket_cb(gnutls_session_t sess, u_int htype, unsigned when,
unsigned incoming, const gnutls_datum_t * msg)
DEBUG(D_tls) debug_printf("initialising GnuTLS as a server\n");
-if ((rc = tls_init(NULL, tls_certificate, tls_privatekey,
- NULL, tls_verify_certificates, tls_crl,
- require_ciphers, &state, &tls_in, errstr)) != OK) return rc;
+ {
+#ifdef MEASURE_TIMING
+ struct timeval t0;
+ gettimeofday(&t0, NULL);
+#endif
+
+ if ((rc = tls_init(NULL, tls_certificate, tls_privatekey,
+ NULL, tls_verify_certificates, tls_crl,
+ require_ciphers, &state, &tls_in, errstr)) != OK) return rc;
+
+#ifdef MEASURE_TIMING
+ report_time_since(&t0, US"server tls_init (delta)");
+#endif
+ }
-#ifdef EXPERIMENTAL_TLS_RESUME
+#ifdef EXIM_HAVE_TLS_RESUME
tls_server_resume_prehandshake(state);
#endif
return FAIL;
}
-#ifdef EXPERIMENTAL_TLS_RESUME
+#ifdef GNUTLS_SFLAGS_EXT_MASTER_SECRET
+if (gnutls_session_get_flags(state->session) & GNUTLS_SFLAGS_EXT_MASTER_SECRET)
+ tls_in.ext_master_secret = TRUE;
+#endif
+
+#ifdef EXIM_HAVE_TLS_RESUME
tls_server_resume_posthandshake(state);
#endif
-#ifdef EXPERIMENTAL_TLS_RESUME
+#ifdef EXIM_HAVE_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. Although
there is a gnutls_session_ticket_enable_client() interface it is
tls_save_session(tlsp, state->session, host);
}
-#endif /* EXPERIMENTAL_TLS_RESUME */
+#endif /* !DISABLE_TLS_RESUME */
/*************************************************
if (!cipher_list)
cipher_list = ob->tls_require_ciphers;
-if (tls_init(host, ob->tls_certificate, ob->tls_privatekey,
- ob->tls_sni, ob->tls_verify_certificates, ob->tls_crl,
- cipher_list, &state, tlsp, errstr) != OK)
- return FALSE;
+ {
+#ifdef MEASURE_TIMING
+ struct timeval t0;
+ gettimeofday(&t0, NULL);
+#endif
+
+ if (tls_init(host, ob->tls_certificate, ob->tls_privatekey,
+ ob->tls_sni, ob->tls_verify_certificates, ob->tls_crl,
+ cipher_list, &state, tlsp, errstr) != OK)
+ return FALSE;
+
+#ifdef MEASURE_TIMING
+ report_time_since(&t0, US"client tls_init (delta)");
+#endif
+ }
{
int dh_min_bits = ob->tls_dh_min_bits;
}
#endif
-#ifdef EXPERIMENTAL_TLS_RESUME
+#ifdef EXIM_HAVE_TLS_RESUME
tls_client_resume_prehandshake(state, tlsp, host, ob);
#endif
return FALSE;
}
+#ifdef GNUTLS_SFLAGS_EXT_MASTER_SECRET
+if (gnutls_session_get_flags(state->session) & GNUTLS_SFLAGS_EXT_MASTER_SECRET)
+ tlsp->ext_master_secret = TRUE;
+#endif
+
#ifndef DISABLE_OCSP
if (request_ocsp)
{
}
#endif
-#ifdef EXPERIMENTAL_TLS_RESUME
+#ifdef EXIM_HAVE_TLS_RESUME
tls_client_resume_posthandshake(state, tlsp, host);
#endif
tlsp->active.sock = -1;
tlsp->active.tls_ctx = NULL;
/* Leave bits, peercert, cipher, peerdn, certificate_verified set, for logging */
-tls_channelbinding_b64 = NULL;
+tlsp->channelbinding = NULL;
if (state->xfer_buffer) store_free(state->xfer_buffer);
exim_gnutls_state_st * state = &state_server;
ssize_t inbytes;
-DEBUG(D_tls) debug_printf("Calling gnutls_record_recv(%p, %p, %u)\n",
+DEBUG(D_tls) debug_printf("Calling gnutls_record_recv(session=%p, buffer=%p, buffersize=%u)\n",
state->session, state->xfer_buffer, ssl_xfer_buffer_size);
sigalrm_seen = FALSE;
state->xfer_buffer_hwm - state->xfer_buffer_lwm);
DEBUG(D_tls)
- debug_printf("Calling gnutls_record_recv(%p, %p, " SIZE_T_FMT ")\n",
+ debug_printf("Calling gnutls_record_recv(session=%p, buffer=%p, len=" SIZE_T_FMT ")\n",
state->session, buff, len);
do
len number of bytes
more more data expected soon
+Calling with len zero and more unset will flush buffered writes. The buff
+argument can be null for that case.
+
Returns: the number of bytes after a successful write,
-1 after a failed write
*/
ssize_t outbytes;
size_t left = len;
exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server;
-#ifdef SUPPORT_CORK
-static BOOL corked = FALSE;
-if (more && !corked) gnutls_record_cork(state->session);
+#ifdef SUPPORT_CORK
+if (more && !state->corked)
+ {
+ DEBUG(D_tls) debug_printf("gnutls_record_cork(session=%p)\n", state->session);
+ gnutls_record_cork(state->session);
+ state->corked = TRUE;
+ }
#endif
DEBUG(D_tls) debug_printf("%s(%p, " SIZE_T_FMT "%s)\n", __FUNCTION__,
while (left > 0)
{
- DEBUG(D_tls) debug_printf("gnutls_record_send(SSL, %p, " SIZE_T_FMT ")\n",
- buff, left);
+ DEBUG(D_tls) debug_printf("gnutls_record_send(session=%p, buffer=%p, left=" SIZE_T_FMT ")\n",
+ state->session, buff, left);
do
outbytes = gnutls_record_send(state->session, buff, left);
while (outbytes == GNUTLS_E_AGAIN);
DEBUG(D_tls) debug_printf("outbytes=" SSIZE_T_FMT "\n", outbytes);
+
if (outbytes < 0)
{
DEBUG(D_tls) debug_printf("%s: gnutls_record_send err\n", __FUNCTION__);
}
#ifdef SUPPORT_CORK
-if (more != corked)
+if (!more && state->corked)
{
- if (!more) (void) gnutls_record_uncork(state->session, 0);
- corked = more;
+ DEBUG(D_tls) debug_printf("gnutls_record_uncork(session=%p)\n", state->session);
+ do
+ /* We can't use GNUTLS_RECORD_WAIT here, as it retries on
+ GNUTLS_E_AGAIN || GNUTLS_E_INTR, which would break our timeout set by alarm().
+ The GNUTLS_E_AGAIN should not happen ever, as our sockets are blocking anyway.
+ But who knows. (That all relies on the fact that GNUTLS_E_INTR and GNUTLS_E_AGAIN
+ match the EINTR and EAGAIN errno values.) */
+ outbytes = gnutls_record_uncork(state->session, 0);
+ while (outbytes == GNUTLS_E_AGAIN);
+
+ if (outbytes < 0)
+ {
+ record_io_error(state, len, US"uncork", NULL);
+ return -1;
+ }
+
+ state->corked = FALSE;
}
#endif