Basic DANE entry points
[exim.git] / src / src / tls-openssl.c
index db2544c037f56ffcfa1a0ebb985fbc5f57445ca9..b96dbbf04eb894ca23d63bfdca6559223b07e1cd 100644 (file)
@@ -22,17 +22,22 @@ functions from the OpenSSL library. */
 #include <openssl/ssl.h>
 #include <openssl/err.h>
 #include <openssl/rand.h>
-#ifdef EXPERIMENTAL_OCSP
-#include <openssl/ocsp.h>
+#ifndef DISABLE_OCSP
+# include <openssl/ocsp.h>
 #endif
 
-#ifdef EXPERIMENTAL_OCSP
-#define EXIM_OCSP_SKEW_SECONDS (300L)
-#define EXIM_OCSP_MAX_AGE (-1L)
+#ifndef DISABLE_OCSP
+# define EXIM_OCSP_SKEW_SECONDS (300L)
+# define EXIM_OCSP_MAX_AGE (-1L)
 #endif
 
 #if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT)
-#define EXIM_HAVE_OPENSSL_TLSEXT
+# 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. */
@@ -88,7 +93,7 @@ static BOOL reexpand_tls_files_for_sni = FALSE;
 typedef struct tls_ext_ctx_cb {
   uschar *certificate;
   uschar *privatekey;
-#ifdef EXPERIMENTAL_OCSP
+#ifndef DISABLE_OCSP
   BOOL is_server;
   union {
     struct {
@@ -107,6 +112,10 @@ typedef struct tls_ext_ctx_cb {
   uschar *server_cipher_list;
   /* only passed down to tls_error: */
   host_item *host;
+
+#ifdef EXPERIMENTAL_CERTNAMES
+  uschar * verify_cert_hostnames;
+#endif
 } tls_ext_ctx_cb;
 
 /* should figure out a cleanup of API to handle state preserved per
@@ -123,7 +132,7 @@ setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL opt
 #ifdef EXIM_HAVE_OPENSSL_TLSEXT
 static int tls_servername_cb(SSL *s, int *ad ARG_UNUSED, void *arg);
 #endif
-#ifdef EXPERIMENTAL_OCSP
+#ifndef DISABLE_OCSP
 static int tls_server_stapling_cb(SSL *s, void *arg);
 #endif
 
@@ -209,7 +218,7 @@ return rsa_key;
 
 
 /* Extreme debug
-#if defined(EXPERIMENTAL_OCSP)
+#ifndef DISABLE_OCSP
 void
 x509_store_dump_cert_s_names(X509_STORE * store)
 {
@@ -262,64 +271,103 @@ Returns:     1 if verified, 0 if not
 */
 
 static int
-verify_callback(int state, X509_STORE_CTX *x509ctx, tls_support *tlsp, BOOL *calledp, BOOL *optionalp)
+verify_callback(int state, X509_STORE_CTX *x509ctx,
+  tls_support *tlsp, BOOL *calledp, BOOL *optionalp)
 {
+X509 * cert = X509_STORE_CTX_get_current_cert(x509ctx);
 static uschar txt[256];
 
-X509_NAME_oneline(X509_get_subject_name(x509ctx->current_cert),
-  CS txt, sizeof(txt));
+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",
-    x509ctx->error_depth,
-    X509_verify_cert_error_string(x509ctx->error),
+    X509_STORE_CTX_get_error_depth(x509ctx),
+    X509_verify_cert_error_string(X509_STORE_CTX_get_error(x509ctx)),
     txt);
   tlsp->certificate_verified = FALSE;
   *calledp = TRUE;
   if (!*optionalp)
     {
-    tlsp->peercert = X509_dup(x509ctx->current_cert);
+    tlsp->peercert = X509_dup(cert);
     return 0;                      /* reject */
     }
   DEBUG(D_tls) debug_printf("SSL verify failure overridden (host in "
     "tls_try_verify_hosts)\n");
-  return 1;                          /* accept */
   }
 
-if (x509ctx->error_depth != 0)
+else if (X509_STORE_CTX_get_error_depth(x509ctx) != 0)
   {
-  DEBUG(D_tls) debug_printf("SSL verify ok: depth=%d cert=%s\n",
-     x509ctx->error_depth, txt);
-#ifdef EXPERIMENTAL_OCSP
+  DEBUG(D_tls) debug_printf("SSL verify ok: depth=%d SN=%s\n",
+     X509_STORE_CTX_get_error_depth(x509ctx), txt);
+#ifndef DISABLE_OCSP
   if (tlsp == &tls_out && client_static_cbinfo->u_ocsp.client.verify_store)
     {  /* client, wanting stapling  */
     /* Add the server cert's signing chain as the one
     for the verification of the OCSP stapled information. */
   
     if (!X509_STORE_add_cert(client_static_cbinfo->u_ocsp.client.verify_store,
-                             x509ctx->current_cert))
+                             cert))
       ERR_clear_error();
     }
 #endif
   }
 else
   {
-  DEBUG(D_tls) debug_printf("SSL%s peer: %s\n",
-    *calledp ? "" : " authenticated", txt);
+#ifdef EXPERIMENTAL_CERTNAMES
+  uschar * verify_cert_hostnames;
+#endif
+
   tlsp->peerdn = txt;
-  tlsp->peercert = X509_dup(x509ctx->current_cert);
-  }
+  tlsp->peercert = X509_dup(cert);
 
