Fix needless OCSP request under DANE
[exim.git] / src / src / tls-openssl.c
index de2e7a3bd4d8aa960b04850588f2b328f9e71859..b77ed32e1c99cbb513a992e99692a977e7880e56 100644 (file)
@@ -120,6 +120,9 @@ typedef struct tls_ext_ctx_cb {
 #ifdef EXPERIMENTAL_CERTNAMES
   uschar * verify_cert_hostnames;
 #endif
+#ifdef EXPERIMENTAL_TPDA
+  uschar * event_action;
+#endif
 } tls_ext_ctx_cb;
 
 /* should figure out a cleanup of API to handle state preserved per
@@ -266,6 +269,9 @@ when asked. We get here only if a certificate has been received. Handling of
 optional verification for this case is done when requesting SSL to verify, by
 setting SSL_VERIFY_FAIL_IF_NO_PEER_CERT in the non-optional case.
 
+May be called multiple times for different issues with a certificate, even
+for a given "depth" in the certificate chain.
+
 Arguments:
   state      current yes/no state as 1/0
   x509ctx    certificate information.
@@ -279,6 +285,7 @@ verify_callback(int state, X509_STORE_CTX *x509ctx,
   tls_support *tlsp, BOOL *calledp, BOOL *optionalp)
 {
 X509 * cert = X509_STORE_CTX_get_current_cert(x509ctx);
+int depth = X509_STORE_CTX_get_error_depth(x509ctx);
 static uschar txt[256];
 
 X509_NAME_oneline(X509_get_subject_name(cert), CS txt, sizeof(txt));
@@ -286,7 +293,7 @@ X509_NAME_oneline(X509_get_subject_name(cert), CS txt, sizeof(txt));
 if (state == 0)
   {
   log_write(0, LOG_MAIN, "SSL verify error: depth=%d error=%s cert=%s",
-    X509_STORE_CTX_get_error_depth(x509ctx),
+    depth,
     X509_verify_cert_error_string(X509_STORE_CTX_get_error(x509ctx)),
     txt);
   tlsp->certificate_verified = FALSE;
@@ -300,10 +307,9 @@ if (state == 0)
     "tls_try_verify_hosts)\n");
   }
 
-else if (X509_STORE_CTX_get_error_depth(x509ctx) != 0)
+else if (depth != 0)
   {
-  DEBUG(D_tls) debug_printf("SSL verify ok: depth=%d SN=%s\n",
-     X509_STORE_CTX_get_error_depth(x509ctx), txt);
+  DEBUG(D_tls) debug_printf("SSL verify ok: depth=%d SN=%s\n", depth, txt);
 #ifndef DISABLE_OCSP
   if (tlsp == &tls_out && client_static_cbinfo->u_ocsp.client.verify_store)
     {  /* client, wanting stapling  */
@@ -314,6 +320,23 @@ else if (X509_STORE_CTX_get_error_depth(x509ctx) != 0)
                              cert))
       ERR_clear_error();
     }
+#endif
+#ifdef EXPERIMENTAL_TPDA
+  if (tlsp == &tls_out && client_static_cbinfo->event_action)
+    {
+    tlsp->peercert = X509_dup(cert);
+    if (tpda_raise_event(client_static_cbinfo->event_action,
+                   US"tls:cert", string_sprintf("%d", depth)) == DEFER)
+      {
+      log_write(0, LOG_MAIN, "SSL verify denied by event-action: "
+                             "depth=%d cert=%s", depth, txt);
+      tlsp->certificate_verified = FALSE;
+      *calledp = TRUE;
+      return 0;                            /* reject */
+      }
+    X509_free(tlsp->peercert);
+    tlsp->peercert = NULL;
+    }
 #endif
   }
 else
@@ -367,13 +390,28 @@ else
 # endif
 #endif /*EXPERIMENTAL_CERTNAMES*/
 
