diff options
author | Jeremy Harris <jgh146exb@wizmail.org> | 2014-09-04 22:40:09 +0100 |
---|---|---|
committer | Jeremy Harris <jgh146exb@wizmail.org> | 2014-09-04 23:14:55 +0100 |
commit | 0e66b3b655cefaf20e3ce7347b45e158c3427831 (patch) | |
tree | 6c0e3a3e1be436c79b5d90263e0993411acd5143 | |
parent | 82dbd376b5de9b9d91e93e91a4e058a80a43de99 (diff) |
Enforce TLS under DANE when host has TLSA records
-rw-r--r-- | doc/doc-txt/experimental-spec.txt | 28 | ||||
-rw-r--r-- | src/src/functions.h | 11 | ||||
-rw-r--r-- | src/src/tls-gnu.c | 6 | ||||
-rw-r--r-- | src/src/tls-openssl.c | 84 | ||||
-rw-r--r-- | src/src/transports/smtp.c | 98 | ||||
-rw-r--r-- | src/src/verify.c | 53 |
6 files changed, 175 insertions, 105 deletions
diff --git a/doc/doc-txt/experimental-spec.txt b/doc/doc-txt/experimental-spec.txt index 2f44fce26..e7a0d0668 100644 --- a/doc/doc-txt/experimental-spec.txt +++ b/doc/doc-txt/experimental-spec.txt @@ -1178,6 +1178,10 @@ admins of the target server. The attack surface presented by (a) is thought to be smaller than that of the set of root CAs. +It also allows the server to declare (implicitly) that +connections to it should use TLS. An MITM could simply +fail to pass on a server's STARTTLS. + DANE scales better than having to maintain (and side-channel communicate) copies of server certificates for every possible target server. It also scales @@ -1202,12 +1206,12 @@ There are no changes to Exim specific to server-side operation of DANE. The TLSA record for the server may have "certificate -usage" of DANE_TA(2) or DANE_EE(3). The latter specifies +usage" of DANE-TA(2) or DANE-EE(3). The latter specifies the End Entity directly, i.e. the certificate involved is that of the server (and should be the sole one transmitted during the TLS handshake); this is appropriate for a single system, using a self-signed certificate. - DANE_TA usage is effectively declaring a specific CA + DANE-TA usage is effectively declaring a specific CA to be used; this might be a private CA or a public, well-known one. A private CA at simplest is just a self-signed certificate which is used to sign @@ -1219,7 +1223,7 @@ the entire certificate chain from CA to server-certificate. If a public CA is used then all clients must be primed with it (losing one advantage of DANE) - but the attack surface is reduced from all public CAs to that single CA. -DANE_TA is commonly used for several services and/or +DANE-TA is commonly used for several services and/or servers, each having a TLSA query-domain CNAME record, all of which point to a single TLSA record. @@ -1236,13 +1240,13 @@ is useful for quickly generating TLSA records; and commands like are workable for 4th-field hashes. -For use with the DANE_TA model, server certificates +For use with the DANE-TA model, server certificates must have a correct name (SubjectName or SubjectAltName). The use of OCSP-stapling should be considered, allowing for fast revocation of certificates (which would otherwise be limited by the DNS TTL on the TLSA records). However, -this is likely to only be usable with DANE_TA. NOTE: the +this is likely to only be usable with DANE-TA. NOTE: the default of requesting OCSP for all hosts is modified iff DANE is in use, to: @@ -1253,10 +1257,10 @@ DANE is in use, to: The (new) variable $tls_out_tlsa_usage is a bitfield with numbered bits set for TLSA record usage codes. The zero above means DANE was not in use, -the four means that only DANE_TA usage TLSA records were -found. If the definition of hosts_require_ocsp or -hosts_request_ocsp includes the string "tls_out_tlsa_usage", -they are re-expanded in time to control the OCSP request. +the four means that only DANE-TA usage TLSA records were +found. If the definition of hosts_request_ocsp includes the +string "tls_out_tlsa_usage", they are re-expanded in time to +control the OCSP request. This modification of hosts_request_ocsp is only done if it has the default value of "*". Admins who change it, and @@ -1271,9 +1275,15 @@ hosts_try_dane and hosts_require_dane. They do the obvious thing. 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 the TLSA lookup succeeds, a TLS connection will be required +for the host. + (TODO: specify when fallback happens vs. when the host is not used) If dane is in use the following transport options are ignored: + hosts_require_tls tls_verify_hosts tls_try_verify_hosts tls_verify_certificates diff --git a/src/src/functions.h b/src/src/functions.h index fee4429a5..d10a68a31 100644 --- a/src/src/functions.h +++ b/src/src/functions.h @@ -44,7 +44,11 @@ extern uschar * tls_cert_fprt_sha1(void *); extern uschar * tls_cert_fprt_sha256(void *); extern int tls_client_start(int, host_item *, address_item *, - transport_instance *); + transport_instance * +#ifdef EXPERIMENTAL_DANE + , dns_answer * +#endif + ); extern void tls_close(BOOL, BOOL); extern int tls_export_cert(uschar *, size_t, void *); extern int tls_feof(void); @@ -66,6 +70,11 @@ extern uschar * tls_field_from_dn(uschar *, uschar *); # ifdef EXPERIMENTAL_CERTNAMES extern BOOL tls_is_name_for_cert(uschar *, void *); # endif + +# ifdef EXPERIMENTAL_DANE +extern int tlsa_lookup(host_item *, dns_answer *, BOOL, BOOL *); +# endif + #endif /*SUPPORT_TLS*/ diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index b7eae1793..3043e3abc 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -1761,7 +1761,11 @@ Returns: OK/DEFER/FAIL (because using common functions), int tls_client_start(int fd, host_item *host, address_item *addr ARG_UNUSED, - transport_instance *tb) + transport_instance *tb +#ifdef EXPERIMENTAL_DANE + , dne_answer * unused_tlsa_dnsa +#endif + ) { smtp_transport_options_block *ob = (smtp_transport_options_block *)tb->options_block; diff --git a/src/src/tls-openssl.c b/src/src/tls-openssl.c index 2e95a467a..5056e6188 100644 --- a/src/src/tls-openssl.c +++ b/src/src/tls-openssl.c @@ -1678,44 +1678,6 @@ return OK; #ifdef EXPERIMENTAL_DANE static int -tlsa_lookup(host_item * host, dns_answer * dnsa, - BOOL dane_required, BOOL * dane) -{ -/* move this out to host.c given the similarity to dns_lookup() ? */ -uschar buffer[300]; -uschar * fullname = buffer; - -/* TLSA lookup string */ -(void)sprintf(CS buffer, "_%d._tcp.%.256s", host->port, host->name); - -switch (dns_lookup(dnsa, buffer, T_TLSA, &fullname)) - { - case DNS_AGAIN: - return DEFER; /* just defer this TLS'd conn */ - - default: - case DNS_FAIL: - if (dane_required) - { - log_write(0, LOG_MAIN, "DANE error: TLSA lookup failed"); - return FAIL; - } - break; - - case DNS_SUCCEED: - if (!dns_is_secure(dnsa)) - { - log_write(0, LOG_MAIN, "DANE error: TLSA lookup not DNSSEC"); - return DEFER; - } - *dane = TRUE; - break; - } -return OK; -} - - -static int dane_tlsa_load(SSL * ssl, host_item * host, dns_answer * dnsa) { dns_record * rr; @@ -1783,6 +1745,7 @@ Argument: host connected host (for messages) addr the first address tb transport (always smtp) + tlsa_dnsa tlsa lookup, if DANE, else null Returns: OK on success FAIL otherwise - note that tls_error() will not give DEFER @@ -1791,7 +1754,11 @@ Returns: OK on success int tls_client_start(int fd, host_item *host, address_item *addr, - transport_instance *tb) + transport_instance *tb +#ifdef EXPERIMENTAL_DANE + , dns_answer * tlsa_dnsa +#endif + ) { smtp_transport_options_block * ob = (smtp_transport_options_block *)tb->options_block; @@ -1805,34 +1772,9 @@ static uschar cipherbuf[256]; BOOL request_ocsp = FALSE; BOOL require_ocsp = FALSE; #endif -#ifdef EXPERIMENTAL_DANE -dns_answer tlsa_dnsa; -BOOL dane = FALSE; -BOOL dane_required; -#endif #ifdef EXPERIMENTAL_DANE -tls_out.dane_verified = FALSE; tls_out.tlsa_usage = 0; -dane_required = verify_check_this_host(&ob->hosts_require_dane, NULL, - host->name, host->address, NULL) == OK; - -if (host->dnssec == DS_YES) - { - if( dane_required - || verify_check_this_host(&ob->hosts_try_dane, NULL, - host->name, host->address, NULL) == OK - ) - if ((rc = tlsa_lookup(host, &tlsa_dnsa, dane_required, &dane)) != OK) - return rc; - } -else if (dane_required) - { - /*XXX a shame we only find this after making tcp & smtp connection */ - /* move the test earlier? */ - log_write(0, LOG_MAIN, "DANE error: previous lookup not DNSSEC"); - return FAIL; - } #endif #ifndef DISABLE_OCSP @@ -1843,7 +1785,7 @@ else if (dane_required) else { # ifdef EXPERIMENTAL_DANE - if ( dane + if ( tlsa_dnsa && ob->hosts_request_ocsp[0] == '*' && ob->hosts_request_ocsp[1] == '\0' ) @@ -1891,7 +1833,7 @@ if (expciphers != NULL) } #ifdef EXPERIMENTAL_DANE -if (dane) +if (tlsa_dnsa) { SSL_CTX_set_verify(client_ctx, SSL_VERIFY_PEER, verify_callback_client_dane); @@ -1941,8 +1883,8 @@ if (ob->tls_sni) } #ifdef EXPERIMENTAL_DANE -if (dane) - if ((rc = dane_tlsa_load(client_ssl, host, &tlsa_dnsa)) != OK) +if (tlsa_dnsa) + if ((rc = dane_tlsa_load(client_ssl, host, tlsa_dnsa)) != OK) return rc; #endif @@ -1980,10 +1922,6 @@ if (request_ocsp) client_static_cbinfo->event_action = tb->tpda_event_action; #endif -#ifdef EXPERIMENTAL_TPDA -client_static_cbinfo->event_action = tb->tpda_event_action; -#endif - /* There doesn't seem to be a built-in timeout on connection. */ DEBUG(D_tls) debug_printf("Calling SSL_connect\n"); @@ -1993,7 +1931,7 @@ rc = SSL_connect(client_ssl); alarm(0); #ifdef EXPERIMENTAL_DANE -if (dane) +if (tlsa_dnsa) DANESSL_cleanup(client_ssl); #endif diff --git a/src/src/transports/smtp.c b/src/src/transports/smtp.c index 7b2a7d559..9986e80f4 100644 --- a/src/src/transports/smtp.c +++ b/src/src/transports/smtp.c @@ -212,7 +212,7 @@ smtp_transport_options_block smtp_transport_option_defaults = { NULL, /* hosts_try_prdr */ #endif #ifndef DISABLE_OCSP - US"*", /* hosts_request_ocsp (except under DANE) */ + US"*", /* hosts_request_ocsp (except under DANE; tls_client_start()) */ NULL, /* hosts_require_ocsp */ #endif NULL, /* hosts_require_tls */ @@ -1148,6 +1148,46 @@ return FALSE; +#ifdef EXPERIMENTAL_DANE +int +tlsa_lookup(host_item * host, dns_answer * dnsa, + BOOL dane_required, BOOL * dane) +{ +/* move this out to host.c given the similarity to dns_lookup() ? */ +uschar buffer[300]; +uschar * fullname = buffer; + +/* TLSA lookup string */ +(void)sprintf(CS buffer, "_%d._tcp.%.256s", host->port, host->name); + +switch (dns_lookup(dnsa, buffer, T_TLSA, &fullname)) + { + case DNS_AGAIN: + return DEFER; /* just defer this TLS'd conn */ + + default: + case DNS_FAIL: + if (dane_required) + { + log_write(0, LOG_MAIN, "DANE error: TLSA lookup failed"); + return FAIL; + } + break; + + case DNS_SUCCEED: + if (!dns_is_secure(dnsa)) + { + log_write(0, LOG_MAIN, "DANE error: TLSA lookup not DNSSEC"); + return DEFER; + } + *dane = TRUE; + break; + } +return OK; +} +#endif + + /************************************************* * Deliver address list to given host * *************************************************/ @@ -1226,6 +1266,10 @@ BOOL prdr_active; #ifdef EXPERIMENTAL_DSN BOOL dsn_all_lasthop = TRUE; #endif +#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE) +BOOL dane = FALSE; +dns_answer tlsa_dnsa; +#endif smtp_inblock inblock; smtp_outblock outblock; int max_rcpt = tblock->max_addresses; @@ -1307,6 +1351,36 @@ if (continue_hostname == NULL) return DEFER; } +#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE) + { + BOOL dane_required; + + tls_out.dane_verified = FALSE; + tls_out.tlsa_usage = 0; + + dane_required = verify_check_this_host(&ob->hosts_require_dane, NULL, + host->name, host->address, NULL) == OK; + + if (host->dnssec == DS_YES) + { + if( dane_required + || verify_check_this_host(&ob->hosts_try_dane, NULL, + host->name, host->address, NULL) == OK + ) + if ((rc = tlsa_lookup(host, &tlsa_dnsa, dane_required, &dane)) != OK) + return rc; + } + else if (dane_required) + { + log_write(0, LOG_MAIN, "DANE error: %s lookup not DNSSEC", host->name); + return FAIL; + } + + if (dane) + ob->tls_tempfail_tryclear = FALSE; + } +#endif /*DANE*/ + /* Expand the greeting message while waiting for the initial response. (Makes sense if helo_data contains ${lookup dnsdb ...} stuff). The expansion is delayed till here so that $sending_interface and $sending_port are set. */ @@ -1505,7 +1579,11 @@ if (tls_offered && !suppress_tls && else TLS_NEGOTIATE: { - int rc = tls_client_start(inblock.sock, host, addrlist, tblock); + int rc = tls_client_start(inblock.sock, host, addrlist, tblock +# ifdef EXPERIMENTAL_DANE + , dane ? &tlsa_dnsa : NULL +# endif + ); /* TLS negotiation failed; give an error. From outside, this function may be called again to try in clear on a new connection, if the options permit @@ -1588,12 +1666,12 @@ if (tls_out.active >= 0) /* If the host is required to use a secure channel, ensure that we have one. */ -else if ( verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name, - host->address, NULL) == OK -#ifdef EXPERIMENTAL_DANE - || verify_check_this_host(&(ob->hosts_require_dane), NULL, host->name, +else if ( +# ifdef EXPERIMENTAL_DANE + dane || +# endif + verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name, host->address, NULL) == OK -#endif ) { save_errno = ERRNO_TLSREQUIRED; @@ -1603,7 +1681,7 @@ else if ( verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name, "the server did not offer TLS support"); goto TLS_FAILED; } -#endif +#endif /*SUPPORT_TLS*/ /* If TLS is active, we have just started it up and re-done the EHLO command, so its response needs to be analyzed. If TLS is not active and this is a @@ -3299,10 +3377,6 @@ for (cutoff_retry = 0; expired && && ob->tls_tempfail_tryclear && verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name, host->address, NULL) != OK -# ifdef EXPERIMENTAL_DANE - && verify_check_this_host(&(ob->hosts_require_dane), NULL, host->name, - host->address, NULL) != OK -# endif ) { log_write(0, LOG_MAIN, "TLS session failure: delivering unencrypted " diff --git a/src/src/verify.c b/src/src/verify.c index d2ecb9cde..c25e6e257 100644 --- a/src/src/verify.c +++ b/src/src/verify.c @@ -426,6 +426,10 @@ else BOOL esmtp; BOOL suppress_tls = FALSE; uschar *interface = NULL; /* Outgoing interface to use; NULL => any */ +#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE) + BOOL dane = FALSE; + dns_answer tlsa_dnsa; +#endif uschar inbuffer[4096]; uschar outbuffer[1024]; uschar responsebuffer[4096]; @@ -478,6 +482,37 @@ else HDEBUG(D_verify) debug_printf("interface=%s port=%d\n", interface, port); +#if defined(SUPPORT_TLS) && defined(EXPERIMENTAL_DANE) + { + BOOL dane_required; + int rc; + + tls_out.dane_verified = FALSE; + tls_out.tlsa_usage = 0; + + dane_required = verify_check_this_host(&ob->hosts_require_dane, NULL, + host->name, host->address, NULL) == OK; + + if (host->dnssec == DS_YES) + { + if( dane_required + || verify_check_this_host(&ob->hosts_try_dane, NULL, + host->name, host->address, NULL) == OK + ) + if ((rc = tlsa_lookup(host, &tlsa_dnsa, dane_required, &dane)) != OK) + return rc; + } + else if (dane_required) + { + log_write(0, LOG_MAIN, "DANE error: %s lookup not DNSSEC", host->name); + return FAIL; + } + + if (dane) + ob->tls_tempfail_tryclear = FALSE; + } +#endif /*DANE*/ + /* Set up the buffer for reading SMTP response packets. */ inblock.buffer = inbuffer; @@ -654,7 +689,11 @@ else int rc; ob->command_timeout = callout; - rc = tls_client_start(inblock.sock, host, addr, addr->transport); + rc = tls_client_start(inblock.sock, host, addr, addr->transport +#ifdef EXPERIMENTAL_DANE + , dane ? &tlsa_dnsa : NULL +#endif + ); ob->command_timeout = oldtimeout; /* TLS negotiation failed; give an error. Try in clear on a new connection, @@ -666,10 +705,6 @@ else && !smtps && verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name, host->address, NULL) != OK -#ifdef EXPERIMENTAL_DANE - && verify_check_this_host(&(ob->hosts_require_dane), NULL, - host->name, host->address, NULL) != OK -#endif ) { (void)close(inblock.sock); @@ -704,12 +739,12 @@ else /* If the host is required to use a secure channel, ensure that we have one. */ if (tls_out.active < 0) - if ( verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name, - host->address, NULL) == OK + if ( #ifdef EXPERIMENTAL_DANE - || verify_check_this_host(&(ob->hosts_require_dane), NULL, host->name, - host->address, NULL) == OK + dane || #endif + verify_check_this_host(&(ob->hosts_require_tls), NULL, host->name, + host->address, NULL) == OK ) { /*save_errno = ERRNO_TLSREQUIRED;*/ |