X-Git-Url: https://git.netwichtig.de/gitweb/?a=blobdiff_plain;f=src%2Fsrc%2Ftls-gnu.c;h=f63a8375b59bf387f4efe4280fb36c86225633fa;hb=b634f8eaf52aae84c311d7e306f38f3dc07ff1b0;hp=f5c6a8bd67d6cf6c301c3f53acc2d26a454e9f80;hpb=6a9cf7f890226aa085842cd3d94b13e78ea31637;p=user%2Fhenk%2Fcode%2Fexim.git diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index f5c6a8bd6..f63a8375b 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 int server_seen_alpn = -1; /* count of names */ +#endif #ifdef EXIM_HAVE_TLS_RESUME static gnutls_datum_t server_sessticket_key; #endif + /* ------------------------------------------------------------------------ */ /* macros */ @@ -345,6 +355,58 @@ 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(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)) + : US gnutls_strerror(err), + state ? state->host : NULL, + 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 +441,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 +456,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(NULL, US"gnutls_pkcs11_init", rc, 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(NULL, US"gnutls_global_init", rc, errstr); #endif #if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0 @@ -416,6 +479,7 @@ if (tls_ocsp_file && (gnutls_buggy_ocsp = tls_is_buggy_ocsp())) #endif exim_gnutls_base_init_done = TRUE; +return OK; } @@ -432,10 +496,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 +521,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 * @@ -703,7 +720,7 @@ host_item *host = NULL; /* dummy for macros */ DEBUG(D_tls) debug_printf("Initialising GnuTLS server params\n"); if ((rc = gnutls_dh_params_init(&dh_server_params))) - return tls_error_gnu(US"gnutls_dh_params_init", rc, host, errstr); + return tls_error_gnu(NULL, US"gnutls_dh_params_init", rc, errstr); if (!expand_check(tls_dhparam, US"tls_dhparam", &exp_tls_dhparam, errstr)) return DEFER; @@ -733,7 +750,7 @@ else if (m.data) { if ((rc = gnutls_dh_params_import_pkcs3(dh_server_params, &m, GNUTLS_X509_FMT_PEM))) - return tls_error_gnu(US"gnutls_dh_params_import_pkcs3", rc, host, errstr); + return tls_error_gnu(NULL, US"gnutls_dh_params_import_pkcs3", rc, errstr); DEBUG(D_tls) debug_printf("Loaded fixed standard D-H parameters\n"); return OK; } @@ -817,7 +834,7 @@ if ((fd = Uopen(filename, O_RDONLY, 0)) >= 0) rc = gnutls_dh_params_import_pkcs3(dh_server_params, &m, GNUTLS_X509_FMT_PEM); store_free(m.data); if (rc) - return tls_error_gnu(US"gnutls_dh_params_import_pkcs3", rc, host, errstr); + return tls_error_gnu(NULL, US"gnutls_dh_params_import_pkcs3", rc, errstr); DEBUG(D_tls) debug_printf("read D-H parameters from file \"%s\"\n", filename); } @@ -849,7 +866,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 */ @@ -872,7 +889,7 @@ if (rc < 0) debug_printf("requesting generation of %d bit Diffie-Hellman prime ...\n", dh_bits_gen); if ((rc = gnutls_dh_params_generate2(dh_server_params, dh_bits_gen))) - return tls_error_gnu(US"gnutls_dh_params_generate2", rc, host, errstr); + return tls_error_gnu(NULL, US"gnutls_dh_params_generate2", rc, errstr); /* gnutls_dh_params_export_pkcs3() will tell us the exact size, every time, and I confirmed that a NULL call to get the size first is how the GnuTLS @@ -883,8 +900,8 @@ if (rc < 0) if ( (rc = gnutls_dh_params_export_pkcs3(dh_server_params, GNUTLS_X509_FMT_PEM, m.data, &sz)) && rc != GNUTLS_E_SHORT_MEMORY_BUFFER) - return tls_error_gnu(US"gnutls_dh_params_export_pkcs3(NULL) sizing", - rc, host, errstr); + return tls_error_gnu(NULL, US"gnutls_dh_params_export_pkcs3(NULL) sizing", + rc, errstr); m.size = sz; if (!(m.data = store_malloc(m.size))) return tls_error_sys(US"memory allocation failed", errno, NULL, errstr); @@ -894,7 +911,7 @@ if (rc < 0) m.data, &sz))) { store_free(m.data); - return tls_error_gnu(US"gnutls_dh_params_export_pkcs3() real", rc, host, errstr); + return tls_error_gnu(NULL, US"gnutls_dh_params_export_pkcs3() real", rc, errstr); } m.size = sz; /* shrink by 1, probably */ @@ -927,7 +944,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 +961,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 +986,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, @@ -998,7 +1016,7 @@ out: return rc; err: - rc = tls_error_gnu(where, rc, NULL, errstr); + rc = tls_error_gnu(state, where, rc, errstr); goto out; } @@ -1019,9 +1037,9 @@ tls_add_certfile(exim_gnutls_state_st * state, const host_item * host, int rc = gnutls_certificate_set_x509_key_file(state->lib_state.x509_cred, CCS certfile, CCS keyfile, GNUTLS_X509_FMT_PEM); if (rc < 0) - return tls_error_gnu( + return tls_error_gnu(state, string_sprintf("cert/key setup: cert=%s key=%s", certfile, keyfile), - rc, host, errstr); + rc, errstr); return -rc; } @@ -1056,13 +1074,36 @@ return 0; /* Make a note that we saw a status-request */ static int tls_server_clienthello_ext(void * ctx, unsigned tls_id, - const unsigned char *data, unsigned size) + const uschar * 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 */ + /* 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. */ + + 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++; + DEBUG(D_tls) debug_printf(" '%.*s'", (int)*s, s+1); + } + DEBUG(D_tls) debug_printf("\n"); + if (server_seen_alpn > 1) + { + DEBUG(D_tls) debug_printf("TLS: too many ALPNs presented in handshake\n"); + return GNUTLS_E_NO_APPLICATION_PROTOCOL; + } + break; +#endif } return 0; } @@ -1078,6 +1119,7 @@ return gnutls_ext_raw_parse(NULL, tls_server_clienthello_ext, msg, } +# ifdef notdef_crashes /* Make a note that we saw a status-response */ static int tls_server_servercerts_ext(void * ctx, unsigned tls_id, @@ -1093,6 +1135,7 @@ if (FALSE && tls_id == 5) /* status_request */ } return 0; } +# endif /* Callback for certificates packet, on server, if we think we might serve stapled-OCSP */ static int @@ -1100,12 +1143,12 @@ tls_server_servercerts_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 */ -#ifdef notdef -/*XXX crashes */ +# ifdef notdef_crashes + /*XXX crashes */ return gnutls_ext_raw_parse(NULL, tls_server_servercerts_ext, msg, 0); -#endif +# endif } -#endif +#endif /*SUPPORT_GNUTLS_EXT_RAW_PARSE*/ /*XXX in tls1.3 the cert-status travel as an extension next to the cert, in the "Handshake Protocol: Certificate" record. @@ -1121,12 +1164,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; } @@ -1256,9 +1299,9 @@ while (cfile = string_nextinlist(&clist, &csep, NULL, 0)) if ((rc = gnutls_certificate_set_ocsp_status_request_file2( state->lib_state.x509_cred, CCS ofile, gnutls_cert_index, ocsp_fmt)) < 0) - return tls_error_gnu( + return tls_error_gnu(state, US"gnutls_certificate_set_ocsp_status_request_file2", - rc, NULL, errstr); + rc, errstr); DEBUG(D_tls) debug_printf(" %d response%s loaded\n", rc, rc>1 ? "s":""); @@ -1276,9 +1319,9 @@ while (cfile = string_nextinlist(&clist, &csep, NULL, 0)) if ((rc = gnutls_certificate_set_ocsp_status_request_function2( state->lib_state.x509_cred, gnutls_cert_index, server_ocsp_stapling_cb, ofile))) - return tls_error_gnu( + return tls_error_gnu(state, US"gnutls_certificate_set_ocsp_status_request_function2", - rc, NULL, errstr); + rc, errstr); else # endif { @@ -1380,7 +1423,7 @@ else } if (cert_count < 0) - return tls_error_gnu(US"setting certificate trust", cert_count, host, errstr); + return tls_error_gnu(state, US"setting certificate trust", cert_count, errstr); DEBUG(D_tls) debug_printf("Added %d certificate authorities\n", cert_count); @@ -1395,8 +1438,8 @@ int cert_count; DEBUG(D_tls) debug_printf("loading CRL file = %s\n", crl); if ((cert_count = gnutls_certificate_set_x509_crl_file(state->lib_state.x509_cred, CS crl, GNUTLS_X509_FMT_PEM)) < 0) - return tls_error_gnu(US"gnutls_certificate_set_x509_crl_file", - cert_count, state->host, errstr); + return tls_error_gnu(state, US"gnutls_certificate_set_x509_crl_file", + cert_count, errstr); DEBUG(D_tls) debug_printf("Processed %d CRLs\n", cert_count); return OK; @@ -1417,47 +1460,66 @@ 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) - && opt_unset_or_noexpand(tls_privatekey) - && opt_unset_or_noexpand(tls_ocsp_file)) +# ifndef DISABLE_OCSP + && opt_unset_or_noexpand(tls_ocsp_file) +# endif + && opt_unset_or_noexpand(tls_privatekey)) { /* Set watches on the filenames. The implementation does de-duplication so we can just blindly do them all. */ if ( tls_set_watch(tls_certificate, TRUE) - && tls_set_watch(tls_privatekey, TRUE) +# ifndef DISABLE_OCSP && tls_set_watch(tls_ocsp_file, TRUE) - ) +# endif + && tls_set_watch(tls_privatekey, TRUE)) { DEBUG(D_tls) debug_printf("TLS: preloading server certs\n"); if (creds_load_server_certs(&state_server, tls_certificate, tls_privatekey && *tls_privatekey ? tls_privatekey : tls_certificate, - tls_ocsp_file, &dummy_errstr) == 0) +# ifdef DISABLE_OCSP + NULL, +# else + tls_ocsp_file, +# endif + &dummy_errstr) == 0) 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"); @@ -1470,7 +1532,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 */ @@ -1481,7 +1543,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; } } @@ -1508,6 +1570,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; } @@ -1522,8 +1585,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( @@ -1537,7 +1601,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)) { @@ -1601,7 +1665,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. */ @@ -1686,8 +1750,8 @@ if (!state->lib_state.x509_cred) { if ((rc = gnutls_certificate_allocate_credentials( (gnutls_certificate_credentials_t *) &state->lib_state.x509_cred))) - return tls_error_gnu(US"gnutls_certificate_allocate_credentials", - rc, host, errstr); + return tls_error_gnu(state, US"gnutls_certificate_allocate_credentials", + rc, errstr); creds_basic_init(state->lib_state.x509_cred, !host); } @@ -1750,7 +1814,13 @@ if (!state->lib_state.conn_certs) ? creds_load_client_certs(state, host, state->exp_tls_certificate, state->exp_tls_privatekey, errstr) : creds_load_server_certs(state, state->exp_tls_certificate, - state->exp_tls_privatekey, tls_ocsp_file, errstr) + state->exp_tls_privatekey, +#ifdef DISABLE_OCSP + NULL, +#else + tls_ocsp_file, +#endif + errstr) ) ) return rc; } } @@ -1763,9 +1833,11 @@ else state->exp_tls_certificate = US state->tls_certificate; state->exp_tls_privatekey = US state->tls_privatekey; +#ifdef SUPPORT_GNUTLS_EXT_RAW_PARSE if (state->lib_state.ocsp_hook) gnutls_handshake_set_hook_function(state->session, GNUTLS_HANDSHAKE_ANY, GNUTLS_HOOK_POST, tls_server_hook_cb); +#endif } @@ -1871,12 +1943,11 @@ client-side params. */ if (!state->host) { -/*XXX DDD done-bit */ if (!dh_server_params) if ((rc = init_server_dh(errstr)) != OK) return rc; /* Unnecessary & discouraged with 3.6.0 or later */ - gnutls_certificate_set_dh_params(state->.lib_statex509_cred, dh_server_params); + gnutls_certificate_set_dh_params(state->lib_state.x509_cred, dh_server_params); } #endif @@ -1884,7 +1955,7 @@ if (!state->host) if ((rc = gnutls_credentials_set(state->session, GNUTLS_CRD_CERTIFICATE, state->lib_state.x509_cred))) - return tls_error_gnu(US"gnutls_credentials_set", rc, host, errstr); + return tls_error_gnu(state, US"gnutls_credentials_set", rc, errstr); return OK; } @@ -1920,8 +1991,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) { @@ -1963,13 +2035,13 @@ else state->tls_crl = tls_crl; } if (rc) - return tls_error_gnu(US"gnutls_init", rc, host, errstr); + return tls_error_gnu(state, US"gnutls_init", rc, errstr); 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"); @@ -1992,7 +2064,7 @@ if (host) sz = Ustrlen(state->tlsp->sni); if ((rc = gnutls_server_name_set(state->session, GNUTLS_NAME_DNS, state->tlsp->sni, sz))) - return tls_error_gnu(US"gnutls_server_name_set", rc, host, errstr); + return tls_error_gnu(state, US"gnutls_server_name_set", rc, errstr); } } else if (state->tls_sni) @@ -2022,10 +2094,10 @@ if (!state->lib_state.pri_string) } if ((rc = creds_load_pristring(state, p, &errpos))) - return tls_error_gnu(string_sprintf( + return tls_error_gnu(state, string_sprintf( "gnutls_priority_init(%s) failed at offset %ld, \"%.6s..\"", p, errpos - CS p, errpos), - rc, host, errstr); + rc, errstr); } else { @@ -2035,7 +2107,7 @@ else if ((rc = gnutls_priority_set(state->session, state->lib_state.pri_cache))) - return tls_error_gnu(US"gnutls_priority_set", rc, host, errstr); + return tls_error_gnu(state, US"gnutls_priority_set", rc, errstr); /* This also sets the server ticket expiration time to the same, and the STEK rotation time to 3x. */ @@ -2233,7 +2305,7 @@ if ((ct = gnutls_certificate_type_get(session)) != GNUTLS_CRT_X509) DEBUG(D_tls) debug_printf("TLS: peer cert problem: %s: %s\n", \ (Label), gnutls_strerror(rc)); \ if (state->verify_requirement >= VERIFY_REQUIRED) \ - return tls_error_gnu((Label), rc, state->host, errstr); \ + return tls_error_gnu(state, (Label), rc, errstr); \ return OK; \ } \ } while (0) @@ -2756,7 +2828,71 @@ if (gnutls_session_is_resumed(state->session)) DEBUG(D_tls) debug_printf("Session resumed\n"); } } -#endif +#endif /* EXIM_HAVE_TLS_RESUME */ + + +#ifdef EXIM_HAVE_ALPN +/* Expand and convert an Exim list to a gnutls_datum list. False return for fail. +NULL plist return for silent no-ALPN. +*/ + +static BOOL +tls_alpn_plist(const 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)) + return FALSE; + +if (!exp_alpn) + { + DEBUG(D_tls) debug_printf("Setting TLS ALPN forced to fail, not sending\n"); + *plist = NULL; + } +else + { + const uschar * list = exp_alpn; + int sep = 0; + unsigned cnt = 0; + gnutls_datum_t * p; + uschar * s; + + while (string_nextinlist(&list, &sep, NULL, 0)) cnt++; + + p = store_get(sizeof(gnutls_datum_t) * cnt, is_tainted(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); } + *plist = (*plen = cnt) ? p : NULL; + } +return TRUE; +} + +static void +tls_server_set_acceptable_alpns(exim_gnutls_state_st * state, uschar ** errstr) +{ +int rc; +const gnutls_datum_t * plist; +unsigned plen; + +if (tls_alpn_plist(tls_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. */ + + server_seen_alpn = 0; + if (!(rc = gnutls_alpn_set_protocols(state->session, plist, plen, + GNUTLS_ALPN_MANDATORY))) + 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 /* EXIM_HAVE_ALPN */ + /* ------------------------------------------------------------------------ */ /* Exported functions */ @@ -2813,6 +2949,10 @@ DEBUG(D_tls) debug_printf("initialising GnuTLS as a server\n"); #endif } +#ifdef EXIM_HAVE_ALPN +tls_server_set_acceptable_alpns(state, errstr); +#endif + #ifdef EXIM_HAVE_TLS_RESUME tls_server_resume_prehandshake(state); #endif @@ -2901,7 +3041,7 @@ if (rc != GNUTLS_E_SUCCESS) } else { - tls_error_gnu(US"gnutls_handshake", rc, NULL, errstr); + tls_error_gnu(state, US"gnutls_handshake", rc, errstr); (void) gnutls_alert_send_appropriate(state->session, rc); gnutls_deinit(state->session); gnutls_certificate_free_credentials(state->lib_state.x509_cred); @@ -2928,6 +3068,33 @@ tls_server_resume_posthandshake(state); DEBUG(D_tls) post_handshake_debug(state); +#ifdef EXIM_HAVE_ALPN +if (server_seen_alpn > 0) + { + DEBUG(D_tls) + { /* The client offered ALPN. See what was negotiated. */ + gnutls_datum_t p = {.size = 0}; + int rc = gnutls_alpn_get_selected_protocol(state->session, &p); + if (!rc) + debug_printf("ALPN negotiated: %.*s\n", (int)p.size, p.data); + else + debug_printf("getting alpn protocol: %s\n", US gnutls_strerror(rc)); + + } + } +else if (server_seen_alpn == 0) + if (verify_check_host(&hosts_require_alpn) == OK) + { + gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_NO_APPLICATION_PROTOCOL); + tls_error(US"handshake", US"ALPN required but not negotiated", NULL, errstr); + return FAIL; + } + else + DEBUG(D_tls) debug_printf("TLS: no ALPN presented in handshake\n"); +else + DEBUG(D_tls) debug_printf("TLS: was not watching for ALPN\n"); +#endif + /* Verify after the fact */ if (!verify_certificate(state, errstr)) @@ -3265,6 +3432,28 @@ if (!cipher_list) #endif } +if (ob->tls_alpn) +#ifdef EXIM_HAVE_ALPN + { + const gnutls_datum_t * plist; + unsigned plen; + + 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) + { + tls_error(US"alpn init", NULL, state->host, errstr); + return FALSE; + } + else + DEBUG(D_tls) debug_printf("Setting TLS ALPN '%s'\n", ob->tls_alpn); + } +#else + log_write(0, LOG_MAIN, "ALPN unusable with this GnuTLS library version; ignoring \"%s\"\n", + ob->tls_alpn); +#endif + { int dh_min_bits = ob->tls_dh_min_bits; if (dh_min_bits < EXIM_CLIENT_DH_MIN_MIN_BITS) @@ -3333,7 +3522,7 @@ if (request_ocsp) if ((rc = gnutls_ocsp_status_request_enable_client(state->session, NULL, 0, NULL)) != OK) { - tls_error_gnu(US"cert-status-req", rc, state->host, errstr); + tls_error_gnu(state, US"cert-status-req", rc, errstr); return FALSE; } tlsp->ocsp = OCSP_NOT_RESP; @@ -3375,7 +3564,7 @@ if (rc != GNUTLS_E_SUCCESS) tls_error(US"gnutls_handshake", US"timed out", state->host, errstr); } else - tls_error_gnu(US"gnutls_handshake", rc, state->host, errstr); + tls_error_gnu(state, US"gnutls_handshake", rc, errstr); return FALSE; } @@ -3420,9 +3609,9 @@ if (request_ocsp) gnutls_free(printed.data); } else - (void) tls_error_gnu(US"ocsp decode", rc, state->host, errstr); + (void) tls_error_gnu(state, US"ocsp decode", rc, errstr); if (idx == 0 && rc) - (void) tls_error_gnu(US"ocsp decode", rc, state->host, errstr); + (void) tls_error_gnu(state, US"ocsp decode", rc, errstr); } if (gnutls_ocsp_status_request_is_checked(state->session, 0) == 0) @@ -3444,6 +3633,24 @@ if (request_ocsp) tls_client_resume_posthandshake(state, tlsp, host); #endif +#ifdef EXIM_HAVE_ALPN +if (ob->tls_alpn) /* We requested. See what was negotiated. */ + { + gnutls_datum_t p = {.size = 0}; + + if (gnutls_alpn_get_selected_protocol(state->session, &p) == 0) + { DEBUG(D_tls) debug_printf("ALPN negotiated: '%.*s'\n", (int)p.size, p.data); } + else if (verify_check_given_host(CUSS &ob->hosts_require_alpn, host) == OK) + { + gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_NO_APPLICATION_PROTOCOL); + tls_error(US"handshake", US"ALPN required but not negotiated", state->host, errstr); + return FALSE; + } + else + DEBUG(D_tls) debug_printf("No ALPN negotiated"); + } +#endif + /* Sets various Exim expansion variables; may need to adjust for ACL callouts */ extract_exim_vars_from_tls_state(state); @@ -3455,6 +3662,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 * *************************************************/ @@ -3465,27 +3691,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); } @@ -3633,7 +3862,7 @@ return buf; void -tls_get_cache() +tls_get_cache(void) { #ifndef DISABLE_DKIM exim_gnutls_state_st * state = &state_server; @@ -3652,8 +3881,6 @@ return state_server.xfer_buffer_lwm < state_server.xfer_buffer_hwm } - - /************************************************* * Read bytes from TLS channel * *************************************************/