X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/d4fd1b83a197d73cbac114fe53f3448d8b5c7cc2..777e3beace88a39457ee4a856a094e16649f333f:/src/src/tls-gnu.c diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index 9f166691a..35816cd60 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2017 */ +/* Copyright (c) University of Cambridge 1995 - 2018 */ /* See the file NOTICE for conditions of use and distribution. */ /* Copyright (c) Phil Pennock 2012 */ @@ -66,8 +66,17 @@ require current GnuTLS, then we'll drop support for the ancient libraries). #if GNUTLS_VERSION_NUMBER >= 0x030506 && !defined(DISABLE_OCSP) # define SUPPORT_SRV_OCSP_STACK #endif -#if GNUTLS_VERSION_NUMBER >= 0x030000 && defined(EXPERIMENTAL_DANE) -# define SUPPORT_DANE + +#ifdef SUPPORT_DANE +# if GNUTLS_VERSION_NUMBER >= 0x030000 +# define DANESSL_USAGE_DANE_TA 2 +# define DANESSL_USAGE_DANE_EE 3 +# else +# error GnuTLS version too early for DANE +# endif +# if GNUTLS_VERSION_NUMBER < 0x999999 +# define GNUTLS_BROKEN_DANE_VALIDATION +# endif #endif #ifndef DISABLE_OCSP @@ -147,8 +156,8 @@ typedef struct exim_gnutls_state { uschar *xfer_buffer; int xfer_buffer_lwm; int xfer_buffer_hwm; - int xfer_eof; - int xfer_error; + BOOL xfer_eof; /*XXX never gets set! */ + BOOL xfer_error; } exim_gnutls_state_st; static const exim_gnutls_state_st exim_gnutls_state_init = { @@ -189,8 +198,8 @@ static const exim_gnutls_state_st exim_gnutls_state_init = { .xfer_buffer = NULL, .xfer_buffer_lwm = 0, .xfer_buffer_hwm = 0, - .xfer_eof = 0, - .xfer_error = 0, + .xfer_eof = FALSE, + .xfer_error = FALSE, }; /* Not only do we have our own APIs which don't pass around state, assuming @@ -1573,7 +1582,7 @@ Returns: */ static BOOL -verify_certificate(exim_gnutls_state_st *state, uschar ** errstr) +verify_certificate(exim_gnutls_state_st * state, uschar ** errstr) { int rc; uint verify; @@ -1600,24 +1609,91 @@ else dane_state_t s; dane_query_t r; - const gnutls_datum_t * certlist; uint lsize; + const gnutls_datum_t * certlist = + gnutls_certificate_get_peers(state->session, &lsize); + int usage = tls_out.tlsa_usage; + +# ifdef GNUTLS_BROKEN_DANE_VALIDATION + /* Split the TLSA records into two sets, TA and EE selectors. Run the + dane-verification separately so that we know which selector verified; + then we know whether to do CA-chain-verification and name-verification + (needed for TA but not EE). */ + + if (usage == ((1<dane_data_len[nrec]; ) nrec++; + nrec++; + + dd = store_get(nrec * sizeof(uschar *)); + ddl = store_get(nrec * sizeof(int)); + nrec--; + + if ((rc = dane_state_init(&s, 0))) + goto tlsa_prob; + + for (usage = DANESSL_USAGE_DANE_EE; + usage >= DANESSL_USAGE_DANE_TA; usage--) + { /* take records with this usage */ + for (j = i = 0; i < nrec; i++) + if (state->dane_data[i][0] == usage) + { + dd[j] = state->dane_data[i]; + ddl[j++] = state->dane_data_len[i]; + } + if (j) + { + dd[j] = NULL; + ddl[j] = 0; + + if ((rc = dane_raw_tlsa(s, &r, (char * const *)dd, ddl, 1, 0))) + goto tlsa_prob; + + if ((rc = dane_verify_crt_raw(s, certlist, lsize, + gnutls_certificate_type_get(state->session), + r, 0, + usage == DANESSL_USAGE_DANE_EE + ? DANE_VFLAG_ONLY_CHECK_EE_USAGE : 0, + &verify))) + { + DEBUG(D_tls) + debug_printf("TLSA record problem: %s\n", dane_strerror(rc)); + } + else if (verify == 0) /* verification passed */ + { + usage = 1 << usage; + break; + } + } + } - certlist = gnutls_certificate_get_peers(state->session, &lsize); - - if ( (rc = dane_state_init(&s, 0)) - || (rc = dane_raw_tlsa(s, &r, state->dane_data, state->dane_data_len, - 1, 0)) - || (rc = dane_verify_crt_raw(s, certlist, lsize, - gnutls_certificate_type_get(state->session), - r, 0, 0, &verify)) - ) - + if (rc) goto tlsa_prob; + } + else +# endif { - *errstr = string_sprintf("TLSA record problem: %s", dane_strerror(rc)); - goto badcert; + if ( (rc = dane_state_init(&s, 0)) + || (rc = dane_raw_tlsa(s, &r, state->dane_data, state->dane_data_len, + 1, 0)) + || (rc = dane_verify_crt_raw(s, certlist, lsize, + gnutls_certificate_type_get(state->session), + r, 0, +# ifdef GNUTLS_BROKEN_DANE_VALIDATION + usage == (1 << DANESSL_USAGE_DANE_EE) + ? DANE_VFLAG_ONLY_CHECK_EE_USAGE : 0, +# else + 0, +# endif + &verify)) + ) + goto tlsa_prob; } - if (verify != 0) + + if (verify != 0) /* verification failed */ { gnutls_datum_t str; (void) dane_verification_status_print(verify, &str, 0); @@ -1625,6 +1701,17 @@ else goto badcert; } state->peer_dane_verified = TRUE; + +# ifdef GNUTLS_BROKEN_DANE_VALIDATION + /* If a TA-mode TLSA record was used for verification we must additionally + verify the CA chain and the cert name. For EE-mode, skip it. */ + + if (usage & (1 << DANESSL_USAGE_DANE_EE)) +# endif + { + state->peer_cert_verified = TRUE; + goto goodcert; + } } #endif @@ -1633,9 +1720,7 @@ else /* Handle the result of verification. INVALID is set if any others are. */ -if (rc < 0 || - verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED) - ) +if (rc < 0 || verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED)) { state->peer_cert_verified = FALSE; if (!*errstr) @@ -1676,8 +1761,14 @@ else state->peerdn ? state->peerdn : US""); } -state->tlsp->peerdn = state->peerdn; -return TRUE; +goodcert: + state->tlsp->peerdn = state->peerdn; + return TRUE; + +#ifdef SUPPORT_DANE +tlsa_prob: + *errstr = string_sprintf("TLSA record problem: %s", dane_strerror(rc)); +#endif badcert: gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE); @@ -1829,12 +1920,10 @@ int rc; uschar * yield; exim_gnutls_state_st * state = gnutls_session_get_ptr(session); -cert_list = gnutls_certificate_get_peers(session, &cert_list_size); -if (cert_list) +if ((cert_list = gnutls_certificate_get_peers(session, &cert_list_size))) while (cert_list_size--) { - rc = import_cert(&cert_list[cert_list_size], &crt); - if (rc != GNUTLS_E_SUCCESS) + 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)); @@ -2075,7 +2164,7 @@ use in DANE verification. We point at the dnsa data not copy it, so it must remain valid until after verification is done.*/ -static void +static BOOL dane_tlsa_load(exim_gnutls_state_st * state, dns_answer * dnsa) { dns_record * rr; @@ -2098,17 +2187,39 @@ for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS), i = 0; ) if (rr->type == T_TLSA) { const uschar * p = rr->data; - uint8_t usage = *p; + uint8_t usage = p[0], sel = p[1], type = p[2]; + + DEBUG(D_tls) + debug_printf("TLSA: %d %d %d size %d\n", usage, sel, type, rr->size); + + if ( (usage != DANESSL_USAGE_DANE_TA && usage != DANESSL_USAGE_DANE_EE) + || (sel != 0 && sel != 1) + ) + continue; + switch(type) + { + case 0: /* Full: cannot check at present */ + break; + case 1: if (rr->size != 3 + 256/8) continue; /* sha2-256 */ + break; + case 2: if (rr->size != 3 + 512/8) continue; /* sha2-512 */ + break; + default: continue; + } tls_out.tlsa_usage |= 1<size; } + +if (!i) return FALSE; + dane_data[i] = NULL; dane_data_len[i] = 0; state->dane_data = (char * const *)dane_data; state->dane_data_len = dane_data_len; +return TRUE; } #endif @@ -2140,7 +2251,7 @@ int tls_client_start(int fd, host_item *host, address_item *addr ARG_UNUSED, transport_instance * tb, -#ifdef EXPERIMENTAL_DANE +#ifdef SUPPORT_DANE dns_answer * tlsa_dnsa, #endif uschar ** errstr) @@ -2149,6 +2260,7 @@ smtp_transport_options_block *ob = (smtp_transport_options_block *)tb->options_block; int rc; exim_gnutls_state_st * state = NULL; +uschar *cipher_list = NULL; #ifndef DISABLE_OCSP BOOL require_ocsp = verify_check_given_host(&ob->hosts_require_ocsp, host) == OK; @@ -2158,9 +2270,24 @@ BOOL request_ocsp = require_ocsp ? TRUE DEBUG(D_tls) debug_printf("initialising GnuTLS as a client on fd %d\n", fd); +#ifdef SUPPORT_DANE +if (tlsa_dnsa && ob->dane_require_tls_ciphers) + { + /* not using expand_check_tlsvar because not yet in state */ + if (!expand_check(ob->dane_require_tls_ciphers, US"dane_require_tls_ciphers", + &cipher_list, errstr)) + return DEFER; + cipher_list = cipher_list && *cipher_list + ? ob->dane_require_tls_ciphers : ob->tls_require_ciphers; + } +#endif + +if (!cipher_list) + cipher_list = ob->tls_require_ciphers; + if ((rc = tls_init(host, ob->tls_certificate, ob->tls_privatekey, ob->tls_sni, ob->tls_verify_certificates, ob->tls_crl, - ob->tls_require_ciphers, &state, errstr)) != OK) + cipher_list, &state, errstr)) != OK) return rc; { @@ -2185,13 +2312,12 @@ set but both tls_verify_hosts and tls_try_verify_hosts are unset. Check only the specified host patterns if one of them is defined */ #ifdef SUPPORT_DANE -if (tlsa_dnsa) +if (tlsa_dnsa && dane_tlsa_load(state, tlsa_dnsa)) { DEBUG(D_tls) debug_printf("TLS: server certificate DANE required.\n"); state->verify_requirement = VERIFY_DANE; gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE); - dane_tlsa_load(state, tlsa_dnsa); } else #endif @@ -2256,10 +2382,8 @@ DEBUG(D_tls) debug_printf("about to gnutls_handshake\n"); sigalrm_seen = FALSE; alarm(ob->command_timeout); do - { rc = gnutls_handshake(state->session); - } while ((rc == GNUTLS_E_AGAIN) || - (rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen)); +while (rc == GNUTLS_E_AGAIN || rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen); alarm(0); if (rc != GNUTLS_E_SUCCESS) @@ -2332,12 +2456,15 @@ return OK; daemon, to shut down the TLS library, without actually doing a shutdown (which would tamper with the TLS session in the parent process). -Arguments: TRUE if gnutls_bye is to be called +Arguments: + shutdown 1 if TLS close-alert is to be sent, + 2 if also response to be waited for + Returns: nothing */ void -tls_close(BOOL is_server, BOOL shutdown) +tls_close(BOOL is_server, int shutdown) { exim_gnutls_state_st *state = is_server ? &state_server : &state_client; @@ -2345,8 +2472,12 @@ if (!state->tlsp || state->tlsp->active < 0) return; /* TLS was not active */ if (shutdown) { - DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS\n"); - gnutls_bye(state->session, GNUTLS_SHUT_WR); + DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS%s\n", + shutdown > 1 ? " (with response-wait)" : ""); + + alarm(2); + gnutls_bye(state->session, shutdown > 1 ? GNUTLS_SHUT_RDWR : GNUTLS_SHUT_WR); + alarm(0); } gnutls_deinit(state->session); @@ -2354,9 +2485,10 @@ gnutls_certificate_free_credentials(state->x509_cred); state->tlsp->active = -1; +if (state->xfer_buffer) store_free(state->xfer_buffer); memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init)); -if ((state_server.session == NULL) && (state_client.session == NULL)) +if (!state_server.session && !state_client.session) { gnutls_global_deinit(); exim_gnutls_base_init_done = FALSE; @@ -2375,20 +2507,29 @@ ssize_t inbytes; DEBUG(D_tls) debug_printf("Calling gnutls_record_recv(%p, %p, %u)\n", state->session, state->xfer_buffer, ssl_xfer_buffer_size); +sigalrm_seen = FALSE; if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout); inbytes = gnutls_record_recv(state->session, state->xfer_buffer, MIN(ssl_xfer_buffer_size, lim)); -alarm(0); +if (smtp_receive_timeout > 0) alarm(0); + +if (had_command_timeout) /* set by signal handler */ + smtp_command_timeout_exit(); /* does not return */ +if (had_command_sigterm) + smtp_command_sigterm_exit(); +if (had_data_timeout) + smtp_data_timeout_exit(); +if (had_data_sigint) + smtp_data_sigint_exit(); -/* Timeouts do not get this far; see command_timeout_handler(). - A zero-byte return appears to mean that the TLS session has been - closed down, not that the socket itself has been closed down. Revert to - non-TLS handling. */ +/* Timeouts do not get this far. A zero-byte return appears to mean that the +TLS session has been closed down, not that the socket itself has been closed +down. Revert to non-TLS handling. */ if (sigalrm_seen) { DEBUG(D_tls) debug_printf("Got tls read timeout\n"); - state->xfer_error = 1; + state->xfer_error = TRUE; return FALSE; } @@ -2424,7 +2565,7 @@ else if (inbytes == 0) else if (inbytes < 0) { record_io_error(state, (int) inbytes, US"recv", NULL); - state->xfer_error = 1; + state->xfer_error = TRUE; return FALSE; } #ifndef DISABLE_DKIM