Feature compile-guard
[exim.git] / src / src / tls-openssl.c
index 70ac63f16bee0c286133a1469edf126eea00b715..de2e7a3bd4d8aa960b04850588f2b328f9e71859 100644 (file)
@@ -25,6 +25,10 @@ functions from the OpenSSL library. */
 #ifndef DISABLE_OCSP
 # include <openssl/ocsp.h>
 #endif
+#ifdef EXPERIMENTAL_DANE
+# include <danessl.h>
+#endif
+
 
 #ifndef DISABLE_OCSP
 # define EXIM_OCSP_SKEW_SECONDS (300L)
@@ -361,7 +365,7 @@ else
       return 0;                                /* reject */
       }
 # endif
-#endif
+#endif /*EXPERIMENTAL_CERTNAMES*/
 
   DEBUG(D_tls) debug_printf("SSL%s verify ok: depth=0 SN=%s\n",
     *calledp ? "" : " authenticated", txt);
@@ -385,6 +389,31 @@ return verify_callback(state, x509ctx, &tls_in, &server_verify_callback_called,
 }
 
 
+#ifdef EXPERIMENTAL_DANE
+
+/* This gets called *by* the dane library verify callback, which interposes
+itself.
+*/
+static int
+verify_callback_client_dane(int state, X509_STORE_CTX * x509ctx)
+{
+X509 * cert = X509_STORE_CTX_get_current_cert(x509ctx);
+static uschar txt[256];
+
+X509_NAME_oneline(X509_get_subject_name(cert), CS txt, sizeof(txt));
+
+DEBUG(D_tls) debug_printf("verify_callback_client_dane: %s\n", txt);
+tls_out.peerdn = txt;
+tls_out.peercert = X509_dup(cert);
+
+if (state == 1)
+  tls_out.dane_verified =
+  tls_out.certificate_verified = TRUE;
+return 1;
+}
+
+#endif /*EXPERIMENTAL_DANE*/
+
 
 /*************************************************
 *           Information callback                 *
@@ -999,7 +1028,6 @@ return i;
 #endif /*!DISABLE_OCSP*/
 
 
-
 /*************************************************
 *            Initialize for TLS                  *
 *************************************************/
@@ -1421,6 +1449,9 @@ if (expciphers != NULL)
 optional, set up appropriately. */
 
 tls_in.certificate_verified = FALSE;
+#ifdef EXPERIMENTAL_DANE
+tls_in.dane_verified = FALSE;
+#endif
 server_verify_callback_called = FALSE;
 
 if (verify_check_host(&tls_verify_hosts) == OK)
@@ -1579,6 +1610,103 @@ else if (verify_check_host(&ob->tls_try_verify_hosts) == OK)
 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;
+dns_scan dnss;
+const char * hostnames[2] = { CS host->name, NULL };
+int found = 0;
+
+if (DANESSL_init(ssl, NULL, hostnames) != 1)
+  return tls_error(US"hostnames load", host, NULL);
+
+for (rr = dns_next_rr(dnsa, &dnss, RESET_ANSWERS);
+     rr;
+     rr = dns_next_rr(dnsa, &dnss, RESET_NEXT)
+    ) if (rr->type == T_TLSA)
+  {
+  uschar * p = rr->data;
+  uint8_t usage, selector, mtype;
+  const char * mdname;
+
+  found++;
+  usage = *p++;
+  selector = *p++;
+  mtype = *p++;
+
+  switch (mtype)
+    {
+    default:
+      log_write(0, LOG_MAIN,
+               "DANE error: TLSA record w/bad mtype 0x%x", mtype);
+      return FAIL;
+    case 0:    mdname = NULL; break;
+    case 1:    mdname = "sha256"; break;
+    case 2:    mdname = "sha512"; break;
+    }
+
+  switch (DANESSL_add_tlsa(ssl, usage, selector, mdname, p, rr->size - 3))
+    {
+    default:
+    case 0:    /* action not taken */
+      return tls_error(US"tlsa load", host, NULL);
+    case 1:    break;
+    }
+
+  tls_out.tlsa_usage |= 1<<usage;
+  }
+
+if (found)
+  return OK;
+
+log_write(0, LOG_MAIN, "DANE error: No TLSA records");
+return FAIL;
+}
+#endif /*EXPERIMENTAL_DANE*/
+
+
+
 /*************************************************
 *    Start a TLS session in a client             *
 *************************************************/
@@ -1618,6 +1746,8 @@ 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;
 
@@ -1627,39 +1757,8 @@ if (host->dnssec == DS_YES)
     || verify_check_this_host(&ob->hosts_try_dane, NULL,
                          host->name, host->address, NULL) == OK
     )
