[exim-cvs] Enforce TLS under DANE when host has TLSA records

Góra strony
Delete this message
Reply to this message
Autor: Exim Git Commits Mailing List
Data:  
Dla: exim-cvs
Temat: [exim-cvs] Enforce TLS under DANE when host has TLSA records
Gitweb: http://git.exim.org/exim.git/commitdiff/0e66b3b655cefaf20e3ce7347b45e158c3427831
Commit:     0e66b3b655cefaf20e3ce7347b45e158c3427831
Parent:     82dbd376b5de9b9d91e93e91a4e058a80a43de99
Author:     Jeremy Harris <jgh146exb@???>
AuthorDate: Thu Sep 4 22:40:09 2014 +0100
Committer:  Jeremy Harris <jgh146exb@???>
CommitDate: Thu Sep 4 23:14:55 2014 +0100


    Enforce TLS under DANE when host has TLSA records
---
 doc/doc-txt/experimental-spec.txt |   28 +++++++---
 src/src/functions.h               |   11 ++++-
 src/src/tls-gnu.c                 |    6 ++-
 src/src/tls-openssl.c             |   84 ++++---------------------------
 src/src/transports/smtp.c         |   98 ++++++++++++++++++++++++++++++++-----
 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 2f44fce..e7a0d06 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 fee4429..d10a68a 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 b7eae17..3043e3a 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 2e95a46..5056e61 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 7b2a7d5..9986e80 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 d2ecb9c..c25e6e2 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;*/