X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/ea19ad2276a93548c8a799b1466fd7996c48be04..1013a770f1be6eff05c4b835bb92b2c916fdd341:/src/src/tls-gnu.c diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index 4a3e1651e..2d7041f3e 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -119,6 +119,12 @@ require current GnuTLS, then we'll drop support for the ancient libraries). # endif #endif +#if GNUTLS_VERSION_NUMBER >= 0x030200 +# ifdef SUPPORT_GNUTLS_EXT_RAW_PARSE +# define EXIM_HAVE_ALPN +# endif +#endif + #ifndef DISABLE_OCSP # include #endif @@ -145,7 +151,7 @@ builtin_macro_create(US"_HAVE_TLS_OCSP"); # ifdef SUPPORT_SRV_OCSP_STACK builtin_macro_create(US"_HAVE_TLS_OCSP_LIST"); # endif -#ifdef EXIM_HAVE_INOTIFY +#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT) builtin_macro_create(US"_HAVE_TLS_CA_CACHE"); # endif } @@ -278,10 +284,14 @@ static BOOL gnutls_buggy_ocsp = FALSE; static BOOL exim_testharness_disable_ocsp_validity_check = FALSE; #endif +#ifdef EXIM_HAVE_ALPN +static BOOL server_seen_alpn = FALSE; +#endif #ifdef EXIM_HAVE_TLS_RESUME static gnutls_datum_t server_sessticket_key; #endif + /* ------------------------------------------------------------------------ */ /* macros */ @@ -345,6 +355,53 @@ tls_server_ticket_cb(gnutls_session_t sess, u_int htype, unsigned when, #endif +/************************************************* +* Handle TLS error * +*************************************************/ + +/* Called from lots of places when errors occur before actually starting to do +the TLS handshake, that is, while the session is still in clear. Always returns +DEFER for a server and FAIL for a client so that most calls can use "return +tls_error(...)" to do this processing and then give an appropriate return. A +single function is used for both server and client, because it is called from +some shared functions. + +Argument: + prefix text to include in the logged error + msg additional error string (may be NULL) + usually obtained from gnutls_strerror() + host NULL if setting up a server; + the connected host if setting up a client + errstr pointer to returned error string + +Returns: OK/DEFER/FAIL +*/ + +static int +tls_error(const uschar *prefix, const uschar *msg, const host_item *host, + uschar ** errstr) +{ +if (errstr) + *errstr = string_sprintf("(%s)%s%s", prefix, msg ? ": " : "", msg ? msg : US""); +return host ? FAIL : DEFER; +} + + +static int +tls_error_gnu(const uschar *prefix, int err, const host_item *host, + uschar ** errstr) +{ +return tls_error(prefix, US gnutls_strerror(err), host, errstr); +} + +static int +tls_error_sys(const uschar *prefix, int err, const host_item *host, + uschar ** errstr) +{ +return tls_error(prefix, US strerror(err), host, errstr); +} + + /* ------------------------------------------------------------------------ */ /* Initialisation */ @@ -379,9 +436,10 @@ return FALSE; #endif -static void -tls_g_init(void) +static int +tls_g_init(uschar ** errstr) { +int rc; DEBUG(D_tls) debug_printf("GnuTLS global init required\n"); #if defined(HAVE_GNUTLS_PKCS11) && !defined(GNUTLS_AUTO_PKCS11_MANUAL) @@ -393,12 +451,12 @@ To prevent this, we init PKCS11 first, which is the documented approach. */ if (!gnutls_allow_auto_pkcs11) if ((rc = gnutls_pkcs11_init(GNUTLS_PKCS11_FLAG_MANUAL, NULL))) - return tls_error_gnu(US"gnutls_pkcs11_init", rc, host, errstr); + return tls_error_gnu(US"gnutls_pkcs11_init", rc, NULL, errstr); #endif #ifndef GNUTLS_AUTO_GLOBAL_INIT if ((rc = gnutls_global_init())) - return tls_error_gnu(US"gnutls_global_init", rc, host, errstr); + return tls_error_gnu(US"gnutls_global_init", rc, NULL, errstr); #endif #if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0 @@ -416,6 +474,7 @@ if (tls_ocsp_file && (gnutls_buggy_ocsp = tls_is_buggy_ocsp())) #endif exim_gnutls_base_init_done = TRUE; +return OK; } @@ -432,10 +491,11 @@ tls_per_lib_daemon_tick(void) static void tls_per_lib_daemon_init(void) { +uschar * dummy_errstr; static BOOL once = FALSE; if (!exim_gnutls_base_init_done) - tls_g_init(); + tls_g_init(&dummy_errstr); if (!once) { @@ -456,54 +516,6 @@ if (!once) } /* ------------------------------------------------------------------------ */ -/* Static functions */ - -/************************************************* -* Handle TLS error * -*************************************************/ - -/* Called from lots of places when errors occur before actually starting to do -the TLS handshake, that is, while the session is still in clear. Always returns -DEFER for a server and FAIL for a client so that most calls can use "return -tls_error(...)" to do this processing and then give an appropriate return. A -single function is used for both server and client, because it is called from -some shared functions. - -Argument: - prefix text to include in the logged error - msg additional error string (may be NULL) - usually obtained from gnutls_strerror() - host NULL if setting up a server; - the connected host if setting up a client - errstr pointer to returned error string - -Returns: OK/DEFER/FAIL -*/ - -static int -tls_error(const uschar *prefix, const uschar *msg, const host_item *host, - uschar ** errstr) -{ -if (errstr) - *errstr = string_sprintf("(%s)%s%s", prefix, msg ? ": " : "", msg ? msg : US""); -return host ? FAIL : DEFER; -} - - -static int -tls_error_gnu(const uschar *prefix, int err, const host_item *host, - uschar ** errstr) -{ -return tls_error(prefix, US gnutls_strerror(err), host, errstr); -} - -static int -tls_error_sys(const uschar *prefix, int err, const host_item *host, - uschar ** errstr) -{ -return tls_error(prefix, US strerror(err), host, errstr); -} - /************************************************* * Deal with logging errors during I/O * @@ -849,7 +861,7 @@ if (rc < 0) return tls_error(US"Filename too long to generate replacement", filename, NULL, errstr); - temp_fn = string_copy(US"%s.XXXXXXX"); + temp_fn = string_copy(US"exim-dh.XXXXXXX"); if ((fd = mkstemp(CS temp_fn)) < 0) /* modifies temp_fn */ 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 */ @@ -927,7 +939,7 @@ return OK; -/* Create and install a selfsigned certificate, for use in server mode */ +/* Create and install a selfsigned certificate, for use in server mode. */ static int tls_install_selfsign(exim_gnutls_state_st * state, uschar ** errstr) @@ -944,6 +956,7 @@ rc = GNUTLS_E_NO_CERTIFICATE_FOUND; if (TRUE) goto err; #endif +DEBUG(D_tls) debug_printf("TLS: generating selfsigned server cert\n"); where = US"initialising pkey"; if ((rc = gnutls_x509_privkey_init(&pkey))) goto err; @@ -968,7 +981,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, now + 60 * 60)) /* 1 hr */ + || (rc = gnutls_x509_crt_set_expiration_time(cert, (long)2 * 60 * 60)) /* 2 hour */ || (rc = gnutls_x509_crt_set_key(cert, pkey)) || (rc = gnutls_x509_crt_set_dn_by_oid(cert, @@ -1059,10 +1072,18 @@ tls_server_clienthello_ext(void * ctx, unsigned tls_id, const unsigned char *data, unsigned size) { /* https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml */ -if (tls_id == 5) /* status_request */ +switch (tls_id) { - DEBUG(D_tls) debug_printf("Seen status_request extension from client\n"); - tls_in.ocsp = OCSP_NOT_RESP; + case 5: /* status_request */ + DEBUG(D_tls) debug_printf("Seen status_request extension from client\n"); + tls_in.ocsp = OCSP_NOT_RESP; + break; +#ifdef EXIM_HAVE_ALPN + case 16: /* Application Layer Protocol Notification */ + DEBUG(D_tls) debug_printf("Seen ALPN extension from client\n"); + server_seen_alpn = TRUE; + break; +#endif } return 0; } @@ -1123,12 +1144,12 @@ tls_server_certstatus_cb(gnutls_session_t session, unsigned int htype, unsigned when, unsigned int incoming, const gnutls_datum_t * msg) { DEBUG(D_tls) debug_printf("Sending certificate-status\n"); /*XXX we get this for tls1.2 but not for 1.3 */ -#ifdef SUPPORT_SRV_OCSP_STACK +# ifdef SUPPORT_SRV_OCSP_STACK tls_in.ocsp = exim_testharness_disable_ocsp_validity_check ? OCSP_VFY_NOT_TRIED : OCSP_VFIED; /* We know that GnuTLS verifies responses */ -#else +# else tls_in.ocsp = OCSP_VFY_NOT_TRIED; -#endif +# endif return 0; } @@ -1419,26 +1440,25 @@ return gnutls_priority_init( (gnutls_priority_t *) &state->lib_state.pri_cache, CCS p, errpos); } -static void +static unsigned tls_server_creds_init(void) { uschar * dummy_errstr; +unsigned lifetime = 0; state_server.lib_state = null_tls_preload; if (gnutls_certificate_allocate_credentials( (gnutls_certificate_credentials_t *) &state_server.lib_state.x509_cred)) { state_server.lib_state.x509_cred = NULL; - return; + return lifetime; } creds_basic_init(state_server.lib_state.x509_cred, TRUE); -#ifdef EXIM_HAVE_INOTIFY +#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT) /* If tls_certificate has any $ indicating expansions, it is not good. If tls_privatekey is set but has $, not good. Likewise for tls_ocsp_file. -If all good (and tls_certificate set), load the cert(s). Do not try -to handle selfsign generation for now (tls_certificate null/empty; -XXX will want to do that later though) due to the lifetime/expiry issue. */ +If all good (and tls_certificate set), load the cert(s). */ if ( opt_set_and_noexpand(tls_certificate) # ifndef DISABLE_OCSP @@ -1468,6 +1488,18 @@ if ( opt_set_and_noexpand(tls_certificate) state_server.lib_state.conn_certs = TRUE; } } +else if ( !tls_certificate && !tls_privatekey +# ifndef DISABLE_OCSP + && !tls_ocsp_file +# endif + ) + { /* Generate & preload a selfsigned cert. No files to watch. */ + if ((tls_install_selfsign(&state_server, &dummy_errstr)) == OK) + { + state_server.lib_state.conn_certs = TRUE; + lifetime = f.running_in_test_harness ? 2 : 60 * 60; /* 1 hour */ + } + } else DEBUG(D_tls) debug_printf("TLS: not preloading server certs\n"); @@ -1480,7 +1512,7 @@ if (opt_set_and_noexpand(tls_verify_certificates)) DEBUG(D_tls) debug_printf("TLS: preloading CA bundle for server\n"); if (creds_load_cabundle(&state_server, tls_verify_certificates, NULL, &dummy_errstr) != OK) - return; + return lifetime; state_server.lib_state.cabundle = TRUE; /* If CAs loaded and tls_crl is non-empty and has no $, load it */ @@ -1491,7 +1523,7 @@ if (opt_set_and_noexpand(tls_verify_certificates)) { DEBUG(D_tls) debug_printf("TLS: preloading CRL for server\n"); if (creds_load_crl(&state_server, tls_crl, &dummy_errstr) != OK) - return; + return lifetime; state_server.lib_state.crl = TRUE; } } @@ -1518,6 +1550,7 @@ if (!tls_require_ciphers || opt_set_and_noexpand(tls_require_ciphers)) } else DEBUG(D_tls) debug_printf("TLS: not preloading cipher list for server\n"); +return lifetime; } @@ -1532,8 +1565,9 @@ exim_gnutls_state_st tpt_dummy_state; host_item * dummy_host = (host_item *)1; uschar * dummy_errstr; -if (!exim_gnutls_base_init_done) - tls_g_init(); +if ( !exim_gnutls_base_init_done + && tls_g_init(&dummy_errstr) != OK) + return; ob->tls_preload = null_tls_preload; if (gnutls_certificate_allocate_credentials( @@ -1547,7 +1581,7 @@ creds_basic_init(ob->tls_preload.x509_cred, FALSE); tpt_dummy_state.session = NULL; tpt_dummy_state.lib_state = ob->tls_preload; -#ifdef EXIM_HAVE_INOTIFY +#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT) if ( opt_set_and_noexpand(ob->tls_certificate) && opt_unset_or_noexpand(ob->tls_privatekey)) { @@ -1611,7 +1645,7 @@ depends on DANE or plain usage. */ } -#ifdef EXIM_HAVE_INOTIFY +#if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT) /* Invalidate the creds cached, by dropping the current ones. Call when we notice one of the source files has changed. */ @@ -1937,8 +1971,9 @@ exim_gnutls_state_st * state; int rc; size_t sz; -if (!exim_gnutls_base_init_done) - tls_g_init(); +if ( !exim_gnutls_base_init_done + && (rc = tls_g_init(errstr)) != OK) + return rc; if (host) { @@ -1986,7 +2021,7 @@ state->tls_require_ciphers = require_ciphers; state->host = host; /* This handles the variables that might get re-expanded after TLS SNI; -that's tls_certificate, tls_privatekey, tls_verify_certificates, tls_crl */ +tls_certificate, tls_privatekey, tls_verify_certificates, tls_crl */ DEBUG(D_tls) debug_printf("Expanding various TLS configuration options for session credentials\n"); @@ -2774,6 +2809,26 @@ if (gnutls_session_is_resumed(state->session)) } } #endif + + +#ifdef EXIM_HAVE_ALPN +static void +tls_server_set_acceptable_alpns(exim_gnutls_state_st * state) +{ +int rc; +gnutls_datum_t protocols[2] = {[0] = {.data = US"smtp", .size = 4}, + [1] = {.data = US"esmtp", .size = 5}}; + +/* Set non-mandatory set of protocol names */ +if (!(rc = gnutls_alpn_set_protocols(state->session, protocols, 2, 0))) + gnutls_handshake_set_hook_function(state->session, + GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST, tls_server_hook_cb); +else + DEBUG(D_tls) + debug_printf("setting alpn protocols: %s\n", US gnutls_strerror(rc)); +} +#endif + /* ------------------------------------------------------------------------ */ /* Exported functions */ @@ -2830,6 +2885,10 @@ DEBUG(D_tls) debug_printf("initialising GnuTLS as a server\n"); #endif } +#ifdef EXIM_HAVE_ALPN +tls_server_set_acceptable_alpns(state); +#endif + #ifdef EXIM_HAVE_TLS_RESUME tls_server_resume_prehandshake(state); #endif @@ -2945,6 +3004,32 @@ tls_server_resume_posthandshake(state); DEBUG(D_tls) post_handshake_debug(state); +#ifdef EXIM_HAVE_ALPN +if (server_seen_alpn) + { + /* The client offered ALPN. We were set up with a nonmandatory list; + see what was negotiated. We require a match now, given that something + was offered. */ + gnutls_datum_t p = {.size = 0}; + int rc = gnutls_alpn_get_selected_protocol(state->session, &p); + if (!rc || rc == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) + { + if (p.size == 0) + { + *errstr = US"ALPN rejected"; + return FAIL; + } + else + DEBUG(D_tls) + debug_printf("ALPN negotiated: %.*s\n", (int)p.size, p.data); + } + else + DEBUG(D_tls) + debug_printf("getting alpn protocol: %s\n", US gnutls_strerror(rc)); + + } +#endif + /* Verify after the fact */ if (!verify_certificate(state, errstr)) @@ -3472,6 +3557,25 @@ return TRUE; +/* +Arguments: + ct_ctx client TLS context pointer, or NULL for the one global server context +*/ + +void +tls_shutdown_wr(void * ct_ctx) +{ +exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server; +tls_support * tlsp = state->tlsp; + +if (!tlsp || tlsp->active.sock < 0) return; /* TLS was not active */ + +tls_write(ct_ctx, NULL, 0, FALSE); /* flush write buffer */ + +HDEBUG(D_transport|D_tls|D_acl|D_v) debug_printf_indent(" SMTP(TLS shutdown)>>\n"); +gnutls_bye(state->session, GNUTLS_SHUT_WR); +} + /************************************************* * Close down a TLS session * *************************************************/ @@ -3482,27 +3586,30 @@ would tamper with the TLS session in the parent process). Arguments: ct_ctx client context pointer, or NULL for the one global server context - shutdown 1 if TLS close-alert is to be sent, - 2 if also response to be waited for + do_shutdown 0 no data-flush or TLS close-alert + 1 if TLS close-alert is to be sent, + 2 if also response to be waited for (2s timeout) Returns: nothing */ void -tls_close(void * ct_ctx, int shutdown) +tls_close(void * ct_ctx, int do_shutdown) { exim_gnutls_state_st * state = ct_ctx ? ct_ctx : &state_server; tls_support * tlsp = state->tlsp; if (!tlsp || tlsp->active.sock < 0) return; /* TLS was not active */ -if (shutdown) +if (do_shutdown) { DEBUG(D_tls) debug_printf("tls_close(): shutting down TLS%s\n", - shutdown > 1 ? " (with response-wait)" : ""); + do_shutdown > 1 ? " (with response-wait)" : ""); + + tls_write(ct_ctx, NULL, 0, FALSE); /* flush write buffer */ ALARM(2); - gnutls_bye(state->session, shutdown > 1 ? GNUTLS_SHUT_RDWR : GNUTLS_SHUT_WR); + gnutls_bye(state->session, do_shutdown > 1 ? GNUTLS_SHUT_RDWR : GNUTLS_SHUT_WR); ALARM_CLR(0); } @@ -3650,7 +3757,7 @@ return buf; void -tls_get_cache() +tls_get_cache(void) { #ifndef DISABLE_DKIM exim_gnutls_state_st * state = &state_server; @@ -3669,8 +3776,6 @@ return state_server.xfer_buffer_lwm < state_server.xfer_buffer_hwm } - - /************************************************* * Read bytes from TLS channel * *************************************************/