Introduce EXPERIMENTAL_DANE feature
authorJeremy Harris <jgh146exb@wizmail.org>
Tue, 2 Sep 2014 12:14:01 +0000 (13:14 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Tue, 2 Sep 2014 12:14:01 +0000 (13:14 +0100)
1  2 
doc/doc-txt/ChangeLog
doc/doc-txt/experimental-spec.txt
src/src/deliver.c
src/src/globals.c
src/src/globals.h

diff --combined doc/doc-txt/ChangeLog
index 2caf9ed52f04a75d2c30a2618079502982cfb8f8,483528b970ad7f9dd94d0b35f1fe0aa0c819d7d0..f3f4324593499eae6e5f1c6741de53b9c69d9717
@@@ -17,14 -17,9 +17,17 @@@ TL/02 The BSD's have an arc4random API
        OpenBSD 5.5. Detect this OpenBSD version and skip calling this
        function when detected.
  
 -JH/01 Add EXPERIMENTAL_DANE, allowing for using the DNS as trust-anchor for
 +JH/01 Expand the EXPERIMENTAL_TPDA feature.  Several different events now
 +      cause callback expansion.
 +
 +TL/03 Bugzilla 1518: Clarify "condition" processing in routers; that
 +      syntax errors in an expansion can be treated as a string instead of
 +      logging or causing an error, due to the internal use of bool_lax
 +      instead of bool when processing it.
 +
++JH/02 Add EXPERIMENTAL_DANE, allowing for using the DNS as trust-anchor for
+       server certificates when making smtp deliveries.
  
  Exim version 4.84
  -----------------
@@@ -38,7 -33,7 +41,7 @@@ JH/01 Bug 1513: Fix parsing of quoted p
  JH/02 Fix broken compilation when EXPERIMENTAL_DSN is enabled.
  
  TL/02 Bug 1509: Fix exipick for enhanced spoolfile specification used when
 -      EXPERIMENTAL_DNS is enabled.  Fix from Wolfgang Breyha.
 +      EXPERIMENTAL_DSN is enabled.  Fix from Wolfgang Breyha.
  
  
  Exim version 4.83
index d8bd0bf46ed103f0641f603a8231e5c958531f61,769f0229de03171266cee6c9d47a5e1b60a7afa9..2f44fce26fb7ff7be6788c832265f4842e70c34a
@@@ -762,10 -762,8 +762,10 @@@ b. Configure, somewhere before the DAT
  Transport post-delivery actions
  --------------------------------------------------------------
  
 -An arbitrary per-transport string can be expanded on successful delivery,
 +An arbitrary per-transport string can be expanded upon various transport events
  and (for SMTP transports) a second string on deferrals caused by a host error.
 +Additionally a main-section configuration option can be expanded on some
 +per-message events.
  This feature may be used, for example, to write exim internal log information
  (not available otherwise) into a database.
  
@@@ -775,23 -773,18 +775,23 @@@ EXPERIMENTAL_TPDA=ye
  
  in your Local/Makefile
  
 -and define the tpda_event_action option in the transport, to
 -be expanded when the event fires.
 +and define one or both of
 +- the tpda_event_action option in the transport
 +- the delivery_event_action
 +to be expanded when the event fires.
  
  A new variable, $tpda_event, is set to the event type when the
  expansion is done.  The current list of events is:
  
 -      msg:delivery
 -      msg:host:defer
 -      tcp:connect
 -      tcp:close
 -      tls:cert
 -      smtp:connect
 +      msg:complete            main            per message
 +      msg:delivery            transport       per recipient
 +      msg:host:defer          transport       per attempt
 +      msg:fail:delivery       main            per recipient
 +      msg:fail:internal       main            per recipient
 +      tcp:connect             transport       per connection
 +      tcp:close               transport       per connection
 +      tls:cert                transport       per certificate in verification chain
 +      smtp:connect            transport       per connection
  
  The expansion is called for all event types, and should use the $tpda_event
  value to decide when to act.  The variable data is a colon-separated
@@@ -807,7 -800,7 +807,7 @@@ content is event_dependent
  
  The msg:host:defer event populates one extra variable, $tpda_defer_errno.
  
 -The following variables are likely to be useful for most event types:
 +The following variables are likely to be useful depending on the event type:
  
        router_name, transport_name
        local_part, domain
        tls_out_peercert
        lookup_dnssec_authenticated, tls_out_dane
        sending_ip_address, sending_port
 +      message_exim_id
  
  
  An example might look like:
@@@ -831,10 -823,13 +831,10 @@@ tpda_event_action = ${if = {msg:deliver
      '${quote_pgsql:$message_exim_id}')}} \
  } {}}
  
 -The string is expanded after the delivery completes and any
 +The string is expanded for each of the supported events and any
  side-effects will happen.  The result is then discarded.
  Note that for complex operations an ACL expansion can be used.
  
 -During the expansion the tpda_event variable will contain the
 -string-list "msg:delivery".
 -
  
  The expansion of the tpda_event_action option should normally
  return an empty string.  Should it return anything else the