-    {
-    /* 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 (rc = dns_lookup(&tlsa_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(&tlsa_dnsa))
-         {
-         log_write(0, LOG_MAIN, "DANE error: TLSA lookup not DNSSEC");
-         return DEFER;
-         }
-       dane = TRUE;
-       break;
-      }
-    }
+    if ((rc = tlsa_lookup(host, &tlsa_dnsa, dane_required, &dane)) != OK)
+      return rc;
   }
 else if (dane_required)
   {
@@ -1668,8 +1767,6 @@ else if (dane_required)
   log_write(0, LOG_MAIN, "DANE error: previous lookup not DNSSEC");
   return FAIL;
   }
-
-if (!dane)     /*XXX todo: enable ocsp with dane */
 #endif
 
 #ifndef DISABLE_OCSP
@@ -1713,6 +1810,8 @@ if (expciphers != NULL)
 #ifdef EXPERIMENTAL_DANE
 if (dane)
   {
+  SSL_CTX_set_verify(client_ctx, SSL_VERIFY_PEER, verify_callback_client_dane);
+
   if (!DANESSL_library_init())
     return tls_error(US"library init", host, NULL);
   if (DANESSL_CTX_init(client_ctx) <= 0)
@@ -1758,58 +1857,39 @@ if (ob->tls_sni)
     }
   }
 
+#ifdef EXPERIMENTAL_DANE
+if (dane)
+  if ((rc = dane_tlsa_load(client_ssl, host, &tlsa_dnsa)) != OK)
+    return rc;
+#endif
+
 #ifndef DISABLE_OCSP
 /* Request certificate status at connection-time.  If the server
 does OCSP stapling we will get the callback (set in tls_init()) */
+# ifdef EXPERIMENTAL_DANE
 if (request_ocsp)
   {
-  SSL_set_tlsext_status_type(client_ssl, TLSEXT_STATUSTYPE_ocsp);
-  client_static_cbinfo->u_ocsp.client.verify_required = require_ocsp;
-  tls_out.ocsp = OCSP_NOT_RESP;
+  const uschar * s;
+  if (  (s = ob->hosts_require_ocsp) && Ustrstr(s, US"tls_out_tlsa_usage")
+     || (s = ob->hosts_request_ocsp) && Ustrstr(s, US"tls_out_tlsa_usage")
+     )
+    {  /* Re-eval now $tls_out_tlsa_usage is populated.  If
+       this means we avoid the OCSP request, we wasted the setup
+       cost in tls_init(). */
+    require_ocsp = verify_check_this_host(&ob->hosts_require_ocsp,
+      NULL, host->name, host->address, NULL) == OK;
+    request_ocsp = require_ocsp ? TRUE
+      : verify_check_this_host(&ob->hosts_request_ocsp,
+         NULL, host->name, host->address, NULL) == OK;
+    }
   }
-#endif
+# endif
 
-#ifdef EXPERIMENTAL_DANE
-if (dane)
+if (request_ocsp)
   {
-  dns_record * rr;
-  dns_scan dnss;
-  uschar * hostnames[2] = { host->name, NULL };
-
-  if (DANESSL_init(client_ssl, NULL, hostnames) != 1)
-    return tls_error(US"hostnames load", host, NULL);
-
-  for (rr = dns_next_rr(&tlsa_dnsa, &dnss, RESET_ANSWERS);
-       rr;
-       rr = dns_next_rr(&tlsa_dnsa, &dnss, RESET_NEXT)
-      ) if (rr->type == T_TLSA)
-    {
-    uschar * p = rr->data;
-    int usage, selector, mtype;
-    const char * mdname;
-
-    GETSHORT(usage, p);
-    GETSHORT(selector, p);
-    GETSHORT(mtype, p);
-
-    switch (mtype)
-      {
-      default: /* log bad */ return FAIL;
-      case 0:  mdname = NULL; break;
-      case 1:  mdname = "SHA2-256"; break;
-      case 2:  mdname = "SHA2-512"; break;
-      }
-
-    switch (DANESSL_add_tlsa(client_ssl,
-               (uint8_t) usage, (uint8_t) selector,
-               mdname, p, rr->size - (p - rr->data)))
-      {
-      default:
-      case 0:  /* action not taken */
-       return tls_error(US"tlsa load", host, NULL);
-      case 1:  break;
-      }
-    }
+  SSL_set_tlsext_status_type(client_ssl, TLSEXT_STATUSTYPE_ocsp);
+  client_static_cbinfo->u_ocsp.client.verify_required = require_ocsp;
+  tls_out.ocsp = OCSP_NOT_RESP;
   }
 #endif
 
@@ -1824,7 +1904,7 @@ alarm(0);
 
 #ifdef EXPERIMENTAL_DANE
 if (dane)
-  DANESSL_cleanup(client_ssl); /*XXX earliest possible callpoint. Too early? */
+  DANESSL_cleanup(client_ssl);
 #endif
 
 if (rc <= 0)