X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/4a852e8c97fa4de42c443107121c7717e1f0c9b2..a85c067ba6c6940512cf57ec213277a370d87e70:/src/src/tls-gnu.c diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index 618caf6a2..c98760202 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -2,11 +2,11 @@ * Exim - an Internet mail transport agent * *************************************************/ +/* Copyright (c) The Exim Maintainers 2020 - 2022 */ /* 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 */ +/* See the file NOTICE for conditions of use and distribution. */ +/* SPDX-License-Identifier: GPL-2.0-only */ /* This file provides TLS/SSL support for Exim using the GnuTLS library, one of the available supported implementations. This file is #included into @@ -90,9 +90,6 @@ 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 >= 0x030600 -# define GNUTLS_AUTO_DHPARAMS -#endif #if GNUTLS_VERSION_NUMBER >= 0x030603 # define EXIM_HAVE_TLS1_3 # define SUPPORT_GNUTLS_EXT_RAW_PARSE @@ -125,6 +122,10 @@ require current GnuTLS, then we'll drop support for the ancient libraries). # endif #endif +#if GNUTLS_VERSION_NUMBER >= 0x030702 +# define HAVE_GNUTLS_EXPORTER +#endif + #ifndef DISABLE_OCSP # include #endif @@ -266,13 +267,11 @@ static exim_gnutls_state_st state_server = { .fd_out = -1, }; -#ifndef GNUTLS_AUTO_DHPARAMS /* dh_params are initialised once within the lifetime of a process using TLS; if we used TLS in a long-lived daemon, we'd have to reconsider this. But we don't want to repeat this. */ static gnutls_dh_params_t dh_server_params = NULL; -#endif static int ssl_session_timeout = 7200; /* Two hours */ @@ -339,6 +338,9 @@ before, for now. */ # endif /* AVOID_GNUTLS_PKCS11 */ #endif +#if GNUTLS_VERSION_NUMBER >= 0x030404 +# define HAVE_GNUTLS_PRF_RFC5705 +#endif @@ -630,11 +632,6 @@ Argument: static void extract_exim_vars_from_tls_state(exim_gnutls_state_st * state) { -#ifdef HAVE_GNUTLS_SESSION_CHANNEL_BINDING -int old_pool; -int rc; -gnutls_datum_t channel; -#endif tls_support * tlsp = state->tlsp; tlsp->active.sock = state->fd_out; @@ -652,21 +649,46 @@ only available for use for authenticators while this TLS session is running. */ tlsp->channelbinding = NULL; #ifdef HAVE_GNUTLS_SESSION_CHANNEL_BINDING -channel.data = NULL; -channel.size = 0; -if ((rc = gnutls_session_channel_binding(state->session, GNUTLS_CB_TLS_UNIQUE, &channel))) - { 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. */ + gnutls_datum_t channel = {.data = NULL, .size = 0}; + int rc; - old_pool = store_pool; - store_pool = POOL_PERM; - 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"); +# 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) + { + 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); + channel.data = buf; + channel.size = 32; + } + else +# endif + rc = gnutls_session_channel_binding(state->session, GNUTLS_CB_TLS_UNIQUE, &channel); + + if (rc) + { DEBUG(D_tls) debug_printf("extracting channel binding: %s\n", gnutls_strerror(rc)); } + else + { + int old_pool = store_pool; + /* Declare the taintedness of the binding info. On server, untainted; on + 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, + !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"); + } } #endif @@ -689,7 +711,6 @@ if (!state->host) -#ifndef GNUTLS_AUTO_DHPARAMS /************************************************* * Setup up DH parameters * *************************************************/ @@ -712,7 +733,7 @@ init_server_dh(uschar ** errstr) { int fd, rc; unsigned int dh_bits; -gnutls_datum_t m = {.data = NULL, .size = 0}; +gnutls_datum_t m; uschar filename_buf[PATH_MAX]; uschar *filename = NULL; size_t sz; @@ -725,6 +746,9 @@ DEBUG(D_tls) debug_printf("Initialising GnuTLS server params\n"); if ((rc = gnutls_dh_params_init(&dh_server_params))) return tls_error_gnu(NULL, US"gnutls_dh_params_init", rc, errstr); +m.data = NULL; +m.size = 0; + if (!expand_check(tls_dhparam, US"tls_dhparam", &exp_tls_dhparam, errstr)) return DEFER; @@ -874,12 +898,14 @@ if (rc < 0) return tls_error_sys(US"Unable to open temp file", errno, NULL, errstr); (void)exim_chown(temp_fn, exim_uid, exim_gid); /* Probably not necessary */ - /* GnuTLS overshoots! If we ask for 2236, we might get 2237 or more. But - there's no way to ask GnuTLS how many bits there really are. We can ask - how many bits were used in a TLS session, but that's it! The prime itself - is hidden behind too much abstraction. So we ask for less, and proceed on - a wing and a prayer. First attempt, subtracted 3 for 2233 and got 2240. */ - + /* GnuTLS overshoots! + * If we ask for 2236, we might get 2237 or more. + * But there's no way to ask GnuTLS how many bits there really are. + * We can ask how many bits were used in a TLS session, but that's it! + * The prime itself is hidden behind too much abstraction. + * So we ask for less, and proceed on a wing and a prayer. + * First attempt, subtracted 3 for 2233 and got 2240. + */ if (dh_bits >= EXIM_CLIENT_DH_MIN_BITS + 10) { dh_bits_gen = dh_bits - 10; @@ -942,7 +968,6 @@ if (rc < 0) DEBUG(D_tls) debug_printf("initialized server D-H parameters\n"); return OK; } -#endif @@ -1079,7 +1104,8 @@ static int tls_server_clienthello_ext(void * ctx, unsigned tls_id, const uschar * data, unsigned size) { -/* https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml */ +/* The values for tls_id are documented here: +https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml */ switch (tls_id) { case 5: /* Status Request */ @@ -1117,8 +1143,9 @@ tls_server_clienthello_cb(gnutls_session_t session, unsigned int htype, 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; } @@ -1585,6 +1612,9 @@ return lifetime; /* 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) { @@ -1952,7 +1982,6 @@ tls_set_remaining_x509(exim_gnutls_state_st *state, uschar ** errstr) int rc; const host_item *host = state->host; /* macro should be reconsidered? */ -#ifndef GNUTLS_AUTO_DHPARAMS /* Create D-H parameters, or read them from the cache file. This function does its own SMTP error messaging. This only happens for the server, TLS D-H ignores client-side params. */ @@ -1962,10 +1991,10 @@ if (!state->host) if (!dh_server_params) if ((rc = init_server_dh(errstr)) != OK) return rc; - /* Unnecessary & discouraged with 3.6.0 or later */ + /* Unnecessary & discouraged with 3.6.0 or later, according to docs. But without it, + no DHE- ciphers are advertised. */ gnutls_certificate_set_dh_params(state->lib_state.x509_cred, dh_server_params); } -#endif /* Link the credentials to the session. */ @@ -2018,7 +2047,7 @@ if (host) int old_pool = store_pool; store_pool = POOL_PERM; - state = store_get(sizeof(exim_gnutls_state_st), FALSE); + state = store_get(sizeof(exim_gnutls_state_st), GET_UNTAINTED); store_pool = old_pool; memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init)); @@ -2338,7 +2367,7 @@ if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER) exim_gnutls_peer_err(US"getting size for cert DN failed"); return FAIL; /* should not happen */ } -dn_buf = store_get_perm(sz, TRUE); /* tainted */ +dn_buf = store_get_perm(sz, GET_TAINTED); rc = gnutls_x509_crt_get_dn(crt, CS dn_buf, &sz); exim_gnutls_peer_err(US"failed to extract certificate DN [gnutls_x509_crt_get_dn(cert 0)]"); @@ -2418,8 +2447,8 @@ else for (nrec = 0; state->dane_data_len[nrec]; ) nrec++; nrec++; - dd = store_get(nrec * sizeof(uschar *), FALSE); - ddl = store_get(nrec * sizeof(int), FALSE); + dd = store_get(nrec * sizeof(uschar *), GET_UNTAINTED); + ddl = store_get(nrec * sizeof(int), GET_UNTAINTED); nrec--; if ((rc = dane_state_init(&s, 0))) @@ -2665,7 +2694,7 @@ if (sni_type != GNUTLS_NAME_DNS) /* We now have a UTF-8 string in sni_name */ old_pool = store_pool; store_pool = POOL_PERM; -state->received_sni = string_copy_taint(US sni_name, TRUE); +state->received_sni = string_copy_taint(US sni_name, GET_TAINTED); store_pool = old_pool; /* We set this one now so that variable expansions below will work */ @@ -2724,7 +2753,7 @@ if ((cert_list = gnutls_certificate_get_peers(session, &cert_list_size))) state->tlsp->peercert = crt; if ((yield = event_raise(state->event_action, - US"tls:cert", string_sprintf("%d", cert_list_size)))) + US"tls:cert", string_sprintf("%d", cert_list_size), &errno))) { log_write(0, LOG_MAIN, "SSL verify denied by event-action: depth=%d: %s", @@ -2853,12 +2882,12 @@ NULL plist return for silent no-ALPN. */ static BOOL -tls_alpn_plist(const uschar * tls_alpn, const gnutls_datum_t ** plist, unsigned * plen, +tls_alpn_plist(uschar ** tls_alpn, const gnutls_datum_t ** plist, unsigned * plen, uschar ** errstr) { uschar * exp_alpn; -if (!expand_check(tls_alpn, US"tls_alpn", &exp_alpn, errstr)) +if (!expand_check(*tls_alpn, US"tls_alpn", &exp_alpn, errstr)) return FALSE; if (!exp_alpn) @@ -2876,7 +2905,7 @@ else while (string_nextinlist(&list, &sep, NULL, 0)) cnt++; - p = store_get(sizeof(gnutls_datum_t) * cnt, is_tainted(exp_alpn)); + p = store_get(sizeof(gnutls_datum_t) * cnt, exp_alpn); list = exp_alpn; for (int i = 0; s = string_nextinlist(&list, &sep, NULL, 0); i++) { p[i].data = s; p[i].size = Ustrlen(s); } @@ -2888,11 +2917,12 @@ return TRUE; static void tls_server_set_acceptable_alpns(exim_gnutls_state_st * state, uschar ** errstr) { +uschar * local_alpn = string_copy(tls_alpn); int rc; const gnutls_datum_t * plist; unsigned plen; -if (tls_alpn_plist(tls_alpn, &plist, &plen, errstr) && plist) +if (tls_alpn_plist(&local_alpn, &plist, &plen, errstr) && plist) { /* This seems to be only mandatory if the client sends an ALPN extension; not trying ALPN is ok. Need to decide how to support server-side must-alpn. */ @@ -3046,6 +3076,9 @@ ALARM_CLR(0); if (rc != GNUTLS_E_SUCCESS) { + DEBUG(D_tls) debug_printf(" error %d from gnutls_handshake: %s\n", + rc, gnutls_strerror(rc)); + /* It seems that, except in the case of a timeout, we have to close the connection right here; otherwise if the other end is running OpenSSL it hangs until the server times out. */ @@ -3053,15 +3086,19 @@ if (rc != GNUTLS_E_SUCCESS) if (sigalrm_seen) { tls_error(US"gnutls_handshake", US"timed out", NULL, errstr); +#ifndef DISABLE_EVENT + (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL); +#endif gnutls_db_remove_session(state->session); } else { tls_error_gnu(state, US"gnutls_handshake", rc, errstr); +#ifndef DISABLE_EVENT + (void) event_raise(event_action, US"tls:fail:connect", *errstr, NULL); +#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 */ @@ -3137,10 +3174,10 @@ state->xfer_buffer = store_malloc(ssl_xfer_buffer_size); receive_getc = tls_getc; receive_getbuf = tls_getbuf; receive_get_cache = tls_get_cache; +receive_hasc = tls_hasc; receive_ungetc = tls_ungetc; receive_feof = tls_feof; receive_ferror = tls_ferror; -receive_smtp_buffered = tls_smtp_buffered; return OK; } @@ -3190,8 +3227,8 @@ for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr; rr = dns_next_rr(dnsa, &dnss, RESET_NEXT) ) if (rr->type == T_TLSA) i++; -dane_data = store_get(i * sizeof(uschar *), FALSE); -dane_data_len = store_get(i * sizeof(int), FALSE); +dane_data = store_get(i * sizeof(uschar *), GET_UNTAINTED); +dane_data_len = store_get(i * sizeof(int), GET_UNTAINTED); i = 0; for (dns_record * rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS); rr; @@ -3249,25 +3286,28 @@ however avoid storing and retrieving session information. */ static void tls_retrieve_session(tls_support * tlsp, gnutls_session_t session, - host_item * host, smtp_transport_options_block * ob) + smtp_connect_args * conn_args, smtp_transport_options_block * ob) { tlsp->resumption = RESUME_SUPPORTED; -if (verify_check_given_host(CUSS &ob->tls_resumption_hosts, 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; open_db dbblock, * dbm_file; - DEBUG(D_tls) - debug_printf("check for resumable session for %s\n", host->address); tlsp->host_resumable = TRUE; + tls_client_resmption_key(tlsp, conn_args, ob); + tlsp->resumption |= RESUME_CLIENT_REQUESTED; if ((dbm_file = dbfn_open(US"tls", O_RDONLY, &dbblock, FALSE, FALSE))) { - /* Key for the db is the IP. We'd like to filter the retrieved session - for ticket advisory expiry, but 3.6.1 seems to give no access to that */ + /* We'd like to filter the retrieved session for ticket advisory expiry, + but 3.6.1 seems to give no access to that */ - if ((dt = dbfn_read_with_length(dbm_file, host->address, &len))) + if ((dt = dbfn_read_with_length(dbm_file, tlsp->resume_index, &len))) if (!(rc = gnutls_session_set_data(session, CUS dt->session, (size_t)len - sizeof(dbdata_tls_session)))) { @@ -3304,7 +3344,7 @@ if (gnutls_session_get_flags(session) & GNUTLS_SFLAGS_SESSION_TICKET) { open_db dbblock, * dbm_file; int dlen = sizeof(dbdata_tls_session) + tkt.size; - dbdata_tls_session * dt = store_get(dlen, TRUE); + dbdata_tls_session * dt = store_get(dlen, GET_TAINTED); DEBUG(D_tls) debug_printf("session data size %u\n", (unsigned)tkt.size); memcpy(dt->session, tkt.data, tkt.size); @@ -3313,8 +3353,7 @@ if (gnutls_session_get_flags(session) & GNUTLS_SFLAGS_SESSION_TICKET) if ((dbm_file = dbfn_open(US"tls", O_RDWR, &dbblock, FALSE, FALSE))) { /* key for the db is the IP */ - dbfn_delete(dbm_file, host->address); - dbfn_write(dbm_file, host->address, dt, dlen); + dbfn_write(dbm_file, tlsp->resume_index, dt, dlen); dbfn_close(dbm_file); DEBUG(D_tls) @@ -3349,14 +3388,14 @@ return 0; static void tls_client_resume_prehandshake(exim_gnutls_state_st * state, - tls_support * tlsp, host_item * host, + tls_support * tlsp, smtp_connect_args * conn_args, smtp_transport_options_block * ob) { gnutls_session_set_ptr(state->session, state); gnutls_handshake_set_hook_function(state->session, GNUTLS_HANDSHAKE_NEW_SESSION_TICKET, GNUTLS_HOOK_POST, tls_client_ticket_cb); -tls_retrieve_session(tlsp, state->session, host, ob); +tls_retrieve_session(tlsp, state->session, conn_args, ob); } static void @@ -3454,7 +3493,7 @@ if (ob->tls_alpn) const gnutls_datum_t * plist; unsigned plen; - if (!tls_alpn_plist(ob->tls_alpn, &plist, &plen, errstr)) + if (!tls_alpn_plist(&ob->tls_alpn, &plist, &plen, errstr)) return FALSE; if (plist) if (gnutls_alpn_set_protocols(state->session, plist, plen, 0) != 0) @@ -3546,7 +3585,7 @@ if (request_ocsp) #endif #ifdef EXIM_HAVE_TLS_RESUME -tls_client_resume_prehandshake(state, tlsp, host, ob); +tls_client_resume_prehandshake(state, tlsp, conn_args, ob); #endif #ifndef DISABLE_EVENT @@ -3725,12 +3764,21 @@ if (!tlsp || tlsp->active.sock < 0) return; /* TLS was not active */ if (do_shutdown) { DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS%s\n", - do_shutdown > 1 ? " (with response-wait)" : ""); + do_shutdown > TLS_SHUTDOWN_NOWAIT ? " (with response-wait)" : ""); tls_write(ct_ctx, NULL, 0, FALSE); /* flush write buffer */ +#ifdef EXIM_TCP_CORK + if (do_shutdown == TLS_SHUTDOWN_WAIT) + (void) setsockopt(tlsp->active.sock, IPPROTO_TCP, EXIM_TCP_CORK, US &off, sizeof(off)); +#endif + + /* The library seems to have no way to only wait for a peer's + shutdown, so handle the same as TLS_SHUTDOWN_WAIT */ + ALARM(2); - gnutls_bye(state->session, do_shutdown > 1 ? GNUTLS_SHUT_RDWR : GNUTLS_SHUT_WR); + gnutls_bye(state->session, + do_shutdown > TLS_SHUTDOWN_NOWAIT ? GNUTLS_SHUT_RDWR : GNUTLS_SHUT_WR); ALARM_CLR(0); } @@ -3739,16 +3787,13 @@ if (!ct_ctx) /* server */ receive_getc = smtp_getc; receive_getbuf = smtp_getbuf; receive_get_cache = smtp_get_cache; + receive_hasc = smtp_hasc; receive_ungetc = smtp_ungetc; receive_feof = smtp_feof; receive_ferror = smtp_ferror; - receive_smtp_buffered = smtp_buffered; } 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 */ @@ -3853,6 +3898,13 @@ if (state->xfer_buffer_lwm >= state->xfer_buffer_hwm) return state->xfer_buffer[state->xfer_buffer_lwm++]; } +BOOL +tls_hasc(void) +{ +exim_gnutls_state_st * state = &state_server; +return state->xfer_buffer_lwm < state->xfer_buffer_hwm; +} + uschar * tls_getbuf(unsigned * len) { @@ -3877,12 +3929,15 @@ return buf; } +/* Get up to the given number of bytes from any cached data, and feed to dkim. */ void -tls_get_cache(void) +tls_get_cache(unsigned lim) { #ifndef DISABLE_DKIM exim_gnutls_state_st * state = &state_server; int n = state->xfer_buffer_hwm - state->xfer_buffer_lwm; +if (n > lim) + n = lim; if (n > 0) dkim_exim_verify_feed(state->xfer_buffer+state->xfer_buffer_lwm, n); #endif @@ -3890,7 +3945,7 @@ if (n > 0) BOOL -tls_could_read(void) +tls_could_getc(void) { return state_server.xfer_buffer_lwm < state_server.xfer_buffer_hwm || gnutls_record_check_pending(state_server.session) > 0; @@ -4217,17 +4272,18 @@ return NULL; /* See a description in tls-openssl.c for an explanation of why this exists. -Arguments: a FILE* to print the results to -Returns: nothing +Arguments: string to append to +Returns: string */ -void -tls_version_report(FILE *f) +gstring * +tls_version_report(gstring * g) { -fprintf(f, "Library version: GnuTLS: Compile: %s\n" - " Runtime: %s\n", - LIBGNUTLS_VERSION, - gnutls_check_version(NULL)); +return string_fmt_append(g, + "Library version: GnuTLS: Compile: %s\n" + " Runtime: %s\n", + LIBGNUTLS_VERSION, + gnutls_check_version(NULL)); } #endif /*!MACRO_PREDEF*/