@@@ -842,7 -837,6 +842,7 @@@ following will be forced
  
        msg:delivery    (ignored)
        msg:host:defer  (ignored)
 +      msg:fail:delivery (ignored)
        tcp:connect     do not connect
        tcp:close       (ignored)
        tls:cert        refuse verification
@@@ -1137,6 -1131,7 +1137,7 @@@ in a router. Exim will then send the su
  the next hop does not support DSN.
  Adding it to a redirect router makes no difference.
  
  Certificate name checking
  --------------------------------------------------------------
  The X509 certificates used for TLS are supposed be verified
@@@ -1155,6 -1150,141 +1156,141 @@@ a single wildcard being the initial com
  component FQDN).
  
  
+ DANE
+ ------------------------------------------------------------
+ DNS-based Authentication of Named Entities, as applied
+ to SMTP over TLS, provides assurance to a client that
+ it is actually talking to the server it wants to rather
+ than some attacker operating a Man In The Middle (MITM)
+ operation.  The latter can terminate the TLS connection
+ you make, and make another one to the server (so both
+ you and the server still think you have an encrypted
+ connection) and, if one of the "well known" set of
+ Certificate Authorities has been suborned - something
+ which *has* been seen already (2014), a verifiable
+ certificate (if you're using normal root CAs, eg. the
+ Mozilla set, as your trust anchors).
+ What DANE does is replace the CAs with the DNS as the
+ trust anchor.  The assurance is limited to a) the possibility
+ that the DNS has been suborned, b) mistakes made by the
+ admins of the target server.   The attack surface presented
+ by (a) is thought to be smaller than that of the set
+ of root CAs.
+ DANE scales better than having to maintain (and
+ side-channel communicate) copies of server certificates
+ for every possible target server.  It also scales
+ (slightly) better than having to maintain on an SMTP
+ client a copy of the standard CAs bundle.  It also
+ means not having to pay a CA for certificates.
+ DANE requires a server operator to do three things:
+ 1) run DNSSEC.  This provides assurance to clients
+ that DNS lookups they do for the server have not
+ been tampered with.  The domain MX record applying
+ to this server, its A record, its TLSA record and
+ any associated CNAME records must all be covered by
+ DNSSEC.
+ 2) add TLSA DNS records.  These say what the server
+ certificate for a TLS connection should be.
+ 3) offer a server certificate, or certificate chain,
+ in TLS connections which is traceable to the one
+ defined by (one of?) the TSLA records
+ 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
+ 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
+ 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
+ cerver certificates, but running one securely does
+ require careful arrangement.  If a private CA is used
+ then either all clients must be primed with it, or
+ (probably simpler) the server TLS handshake must transmit
+ 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
+ servers, each having a TLSA query-domain CNAME record,
+ all of which point to a single TLSA record.
+ The TLSA record should have a Selector field of SPKI(1)
+ and a Matching Type field of SHA2-512(2).
+ At the time of writing, https://www.huque.com/bin/gen_tlsa
+ is useful for quickly generating TLSA records; and commands like
+   openssl x509 -in -pubkey -noout <certificate.pem \
+   | openssl rsa -outform der -pubin 2>/dev/null \
+   | openssl sha512 \
+   | awk '{print $2}'
+ are workable for 4th-field hashes.
+ 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
+ default of requesting OCSP for all hosts is modified iff
+ DANE is in use, to:
+   hosts_request_ocsp = ${if or { {= {0}{$tls_out_tlsa_usage}} \
+                                {= {4}{$tls_out_tlsa_usage}} } \
+                          {*}{}}
+ 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.
+ This modification of hosts_request_ocsp is only done if
+ it has the default value of "*".  Admins who change it, and
+ those who use hosts_require_ocsp, should consider the interaction
+ 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.
+ [ should they be domain-based rather than host-based? ]
+ DANE will only be usable if the target host has DNSSEC-secured
+ MX, A and TLSA records.
+ (TODO: specify when fallback happens vs. when the host is not used)
+ If dane is in use the following transport options are ignored:
+   tls_verify_hosts
+   tls_try_verify_hosts
+   tls_verify_certificates
+   tls_crl
+   tls_verify_cert_hostnames
+ Currently dnssec_request_domains must be active (need to think about that)
+ and dnssec_require_domains is ignored.
+ If verification was successful using DANE then the "CV" item
+ in the delivery log line will show as "CV=dane".
+ There is a new variable $tls_out_dane which will have "yes" if
+ verification succeeded using DANE and "no" otherwise (only useful
+ in combination with EXPERIMENTAL_TPDA), and a new variable
+ $tls_out_tlsa_usage (detailed above).
  
  --------------------------------------------------------------
  End of file