+#ifdef EXPERIMENTAL_TPDA
+  if (tlsp == &tls_out)
+    {
+    if (tpda_raise_event(client_static_cbinfo->event_action,
+                   US"tls:cert", US"0") == DEFER)
+      {
+      log_write(0, LOG_MAIN, "SSL verify denied by event-action: "
+                             "depth=0 cert=%s", txt);
+      tlsp->certificate_verified = FALSE;
+      *calledp = TRUE;
+      return 0;                            /* reject */
+      }
+    }
+#endif
+
   DEBUG(D_tls) debug_printf("SSL%s verify ok: depth=0 SN=%s\n",
     *calledp ? "" : " authenticated", txt);
   if (!*calledp) tlsp->certificate_verified = TRUE;
   *calledp = TRUE;
   }
 
-return 1;   /* accept */
+return 1;   /* accept, at least for this level */
 }
 
 static int
@@ -399,6 +437,9 @@ verify_callback_client_dane(int state, X509_STORE_CTX * x509ctx)
 {
 X509 * cert = X509_STORE_CTX_get_current_cert(x509ctx);
 static uschar txt[256];
+#ifdef EXPERIMENTAL_TPDA
+int depth = X509_STORE_CTX_get_error_depth(x509ctx);
+#endif
 
 X509_NAME_oneline(X509_get_subject_name(cert), CS txt, sizeof(txt));
 
@@ -406,6 +447,25 @@ DEBUG(D_tls) debug_printf("verify_callback_client_dane: %s\n", txt);
 tls_out.peerdn = txt;
 tls_out.peercert = X509_dup(cert);
 
+#ifdef EXPERIMENTAL_TPDA
+  if (client_static_cbinfo->event_action)
+    {
+    if (tpda_raise_event(client_static_cbinfo->event_action,
+                   US"tls:cert", string_sprintf("%d", depth)) == DEFER)
+      {
+      log_write(0, LOG_MAIN, "DANE verify denied by event-action: "
+                             "depth=%d cert=%s", depth, txt);
+      tls_out.certificate_verified = FALSE;
+      return 0;                            /* reject */
+      }
+    if (depth != 0)
+      {
+      X509_free(tls_out.peercert);
+      tls_out.peercert = NULL;
+      }
+    }
+#endif
+
 if (state == 1)
   tls_out.dane_verified =
   tls_out.certificate_verified = TRUE;
@@ -917,7 +977,7 @@ if(!(rsp = d2i_OCSP_RESPONSE(NULL, &p, len)))
  {
   tls_out.ocsp = OCSP_FAILED;
   if (log_extra_selector & LX_tls_cipher)
-    log_write(0, LOG_MAIN, "Received TLS status response, parse error");
+    log_write(0, LOG_MAIN, "Received TLS cert status response, parse error");
   else
     DEBUG(D_tls) debug_printf(" parse error\n");
   return 0;
@@ -927,7 +987,7 @@ if(!(bs = OCSP_response_get1_basic(rsp)))
   {
   tls_out.ocsp = OCSP_FAILED;
   if (log_extra_selector & LX_tls_cipher)
-    log_write(0, LOG_MAIN, "Received TLS status response, error parsing response");
+    log_write(0, LOG_MAIN, "Received TLS cert status response, error parsing response");
   else
     DEBUG(D_tls) debug_printf(" error parsing response\n");
   OCSP_RESPONSE_free(rsp);
@@ -957,6 +1017,8 @@ if(!(bs = OCSP_response_get1_basic(rsp)))
              cbinfo->u_ocsp.client.verify_store, 0)) <= 0)
       {
       tls_out.ocsp = OCSP_FAILED;
+      if (log_extra_selector & LX_tls_cipher)
+       log_write(0, LOG_MAIN, "Received TLS cert status response, itself unverifiable");
       BIO_printf(bp, "OCSP response verify failure\n");
       ERR_print_errors(bp);
       i = cbinfo->u_ocsp.client.verify_required ? 0 : 1;
@@ -1059,7 +1121,7 @@ tls_init(SSL_CTX **ctxp, host_item *host, uschar *dhparam, uschar *certificate,
 long init_options;
 int rc;
 BOOL okay;
-tls_ext_ctx_cb *cbinfo;
+tls_ext_ctx_cb * cbinfo;
 
 cbinfo = store_malloc(sizeof(tls_ext_ctx_cb));
 cbinfo->certificate = certificate;
@@ -1077,6 +1139,9 @@ else
 cbinfo->dhparam = dhparam;
 cbinfo->server_cipher_list = NULL;
 cbinfo->host = host;
+#ifdef EXPERIMENTAL_TPDA
+cbinfo->event_action = NULL;
+#endif
 
 SSL_load_error_strings();          /* basic set up */
 OpenSSL_add_ssl_algorithms();
@@ -1311,9 +1376,14 @@ if (expcerts != NULL && *expcerts != '\0')
           !SSL_CTX_load_verify_locations(sctx, CS file, CS dir))
       return tls_error(US"SSL_CTX_load_verify_locations", host, NULL);
 
+    /* Load the list of CAs for which we will accept certs, for sending
+    to the client.  XXX only for file source, not dir? */
     if (file != NULL)
       {
-      SSL_CTX_set_client_CA_list(sctx, SSL_load_client_CA_file(CS file));
+      STACK_OF(X509_NAME) * names = SSL_load_client_CA_file(CS file);
+DEBUG(D_tls) debug_printf("Added %d certificate authorities.\n",
+                                 sk_X509_NAME_num(names));
+      SSL_CTX_set_client_CA_list(sctx, names);
       }
     }
 
