From 4b0fe31936b336d12836875101dcac6599d127ee Mon Sep 17 00:00:00 2001 From: Jeremy Harris Date: Sun, 24 Apr 2016 16:53:25 +0100 Subject: DANE: Remove fallback from hosts_try_dane. If TLSA record not retrieved, do not use this host. --- doc/doc-txt/ChangeLog | 10 +++++++ doc/doc-txt/experimental-spec.txt | 10 ++++--- src/src/dns.c | 61 +++++++++++++++++++-------------------- src/src/functions.h | 2 +- src/src/transports/smtp.c | 48 ++++++++++++++---------------- src/src/verify.c | 40 ++++++++++--------------- test/confs/5840 | 1 + test/log/5840 | 32 +++++++------------- 8 files changed, 95 insertions(+), 109 deletions(-) diff --git a/doc/doc-txt/ChangeLog b/doc/doc-txt/ChangeLog index 496e9d07e..cecd2a038 100644 --- a/doc/doc-txt/ChangeLog +++ b/doc/doc-txt/ChangeLog @@ -10,6 +10,16 @@ JH/02 The obsolete acl condition "demime" is removed (finally, after ten years of being deprecated). The replacements are the ACLs acl_smtp_mime and acl_not_smtp_mime. +JH/03 Upgrade security requirements imposed for hosts_try_dane: previously + a downgraded non-dane trust-anchor for the TLS connection (CA-style) + or even an in-clear connection were permitted. Now, if the host lookup + was dnssec and dane was requested then the host is only used if the + TLSA lookup succeeds and is dnssec. Further hosts (eg. lower priority + MXs) will be tried (for hosts_try_dane though not for hosts_require_dane) + if one fails this test. + This means that a poorly-configured remote DNS will make it incommunicado; + but it protects against a DNS-interception attack on it. + Exim version 4.87 ----------------- diff --git a/doc/doc-txt/experimental-spec.txt b/doc/doc-txt/experimental-spec.txt index 4836a7d51..993b5b05c 100644 --- a/doc/doc-txt/experimental-spec.txt +++ b/doc/doc-txt/experimental-spec.txt @@ -884,18 +884,20 @@ with DANE in their OCSP settings. For client-side DANE there are two new smtp transport options, -hosts_try_dane and hosts_require_dane. They do the obvious thing. +hosts_try_dane and hosts_require_dane. [ should they be domain-based rather than host-based? ] +Hosts_require_dane will result in failure if the target host +is not DNSSEC-secured. + DANE will only be usable if the target host has DNSSEC-secured MX, A and TLSA records. A TLSA lookup will be done if either of the above options match and the host-lookup succeded using dnssec. If a TLSA lookup is done and succeeds, a DANE-verified TLS connection -will be required for the host. - -(TODO: specify when fallback happens vs. when the host is not used) +will be required for the host. If it does not, the host will not +be used; there is no fallback to non-DANE or non-TLS. If DANE is requested and useable (see above) the following transport options are ignored: diff --git a/src/src/dns.c b/src/src/dns.c index 1d5384e9d..575b81560 100644 --- a/src/src/dns.c +++ b/src/src/dns.c @@ -663,8 +663,7 @@ caching for successful lookups. */ sprintf(CS node_name, "%.255s-%s-%lx", name, dns_text_type(type), (unsigned long) resp->options); -previous = tree_search(tree_dns_fails, node_name); -if (previous != NULL) +if ((previous = tree_search(tree_dns_fails, node_name))) { DEBUG(D_dns) debug_printf("DNS lookup of %.255s-%s: using cached value %s\n", name, dns_text_type(type), @@ -769,48 +768,48 @@ if (dnsa->answerlen > MAXPACKET) if (dnsa->answerlen < 0) switch (h_errno) { case HOST_NOT_FOUND: - DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave HOST_NOT_FOUND\n" - "returning DNS_NOMATCH\n", name, dns_text_type(type)); - return dns_return(name, type, DNS_NOMATCH); + DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave HOST_NOT_FOUND\n" + "returning DNS_NOMATCH\n", name, dns_text_type(type)); + return dns_return(name, type, DNS_NOMATCH); case TRY_AGAIN: - DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave TRY_AGAIN\n", - name, dns_text_type(type)); + DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave TRY_AGAIN\n", + name, dns_text_type(type)); - /* Cut this out for various test programs */ + /* Cut this out for various test programs */ #ifndef STAND_ALONE - save_domain = deliver_domain; - deliver_domain = string_copy(name); /* set $domain */ - rc = match_isinlist(name, (const uschar **)&dns_again_means_nonexist, 0, NULL, NULL, - MCL_DOMAIN, TRUE, NULL); - deliver_domain = save_domain; - if (rc != OK) - { - DEBUG(D_dns) debug_printf("returning DNS_AGAIN\n"); - return dns_return(name, type, DNS_AGAIN); - } - DEBUG(D_dns) debug_printf("%s is in dns_again_means_nonexist: returning " - "DNS_NOMATCH\n", name); - return dns_return(name, type, DNS_NOMATCH); + save_domain = deliver_domain; + deliver_domain = string_copy(name); /* set $domain */ + rc = match_isinlist(name, (const uschar **)&dns_again_means_nonexist, 0, NULL, NULL, + MCL_DOMAIN, TRUE, NULL); + deliver_domain = save_domain; + if (rc != OK) + { + DEBUG(D_dns) debug_printf("returning DNS_AGAIN\n"); + return dns_return(name, type, DNS_AGAIN); + } + DEBUG(D_dns) debug_printf("%s is in dns_again_means_nonexist: returning " + "DNS_NOMATCH\n", name); + return dns_return(name, type, DNS_NOMATCH); #else /* For stand-alone tests */ - return dns_return(name, type, DNS_AGAIN); + return dns_return(name, type, DNS_AGAIN); #endif case NO_RECOVERY: - DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave NO_RECOVERY\n" - "returning DNS_FAIL\n", name, dns_text_type(type)); - return dns_return(name, type, DNS_FAIL); + DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave NO_RECOVERY\n" + "returning DNS_FAIL\n", name, dns_text_type(type)); + return dns_return(name, type, DNS_FAIL); case NO_DATA: - DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave NO_DATA\n" - "returning DNS_NODATA\n", name, dns_text_type(type)); - return dns_return(name, type, DNS_NODATA); + DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave NO_DATA\n" + "returning DNS_NODATA\n", name, dns_text_type(type)); + return dns_return(name, type, DNS_NODATA); default: - DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave unknown DNS error %d\n" - "returning DNS_FAIL\n", name, dns_text_type(type), h_errno); - return dns_return(name, type, DNS_FAIL); + DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) gave unknown DNS error %d\n" + "returning DNS_FAIL\n", name, dns_text_type(type), h_errno); + return dns_return(name, type, DNS_FAIL); } DEBUG(D_dns) debug_printf("DNS lookup of %s (%s) succeeded\n", diff --git a/src/src/functions.h b/src/src/functions.h index 4fbabcd81..6c3c37463 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -71,7 +71,7 @@ extern uschar * tls_field_from_dn(uschar *, const uschar *); extern BOOL tls_is_name_for_cert(const uschar *, void *); # ifdef EXPERIMENTAL_DANE -extern int tlsa_lookup(const host_item *, dns_answer *, BOOL, BOOL *); +extern int tlsa_lookup(const host_item *, dns_answer *, BOOL); # endif #endif /*SUPPORT_TLS*/ diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index a8a78d945..361531a9f 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -1216,8 +1216,7 @@ return FALSE; #ifdef EXPERIMENTAL_DANE int -tlsa_lookup(const host_item * host, dns_answer * dnsa, - BOOL dane_required, BOOL * dane) +tlsa_lookup(const host_item * host, dns_answer * dnsa, BOOL dane_required) { /* move this out to host.c given the similarity to dns_lookup() ? */ uschar buffer[300]; @@ -1233,9 +1232,7 @@ switch (dns_lookup(dnsa, buffer, T_TLSA, &fullname)) default: case DNS_FAIL: - if (dane_required) - return FAIL; - break; + return dane_required ? FAIL : DEFER; case DNS_SUCCEED: if (!dns_is_secure(dnsa)) @@ -1243,10 +1240,8 @@ switch (dns_lookup(dnsa, buffer, T_TLSA, &fullname)) log_write(0, LOG_MAIN, "DANE error: TLSA lookup not DNSSEC"); return DEFER; } - *dane = TRUE; - break; + return OK; } -return OK; } #endif @@ -1545,18 +1540,19 @@ if (continue_hostname == NULL) if (host->dnssec == DS_YES) { - if( ( dane_required - || verify_check_given_host(&ob->hosts_try_dane, host) == OK - ) - && (rc = tlsa_lookup(host, &tlsa_dnsa, dane_required, &dane)) != OK - && dane_required /* do not error on only dane-requested */ + if( dane_required + || verify_check_given_host(&ob->hosts_try_dane, host) == OK ) { - set_errno_nohost(addrlist, ERRNO_DNSDEFER, - string_sprintf("DANE error: tlsa lookup %s", - rc == DEFER ? "DEFER" : "FAIL"), - rc, FALSE); - return rc; + if ((rc = tlsa_lookup(host, &tlsa_dnsa, dane_required)) != OK) + { + set_errno_nohost(addrlist, ERRNO_DNSDEFER, + string_sprintf("DANE error: tlsa lookup %s", + rc == DEFER ? "DEFER" : "FAIL"), + rc, FALSE); + return rc; + } + dane = TRUE; } } else if (dane_required) @@ -1564,7 +1560,7 @@ if (continue_hostname == NULL) set_errno_nohost(addrlist, ERRNO_DNSDEFER, string_sprintf("DANE error: %s lookup not DNSSEC", host->name), FAIL, FALSE); - return FAIL; + return FAIL; } if (dane) @@ -1790,8 +1786,10 @@ if ( tls_offered if (!smtp_read_response(&inblock, buffer2, sizeof(buffer2), '2', ob->command_timeout)) { - if (errno != 0 || buffer2[0] == 0 || - (buffer2[0] == '4' && !ob->tls_tempfail_tryclear)) + if ( errno != 0 + || buffer2[0] == 0 + || (buffer2[0] == '4' && !ob->tls_tempfail_tryclear) + ) { Ustrncpy(buffer, buffer2, sizeof(buffer)); goto RESPONSE_FAILED; @@ -1816,13 +1814,11 @@ if ( tls_offered if (rc != OK) { # ifdef EXPERIMENTAL_DANE - if (rc == DEFER && dane && !dane_required) + if (rc == DEFER && dane) { - log_write(0, LOG_MAIN, "DANE attempt failed;" - " trying CA-root TLS to %s [%s] (not in hosts_require_dane)", + log_write(0, LOG_MAIN, + "DANE attempt failed; no TLS connection to %s [%s]", host->name, host->address); - dane = FALSE; - goto TLS_NEGOTIATE; } # endif diff --git a/src/src/verify.c b/src/src/verify.c index f64057fd1..04c781e54 100644 --- a/src/src/verify.c +++ b/src/src/verify.c @@ -640,12 +640,14 @@ can do it there for the non-rcpt-verify case. For this we keep an addresscount. if (host->dnssec == DS_YES) { - if( ( dane_required - || verify_check_given_host(&ob->hosts_try_dane, host) == OK - ) - && (rc = tlsa_lookup(host, &tlsa_dnsa, dane_required, &dane)) != OK + if( dane_required + || verify_check_given_host(&ob->hosts_try_dane, host) == OK ) - return rc; + { + if ((rc = tlsa_lookup(host, &tlsa_dnsa, dane_required)) != OK) + return rc; + dane = TRUE; + } } else if (dane_required) { @@ -792,8 +794,10 @@ can do it there for the non-rcpt-verify case. For this we keep an addresscount. if (!smtps && !smtp_read_response(&inblock, buffer2, sizeof(buffer2), '2', ob->command_timeout)) { - if (errno != 0 || buffer2[0] == 0 || - (buffer2[0] == '4' && !ob->tls_tempfail_tryclear)) + if ( errno != 0 + || buffer2[0] == 0 + || buffer2[0] == '4' && !ob->tls_tempfail_tryclear + ) { Ustrncpy(responsebuffer, buffer2, sizeof(responsebuffer)); done= FALSE; @@ -827,24 +831,10 @@ can do it there for the non-rcpt-verify case. For this we keep an addresscount. (void) event_raise(addr->transport->event_action, US"tcp:close", NULL); # endif -# ifdef EXPERIMENTAL_DANE - if (dane) - { - if (!dane_required) - { - log_write(0, LOG_MAIN, "DANE attempt failed;" - " trying CA-root TLS to %s [%s] (not in hosts_require_dane)", - host->name, host->address); - dane = FALSE; - goto tls_negotiate; - } - } - else -# endif - if ( ob->tls_tempfail_tryclear - && !smtps - && verify_check_given_host(&ob->hosts_require_tls, host) != OK - ) + if ( ob->tls_tempfail_tryclear + && !smtps + && verify_check_given_host(&ob->hosts_require_tls, host) != OK + ) { log_write(0, LOG_MAIN, "TLS session failure:" " delivering unencrypted to %s [%s] (not in hosts_require_tls)", diff --git a/test/confs/5840 b/test/confs/5840 index e83919005..670ce2c19 100644 --- a/test/confs/5840 +++ b/test/confs/5840 @@ -52,6 +52,7 @@ client: dnssec_request_domains = * self = send transport = send_to_server + errors_to = "" server: driver = redirect diff --git a/test/log/5840 b/test/log/5840 index 7823a2ae9..4e45703ca 100644 --- a/test/log/5840 +++ b/test/log/5840 @@ -28,45 +28,33 @@ 1999-03-02 09:44:33 10HmbJ-0005vi-00 <= CALLER@myhost.test.ex U=CALLER P=local S=sss for CALLER@dane.no.2.test.ex 1999-03-02 09:44:33 Start queue run: pid=pppp -qf 1999-03-02 09:44:33 10HmbH-0005vi-00 H=danelazy.test.ex [ip4.ip4.ip4.ip4]: DANE error: tlsa lookup DEFER -1999-03-02 09:44:33 10HmbH-0005vi-00 [127.0.0.1] SSL verify error: depth=0 error=self signed certificate cert=/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock -1999-03-02 09:44:33 10HmbH-0005vi-00 [127.0.0.1] SSL verify error: certificate name mismatch: "/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock" -1999-03-02 09:44:33 10HmbH-0005vi-00 => CALLER@mxdanelazy.test.ex R=client T=send_to_server H=danelazy2.test.ex [127.0.0.1] X=TLSv1:AES256-SHA:256 CV=no DN="/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock" C="250 OK id=10HmbK-0005vi-00" -1999-03-02 09:44:33 10HmbH-0005vi-00 Completed +1999-03-02 09:44:33 10HmbH-0005vi-00 H=danelazy2.test.ex [127.0.0.1]: DANE error: tlsa lookup DEFER +1999-03-02 09:44:33 10HmbH-0005vi-00 == CALLER@mxdanelazy.test.ex R=client T=send_to_server defer (-36): DANE error: tlsa lookup DEFER 1999-03-02 09:44:33 10HmbI-0005vi-00 ** CALLER@dane.no.1.test.ex R=client T=send_to_server: DANE error: tlsa lookup FAIL -1999-03-02 09:44:33 10HmbL-0005vi-00 <= <> R=10HmbI-0005vi-00 U=EXIMUSER P=local S=sss for CALLER@myhost.test.ex -1999-03-02 09:44:33 10HmbL-0005vi-00 H=myhost.test.ex [V4NET.10.10.10] Network Error -1999-03-02 09:44:33 10HmbL-0005vi-00 == CALLER@myhost.test.ex R=client T=send_to_server defer (dd): Network Error +1999-03-02 09:44:33 10HmbI-0005vi-00 CALLER@dane.no.1.test.ex: error ignored 1999-03-02 09:44:33 10HmbI-0005vi-00 Completed -1999-03-02 09:44:33 10HmbJ-0005vi-00 [127.0.0.1] SSL verify error: depth=0 error=self signed certificate cert=/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock -1999-03-02 09:44:33 10HmbJ-0005vi-00 [127.0.0.1] SSL verify error: certificate name mismatch: "/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock" -1999-03-02 09:44:33 10HmbJ-0005vi-00 => CALLER@dane.no.2.test.ex R=client T=send_to_server H=dane.no.2.test.ex [127.0.0.1] X=TLSv1:AES256-SHA:256 CV=no DN="/C=UK/O=The Exim Maintainers/OU=Test Suite/CN=Phil Pennock" C="250 OK id=10HmbM-0005vi-00" -1999-03-02 09:44:33 10HmbJ-0005vi-00 Completed +1999-03-02 09:44:33 10HmbJ-0005vi-00 H=dane.no.2.test.ex [127.0.0.1]: DANE error: tlsa lookup DEFER +1999-03-02 09:44:33 10HmbJ-0005vi-00 == CALLER@dane.no.2.test.ex R=client T=send_to_server defer (-36): DANE error: tlsa lookup DEFER 1999-03-02 09:44:33 End queue run: pid=pppp -qf ******** SERVER ******** 1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 -1999-03-02 09:44:33 10HmaZ-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLSv1:AES256-SHA:256 CV=no S=sss id=E10HmaX-0005vi-00@myhost.test.ex for CALLER@dane256ee.test.ex +1999-03-02 09:44:33 10HmaZ-0005vi-00 <= <> H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLSv1:AES256-SHA:256 CV=no S=sss id=E10HmaX-0005vi-00@myhost.test.ex for CALLER@dane256ee.test.ex 1999-03-02 09:44:33 10HmaZ-0005vi-00 => :blackhole: R=server 1999-03-02 09:44:33 10HmaZ-0005vi-00 Completed -1999-03-02 09:44:33 10HmbA-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLSv1:AES256-SHA:256 CV=no S=sss id=E10HmaY-0005vi-00@myhost.test.ex for CALLER@mxdane512ee.test.ex +1999-03-02 09:44:33 10HmbA-0005vi-00 <= <> H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLSv1:AES256-SHA:256 CV=no S=sss id=E10HmaY-0005vi-00@myhost.test.ex for CALLER@mxdane512ee.test.ex 1999-03-02 09:44:33 10HmbA-0005vi-00 => :blackhole: R=server 1999-03-02 09:44:33 10HmbA-0005vi-00 Completed 1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 -1999-03-02 09:44:33 10HmbC-0005vi-00 <= CALLER@myhost.test.ex H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLSv1:AES256-SHA:256 CV=no S=sss id=E10HmbB-0005vi-00@myhost.test.ex for CALLER@mxdane256ta.test.ex +1999-03-02 09:44:33 10HmbC-0005vi-00 <= <> H=the.local.host.name (myhost.test.ex) [ip4.ip4.ip4.ip4] P=esmtps X=TLSv1:AES256-SHA:256 CV=no S=sss id=E10HmbB-0005vi-00@myhost.test.ex for CALLER@mxdane256ta.test.ex 1999-03-02 09:44:33 10HmbC-0005vi-00 => :blackhole: R=server 1999-03-02 09:44:33 10HmbC-0005vi-00 Completed 1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 -1999-03-02 09:44:33 10HmbE-0005vi-00 <= CALLER@myhost.test.ex H=localhost (myhost.test.ex) [127.0.0.1] P=esmtps X=TLSv1:AES256-SHA:256 CV=no S=sss id=E10HmbD-0005vi-00@myhost.test.ex for CALLER@thishost.test.ex +1999-03-02 09:44:33 10HmbE-0005vi-00 <= <> H=localhost (myhost.test.ex) [127.0.0.1] P=esmtps X=TLSv1:AES256-SHA:256 CV=no S=sss id=E10HmbD-0005vi-00@myhost.test.ex for CALLER@thishost.test.ex 1999-03-02 09:44:33 10HmbE-0005vi-00 => :blackhole: R=server 1999-03-02 09:44:33 10HmbE-0005vi-00 Completed 1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 -1999-03-02 09:44:33 10HmbG-0005vi-00 <= CALLER@myhost.test.ex H=localhost (myhost.test.ex) [127.0.0.1] P=esmtps X=TLSv1:AES256-SHA:256 CV=no S=sss id=E10HmbF-0005vi-00@myhost.test.ex for CALLER@thishost.test.ex +1999-03-02 09:44:33 10HmbG-0005vi-00 <= <> H=localhost (myhost.test.ex) [127.0.0.1] P=esmtps X=TLSv1:AES256-SHA:256 CV=no S=sss id=E10HmbF-0005vi-00@myhost.test.ex for CALLER@thishost.test.ex 1999-03-02 09:44:33 10HmbG-0005vi-00 => :blackhole: R=server 1999-03-02 09:44:33 10HmbG-0005vi-00 Completed 1999-03-02 09:44:33 exim x.yz daemon started: pid=pppp, no queue runs, listening for SMTP on port 1225 -1999-03-02 09:44:33 10HmbK-0005vi-00 <= CALLER@myhost.test.ex H=localhost (myhost.test.ex) [127.0.0.1] P=esmtps X=TLSv1:AES256-SHA:256 CV=no S=sss id=E10HmbH-0005vi-00@myhost.test.ex for CALLER@mxdanelazy.test.ex -1999-03-02 09:44:33 10HmbK-0005vi-00 => :blackhole: R=server -1999-03-02 09:44:33 10HmbK-0005vi-00 Completed -1999-03-02 09:44:33 10HmbM-0005vi-00 <= CALLER@myhost.test.ex H=localhost (myhost.test.ex) [127.0.0.1] P=esmtps X=TLSv1:AES256-SHA:256 CV=no S=sss id=E10HmbJ-0005vi-00@myhost.test.ex for CALLER@dane.no.2.test.ex -1999-03-02 09:44:33 10HmbM-0005vi-00 => :blackhole: R=server -1999-03-02 09:44:33 10HmbM-0005vi-00 Completed -- cgit v1.2.3