diff --combined src/src/deliver.c
index b3a5a49b21a4e5629665a8726518bb81f95b99c1,676de556d132fd84ef69e58fa6733722ebc69d15..78b669ad2cdbddd9ddb5acc852d79ef1c294fd6f
@@@ -699,7 -699,15 +699,15 @@@ d_tlslog(uschar * s, int * sizep, int 
    if ((log_extra_selector & LX_tls_certificate_verified) != 0 &&
         addr->cipher != NULL)
      s = string_append(s, sizep, ptrp, 2, US" CV=",
-       testflag(addr, af_cert_verified)? "yes":"no");
+       testflag(addr, af_cert_verified)
+       ?
+ #ifdef EXPERIMENTAL_DANE
+         testflag(addr, af_dane_verified)
+       ? "dane"
+       :
+ #endif
+         "yes"
+       : "no");
    if ((log_extra_selector & LX_tls_peerdn) != 0 && addr->peerdn != NULL)
      s = string_append(s, sizep, ptrp, 3, US" DN=\"",
        string_printing(addr->peerdn), US"\"");
  #endif
  
  
 +
 +
  #ifdef EXPERIMENTAL_TPDA
  int
  tpda_raise_event(uschar * action, uschar * event, uschar * ev_data)
@@@ -744,32 -750,7 +752,32 @@@ if (action
    }
  return OK;
  }
 -#endif
 +
 +static void
 +tpda_msg_event(uschar * event, address_item * addr)
 +{
 +uschar * save_domain = deliver_domain;
 +uschar * save_local =  deliver_localpart;
 +
 +if (!addr->transport)
 +  return;
 +
 +router_name =    addr->router ? addr->router->name : NULL;
 +transport_name = addr->transport->name;
 +deliver_domain = addr->domain;
 +deliver_localpart = addr->local_part;
 +
 +(void) tpda_raise_event(addr->transport->tpda_event_action, event,
 +        addr->host_used || Ustrcmp(addr->transport->driver_name, "lmtp") == 0
 +        ? addr->message : NULL);
 +
 +deliver_localpart = save_local;
 +deliver_domain =    save_domain;
 +router_name = transport_name = NULL;
 +}
 +#endif        /*EXPERIMENTAL_TPDA*/
 +
 +
  
  /* If msg is NULL this is a delivery log and logchar is used. Otherwise
  this is a nonstandard call; no two-character delivery flag is written
@@@ -929,10 -910,24 +937,10 @@@ s[ptr] = 0
  log_write(0, flags, "%s", s);
  
  #ifdef EXPERIMENTAL_TPDA
 -  {
 -  uschar * save_domain = deliver_domain;
 -  uschar * save_local =  deliver_localpart;
 -
 -  router_name =    addr->router ? addr->router->name : NULL;
 -  transport_name = addr->transport ? addr->transport->name : NULL;
 -  deliver_domain = addr->domain;
 -  deliver_localpart = addr->local_part;
 -
 -  (void) tpda_raise_event(addr->transport->tpda_event_action, US"msg:delivery",
 -          addr->host_used || Ustrcmp(addr->transport->driver_name, "lmtp") == 0
 -          ? addr->message : NULL);
 -
 -  deliver_localpart = save_local;
 -  deliver_domain =    save_domain;
 -  router_name = transport_name = NULL;
 -  }
 +/*XXX cutthrough calls this also for non-delivery...*/
 +tpda_msg_event(US"msg:delivery", addr);
  #endif
 +
  store_reset(reset_point);
  return;
  }
