X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/fc624b8cb4c3312d7450dfa86adfa3fe8dd9cbeb..a85c067ba6c6940512cf57ec213277a370d87e70:/src/src/tls-gnu.c diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index 3adadb80b..c98760202 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -2,10 +2,11 @@ * Exim - an Internet mail transport agent * *************************************************/ +/* Copyright (c) The Exim Maintainers 2020 - 2022 */ /* Copyright (c) University of Cambridge 1995 - 2018 */ /* Copyright (c) Phil Pennock 2012 */ -/* Copyright (c) The Exim Maintainers 2020 - 2021 */ /* 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 @@ -121,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 @@ -333,6 +338,9 @@ before, for now. */ # endif /* AVOID_GNUTLS_PKCS11 */ #endif +#if GNUTLS_VERSION_NUMBER >= 0x030404 +# define HAVE_GNUTLS_PRF_RFC5705 +#endif @@ -624,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; @@ -646,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 @@ -1115,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; } @@ -1583,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) { @@ -2015,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)); @@ -2335,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)]"); @@ -2415,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))) @@ -2662,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 */ @@ -2850,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) @@ -2873,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); } @@ -2885,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. */ @@ -3053,17 +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 */ @@ -3192,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; @@ -3251,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)))) { @@ -3306,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); @@ -3315,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) @@ -3351,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 @@ -3456,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) @@ -3548,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 @@ -3727,17 +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 > 1) + 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); } @@ -3753,9 +3794,6 @@ if (!ct_ctx) /* server */ } 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 */ @@ -4234,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*/