-/*XXX JGH: this looks bogus - we set "verified" first time through, which
-will be for the root CS cert (calls work down the chain).  Why should it
-not be on the last call, where we're setting peerdn?
+#ifdef EXPERIMENTAL_CERTNAMES
+  if (  tlsp == &tls_out
+     && ((verify_cert_hostnames = client_static_cbinfo->verify_cert_hostnames)))
+       /* client, wanting hostname check */
 
-To test: set up a chain anchored by a good root-CA but with a bad server cert.
-Does certificate_verified get set?
-*/
-if (!*calledp) tlsp->certificate_verified = TRUE;
-*calledp = TRUE;
+# 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;
+    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,
+       "SSL verify error: certificate name mismatch: \"%s\"\n", txt);
+      return 0;                                /* reject */
+      }
+    }
+# else
+    if (!tls_is_name_for_cert(verify_cert_hostnames, cert))
+      {
+      log_write(0, LOG_MAIN,
+       "SSL verify error: certificate name mismatch: \"%s\"\n", txt);
+      return 0;                                /* reject */
+      }
+# endif
+#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 */
 }
@@ -388,14 +436,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));
@@ -410,8 +455,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));
@@ -420,8 +464,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),
@@ -455,7 +498,7 @@ return TRUE;
 
 
 
-#ifdef EXPERIMENTAL_OCSP
+#ifndef DISABLE_OCSP
 /*************************************************
 *       Load OCSP information into state         *
 *************************************************/
@@ -589,7 +632,7 @@ bad:
     }
 return;
 }
-#endif /*EXPERIMENTAL_OCSP*/
+#endif /*!DISABLE_OCSP*/
 
 
 
@@ -651,7 +694,7 @@ if (expanded != NULL && *expanded != 0)
       "SSL_CTX_use_PrivateKey_file file=%s", expanded), cbinfo->host, NULL);
   }
 
-#ifdef EXPERIMENTAL_OCSP
+#ifndef DISABLE_OCSP
 if (cbinfo->is_server &&  cbinfo->u_ocsp.server.file != NULL)
   {
   if (!expand_check(cbinfo->u_ocsp.server.file, US"tls_ocsp_file", &expanded))
@@ -722,8 +765,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);
@@ -741,7 +783,7 @@ SSL_CTX_set_tlsext_servername_callback(server_sni, tls_servername_cb);
 SSL_CTX_set_tlsext_servername_arg(server_sni, cbinfo);
 if (cbinfo->server_cipher_list)
   SSL_CTX_set_cipher_list(server_sni, CS cbinfo->server_cipher_list);
