Capture the knowlege that verification succeeded
[exim.git] / src / src / tls-openssl.c
index 05af3db884736e1af76039f1eab6a2464a2fa2c4..e37b1add5001ad6ba8b6a2f02f07dee08f8b34df 100644 (file)
@@ -35,6 +35,11 @@ functions from the OpenSSL library. */
 # define EXIM_HAVE_OPENSSL_TLSEXT
 #endif
 
+#if !defined(EXIM_HAVE_OPENSSL_TLSEXT) && !defined(DISABLE_OCSP)
+# warning "OpenSSL library version too old; define DISABLE_OCSP in Makefile"
+# define DISABLE_OCSP
+#endif
+
 /* Structure for collecting random data for seeding. */
 
 typedef struct randstuff {
@@ -322,13 +327,25 @@ else
        /* client, wanting hostname check */
 
 # if OPENSSL_VERSION_NUMBER >= 0x010100000L || OPENSSL_VERSION_NUMBER >= 0x010002000L
+#  ifndef X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS
+#   define X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS 0
+#  endif
     {
     int sep = 0;
     uschar * list = verify_cert_hostnames;
     uschar * name;
-    while (name = string_nextinlist(&list, &sep, NULL, 0))
-      if (X509_check_host(cert, name, 0, 0))
+    int rc;
+    while ((name = string_nextinlist(&list, &sep, NULL, 0)))
+      if ((rc = X509_check_host(cert, name, 0,
+                 X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS)))
+       {
+       if (rc < 0)
+         {
+         log_write(0, LOG_MAIN, "SSL verify error: internal error\n");
+         name = NULL;
+         }
        break;
+       }
     if (!name)
       {
       log_write(0, LOG_MAIN,
@@ -344,7 +361,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);
@@ -368,6 +385,28 @@ 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.certificate_verified = TRUE;
+return 1;
+}
+#endif
+
 
 /*************************************************
 *           Information callback                 *
@@ -419,14 +458,11 @@ const char *pem;
 if (!expand_check(dhparam, US"tls_dhparam", &dhexpanded))
   return FALSE;
 
-if (dhexpanded == NULL || *dhexpanded == '\0')
-  {
+if (!dhexpanded || !*dhexpanded)
   bio = BIO_new_mem_buf(CS std_dh_prime_default(), -1);
-  }
 else if (dhexpanded[0] == '/')
   {
-  bio = BIO_new_file(CS dhexpanded, "r");
-  if (bio == NULL)
+  if (!(bio = BIO_new_file(CS dhexpanded, "r")))
     {
     tls_error(string_sprintf("could not read dhparams file %s", dhexpanded),
           host, US strerror(errno));
@@ -441,8 +477,7 @@ else
     return TRUE;
     }
 
-  pem = std_dh_prime_named(dhexpanded);
-  if (!pem)
+  if (!(pem = std_dh_prime_named(dhexpanded)))
     {
     tls_error(string_sprintf("Unknown standard DH prime \"%s\"", dhexpanded),
         host, US strerror(errno));
@@ -451,8 +486,7 @@ else
   bio = BIO_new_mem_buf(CS pem, -1);
   }
 
-dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
-if (dh == NULL)
+if (!(dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL)))
   {
   BIO_free(bio);
   tls_error(string_sprintf("Could not read tls_dhparams \"%s\"", dhexpanded),
@@ -753,8 +787,7 @@ if (!reexpand_tls_files_for_sni)
 not confident that memcpy wouldn't break some internal reference counting.
 Especially since there's a references struct member, which would be off. */
 
-server_sni = SSL_CTX_new(SSLv23_server_method());
-if (!server_sni)
+if (!(server_sni = SSL_CTX_new(SSLv23_server_method())))
   {
   ERR_error_string(ERR_get_error(), ssl_errstring);
   DEBUG(D_tls) debug_printf("SSL_CTX_new() failed: %s\n", ssl_errstring);
@@ -788,8 +821,8 @@ OCSP information. */
 rc = tls_expand_session_files(server_sni, cbinfo);
 if (rc != OK) return SSL_TLSEXT_ERR_NOACK;
 
-rc = init_dh(server_sni, cbinfo->dhparam, NULL);
-if (rc != OK) return SSL_TLSEXT_ERR_NOACK;
+if (!init_dh(server_sni, cbinfo->dhparam, NULL))
+  return SSL_TLSEXT_ERR_NOACK;
 
 DEBUG(D_tls) debug_printf("Switching SSL context.\n");
 SSL_set_SSL_CTX(s, server_sni);
@@ -988,7 +1021,6 @@ return i;
 #endif /*!DISABLE_OCSP*/
 
 
-
 /*************************************************
 *            Initialize for TLS                  *
 *************************************************/
@@ -997,13 +1029,14 @@ return i;
 of the library.  We allocate and return a context structure.
 
 Arguments:
+  ctxp            returned SSL context
   host            connected host, if client; NULL if server
   dhparam         DH parameter file
   certificate     certificate file
   privatekey      private key
   ocsp_file       file of stapling info (server); flag for require ocsp (client)
   addr            address if client; NULL if server (for some randomness)
-  cbp             place to put allocated context
+  cbp             place to put allocated callback context
 
 Returns:          OK/DEFER/FAIL
 */
@@ -1035,6 +1068,7 @@ else
   cbinfo->u_ocsp.client.verify_store = NULL;
 #endif
 cbinfo->dhparam = dhparam;
+cbinfo->server_cipher_list = NULL;
 cbinfo->host = host;
 
 SSL_load_error_strings();          /* basic set up */
@@ -1521,6 +1555,50 @@ return OK;
 
 
 
+static int
+tls_client_basic_ctx_init(SSL_CTX * ctx,
+    host_item * host, smtp_transport_options_block * ob
+#ifdef EXPERIMENTAL_CERTNAMES
+    , tls_ext_ctx_cb * cbinfo
+#endif
+                         )
+{
+int rc;
+/* stick to the old behaviour for compatibility if tls_verify_certificates is 
+   set but both tls_verify_hosts and tls_try_verify_hosts is not set. Check only
+   the specified host patterns if one of them is defined */
+
+if ((!ob->tls_verify_hosts && !ob->tls_try_verify_hosts) ||
+    (verify_check_host(&ob->tls_verify_hosts) == OK))
+  {
+  if ((rc = setup_certs(ctx, ob->tls_verify_certificates,
+       ob->tls_crl, host, FALSE, verify_callback_client)) != OK)
+    return rc;
+  client_verify_optional = FALSE;
+
+#ifdef EXPERIMENTAL_CERTNAMES
+  if (ob->tls_verify_cert_hostnames)
+    {
+    if (!expand_check(ob->tls_verify_cert_hostnames,
+                     US"tls_verify_cert_hostnames",
+                     &cbinfo->verify_cert_hostnames))
+      return FAIL;
+    if (cbinfo->verify_cert_hostnames)
+      DEBUG(D_tls) debug_printf("Cert hostname to check: \"%s\"\n",
+                     cbinfo->verify_cert_hostnames);
+    }
+#endif
+  }
+else if (verify_check_host(&ob->tls_try_verify_hosts) == OK)
+  {
+  if ((rc = setup_certs(ctx, ob->tls_verify_certificates,
+       ob->tls_crl, host, TRUE, verify_callback_client)) != OK)
+    return rc;
+  client_verify_optional = TRUE;
+  }
+
+return OK;
+}
 
 /*************************************************
 *    Start a TLS session in a client             *
@@ -1545,16 +1623,84 @@ tls_client_start(int fd, host_item *host, address_item *addr,
 {
 smtp_transport_options_block * ob = v_ob;
 static uschar txt[256];
-uschar *expciphers;
-X509* server_cert;
+uschar * expciphers;
+X509 * server_cert;
 int rc;
 static uschar cipherbuf[256];
+
+#ifndef DISABLE_OCSP
+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
+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
+    )
+    {
+    /* 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;
+      }
+    }
+  }
+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;
+  }
+
+if (!dane)     /*XXX todo: enable ocsp with dane */
+#endif
+
 #ifndef DISABLE_OCSP
-BOOL require_ocsp = verify_check_this_host(&ob->hosts_require_ocsp,
-  NULL, host->name, host->address, NULL) == OK;
-BOOL request_ocsp = require_ocsp ? TRUE
-  : verify_check_this_host(&ob->hosts_request_ocsp,
-      NULL, host->name, host->address, NULL) == OK;
+  {
+  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
 
 rc = tls_init(&client_ctx, host, NULL,
@@ -1585,38 +1731,26 @@ if (expciphers != NULL)
     return tls_error(US"SSL_CTX_set_cipher_list", host, NULL);
   }
 
-/* stick to the old behaviour for compatibility if tls_verify_certificates is 
-   set but both tls_verify_hosts and tls_try_verify_hosts is not set. Check only
-   the specified host patterns if one of them is defined */
-
-if ((!ob->tls_verify_hosts && !ob->tls_try_verify_hosts) ||
-    (verify_check_host(&ob->tls_verify_hosts) == OK))
+#ifdef EXPERIMENTAL_DANE
+if (dane)
   {
-  if ((rc = setup_certs(client_ctx, ob->tls_verify_certificates,
-       ob->tls_crl, host, FALSE, verify_callback_client)) != OK)
-    return rc;
-  client_verify_optional = FALSE;
+  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)
+    return tls_error(US"context init", host, NULL);
+  }
+else
+
+#endif
 
+  if ((rc = tls_client_basic_ctx_init(client_ctx, host, ob
 #ifdef EXPERIMENTAL_CERTNAMES
-  if (ob->tls_verify_cert_hostnames)
-    {
-    if (!expand_check(ob->tls_verify_cert_hostnames,
-                     US"tls_verify_cert_hostnames",
-                     &client_static_cbinfo->verify_cert_hostnames))
-      return FAIL;
-    if (client_static_cbinfo->verify_cert_hostnames)
-      DEBUG(D_tls) debug_printf("Cert hostname to check: \"%s\"\n",
-                     client_static_cbinfo->verify_cert_hostnames);
-    }
+                               , client_static_cbinfo
 #endif
-  }
-else if (verify_check_host(&ob->tls_try_verify_hosts) == OK)
-  {
-  if ((rc = setup_certs(client_ctx, ob->tls_verify_certificates,
-       ob->tls_crl, host, TRUE, verify_callback_client)) != OK)
+                               )) != OK)
     return rc;
-  client_verify_optional = TRUE;
-  }
 
 if ((client_ssl = SSL_new(client_ctx)) == NULL)
   return tls_error(US"SSL_new", host, NULL);
@@ -1658,6 +1792,51 @@ if (request_ocsp)
   }
 #endif
 
+#ifdef EXPERIMENTAL_DANE
+if (dane)
+  {
+  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 = "sha256"; break;
+      case 2:  mdname = "sha512"; 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;
+      }
+    }
+  }
+#endif
+
+
 /* There doesn't seem to be a built-in timeout on connection. */
 
 DEBUG(D_tls) debug_printf("Calling SSL_connect\n");
@@ -1666,6 +1845,11 @@ alarm(ob->command_timeout);
 rc = SSL_connect(client_ssl);
 alarm(0);
 
+#ifdef EXPERIMENTAL_DANE
+if (dane)
+  DANESSL_cleanup(client_ssl); /*XXX earliest possible callpoint. Too early? */
+#endif
+
 if (rc <= 0)
   return tls_error(US"SSL_connect", host, sigalrm_seen ? US"timed out" : NULL);