summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeremy Harris <jgh146exb@wizmail.org>2014-09-04 22:40:09 +0100
committerJeremy Harris <jgh146exb@wizmail.org>2014-09-04 23:14:55 +0100
commit0e66b3b655cefaf20e3ce7347b45e158c3427831 (patch)
tree6c0e3a3e1be436c79b5d90263e0993411acd5143
parent82dbd376b5de9b9d91e93e91a4e058a80a43de99 (diff)
Enforce TLS under DANE when host has TLSA records
-rw-r--r--doc/doc-txt/experimental-spec.txt28
-rw-r--r--src/src/functions.h11
-rw-r--r--src/src/tls-gnu.c6
-rw-r--r--src/src/tls-openssl.c84
-rw-r--r--src/src/transports/smtp.c98
-rw-r--r--src/src/verify.c53
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;*/