-#ifdef EXPERIMENTAL_OCSP
+#ifndef DISABLE_OCSP
 if (cbinfo->u_ocsp.server.file)
   {
   SSL_CTX_set_tlsext_status_cb(server_sni, tls_server_stapling_cb);
@@ -757,8 +799,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);
@@ -770,7 +812,7 @@ return SSL_TLSEXT_ERR_OK;
 
 
 
-#ifdef EXPERIMENTAL_OCSP
+#ifndef DISABLE_OCSP
 
 /*************************************************
 *        Callback to handle OCSP Stapling        *
@@ -791,11 +833,8 @@ const tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg;
 uschar *response_der;
 int response_der_len;
 
-if (log_extra_selector & LX_tls_cipher)
-  log_write(0, LOG_MAIN, "[%s] Recieved OCSP stapling req;%s responding",
-    sender_host_address, cbinfo->u_ocsp.server.response ? "":" not");
-else
-  DEBUG(D_tls) debug_printf("Received TLS status request (OCSP stapling); %s response.",
+DEBUG(D_tls)
+  debug_printf("Received TLS status request (OCSP stapling); %s response.",
     cbinfo->u_ocsp.server.response ? "have" : "lack");
 
 tls_in.ocsp = OCSP_NOT_RESP;
@@ -957,7 +996,7 @@ if(!(bs = OCSP_response_get1_basic(rsp)))
 OCSP_RESPONSE_free(rsp);
 return i;
 }
-#endif /*EXPERIMENTAL_OCSP*/
+#endif /*!DISABLE_OCSP*/
 
 
 
@@ -965,16 +1004,18 @@ return i;
 *            Initialize for TLS                  *
 *************************************************/
 
-/* Called from both server and client code, to do preliminary initialization of
-the library.
+/* Called from both server and client code, to do preliminary initialization
+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 callback context
 
 Returns:          OK/DEFER/FAIL
 */
@@ -982,7 +1023,7 @@ Returns:          OK/DEFER/FAIL
 static int
 tls_init(SSL_CTX **ctxp, host_item *host, uschar *dhparam, uschar *certificate,
   uschar *privatekey,
-#ifdef EXPERIMENTAL_OCSP
+#ifndef DISABLE_OCSP
   uschar *ocsp_file,
 #endif
   address_item *addr, tls_ext_ctx_cb ** cbp)
@@ -995,7 +1036,7 @@ tls_ext_ctx_cb *cbinfo;
 cbinfo = store_malloc(sizeof(tls_ext_ctx_cb));
 cbinfo->certificate = certificate;
 cbinfo->privatekey = privatekey;
