X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/da40b1ec6b91ccd3faa4def9e5cff05ec51ca573..0851a3bbf4667081d47f5d85b6b3a5cb33cbdba6:/src/src/tls-gnu.c diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index fc426a251..fa489902a 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -3,6 +3,7 @@ *************************************************/ /* 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 */ @@ -53,6 +54,9 @@ require current GnuTLS, then we'll drop support for the ancient libraries). # 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 @@ -107,9 +111,11 @@ require current GnuTLS, then we'll drop support for the ancient libraries). # 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 @@ -127,7 +133,7 @@ require current GnuTLS, then we'll drop support for the ancient libraries). 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 @@ -167,7 +173,7 @@ Some of these correspond to variables in globals.c; those variables will 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 { @@ -177,10 +183,17 @@ 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; @@ -213,8 +226,6 @@ typedef struct exim_gnutls_state { 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 = { @@ -257,7 +268,7 @@ static BOOL gnutls_buggy_ocsp = FALSE; 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 @@ -317,7 +328,7 @@ static void exim_gnutls_logger_cb(int level, const char *message); 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); @@ -328,7 +339,7 @@ tls_server_ticket_cb(gnutls_session_t sess, u_int htype, unsigned when, 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 @@ -479,7 +490,7 @@ Sets: 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 @@ -511,10 +522,10 @@ tlsp->certificate_verified = state->peer_cert_verified; 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; @@ -522,11 +533,15 @@ if ((rc = gnutls_session_channel_binding(state->session, GNUTLS_CB_TLS_UNIQUE, & { 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 @@ -815,13 +830,19 @@ gnutls_x509_privkey_t pkey = NULL; 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 @@ -981,10 +1002,10 @@ return gnutls_ext_raw_parse(NULL, tls_server_servercerts_ext, msg, 0); "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, @@ -1016,7 +1037,7 @@ switch (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 @@ -1935,7 +1956,7 @@ else 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); @@ -2298,17 +2319,18 @@ if (TRUE) } 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)" + " 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) @@ -2407,11 +2429,22 @@ and sent an SMTP response. */ 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 -#ifdef EXPERIMENTAL_TLS_RESUME + 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 EXIM_HAVE_TLS_RESUME tls_server_resume_prehandshake(state); #endif @@ -2514,7 +2547,12 @@ if (rc != GNUTLS_E_SUCCESS) 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 @@ -2565,9 +2603,9 @@ if (verify_check_given_host(CUSS &ob->tls_verify_cert_hostnames, host) == OK) { state->exp_tls_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("TLS: server cert verification includes hostname: \"%s\".\n", @@ -2647,7 +2685,7 @@ return TRUE; -#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 @@ -2780,7 +2818,7 @@ if (gnutls_session_is_resumed(state->session)) tls_save_session(tlsp, state->session, host); } -#endif /* EXPERIMENTAL_TLS_RESUME */ +#endif /* !DISABLE_TLS_RESUME */ /************************************************* @@ -2843,10 +2881,21 @@ if (conn_args->dane && ob->dane_require_tls_ciphers) 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; @@ -2923,7 +2972,7 @@ if (request_ocsp) } #endif -#ifdef EXPERIMENTAL_TLS_RESUME +#ifdef EXIM_HAVE_TLS_RESUME tls_client_resume_prehandshake(state, tlsp, host, ob); #endif @@ -2972,6 +3021,11 @@ if (!verify_certificate(state, errstr)) 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) { @@ -3018,7 +3072,7 @@ if (request_ocsp) } #endif -#ifdef EXPERIMENTAL_TLS_RESUME +#ifdef EXIM_HAVE_TLS_RESUME tls_client_resume_posthandshake(state, tlsp, host); #endif @@ -3084,7 +3138,7 @@ gnutls_certificate_free_credentials(state->x509_cred); 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); @@ -3100,7 +3154,7 @@ tls_refill(unsigned lim) 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; @@ -3263,7 +3317,7 @@ if (state->xfer_buffer_lwm < state->xfer_buffer_hwm) 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 @@ -3298,6 +3352,9 @@ Arguments: 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 */ @@ -3308,10 +3365,14 @@ tls_write(void * ct_ctx, const uschar * buff, size_t len, BOOL more) 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__, @@ -3319,14 +3380,15 @@ 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__); @@ -3352,10 +3414,25 @@ if (len > INT_MAX) } #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