X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/a9a1463c2397d5a116f1130904dd8dc7ccd87b75..d4ff6d2a25234f7558baa99a54ca138c3f004b06:/src/src/tls-gnu.c diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index 5b4898014..3e8ec6d84 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) The Exim Maintainers 2020 - 2023 */ /* Copyright (c) University of Cambridge 1995 - 2018 */ /* Copyright (c) Phil Pennock 2012 */ /* See the file NOTICE for conditions of use and distribution. */ +/* SPDX-License-Identifier: GPL-2.0-or-later */ /* 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 @@ -374,7 +379,7 @@ Argument: the connected host if setting up a client errstr pointer to returned error string -Returns: OK/DEFER/FAIL +Returns: DEFER/FAIL */ static int @@ -387,13 +392,15 @@ return host ? FAIL : DEFER; } +/* Returns: DEFER/FAIL */ static int tls_error_gnu(exim_gnutls_state_st * state, const uschar *prefix, int err, uschar ** errstr) { return tls_error(prefix, state && err == GNUTLS_E_FATAL_ALERT_RECEIVED - ? US gnutls_alert_get_name(gnutls_alert_get(state->session)) + ? string_sprintf("rxd alert: %s", + US gnutls_alert_get_name(gnutls_alert_get(state->session))) : US gnutls_strerror(err), state ? state->host : NULL, errstr); @@ -646,14 +653,20 @@ tlsp->channelbinding = NULL; #ifdef HAVE_GNUTLS_SESSION_CHANNEL_BINDING { gnutls_datum_t channel = {.data = NULL, .size = 0}; - uschar * buf; int rc; -# ifdef HAVE_GNUTLS_PRF_RFC5705 +# 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) { - buf = store_get(32, state->host ? GET_TAINTED : GET_UNTAINTED); + 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); @@ -670,11 +683,11 @@ tlsp->channelbinding = NULL; { int old_pool = store_pool; /* Declare the taintedness of the binding info. On server, untainted; on - client, tainted - being the Finish msg from the server. */ + 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, - state->host ? GET_TAINTED : GET_UNTAINTED); + !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"); } @@ -714,7 +727,7 @@ file is never present. If two processes both compute some new parameters, you waste a bit of effort, but it doesn't seem worth messing around with locking to prevent this. -Returns: OK/DEFER/FAIL +Returns: OK/DEFER (expansion issue)/FAIL (requested none) */ static int @@ -752,7 +765,7 @@ else if (Ustrcmp(exp_tls_dhparam, "historic") == 0) else if (Ustrcmp(exp_tls_dhparam, "none") == 0) { DEBUG(D_tls) debug_printf("Requested no DH parameters\n"); - return OK; + return FAIL; } else if (exp_tls_dhparam[0] != '/') { @@ -1003,7 +1016,7 @@ now = 1; if ( (rc = gnutls_x509_crt_set_version(cert, 3)) || (rc = gnutls_x509_crt_set_serial(cert, &now, sizeof(now))) || (rc = gnutls_x509_crt_set_activation_time(cert, now = time(NULL))) - || (rc = gnutls_x509_crt_set_expiration_time(cert, (long)2 * 60 * 60)) /* 2 hour */ + || (rc = gnutls_x509_crt_set_expiration_time(cert, now + (long)2 * 60 * 60)) /* 2 hour */ || (rc = gnutls_x509_crt_set_key(cert, pkey)) || (rc = gnutls_x509_crt_set_dn_by_oid(cert, @@ -1106,21 +1119,28 @@ switch (tls_id) /* The format of "data" here doesn't seem to be documented, but appears to be a 2-byte field with a (redundant, given the "size" arg) total length then a sequence of one-byte size then string (not nul-term) names. The - latter is as described in OpenSSL documentation. */ + latter is as described in OpenSSL documentation. + Note that we do not get called for a match_fail, making it hard to log + a single bad ALPN being offered (the common case). */ + { + gstring * g = NULL; DEBUG(D_tls) debug_printf("Seen ALPN extension from client (s=%u):", size); for (const uschar * s = data+2; s-data < size-1; s += *s + 1) { server_seen_alpn++; + g = string_append_listele_n(g, ':', s+1, *s); DEBUG(D_tls) debug_printf(" '%.*s'", (int)*s, s+1); } DEBUG(D_tls) debug_printf("\n"); if (server_seen_alpn > 1) { + log_write(0, LOG_MAIN, "TLS ALPN (%Y) rejected", g); DEBUG(D_tls) debug_printf("TLS: too many ALPNs presented in handshake\n"); return GNUTLS_E_NO_APPLICATION_PROTOCOL; } break; + } #endif } return 0; @@ -1132,8 +1152,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; } @@ -1164,6 +1185,8 @@ tls_server_servercerts_cb(gnutls_session_t session, unsigned int htype, # ifdef notdef_crashes /*XXX crashes */ return gnutls_ext_raw_parse(NULL, tls_server_servercerts_ext, msg, 0); +# else +return GNUTLS_E_SUCCESS; # endif } #endif /*SUPPORT_GNUTLS_EXT_RAW_PARSE*/ @@ -1212,7 +1235,7 @@ switch (htype) return tls_server_ticket_cb(sess, htype, when, incoming, msg); # endif default: - return 0; + return GNUTLS_E_SUCCESS; } } #endif @@ -1258,6 +1281,7 @@ DEBUG(D_tls) debug_printf("TLS: basic cred init, %s\n", server ? "server" : "client"); } +/* Returns OK/DEFER/FAIL */ static int creds_load_server_certs(exim_gnutls_state_st * state, const uschar * cert, const uschar * pkey, const uschar * ocsp, uschar ** errstr) @@ -1281,7 +1305,7 @@ while (cfile = string_nextinlist(&clist, &csep, NULL, 0)) if (!(kfile = string_nextinlist(&klist, &ksep, NULL, 0))) return tls_error(US"cert/key setup: out of keys", NULL, NULL, errstr); - else if ((rc = tls_add_certfile(state, NULL, cfile, kfile, errstr)) > 0) + else if ((rc = tls_add_certfile(state, NULL, cfile, kfile, errstr)) > OK) return rc; else { @@ -1359,7 +1383,7 @@ while (cfile = string_nextinlist(&clist, &csep, NULL, 0)) } #endif /* DISABLE_OCSP */ } -return 0; +return OK; } static int @@ -1369,7 +1393,7 @@ creds_load_client_certs(exim_gnutls_state_st * state, const host_item * host, int rc = tls_add_certfile(state, host, cert, pkey, errstr); if (rc > 0) return rc; DEBUG(D_tls) debug_printf("TLS: cert/key registered\n"); -return 0; +return OK; } static int @@ -1798,8 +1822,13 @@ D-H generation. */ if (!state->lib_state.conn_certs) { - if (!Expand_check_tlsvar(tls_certificate, errstr)) + if ( !Expand_check_tlsvar(tls_certificate, errstr) + || f.expand_string_forcedfail) + { + if (f.expand_string_forcedfail) + *errstr = US"expansion of tls_certificate failed"; return DEFER; + } /* certificate is mandatory in server, optional in client */ @@ -1811,8 +1840,14 @@ if (!state->lib_state.conn_certs) else DEBUG(D_tls) debug_printf("TLS: no client certificate specified; okay\n"); - if (state->tls_privatekey && !Expand_check_tlsvar(tls_privatekey, errstr)) + if ( state->tls_privatekey && !Expand_check_tlsvar(tls_privatekey, errstr) + || f.expand_string_forcedfail + ) + { + if (f.expand_string_forcedfail) + *errstr = US"expansion of tls_privatekey failed"; return DEFER; + } /* tls_privatekey is optional, defaulting to same file as certificate */ @@ -1854,7 +1889,11 @@ if (!state->lib_state.conn_certs) tls_ocsp_file, #endif errstr) - ) ) return rc; + ) ) + { + DEBUG(D_tls) debug_printf("load-cert: '%s'\n", *errstr); + return rc; + } } } else @@ -1965,10 +2004,10 @@ Returns: OK/DEFER/FAIL */ static int -tls_set_remaining_x509(exim_gnutls_state_st *state, uschar ** errstr) +tls_set_remaining_x509(exim_gnutls_state_st * state, uschar ** errstr) { -int rc; -const host_item *host = state->host; /* macro should be reconsidered? */ +int rc = OK; +const host_item * host = state->host; /* macro should be reconsidered? */ /* 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 @@ -1977,11 +2016,13 @@ client-side params. */ if (!state->host) { if (!dh_server_params) - if ((rc = init_server_dh(errstr)) != OK) return rc; + if ((rc = init_server_dh(errstr)) == DEFER) return rc; /* 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); + + if (rc == OK) + gnutls_certificate_set_dh_params(state->lib_state.x509_cred, dh_server_params); } /* Link the credentials to the session. */ @@ -2265,7 +2306,7 @@ old_pool = store_pool; for (s++; (c = *s) && c != ')'; s++) g = string_catn(g, s, 1); - tlsp->ver = string_copyn(g->s, g->ptr); + tlsp->ver = string_copy_from_gstring(g); for (uschar * p = US tlsp->ver; *p; p++) if (*p == '-') { *p = '\0'; break; } /* TLS1.0-PKIX -> TLS1.0 */ @@ -2583,7 +2624,7 @@ else ) { DEBUG(D_tls) - debug_printf("TLS certificate verification failed: cert name mismatch\n"); + debug_printf("TLS certificate verification failed: cert name mismatch (per GnuTLS)\n"); if (state->verify_requirement >= VERIFY_REQUIRED) goto badcert; return TRUE; @@ -2698,11 +2739,12 @@ if ((rc = tls_expand_session_files(state, &dummy_errstr)) != OK) { /* If the setup of certs/etc failed before handshake, TLS would not have been offered. The best we can do now is abort. */ - return GNUTLS_E_APPLICATION_ERROR_MIN; + DEBUG(D_tls) debug_printf("expansion for SNI-dependent session files failed\n"); + return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE; } rc = tls_set_remaining_x509(state, &dummy_errstr); -if (rc != OK) return GNUTLS_E_APPLICATION_ERROR_MIN; +if (rc != OK) return GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE; return 0; } @@ -2731,25 +2773,25 @@ exim_gnutls_state_st * state = gnutls_session_get_ptr(session); if ((cert_list = gnutls_certificate_get_peers(session, &cert_list_size))) while (cert_list_size--) - { - 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)); - break; - } + 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)); + break; + } - state->tlsp->peercert = crt; - if ((yield = event_raise(state->event_action, - US"tls:cert", string_sprintf("%d", cert_list_size), &errno))) - { - log_write(0, LOG_MAIN, - "SSL verify denied by event-action: depth=%d: %s", - cert_list_size, yield); - return 1; /* reject */ + state->tlsp->peercert = crt; + if ((yield = event_raise(state->event_action, + US"tls:cert", string_sprintf("%d", cert_list_size), &errno))) + { + log_write(0, LOG_MAIN, + "SSL verify denied by event-action: depth=%d: %s", + cert_list_size, yield); + return 1; /* reject */ + } + state->tlsp->peercert = NULL; } - state->tlsp->peercert = NULL; - } return 0; } @@ -2811,7 +2853,7 @@ 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("newticket cb\n"); +DEBUG(D_tls) debug_printf("newticket cb (on server)\n"); tls_in.resumption |= RESUME_CLIENT_REQUESTED; return 0; } @@ -2848,9 +2890,12 @@ tls_server_resume_posthandshake(exim_gnutls_state_st * state) { if (gnutls_session_resumption_requested(state->session)) { - /* This tells us the client sent a full ticket. We use a + /* This tells us the client sent a full (?) ticket. We use a callback on session-ticket request, elsewhere, to tell - if a client asked for a ticket. */ + if a client asked for a ticket. + XXX As of GnuTLS 3.0.1 it seems to be returning true even for + a pure ticket-req (a zero-length Session Ticket extension + in the Client Hello, for 1.2) which mucks up our logic. */ tls_in.resumption |= RESUME_CLIENT_SUGGESTED; DEBUG(D_tls) debug_printf("client requested resumption\n"); @@ -2960,7 +3005,7 @@ exim_gnutls_state_st * state = NULL; if (tls_in.active.sock >= 0) { tls_error(US"STARTTLS received after TLS started", US "", NULL, errstr); - smtp_printf("554 Already in TLS\r\n", FALSE); + smtp_printf("554 Already in TLS\r\n", SP_NO_MORE); return FAIL; } @@ -3039,7 +3084,7 @@ mode, the fflush() happens when smtp_getc() is called. */ if (!state->tlsp->on_connect) { - smtp_printf("220 TLS go ahead\r\n", FALSE); + smtp_printf("220 TLS go ahead\r\n", SP_NO_MORE); fflush(smtp_out); } @@ -3278,8 +3323,9 @@ tls_retrieve_session(tls_support * tlsp, gnutls_session_t session, { tlsp->resumption = RESUME_SUPPORTED; -if (continue_hostname) /* no host_lbserver available for tls_client_resmption_key() */ - { DEBUG(D_tls) debug_printf("resumption not supported on continued-connection\n"); } +if (!conn_args->have_lbserver) + { DEBUG(D_tls) debug_printf( + "resumption not supported: no LB detection done (continued-conn?)\n"); } else if (verify_check_given_host(CUSS &ob->tls_resumption_hosts, conn_args->host) == OK) { dbdata_tls_session * dt; @@ -3307,6 +3353,7 @@ else if (verify_check_given_host(CUSS &ob->tls_resumption_hosts, conn_args->host dbfn_close(dbm_file); } } +else DEBUG(D_tls) debug_printf("no resumption for this host\n"); } @@ -3334,7 +3381,7 @@ if (gnutls_session_get_flags(session) & GNUTLS_SFLAGS_SESSION_TICKET) int dlen = sizeof(dbdata_tls_session) + tkt.size; dbdata_tls_session * dt = store_get(dlen, GET_TAINTED); - DEBUG(D_tls) debug_printf("session data size %u\n", (unsigned)tkt.size); + DEBUG(D_tls) debug_printf(" session data size %u\n", (unsigned)tkt.size); memcpy(dt->session, tkt.data, tkt.size); gnutls_free(tkt.data); @@ -3345,11 +3392,15 @@ if (gnutls_session_get_flags(session) & GNUTLS_SFLAGS_SESSION_TICKET) dbfn_close(dbm_file); DEBUG(D_tls) - debug_printf("wrote session db (len %u)\n", (unsigned)dlen); + debug_printf(" wrote session db (len %u)\n", (unsigned)dlen); } } - else DEBUG(D_tls) - debug_printf("extract session data: %s\n", US gnutls_strerror(rc)); + else + { DEBUG(D_tls) + debug_printf(" extract session data: %s\n", US gnutls_strerror(rc)); + } + else DEBUG(D_tls) + debug_printf(" host not resmable; not saving ticket\n"); } } @@ -3366,7 +3417,7 @@ tls_client_ticket_cb(gnutls_session_t sess, u_int htype, unsigned when, exim_gnutls_state_st * state = gnutls_session_get_ptr(sess); tls_support * tlsp = state->tlsp; -DEBUG(D_tls) debug_printf("newticket cb\n"); +DEBUG(D_tls) debug_printf("newticket cb (on client)\n"); if (!tlsp->ticket_received) tls_save_session(tlsp, sess, state->host);