@@@ -1139,6 -1134,9 +1147,9 @@@ if (result == OK
    tls_out.cipher = addr->cipher;
    tls_out.peerdn = addr->peerdn;
    tls_out.ocsp = addr->ocsp;
+ # ifdef EXPERIMENTAL_DANE
+   tls_out.dane_verified = testflag(addr, af_dane_verified);
+ # endif
  #endif
  
    delivery_log(LOG_MAIN, addr, logchar, NULL);
    tls_out.cipher = NULL;
    tls_out.peerdn = NULL;
    tls_out.ocsp = OCSP_NOT_REQ;
+ # ifdef EXPERIMENTAL_DANE
+   tls_out.dane_verified = FALSE;
+ # endif
  #endif
    }
  
      deliver_msglog("%s %s\n", now, s);
  
    log_write(0, LOG_MAIN, "** %s", s);
 +
 +#ifdef EXPERIMENTAL_TPDA
 +  tpda_msg_event(US"msg:fail:delivery", addr);
 +#endif
 +
    store_reset(reset_point);
    }
  
@@@ -4171,6 -4167,9 +4185,9 @@@ for (delivery_count = 0; addr_remote !
  
        /* The certificate verification status goes into the flags */
        if (tls_out.certificate_verified) setflag(addr, af_cert_verified);
+ #ifdef EXPERIMENTAL_DANE
+       if (tls_out.dane_verified)        setflag(addr, af_dane_verified);
+ #endif
  
        /* Use an X item only if there's something to send */
  #ifdef SUPPORT_TLS
@@@ -5480,25 -5479,6 +5497,25 @@@ if (process_recipients != RECIP_IGNORE
          addr_last = new;
          break;
          }
 +
 +#ifdef EXPERIMENTAL_TPDA
 +      if (process_recipients != RECIP_ACCEPT)
 +      {
 +      uschar * save_local =  deliver_localpart;
 +      uschar * save_domain = deliver_domain;
 +
 +      deliver_localpart = expand_string(
 +                    string_sprintf("${local_part:%s}", new->address));
 +      deliver_domain =    expand_string(
 +                    string_sprintf("${domain:%s}", new->address));
 +
 +      (void) tpda_raise_event(delivery_event_action,
 +                    US"msg:fail:internal", new->message);
 +
 +      deliver_localpart = save_local;
 +      deliver_domain =    save_domain;
 +      }
 +#endif
        }
      }
    }
@@@ -7056,12 -7036,14 +7073,14 @@@ wording. *
            {
            struct stat statbuf;
            if (fstat(deliver_datafile, &statbuf) == 0 && statbuf.st_size > max)
+           {
              if (emf_text)
              fprintf(f, "%s", CS emf_text);
            else
                fprintf(f,
  "------ The body of the message is " OFF_T_FMT " characters long; only the first\n"
  "------ %d or so are included here.\n", statbuf.st_size, max);
+           }
            }
  
        fputc('\n', f);
@@@ -7254,11 -7236,7 +7273,11 @@@ if (addr_defer == NULL
  
    /* Unset deliver_freeze so that we won't try to move the spool files further down */
    deliver_freeze = FALSE;
 -  }
 +
 +#ifdef EXPERIMENTAL_TPDA
 +  (void) tpda_raise_event(delivery_event_action, US"msg:complete", NULL);
 +#endif
 +}
  
  /* If there are deferred addresses, we are keeping this message because it is
  not yet completed. Lose any temporary files that were catching output from
diff --combined src/src/globals.c
index ef1c1fd02f375c56e92df097965bc46fe3377e90,8407e6570f8fa4b30a3f9e954ff8c43a08aa865a..22bd69e0195d19af9ce8237162fcda93fb2b357e
@@@ -103,6 -103,10 +103,10 @@@ tls_support tls_in = 
   -1,   /* tls_active */
   0,    /* tls_bits */
   FALSE,/* tls_certificate_verified */
+ #ifdef EXPERIMENTAL_DANE
+  FALSE,/* dane_verified */
+  0,    /* tlsa_usage */
+ #endif
   NULL, /* tls_cipher */
   FALSE,/* tls_on_connect */
   NULL, /* tls_on_connect_ports */