-#ifdef EXPERIMENTAL_OCSP
+#ifndef DISABLE_OCSP
 if ((cbinfo->is_server = host==NULL))
   {
   cbinfo->u_ocsp.server.file = ocsp_file;
@@ -1006,6 +1047,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 */
@@ -1097,7 +1139,7 @@ if (rc != OK) return rc;
 #ifdef EXIM_HAVE_OPENSSL_TLSEXT
 if (host == NULL)              /* server */
   {
-# ifdef EXPERIMENTAL_OCSP
+# ifndef DISABLE_OCSP
   /* We check u_ocsp.server.file, not server.response, because we care about if
   the option exists, not what the current expansion might be, as SNI might
   change the certificate and OCSP file in use between now and the time the
@@ -1113,7 +1155,7 @@ if (host == NULL)         /* server */
   SSL_CTX_set_tlsext_servername_callback(*ctxp, tls_servername_cb);
   SSL_CTX_set_tlsext_servername_arg(*ctxp, cbinfo);
   }
-# ifdef EXPERIMENTAL_OCSP
+# ifndef DISABLE_OCSP
 else                   /* client */
   if(ocsp_file)                /* wanting stapling */
     {
@@ -1128,6 +1170,10 @@ else                     /* client */
 # endif
 #endif
 
+#ifdef EXPERIMENTAL_CERTNAMES
+cbinfo->verify_cert_hostnames = NULL;
+#endif
+
 /* Set up the RSA callback */
 
 SSL_CTX_set_tmp_rsa_callback(*ctxp, rsa_callback);
@@ -1346,7 +1392,7 @@ if (tls_in.active >= 0)
 the error. */
 
 rc = tls_init(&server_ctx, NULL, tls_dhparam, tls_certificate, tls_privatekey,
-#ifdef EXPERIMENTAL_OCSP
+#ifndef DISABLE_OCSP
     tls_ocsp_file,
 #endif
     NULL, &server_static_cbinfo);
@@ -1488,6 +1534,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             *
@@ -1516,17 +1606,35 @@ uschar *expciphers;
 X509* server_cert;
 int rc;
 static uschar cipherbuf[256];
-#ifdef EXPERIMENTAL_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;
+
+#ifndef DISABLE_OCSP
+BOOL require_ocsp = FALSE;
+BOOL request_ocsp = FALSE;
+#endif
+#ifdef EXPERIMENTAL_DANE
+BOOL dane_in_use;
+#endif
+
+#ifdef EXPERIMENTAL_DANE
+/*XXX TBD: test for transport options, and for TLSA records */
+dane_in_use = FALSE;
+
+if (!dane_in_use)
+#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;
+  }
 #endif
 
 rc = tls_init(&client_ctx, host, NULL,
     ob->tls_certificate, ob->tls_privatekey,
-#ifdef EXPERIMENTAL_OCSP
+#ifndef DISABLE_OCSP
     (void *)(long)request_ocsp,
 #endif
     addr, &client_static_cbinfo);
@@ -1552,24 +1660,24 @@ 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_in_use)
   {
-  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;
+  if (!DANESSL_library_init())
+    return tls_error(US"library init", host, US"DANE library error");
+  if (DANESSL_CTX_init(client_ctx) <= 0)
+    return tls_error(US"context init", host, US"DANE library error");
   }
-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)
+else
+
+#endif
+
+  if ((rc = tls_client_basic_ctx_init(client_ctx, host, ob
+#ifdef EXPERIMENTAL_CERTNAMES
+                               , client_static_cbinfo
+#endif
+                               )) != OK)
     return rc;
-  client_verify_optional = TRUE;
-  }
 
 if ((client_ssl = SSL_new(client_ctx)) == NULL)
   return tls_error(US"SSL_new", host, NULL);
@@ -1600,7 +1708,7 @@ if (ob->tls_sni)
     }
   }
 
-#ifdef EXPERIMENTAL_OCSP
+#ifndef DISABLE_OCSP
 /* Request certificate status at connection-time.  If the server
 does OCSP stapling we will get the callback (set in tls_init()) */
 if (request_ocsp)
@@ -1611,6 +1719,23 @@ if (request_ocsp)
   }
 #endif
 
+#ifdef EXPERIMENTAL_DANE
+if (dane_in_use)
+  {
+  if (DANESSL_init(client_ssl, NULL, NULL /*??? hostnames*/) != 1)
+    return tls_error(US"hostnames load", host, US"DANE library error");
+
+  /*
+  foreach TLSA record
+    
+    DANESSL_add_tlsa(client_ssl, uint8_t usage, uint8_t selector,
+       const char *mdname,
+        unsigned const char *data, size_t dlen)
+  */
+  }
+#endif
+
+
 /* There doesn't seem to be a built-in timeout on connection. */
 
 DEBUG(D_tls) debug_printf("Calling SSL_connect\n");
@@ -1619,6 +1744,10 @@ alarm(ob->command_timeout);
 rc = SSL_connect(client_ssl);
 alarm(0);
 
+#ifdef EXPERIMENTAL_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);