diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/OS/Makefile-Base | 16 | ||||
-rwxr-xr-x | src/scripts/MakeLinks | 2 | ||||
-rw-r--r-- | src/src/EDITME | 5 | ||||
-rw-r--r-- | src/src/dane-gnu.c | 21 | ||||
-rw-r--r-- | src/src/dane.c | 4 | ||||
-rw-r--r-- | src/src/tls-gnu.c | 174 | ||||
-rw-r--r-- | src/src/transports/smtp.c | 11 |
7 files changed, 164 insertions, 69 deletions
diff --git a/src/OS/Makefile-Base b/src/OS/Makefile-Base index dcd87c29c..bb250ff91 100644 --- a/src/OS/Makefile-Base +++ b/src/OS/Makefile-Base @@ -471,13 +471,13 @@ convert4r4: config ../src/convert4r4.src OBJ_WITH_CONTENT_SCAN = malware.o mime.o regex.o spam.o spool_mbox.o OBJ_EXPERIMENTAL = bmi_spam.o \ - dane.o \ - dcc.o \ - dmarc.o \ - imap_utf7.o \ - spf.o \ - srs.o \ - utf8.o + dane.o \ + dcc.o \ + dmarc.o \ + imap_utf7.o \ + spf.o \ + srs.o \ + utf8.o # Targets for final binaries; the main one has a build number which is # updated each time. We don't bother with that for the auxiliaries. @@ -821,7 +821,7 @@ spool_mbox.o: $(HDRS) spool_mbox.c # Dependencies for EXPERIMENTAL_* modules bmi_spam.o: $(HDRS) bmi_spam.c -dane.o: $(HDRS) dane.c dane-gnu.c dane-openssl.c +dane.o: $(HDRS) dane.c dane-openssl.c dcc.o: $(HDRS) dcc.h dcc.c dmarc.o: $(HDRS) pdkim/pdkim.h dmarc.h dmarc.c imap_utf7.o: $(HDRS) imap_utf7.c diff --git a/src/scripts/MakeLinks b/src/scripts/MakeLinks index 770591dc8..7fc1d19b4 100755 --- a/src/scripts/MakeLinks +++ b/src/scripts/MakeLinks @@ -122,7 +122,7 @@ do done # EXPERIMENTAL_* -for f in bmi_spam.c bmi_spam.h dcc.c dcc.h dane.c dane-gnu.c dane-openssl.c \ +for f in bmi_spam.c bmi_spam.h dcc.c dcc.h dane.c dane-openssl.c \ danessl.h imap_utf7.c spf.c spf.h srs.c srs.h utf8.c do ln -s ../src/$f $f diff --git a/src/src/EDITME b/src/src/EDITME index 72e26ce0e..947a4db0e 100644 --- a/src/src/EDITME +++ b/src/src/EDITME @@ -489,7 +489,7 @@ EXIM_MONITOR=eximon.bin # Uncomment the following line to add DANE support # Note: Enabling this unconditionally overrides DISABLE_DNSSEC -# Note: DANE is only supported when using OpenSSL +# For DANE under GnuTLS we need an additional library. See TLS_LIBS below. # EXPERIMENTAL_DANE=yes # Uncomment the following to include extra information in fail DSN message (bounces) @@ -797,6 +797,9 @@ HEADERS_CHARSET="ISO-8859-1" # or # TLS_LIBS=-L/opt/gnu/lib -lgnutls -ltasn1 -lgcrypt +# For DANE under GnuTLS we need an additional library. +TLS_LIBS += -lgnutls-dane + # TLS_LIBS is included only on the command for linking Exim itself, not on any # auxiliary programs. If the include files are not in a standard place, you can # set TLS_INCLUDE to specify where they are, for example: diff --git a/src/src/dane-gnu.c b/src/src/dane-gnu.c deleted file mode 100644 index b98bffad6..000000000 --- a/src/src/dane-gnu.c +++ /dev/null @@ -1,21 +0,0 @@ -/************************************************* -* Exim - an Internet mail transport agent * -*************************************************/ - -/* Copyright (c) University of Cambridge 1995 - 2013 */ -/* See the file NOTICE for conditions of use and distribution. */ - -/* This file (will) provide DANE support for Exim using the GnuTLS library, -but is not yet an available supported implementation. This file is #included -into dane.c when USE_GNUTLS has been set. */ - -/* As of March 2014, the reference implementation for DANE that we are -using was written by Viktor Dukhovny and it supports OpenSSL only. At -some point we will add GnuTLS support, but for right now just abort the -build and explain why. */ - - -#error No support for DANE using GnuTLS yet. - - -/* End of dane-gnu.c */ diff --git a/src/src/dane.c b/src/src/dane.c index 137c75418..b632d80dd 100644 --- a/src/src/dane.c +++ b/src/src/dane.c @@ -38,9 +38,7 @@ static void dummy(int x) { dummy(x-1); } # error DANE support requires that the DNS resolver library supports DNSSEC # endif -# ifdef USE_GNUTLS -# include "dane-gnu.c" -# else +# ifndef USE_GNUTLS # include "dane-openssl.c" # endif diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index e69fc8bee..9f166691a 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -66,10 +66,16 @@ require current GnuTLS, then we'll drop support for the ancient libraries). #if GNUTLS_VERSION_NUMBER >= 0x030506 && !defined(DISABLE_OCSP) # define SUPPORT_SRV_OCSP_STACK #endif +#if GNUTLS_VERSION_NUMBER >= 0x030000 && defined(EXPERIMENTAL_DANE) +# define SUPPORT_DANE +#endif #ifndef DISABLE_OCSP # include <gnutls/ocsp.h> #endif +#ifdef SUPPORT_DANE +# include <gnutls/dane.h> +#endif /* GnuTLS 2 vs 3 @@ -85,7 +91,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 @@ -106,6 +112,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; @@ -130,6 +137,10 @@ typedef struct exim_gnutls_state { #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() */ @@ -148,6 +159,7 @@ static const exim_gnutls_state_st exim_gnutls_state_init = { .fd_in = -1, .fd_out = -1, .peer_cert_verified = FALSE, + .peer_dane_verified = FALSE, .trigger_sni_changes =FALSE, .have_set_peerdn = FALSE, .host = NULL, @@ -438,6 +450,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. */ @@ -1549,8 +1564,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 @@ -1561,7 +1576,10 @@ static BOOL 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; @@ -1571,10 +1589,49 @@ 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; + const gnutls_datum_t * certlist; + uint lsize; + + certlist = gnutls_certificate_get_peers(state->session, &lsize); + + 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, 0, &verify)) + ) + + { + *errstr = string_sprintf("TLSA record problem: %s", dane_strerror(rc)); + goto badcert; + } + if (verify != 0) + { + 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; + } +#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) @@ -1590,11 +1647,7 @@ if (rc < 0 || *errstr, state->peerdn ? state->peerdn : US"<unset>"); 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"); } @@ -1614,11 +1667,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; } } @@ -1628,8 +1677,11 @@ else } state->tlsp->peerdn = state->peerdn; - return TRUE; + +badcert: + gnutls_alert_send(state->session, GNUTLS_AL_FATAL, GNUTLS_A_BAD_CERTIFICATE); + return FALSE; } @@ -1955,8 +2007,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) { @@ -2014,6 +2065,55 @@ 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 void +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; + + tls_out.tlsa_usage |= 1<<usage; + dane_data[i] = p; + dane_data_len[i++] = rr->size; + } +dane_data[i] = NULL; +dane_data_len[i] = 0; + +state->dane_data = (char * const *)dane_data; +state->dane_data_len = dane_data_len; +} +#endif + + + /************************************************* * Start a TLS session in a client * *************************************************/ @@ -2025,7 +2125,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), @@ -2037,14 +2141,14 @@ 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, + 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; @@ -2080,12 +2184,23 @@ 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) + { + 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); + dane_tlsa_load(state, tlsa_dnsa); + } +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) @@ -2160,8 +2275,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 diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index 61e8d8a4f..a1b677e19 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -1217,14 +1217,15 @@ DEBUG(D_transport) switch (rc) { - case DNS_SUCCEED: - if (sec) return OK; - - log_write(0, LOG_MAIN, "DANE error: TLSA lookup not DNSSEC"); - /*FALLTHROUGH*/ case DNS_AGAIN: return DEFER; /* just defer this TLS'd conn */ + case DNS_SUCCEED: + if (sec) return OK; + log_write(0, LOG_MAIN, + "DANE error: TLSA lookup for %s not DNSSEC", host->name); + /*FALLTRHOUGH*/ + case DNS_NODATA: /* no TLSA RR for this lookup */ case DNS_NOMATCH: /* no records at all for this lookup */ return dane_required ? FAIL : FAIL_FORCED; |