@@@ -116,6 -120,10 +120,10 @@@ tls_support tls_out = 
   -1,   /* tls_active */
   0,    /* tls_bits */
   FALSE,/* tls_certificate_verified */
+ #ifdef EXPERIMENTAL_DANE
+  FALSE,/* dane_verified */
+  0,    /* tlsa_usage */
+ #endif
   NULL, /* tls_cipher */
   FALSE,/* tls_on_connect */
   NULL, /* tls_on_connect_ports */
@@@ -634,6 -642,9 +642,9 @@@ BOOL    dmarc_enable_forensic   = FALSE
  uschar *dns_again_means_nonexist = NULL;
  int     dns_csa_search_limit   = 5;
  BOOL    dns_csa_use_reverse    = TRUE;
+ #ifdef EXPERIMENTAL_DANE
+ int     dns_dane_ok            = -1;
+ #endif
  uschar *dns_ipv4_lookup        = NULL;
  int     dns_retrans            = 0;
  int     dns_retry              = 0;
@@@ -1327,9 -1338,8 +1338,9 @@@ BOOL    timestamps_utc         = FALSE
  
  #ifdef EXPERIMENTAL_TPDA
  int     tpda_defer_errno        = 0;
 -uschar *tpda_event              = NULL;
 -uschar *tpda_data               = NULL;
 +uschar *tpda_event              = NULL;       /* event name */
 +uschar *tpda_data               = NULL;       /* auxilary data for event */
 +uschar *delivery_event_action   = NULL;       /* expansion for delivery events */
  #endif
  
  transport_instance  *transports = NULL;
@@@ -1385,7 -1395,7 +1396,7 @@@ transport_instance  transport_defaults 
      TRUE_UNSET                /* retry_use_local_part: BOOL, but set neither
                                   1 nor 0 so can detect unset */
  #ifdef EXPERIMENTAL_TPDA
-    ,NULL                    /* tpda_delivery_action */
+    ,NULL                    /* tpda_event_action */
  #endif
  };
  
diff --combined src/src/globals.h
index 73793aa2ee24bd00d072fc1a435639a15637d9d3,0b5335e6f83ee052a828829e2bdfd0e778b1c252..800ec9c31d0df905fdff2288fa905db6a52c327b
@@@ -82,6 -82,10 +82,10 @@@ typedef struct 
    int     active;             /* fd/socket when in a TLS session */
    int     bits;               /* bits used in TLS session */
    BOOL    certificate_verified; /* Client certificate verified */
+ #ifdef EXPERIMENTAL_DANE
+   BOOL    dane_verified;        /* ... via DANE */
+   int     tlsa_usage;         /* TLSA record(s) usage */
+ #endif
    uschar *cipher;             /* Cipher used */
    BOOL    on_connect;         /* For older MTAs that don't STARTTLS */
    uschar *on_connect_ports;   /* Ports always tls-on-connect */
@@@ -386,6 -390,9 +390,9 @@@ extern uschar *dns_again_means_nonexist
  extern int     dns_csa_search_limit;   /* How deep to search for CSA SRV records */
  extern BOOL    dns_csa_use_reverse;    /* Check CSA in reverse DNS? (non-standard) */
  extern uschar *dns_ipv4_lookup;        /* For these domains, don't look for AAAA (or A6) */
+ #ifdef EXPERIMENTAL_DANE
+ extern int     dns_dane_ok;            /* Ok to use DANE when checking TLS authenticity */
+ #endif
  extern int     dns_retrans;            /* Retransmission time setting */
  extern int     dns_retry;              /* Number of retries */
  extern int     dns_dnssec_ok;          /* When constructing DNS query, set DO flag */
@@@ -872,8 -879,7 +879,8 @@@ extern BOOL    timestamps_utc;         
  #ifdef EXPERIMENTAL_TPDA
  extern int     tpda_defer_errno;        /* error number set when a remote delivery is deferred with a host error */
  extern uschar *tpda_event;            /* event classification */
 -extern uschar *tpda_data;;            /* event data */
 +extern uschar *tpda_data;             /* event data */
 +extern uschar *delivery_event_action;   /* expansion for delivery events */
  #endif
  
  extern uschar *transport_name;         /* Name of transport last started */