@@ -1612,44 +1682,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)
 {
@@ -1717,7 +1749,8 @@ Argument:
   fd               the fd of the connection
   host             connected host (for messages)
   addr             the first address
-  ob               smtp transport options
+  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
@@ -1726,9 +1759,14 @@ Returns:           OK on success
 
 int
 tls_client_start(int fd, host_item *host, address_item *addr,
-  void *v_ob)
+  transport_instance *tb
+#ifdef EXPERIMENTAL_DANE
+  , dns_answer * tlsa_dnsa
+#endif
+  )
 {
-smtp_transport_options_block * ob = v_ob;
+smtp_transport_options_block * ob =
+  (smtp_transport_options_block *)tb->options_block;
 static uschar txt[256];
 uschar * expciphers;
 X509 * server_cert;
@@ -1739,43 +1777,36 @@ 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
   {
-  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;
+# ifdef EXPERIMENTAL_DANE
+  if (  tlsa_dnsa
+     && ob->hosts_request_ocsp[0] == '*'
+     && ob->hosts_request_ocsp[1] == '\0'
+     )
+    {
+    /* Unchanged from default.  Use a safer one under DANE */
+    request_ocsp = TRUE;
+    ob->hosts_request_ocsp = US"${if or { {= {0}{$tls_out_tlsa_usage}} "
+                                     "   {= {4}{$tls_out_tlsa_usage}} } "
+                                " {*}{}}";
+    }
+# endif
+
+  if ((require_ocsp = verify_check_this_host(&ob->hosts_require_ocsp,
+    NULL, host->name, host->address, NULL) == OK))
+    request_ocsp = TRUE;
+  else
+# ifdef EXPERIMENTAL_DANE
+    if (!request_ocsp)
+# endif
+      request_ocsp = verify_check_this_host(&ob->hosts_request_ocsp,
+         NULL, host->name, host->address, NULL) == OK;
   }
 #endif
 
@@ -1808,7 +1839,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);
 
@@ -1858,8 +1889,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
 
@@ -1870,8 +1901,8 @@ does OCSP stapling we will get the callback (set in tls_init()) */
 if (request_ocsp)
   {
   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")
+  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
@@ -1893,6 +1924,9 @@ if (request_ocsp)
   }
 #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. */
 
@@ -1903,7 +1937,7 @@ rc = SSL_connect(client_ssl);
 alarm(0);
 
 #ifdef EXPERIMENTAL_DANE
-if (dane)
+if (tlsa_dnsa)
   DANESSL_cleanup(client_ssl);
 #endif