X-Git-Url: https://vcs.fsf.org/?p=exim.git;a=blobdiff_plain;f=src%2Fsrc%2Ftls-gnu.c;h=38e8eab09a3efc451a0579aa56dac61cd326846b;hp=d623d8e4aae0caf046104d577a0c5c2ce50d2c3a;hb=b808677c8f0d6a1cf93ff75f4ad5b1199bd85311;hpb=0d81dabc92972f340421d0f80fc04156215e2eb8 diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index d623d8e4a..38e8eab09 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -2,7 +2,7 @@ * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2015 */ +/* Copyright (c) University of Cambridge 1995 - 2018 */ /* See the file NOTICE for conditions of use and distribution. */ /* Copyright (c) Phil Pennock 2012 */ @@ -60,10 +60,31 @@ require current GnuTLS, then we'll drop support for the ancient libraries). #if GNUTLS_VERSION_NUMBER >= 0x030014 # define SUPPORT_SYSDEFAULT_CABUNDLE #endif +#if GNUTLS_VERSION_NUMBER >= 0x030109 +# define SUPPORT_CORK +#endif +#if GNUTLS_VERSION_NUMBER >= 0x030506 && !defined(DISABLE_OCSP) +# define SUPPORT_SRV_OCSP_STACK +#endif + +#ifdef SUPPORT_DANE +# if GNUTLS_VERSION_NUMBER >= 0x030000 +# define DANESSL_USAGE_DANE_TA 2 +# define DANESSL_USAGE_DANE_EE 3 +# else +# error GnuTLS version too early for DANE +# endif +# if GNUTLS_VERSION_NUMBER < 0x999999 +# define GNUTLS_BROKEN_DANE_VALIDATION +# endif +#endif #ifndef DISABLE_OCSP # include #endif +#ifdef SUPPORT_DANE +# include +#endif /* GnuTLS 2 vs 3 @@ -79,7 +100,7 @@ Changes: /* Values for verify_requirement */ enum peer_verify_requirement - { VERIFY_NONE, VERIFY_OPTIONAL, VERIFY_REQUIRED }; + { VERIFY_NONE, VERIFY_OPTIONAL, VERIFY_REQUIRED, VERIFY_DANE }; /* This holds most state for server or client; with this, we can set up an outbound TLS-enabled connection in an ACL callout, while not stomping all @@ -100,6 +121,7 @@ typedef struct exim_gnutls_state { int fd_in; int fd_out; BOOL peer_cert_verified; + BOOL peer_dane_verified; BOOL trigger_sni_changes; BOOL have_set_peerdn; const struct host_item *host; @@ -120,11 +142,14 @@ typedef struct exim_gnutls_state { uschar *exp_tls_verify_certificates; uschar *exp_tls_crl; uschar *exp_tls_require_ciphers; - uschar *exp_tls_ocsp_file; const uschar *exp_tls_verify_cert_hostnames; #ifndef DISABLE_EVENT uschar *event_action; #endif +#ifdef SUPPORT_DANE + char * const * dane_data; + const int * dane_data_len; +#endif tls_support *tlsp; /* set in tls_init() */ @@ -136,16 +161,45 @@ typedef struct exim_gnutls_state { } exim_gnutls_state_st; static const exim_gnutls_state_st exim_gnutls_state_init = { - NULL, NULL, NULL, VERIFY_NONE, -1, -1, FALSE, FALSE, FALSE, - NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, - NULL, NULL, NULL, NULL, NULL, NULL, NULL, - NULL, + .session = NULL, + .x509_cred = NULL, + .priority_cache = NULL, + .verify_requirement = VERIFY_NONE, + .fd_in = -1, + .fd_out = -1, + .peer_cert_verified = FALSE, + .peer_dane_verified = FALSE, + .trigger_sni_changes =FALSE, + .have_set_peerdn = FALSE, + .host = NULL, + .peercert = NULL, + .peerdn = NULL, + .ciphersuite = NULL, + .received_sni = NULL, + + .tls_certificate = NULL, + .tls_privatekey = NULL, + .tls_sni = NULL, + .tls_verify_certificates = NULL, + .tls_crl = NULL, + .tls_require_ciphers =NULL, + + .exp_tls_certificate = NULL, + .exp_tls_privatekey = NULL, + .exp_tls_verify_certificates = NULL, + .exp_tls_crl = NULL, + .exp_tls_require_ciphers = NULL, + .exp_tls_verify_cert_hostnames = NULL, #ifndef DISABLE_EVENT - NULL, + .event_action = NULL, #endif - NULL, - NULL, 0, 0, 0, 0, + .tlsp = NULL, + + .xfer_buffer = NULL, + .xfer_buffer_lwm = 0, + .xfer_buffer_hwm = 0, + .xfer_eof = 0, + .xfer_error = 0, }; /* Not only do we have our own APIs which don't pass around state, assuming @@ -189,7 +243,8 @@ static BOOL gnutls_buggy_ocsp = FALSE; /* Set this to control gnutls_global_set_log_level(); values 0 to 9 will setup the library logging; a value less than 0 disables the calls to set up logging -callbacks. */ +callbacks. Possibly GNuTLS also looks for an environment variable +"GNUTLS_DEBUG_LEVEL". */ #ifndef EXIM_GNUTLS_LIBRARY_LOG_LEVEL # define EXIM_GNUTLS_LIBRARY_LOG_LEVEL -1 #endif @@ -205,8 +260,8 @@ before, for now. */ # define EXIM_SERVER_DH_BITS_PRE2_12 1024 #endif -#define exim_gnutls_err_check(Label) do { \ - if (rc != GNUTLS_E_SUCCESS) \ +#define exim_gnutls_err_check(rc, Label) do { \ + if ((rc) != GNUTLS_E_SUCCESS) \ return tls_error((Label), gnutls_strerror(rc), host, errstr); \ } while (0) @@ -404,6 +459,9 @@ tlsp->cipher = state->ciphersuite; DEBUG(D_tls) debug_printf("cipher: %s\n", state->ciphersuite); tlsp->certificate_verified = state->peer_cert_verified; +#ifdef SUPPORT_DANE +tlsp->dane_verified = state->peer_dane_verified; +#endif /* note that tls_channelbinding_b64 is not saved to the spool file, since it's only available for use for authenticators while this TLS session is running. */ @@ -474,7 +532,7 @@ host_item *host = NULL; /* dummy for macros */ DEBUG(D_tls) debug_printf("Initialising GnuTLS server params.\n"); rc = gnutls_dh_params_init(&dh_server_params); -exim_gnutls_err_check(US"gnutls_dh_params_init"); +exim_gnutls_err_check(rc, US"gnutls_dh_params_init"); m.data = NULL; m.size = 0; @@ -510,7 +568,7 @@ else if (m.data) { rc = gnutls_dh_params_import_pkcs3(dh_server_params, &m, GNUTLS_X509_FMT_PEM); - exim_gnutls_err_check(US"gnutls_dh_params_import_pkcs3"); + exim_gnutls_err_check(rc, US"gnutls_dh_params_import_pkcs3"); DEBUG(D_tls) debug_printf("Loaded fixed standard D-H parameters\n"); return OK; } @@ -593,7 +651,7 @@ if ((fd = Uopen(filename, O_RDONLY, 0)) >= 0) rc = gnutls_dh_params_import_pkcs3(dh_server_params, &m, GNUTLS_X509_FMT_PEM); free(m.data); - exim_gnutls_err_check(US"gnutls_dh_params_import_pkcs3"); + exim_gnutls_err_check(rc, US"gnutls_dh_params_import_pkcs3"); DEBUG(D_tls) debug_printf("read D-H parameters from file \"%s\"\n", filename); } @@ -650,7 +708,7 @@ if (rc < 0) debug_printf("requesting generation of %d bit Diffie-Hellman prime ...\n", dh_bits_gen); rc = gnutls_dh_params_generate2(dh_server_params, dh_bits_gen); - exim_gnutls_err_check(US"gnutls_dh_params_generate2"); + exim_gnutls_err_check(rc, US"gnutls_dh_params_generate2"); /* 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 @@ -661,7 +719,7 @@ if (rc < 0) rc = gnutls_dh_params_export_pkcs3(dh_server_params, GNUTLS_X509_FMT_PEM, m.data, &sz); if (rc != GNUTLS_E_SHORT_MEMORY_BUFFER) - exim_gnutls_err_check(US"gnutls_dh_params_export_pkcs3(NULL) sizing"); + exim_gnutls_err_check(rc, US"gnutls_dh_params_export_pkcs3(NULL) sizing"); m.size = sz; if (!(m.data = malloc(m.size))) return tls_error(US"memory allocation failed", strerror(errno), NULL, errstr); @@ -672,7 +730,7 @@ if (rc < 0) if (rc != GNUTLS_E_SUCCESS) { free(m.data); - exim_gnutls_err_check(US"gnutls_dh_params_export_pkcs3() real"); + exim_gnutls_err_check(rc, US"gnutls_dh_params_export_pkcs3() real"); } m.size = sz; /* shrink by 1, probably */ @@ -772,6 +830,27 @@ err: +/* Add certificate and key, from files. + +Return: + Zero or negative: good. Negate value for certificate index if < 0. + Greater than zero: FAIL or DEFER code. +*/ + +static int +tls_add_certfile(exim_gnutls_state_st * state, const host_item * host, + uschar * certfile, uschar * keyfile, uschar ** errstr) +{ +int rc = gnutls_certificate_set_x509_key_file(state->x509_cred, + CS certfile, CS keyfile, GNUTLS_X509_FMT_PEM); +if (rc < 0) + return tls_error( + string_sprintf("cert/key setup: cert=%s key=%s", certfile, keyfile), + gnutls_strerror(rc), host, errstr); +return -rc; +} + + /************************************************* * Variables re-expanded post-SNI * *************************************************/ @@ -792,7 +871,7 @@ Returns: OK/DEFER/FAIL */ static int -tls_expand_session_files(exim_gnutls_state_st *state, uschar ** errstr) +tls_expand_session_files(exim_gnutls_state_st * state, uschar ** errstr) { struct stat statbuf; int rc; @@ -807,11 +886,11 @@ int cert_count; if (!host) /* server */ if (!state->received_sni) { - if (state->tls_certificate && - (Ustrstr(state->tls_certificate, US"tls_sni") || - Ustrstr(state->tls_certificate, US"tls_in_sni") || - Ustrstr(state->tls_certificate, US"tls_out_sni") - )) + if ( state->tls_certificate + && ( Ustrstr(state->tls_certificate, US"tls_sni") + || Ustrstr(state->tls_certificate, US"tls_in_sni") + || Ustrstr(state->tls_certificate, US"tls_out_sni") + ) ) { DEBUG(D_tls) debug_printf("We will re-expand TLS session files if we receive SNI.\n"); state->trigger_sni_changes = TRUE; @@ -827,7 +906,11 @@ if (!host) /* server */ } rc = gnutls_certificate_allocate_credentials(&state->x509_cred); -exim_gnutls_err_check(US"gnutls_certificate_allocate_credentials"); +exim_gnutls_err_check(rc, US"gnutls_certificate_allocate_credentials"); + +#ifdef SUPPORT_SRV_OCSP_STACK +gnutls_certificate_set_flags(state->x509_cred, GNUTLS_CERTIFICATE_API_V2); +#endif /* remember: expand_check_tlsvar() is expand_check() but fiddling with state members, assuming consistent naming; and expand_check() returns @@ -878,44 +961,81 @@ if (state->exp_tls_certificate && *state->exp_tls_certificate) DEBUG(D_tls) debug_printf("TLS SNI: have a changed cert/key pair.\n"); } - rc = gnutls_certificate_set_x509_key_file(state->x509_cred, - CS state->exp_tls_certificate, CS state->exp_tls_privatekey, - GNUTLS_X509_FMT_PEM); - exim_gnutls_err_check( - string_sprintf("cert/key setup: cert=%s key=%s", - state->exp_tls_certificate, state->exp_tls_privatekey)); - DEBUG(D_tls) debug_printf("TLS: cert/key registered\n"); - } /* tls_certificate */ + if (!host) /* server */ + { + const uschar * clist = state->exp_tls_certificate; + const uschar * klist = state->exp_tls_privatekey; + const uschar * olist; + int csep = 0, ksep = 0, osep = 0, cnt = 0; + uschar * cfile, * kfile, * ofile; +#ifndef DISABLE_OCSP + if (!expand_check(tls_ocsp_file, US"tls_ocsp_file", &ofile, errstr)) + return DEFER; + olist = ofile; +#endif -/* Set the OCSP stapling server info */ + 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, host, errstr); + else if (0 < (rc = tls_add_certfile(state, host, cfile, kfile, errstr))) + return rc; + else + { + int gnutls_cert_index = -rc; + DEBUG(D_tls) debug_printf("TLS: cert/key %s registered\n", cfile); + + /* Set the OCSP stapling server info */ #ifndef DISABLE_OCSP -if ( !host /* server */ - && tls_ocsp_file - ) - { - if (gnutls_buggy_ocsp) - { - DEBUG(D_tls) debug_printf("GnuTLS library is buggy for OCSP; avoiding\n"); + if (tls_ocsp_file) + if (gnutls_buggy_ocsp) + { + DEBUG(D_tls) + debug_printf("GnuTLS library is buggy for OCSP; avoiding\n"); + } + else if ((ofile = string_nextinlist(&olist, &osep, NULL, 0))) + { + /* Use the full callback method for stapling just to get + observability. More efficient would be to read the file once only, + if it never changed (due to SNI). Would need restart on file update, + or watch datestamp. */ + +# ifdef SUPPORT_SRV_OCSP_STACK + rc = gnutls_certificate_set_ocsp_status_request_function2( + state->x509_cred, gnutls_cert_index, + server_ocsp_stapling_cb, ofile); + + exim_gnutls_err_check(rc, + US"gnutls_certificate_set_ocsp_status_request_function2"); +# else + if (cnt++ > 0) + { + DEBUG(D_tls) + debug_printf("oops; multiple OCSP files not supported\n"); + break; + } + gnutls_certificate_set_ocsp_status_request_function( + state->x509_cred, server_ocsp_stapling_cb, ofile); +# endif + + DEBUG(D_tls) debug_printf("OCSP response file = %s\n", ofile); + } + else + DEBUG(D_tls) debug_printf("ran out of OCSP response files in list\n"); +#endif + } } else { - if (!expand_check(tls_ocsp_file, US"tls_ocsp_file", - &state->exp_tls_ocsp_file, errstr)) - return DEFER; - - /* Use the full callback method for stapling just to get observability. - More efficient would be to read the file once only, if it never changed - (due to SNI). Would need restart on file update, or watch datestamp. */ - - gnutls_certificate_set_ocsp_status_request_function(state->x509_cred, - server_ocsp_stapling_cb, state->exp_tls_ocsp_file); - - DEBUG(D_tls) debug_printf("OCSP response file = %s\n", state->exp_tls_ocsp_file); + if (0 < (rc = tls_add_certfile(state, host, + state->exp_tls_certificate, state->exp_tls_privatekey, errstr))) + return rc; + DEBUG(D_tls) debug_printf("TLS: cert/key registered\n"); } - } -#endif + + } /* tls_certificate */ /* Set the trusted CAs file if one is provided, and then add the CRL if one is @@ -1010,7 +1130,7 @@ else if (cert_count < 0) { rc = cert_count; - exim_gnutls_err_check(US"setting certificate trust"); + exim_gnutls_err_check(rc, US"setting certificate trust"); } DEBUG(D_tls) debug_printf("Added %d certificate authorities.\n", cert_count); @@ -1023,7 +1143,7 @@ if (state->tls_crl && *state->tls_crl && if (cert_count < 0) { rc = cert_count; - exim_gnutls_err_check(US"gnutls_certificate_set_x509_crl_file"); + exim_gnutls_err_check(rc, US"gnutls_certificate_set_x509_crl_file"); } DEBUG(D_tls) debug_printf("Processed %d CRLs.\n", cert_count); } @@ -1074,7 +1194,7 @@ if (!state->host) /* Link the credentials to the session. */ rc = gnutls_credentials_set(state->session, GNUTLS_CRD_CERTIFICATE, state->x509_cred); -exim_gnutls_err_check(US"gnutls_credentials_set"); +exim_gnutls_err_check(rc, US"gnutls_credentials_set"); return OK; } @@ -1164,12 +1284,12 @@ if (!exim_gnutls_base_init_done) if (!gnutls_allow_auto_pkcs11) { rc = gnutls_pkcs11_init(GNUTLS_PKCS11_FLAG_MANUAL, NULL); - exim_gnutls_err_check(US"gnutls_pkcs11_init"); + exim_gnutls_err_check(rc, US"gnutls_pkcs11_init"); } #endif rc = gnutls_global_init(); - exim_gnutls_err_check(US"gnutls_global_init"); + exim_gnutls_err_check(rc, US"gnutls_global_init"); #if EXIM_GNUTLS_LIBRARY_LOG_LEVEL >= 0 DEBUG(D_tls) @@ -1204,7 +1324,7 @@ else DEBUG(D_tls) debug_printf("initialising GnuTLS server session\n"); rc = gnutls_init(&state->session, GNUTLS_SERVER); } -exim_gnutls_err_check(US"gnutls_init"); +exim_gnutls_err_check(rc, US"gnutls_init"); state->host = host; @@ -1239,12 +1359,12 @@ if (host) sz = Ustrlen(state->tlsp->sni); rc = gnutls_server_name_set(state->session, GNUTLS_NAME_DNS, state->tlsp->sni, sz); - exim_gnutls_err_check(US"gnutls_server_name_set"); + exim_gnutls_err_check(rc, US"gnutls_server_name_set"); } } else if (state->tls_sni) DEBUG(D_tls) debug_printf("*** PROBABLY A BUG *** " \ - "have an SNI set for a client [%s]\n", state->tls_sni); + "have an SNI set for a server [%s]\n", state->tls_sni); /* This is the priority string support, http://www.gnutls.org/manual/html_node/Priority-Strings.html @@ -1279,12 +1399,12 @@ if (want_default_priorities) p = US exim_default_gnutls_priority; } -exim_gnutls_err_check(string_sprintf( +exim_gnutls_err_check(rc, string_sprintf( "gnutls_priority_init(%s) failed at offset %ld, \"%.6s..\"", p, errpos - CS p, errpos)); rc = gnutls_priority_set(state->session, state->priority_cache); -exim_gnutls_err_check(US"gnutls_priority_set"); +exim_gnutls_err_check(rc, US"gnutls_priority_set"); gnutls_db_set_cache_expiration(state->session, ssl_session_timeout); @@ -1453,8 +1573,8 @@ gnutls_certificate_set_verify_function() to fail the handshake if we dislike the peer information, but that's too new for some OSes. Arguments: - state exim_gnutls_state_st * - errstr where to put an error message + state exim_gnutls_state_st * + errstr where to put an error message Returns: FALSE if the session should be rejected @@ -1462,10 +1582,13 @@ Returns: */ static BOOL -verify_certificate(exim_gnutls_state_st *state, uschar ** errstr) +verify_certificate(exim_gnutls_state_st * state, uschar ** errstr) { int rc; -unsigned int verify; +uint verify; + +if (state->verify_requirement == VERIFY_NONE) + return TRUE; *errstr = NULL; @@ -1475,14 +1598,129 @@ if ((rc = peer_status(state, errstr)) != OK) *errstr = US"certificate not supplied"; } else + + { +#ifdef SUPPORT_DANE + if (state->verify_requirement == VERIFY_DANE && state->host) + { + /* Using dane_verify_session_crt() would be easy, as it does it all for us + including talking to a DNS resolver. But we want to do that bit ourselves + as the testsuite intercepts and fakes its own DNS environment. */ + + dane_state_t s; + dane_query_t r; + uint lsize; + const gnutls_datum_t * certlist = + gnutls_certificate_get_peers(state->session, &lsize); + int usage = tls_out.tlsa_usage; + +# ifdef GNUTLS_BROKEN_DANE_VALIDATION + /* Split the TLSA records into two sets, TA and EE selectors. Run the + dane-verification separately so that we know which selector verified; + then we know whether to do CA-chain-verification and name-verification + (needed for TA but not EE). */ + + if (usage == ((1<dane_data_len[nrec]; ) nrec++; + nrec++; + + dd = store_get(nrec * sizeof(uschar *)); + ddl = store_get(nrec * sizeof(int)); + nrec--; + + if ((rc = dane_state_init(&s, 0))) + goto tlsa_prob; + + for (usage = DANESSL_USAGE_DANE_EE; + usage >= DANESSL_USAGE_DANE_TA; usage--) + { /* take records with this usage */ + for (j = i = 0; i < nrec; i++) + if (state->dane_data[i][0] == usage) + { + dd[j] = state->dane_data[i]; + ddl[j++] = state->dane_data_len[i]; + } + if (j) + { + dd[j] = NULL; + ddl[j] = 0; + + if ((rc = dane_raw_tlsa(s, &r, (char * const *)dd, ddl, 1, 0))) + goto tlsa_prob; + + if ((rc = dane_verify_crt_raw(s, certlist, lsize, + gnutls_certificate_type_get(state->session), + r, 0, + usage == DANESSL_USAGE_DANE_EE + ? DANE_VFLAG_ONLY_CHECK_EE_USAGE : 0, + &verify))) + { + DEBUG(D_tls) + debug_printf("TLSA record problem: %s\n", dane_strerror(rc)); + } + else if (verify == 0) /* verification passed */ + { + usage = 1 << usage; + break; + } + } + } + + if (rc) goto tlsa_prob; + } + else +# endif + { + if ( (rc = dane_state_init(&s, 0)) + || (rc = dane_raw_tlsa(s, &r, state->dane_data, state->dane_data_len, + 1, 0)) + || (rc = dane_verify_crt_raw(s, certlist, lsize, + gnutls_certificate_type_get(state->session), + r, 0, +# ifdef GNUTLS_BROKEN_DANE_VALIDATION + usage == (1 << DANESSL_USAGE_DANE_EE) + ? DANE_VFLAG_ONLY_CHECK_EE_USAGE : 0, +# else + 0, +# endif + &verify)) + ) + goto tlsa_prob; + } + + if (verify != 0) /* verification failed */ + { + gnutls_datum_t str; + (void) dane_verification_status_print(verify, &str, 0); + *errstr = US str.data; /* don't bother to free */ + goto badcert; + } + state->peer_dane_verified = TRUE; + +# ifdef GNUTLS_BROKEN_DANE_VALIDATION + /* If a TA-mode TLSA record was used for verification we must additionally + verify the CA chain and the cert name. For EE-mode, skip it. */ + + if (usage & (1 << DANESSL_USAGE_DANE_EE)) +# endif + { + state->peer_cert_verified = TRUE; + goto goodcert; + } + } +#endif + rc = gnutls_certificate_verify_peers2(state->session, &verify); + } -/* Handle the result of verification. INVALID seems to be set as well -as REVOKED, but leave the test for both. */ +/* Handle the result of verification. INVALID is set if any others are. */ -if (rc < 0 || - verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED) - ) +if (rc < 0 || verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED)) { state->peer_cert_verified = FALSE; if (!*errstr) @@ -1494,11 +1732,7 @@ if (rc < 0 || *errstr, state->peerdn ? state->peerdn : US""); if (state->verify_requirement >= VERIFY_REQUIRED) - { - gnutls_alert_send(state->session, - GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE); - return FALSE; - } + goto badcert; DEBUG(D_tls) debug_printf("TLS verify failure overridden (host in tls_try_verify_hosts)\n"); } @@ -1518,11 +1752,7 @@ else DEBUG(D_tls) debug_printf("TLS certificate verification failed: cert name mismatch\n"); if (state->verify_requirement >= VERIFY_REQUIRED) - { - gnutls_alert_send(state->session, - GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE); - return FALSE; - } + goto badcert; return TRUE; } } @@ -1531,9 +1761,18 @@ else state->peerdn ? state->peerdn : US""); } -state->tlsp->peerdn = state->peerdn; +goodcert: + state->tlsp->peerdn = state->peerdn; + return TRUE; -return TRUE; +#ifdef SUPPORT_DANE +tlsa_prob: + *errstr = string_sprintf("TLSA record problem: %s", dane_strerror(rc)); +#endif + +badcert: + gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE); + return FALSE; } @@ -1644,11 +1883,12 @@ server_ocsp_stapling_cb(gnutls_session_t session, void * ptr, gnutls_datum_t * ocsp_response) { int ret; +DEBUG(D_tls) debug_printf("OCSP stapling callback: %s\n", US ptr); if ((ret = gnutls_load_file(ptr, ocsp_response)) < 0) { DEBUG(D_tls) debug_printf("Failed to load ocsp stapling file %s\n", - (char *)ptr); + CS ptr); tls_in.ocsp = OCSP_NOT_RESP; return GNUTLS_E_NO_CERTIFICATE_STATUS; } @@ -1745,7 +1985,7 @@ exim_gnutls_state_st * state = NULL; if (tls_in.active >= 0) { tls_error(US"STARTTLS received after TLS started", "", NULL, errstr); - smtp_printf("554 Already in TLS\r\n"); + smtp_printf("554 Already in TLS\r\n", FALSE); return FAIL; } @@ -1806,7 +2046,7 @@ mode, the fflush() happens when smtp_getc() is called. */ if (!state->tlsp->on_connect) { - smtp_printf("220 TLS go ahead\r\n"); + smtp_printf("220 TLS go ahead\r\n", FALSE); fflush(smtp_out); } @@ -1858,8 +2098,7 @@ DEBUG(D_tls) debug_printf("gnutls_handshake was successful\n"); /* Verify after the fact */ -if ( state->verify_requirement != VERIFY_NONE - && !verify_certificate(state, errstr)) +if (!verify_certificate(state, errstr)) { if (state->verify_requirement != VERIFY_OPTIONAL) { @@ -1917,6 +2156,77 @@ if (verify_check_given_host(&ob->tls_verify_cert_hostnames, host) == OK) } + + +#ifdef SUPPORT_DANE +/* Given our list of RRs from the TLSA lookup, build a lookup block in +GnuTLS-DANE's preferred format. Hang it on the state str for later +use in DANE verification. + +We point at the dnsa data not copy it, so it must remain valid until +after verification is done.*/ + +static BOOL +dane_tlsa_load(exim_gnutls_state_st * state, dns_answer * dnsa) +{ +dns_record * rr; +dns_scan dnss; +int i; +const char ** dane_data; +int * dane_data_len; + +for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS), i = 1; + rr; + rr = dns_next_rr(dnsa, &dnss, RESET_NEXT) + ) if (rr->type == T_TLSA) i++; + +dane_data = store_get(i * sizeof(uschar *)); +dane_data_len = store_get(i * sizeof(int)); + +for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS), i = 0; + rr; + rr = dns_next_rr(dnsa, &dnss, RESET_NEXT) + ) if (rr->type == T_TLSA) + { + const uschar * p = rr->data; + uint8_t usage = p[0], sel = p[1], type = p[2]; + + DEBUG(D_tls) + debug_printf("TLSA: %d %d %d size %d\n", usage, sel, type, rr->size); + + if ( (usage != DANESSL_USAGE_DANE_TA && usage != DANESSL_USAGE_DANE_EE) + || (sel != 0 && sel != 1) + ) + continue; + switch(type) + { + case 0: /* Full: cannot check at present */ + break; + case 1: if (rr->size != 3 + 256/8) continue; /* sha2-256 */ + break; + case 2: if (rr->size != 3 + 512/8) continue; /* sha2-512 */ + break; + default: continue; + } + + tls_out.tlsa_usage |= 1<size; + } + +if (!i) return FALSE; + +dane_data[i] = NULL; +dane_data_len[i] = 0; + +state->dane_data = (char * const *)dane_data; +state->dane_data_len = dane_data_len; +return TRUE; +} +#endif + + + /************************************************* * Start a TLS session in a client * *************************************************/ @@ -1928,7 +2238,11 @@ Arguments: host connected host (for messages) addr the first address (not used) tb transport (always smtp) - + tlsa_dnsa non-NULL, either request or require dane for this host, and + a TLSA record found. Therefore, dane verify required. + Which implies cert must be requested and supplied, dane + verify must pass, and cert verify irrelevant (incl. + hostnames), and (caller handled) require_tls errstr error string pointer Returns: OK/DEFER/FAIL (because using common functions), @@ -1939,15 +2253,15 @@ int tls_client_start(int fd, host_item *host, address_item *addr ARG_UNUSED, transport_instance * tb, -#ifdef EXPERIMENTAL_DANE - dns_answer * tlsa_dnsa ARG_UNUSED, +#ifdef SUPPORT_DANE + dns_answer * tlsa_dnsa, #endif uschar ** errstr) { smtp_transport_options_block *ob = (smtp_transport_options_block *)tb->options_block; int rc; -exim_gnutls_state_st *state = NULL; +exim_gnutls_state_st * state = NULL; #ifndef DISABLE_OCSP BOOL require_ocsp = verify_check_given_host(&ob->hosts_require_ocsp, host) == OK; @@ -1983,12 +2297,22 @@ if ((rc = tls_init(host, ob->tls_certificate, ob->tls_privatekey, set but both tls_verify_hosts and tls_try_verify_hosts are unset. Check only the specified host patterns if one of them is defined */ -if ( ( state->exp_tls_verify_certificates - && !ob->tls_verify_hosts - && (!ob->tls_try_verify_hosts || !*ob->tls_try_verify_hosts) - ) - || verify_check_given_host(&ob->tls_verify_hosts, host) == OK - ) +#ifdef SUPPORT_DANE +if (tlsa_dnsa && dane_tlsa_load(state, tlsa_dnsa)) + { + DEBUG(D_tls) + debug_printf("TLS: server certificate DANE required.\n"); + state->verify_requirement = VERIFY_DANE; + gnutls_certificate_server_set_request(state->session, GNUTLS_CERT_REQUIRE); + } +else +#endif + if ( ( state->exp_tls_verify_certificates + && !ob->tls_verify_hosts + && (!ob->tls_try_verify_hosts || !*ob->tls_try_verify_hosts) + ) + || verify_check_given_host(&ob->tls_verify_hosts, host) == OK + ) { tls_client_setup_hostname_checks(host, state, ob); DEBUG(D_tls) @@ -2044,10 +2368,8 @@ DEBUG(D_tls) debug_printf("about to gnutls_handshake\n"); sigalrm_seen = FALSE; alarm(ob->command_timeout); do - { rc = gnutls_handshake(state->session); - } while ((rc == GNUTLS_E_AGAIN) || - (rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen)); +while (rc == GNUTLS_E_AGAIN || rc == GNUTLS_E_INTERRUPTED && !sigalrm_seen); alarm(0); if (rc != GNUTLS_E_SUCCESS) @@ -2063,8 +2385,7 @@ DEBUG(D_tls) debug_printf("gnutls_handshake was successful\n"); /* Verify late */ -if (state->verify_requirement != VERIFY_NONE && - !verify_certificate(state, errstr)) +if (!verify_certificate(state, errstr)) return tls_error(US"certificate verification failed", *errstr, state->host, errstr); #ifndef DISABLE_OCSP @@ -2143,9 +2464,10 @@ gnutls_certificate_free_credentials(state->x509_cred); state->tlsp->active = -1; +if (state->xfer_buffer) store_free(state->xfer_buffer); memcpy(state, &exim_gnutls_state_init, sizeof(exim_gnutls_state_init)); -if ((state_server.session == NULL) && (state_client.session == NULL)) +if (!state_server.session && !state_client.session) { gnutls_global_deinit(); exim_gnutls_base_init_done = FALSE; @@ -2164,6 +2486,7 @@ ssize_t inbytes; DEBUG(D_tls) debug_printf("Calling gnutls_record_recv(%p, %p, %u)\n", state->session, state->xfer_buffer, ssl_xfer_buffer_size); +sigalrm_seen = FALSE; if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout); inbytes = gnutls_record_recv(state->session, state->xfer_buffer, MIN(ssl_xfer_buffer_size, lim)); @@ -2288,6 +2611,14 @@ if (n > 0) } +BOOL +tls_could_read(void) +{ +return state_server.xfer_buffer_lwm < state_server.xfer_buffer_hwm + || gnutls_record_check_pending(state_server.session) > 0; +} + + /************************************************* @@ -2347,19 +2678,27 @@ Arguments: is_server channel specifier buff buffer of data len number of bytes + more more data expected soon Returns: the number of bytes after a successful write, -1 after a failed write */ int -tls_write(BOOL is_server, const uschar *buff, size_t len) +tls_write(BOOL is_server, const uschar *buff, size_t len, BOOL more) { ssize_t outbytes; size_t left = len; exim_gnutls_state_st *state = is_server ? &state_server : &state_client; +#ifdef SUPPORT_CORK +static BOOL corked = FALSE; + +if (more && !corked) gnutls_record_cork(state->session); +#endif + +DEBUG(D_tls) debug_printf("%s(%p, " SIZE_T_FMT "%s)\n", __FUNCTION__, + buff, left, more ? ", more" : ""); -DEBUG(D_tls) debug_printf("tls_do_write(%p, " SIZE_T_FMT ")\n", buff, left); while (left > 0) { DEBUG(D_tls) debug_printf("gnutls_record_send(SSL, %p, " SIZE_T_FMT ")\n", @@ -2390,6 +2729,14 @@ if (len > INT_MAX) len = INT_MAX; } +#ifdef SUPPORT_CORK +if (more != corked) + { + if (!more) (void) gnutls_record_uncork(state->session, 0); + corked = more; + } +#endif + return (int) len; }