OpenSSL: fix OCSP stapling under DANE
[exim.git] / src / src / tls-openssl.c
index 6f10cf5f7d03ca0bba0fd30c36f3d6ee06d0c050..a542d4db06584aa732cd3f814ced7498cf0ccddb 100644 (file)
@@ -29,7 +29,7 @@ functions from the OpenSSL library. */
 # include <openssl/ocsp.h>
 #endif
 #ifdef EXPERIMENTAL_DANE
-# include <danessl.h>
+# include "danessl.h"
 #endif
 
 
@@ -94,6 +94,10 @@ functions from the OpenSSL library. */
 # define DISABLE_OCSP
 #endif
 
+#ifdef EXIM_HAVE_OPENSSL_CHECKHOST
+# include <openssl/x509v3.h>
+#endif
+
 /* Structure for collecting random data for seeding. */
 
 typedef struct randstuff {
@@ -147,8 +151,8 @@ static BOOL reexpand_tls_files_for_sni = FALSE;
 typedef struct tls_ext_ctx_cb {
   uschar *certificate;
   uschar *privatekey;
-#ifndef DISABLE_OCSP
   BOOL is_server;
+#ifndef DISABLE_OCSP
   STACK_OF(X509) *verify_stack;                /* chain for verifying the proof */
   union {
     struct {
@@ -228,14 +232,13 @@ return host ? FAIL : DEFER;
 
 
 
-#ifdef EXIM_HAVE_EPHEM_RSA_KEX
 /*************************************************
 *        Callback to generate RSA key            *
 *************************************************/
 
 /*
 Arguments:
-  s          SSL connection
+  s          SSL connection (not used)
   export     not used
   keylength  keylength
 
@@ -270,7 +273,6 @@ if (!(rsa_key = RSA_generate_key(keylength, RSA_F4, NULL, NULL)))
   }
 return rsa_key;
 }
-#endif
 
 
 
@@ -538,8 +540,21 @@ DEBUG(D_tls) debug_printf("verify_callback_client_dane: %s depth %d %s\n",
 #endif
 
 if (preverify_ok == 1)
-  tls_out.dane_verified =
-  tls_out.certificate_verified = TRUE;
+  {
+  tls_out.dane_verified = tls_out.certificate_verified = TRUE;
+#ifndef DISABLE_OCSP
+  if (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,
+                             cert))
+      ERR_clear_error();
+    sk_X509_push(client_static_cbinfo->verify_stack, cert);
+    }
+#endif
+  }
 else
   {
   int err = X509_STORE_CTX_get_error(x509ctx);
@@ -935,7 +950,7 @@ if (!OCSP_check_validity(thisupd, nextupd, EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX
   }
 
 supply_response:
-  cbinfo->u_ocsp.server.response = resp;
+  cbinfo->u_ocsp.server.response = resp;       /*XXX stack?*/
 return;
 
 bad:
@@ -943,7 +958,7 @@ bad:
     {
     extern char ** environ;
     uschar ** p;
-    if (environ) for (p = USS environ; *p != NULL; p++)
+    if (environ) for (p = USS environ; *p; p++)
       if (Ustrncmp(*p, "EXIM_TESTHARNESS_DISABLE_OCSPVALIDITYCHECK", 42) == 0)
        {
        DEBUG(D_tls) debug_printf("Supplying known bad OCSP response\n");
@@ -977,8 +992,7 @@ if (!(x509 = X509_new()))
   goto err;
 
 where = US"generating pkey";
-               /* deprecated, use RSA_generate_key_ex() */
-if (!(rsa = RSA_generate_key(1024, RSA_F4, NULL, NULL)))
+if (!(rsa = rsa_callback(NULL, 0, 1024)))
   goto err;
 
 where = US"assigning pkey";
@@ -1024,6 +1038,30 @@ err:
 
 
 
+static int
+tls_add_certfile(SSL_CTX * sctx, tls_ext_ctx_cb * cbinfo, uschar * file,
+  uschar ** errstr)
+{
+DEBUG(D_tls) debug_printf("tls_certificate file %s\n", file);
+if (!SSL_CTX_use_certificate_chain_file(sctx, CS file))
+  return tls_error(string_sprintf(
+    "SSL_CTX_use_certificate_chain_file file=%s", file),
+      cbinfo->host, NULL, errstr);
+return 0;
+}
+
+static int
+tls_add_pkeyfile(SSL_CTX * sctx, tls_ext_ctx_cb * cbinfo, uschar * file,
+  uschar ** errstr)
+{
+DEBUG(D_tls) debug_printf("tls_privatekey file %s\n", file);
+if (!SSL_CTX_use_PrivateKey_file(sctx, CS file, SSL_FILETYPE_PEM))
+  return tls_error(string_sprintf(
+    "SSL_CTX_use_PrivateKey_file file=%s", file), cbinfo->host, NULL, errstr);
+return 0;
+}
+
+
 /*************************************************
 *        Expand key and cert file specs          *
 *************************************************/
@@ -1048,7 +1086,7 @@ uschar *expanded;
 
 if (!cbinfo->certificate)
   {
-  if (cbinfo->host)                    /* client */
+  if (!cbinfo->is_server)              /* client */
     return OK;
                                        /* server */
   if (tls_install_selfsign(sctx, errstr) != OK)
@@ -1056,6 +1094,8 @@ if (!cbinfo->certificate)
   }
 else
   {
+  int err;
+
   if (Ustrstr(cbinfo->certificate, US"tls_sni") ||
       Ustrstr(cbinfo->certificate, US"tls_in_sni") ||
       Ustrstr(cbinfo->certificate, US"tls_out_sni")
@@ -1065,14 +1105,20 @@ else
   if (!expand_check(cbinfo->certificate, US"tls_certificate", &expanded, errstr))
     return DEFER;
 
-  if (expanded != NULL)
-    {
-    DEBUG(D_tls) debug_printf("tls_certificate file %s\n", expanded);
-    if (!SSL_CTX_use_certificate_chain_file(sctx, CS expanded))
-      return tls_error(string_sprintf(
-       "SSL_CTX_use_certificate_chain_file file=%s", expanded),
-         cbinfo->host, NULL, errstr);
-    }
+  if (expanded)
+    if (cbinfo->is_server)
+      {
+      const uschar * file_list = expanded;
+      int sep = 0;
+      uschar * file;
+
+      while (file = string_nextinlist(&file_list, &sep, NULL, 0))
+       if ((err = tls_add_certfile(sctx, cbinfo, file, errstr)))
+         return err;
+      }
+    else       /* would there ever be a need for multiple client certs? */
+      if ((err = tls_add_certfile(sctx, cbinfo, expanded, errstr)))
+       return err;
 
   if (cbinfo->privatekey != NULL &&
       !expand_check(cbinfo->privatekey, US"tls_privatekey", &expanded, errstr))
@@ -1083,17 +1129,25 @@ else
   key is in the same file as the certificate. */
 
   if (expanded && *expanded)
-    {
-    DEBUG(D_tls) debug_printf("tls_privatekey file %s\n", expanded);
-    if (!SSL_CTX_use_PrivateKey_file(sctx, CS expanded, SSL_FILETYPE_PEM))
-      return tls_error(string_sprintf(
-       "SSL_CTX_use_PrivateKey_file file=%s", expanded), cbinfo->host, NULL, errstr);
-    }
+    if (cbinfo->is_server)
+      {
+      const uschar * file_list = expanded;
+      int sep = 0;
+      uschar * file;
+
+      while (file = string_nextinlist(&file_list, &sep, NULL, 0))
+       if ((err = tls_add_pkeyfile(sctx, cbinfo, file, errstr)))
+         return err;
+      }
+    else       /* would there ever be a need for multiple client certs? */
+      if ((err = tls_add_pkeyfile(sctx, cbinfo, expanded, errstr)))
+       return err;
   }
 
 #ifndef DISABLE_OCSP
 if (cbinfo->is_server && cbinfo->u_ocsp.server.file)
   {
+  /*XXX stack*/
   if (!expand_check(cbinfo->u_ocsp.server.file, US"tls_ocsp_file", &expanded, errstr))
     return DEFER;
 
@@ -1231,9 +1285,15 @@ static int
 tls_server_stapling_cb(SSL *s, void *arg)
 {
 const tls_ext_ctx_cb *cbinfo = (tls_ext_ctx_cb *) arg;
-uschar *response_der;
+uschar *response_der;  /*XXX blob */
 int response_der_len;
 
+/*XXX stack: use SSL_get_certificate() to see which cert; from that work
+out which ocsp blob to send.  Unfortunately, SSL_get_certificate is known
+buggy in current OpenSSL; it returns the last cert loaded always rather than
+the one actually presented.  So we can't support a stack of OCSP proofs at
+this time. */
+
 DEBUG(D_tls)
   debug_printf("Received TLS status request (OCSP stapling); %s response\n",
     cbinfo->u_ocsp.server.response ? "have" : "lack");
@@ -1243,7 +1303,7 @@ if (!cbinfo->u_ocsp.server.response)
   return SSL_TLSEXT_ERR_NOACK;
 
 response_der = NULL;
-response_der_len = i2d_OCSP_RESPONSE(cbinfo->u_ocsp.server.response,
+response_der_len = i2d_OCSP_RESPONSE(cbinfo->u_ocsp.server.response,   /*XXX stack*/
                      &response_der);
 if (response_der_len <= 0)
   return SSL_TLSEXT_ERR_NOACK;
@@ -1317,7 +1377,7 @@ if(!(bs = OCSP_response_get1_basic(rsp)))
     int status, reason;
     ASN1_GENERALIZEDTIME *rev, *thisupd, *nextupd;
 
-    DEBUG(D_tls) bp = BIO_new_fp(stderr, BIO_NOCLOSE);
+    DEBUG(D_tls) bp = BIO_new_fp(debug_file, BIO_NOCLOSE);
 
     /*OCSP_RESPONSE_print(bp, rsp, 0);   extreme debug: stapling content */
 
@@ -1328,10 +1388,12 @@ if(!(bs = OCSP_response_get1_basic(rsp)))
              cbinfo->u_ocsp.client.verify_store, 0)) <= 0)
       {
       tls_out.ocsp = OCSP_FAILED;
-      if (LOGGING(tls_cipher))
-       log_write(0, LOG_MAIN, "Received TLS cert status response, itself unverifiable");
+      if (LOGGING(tls_cipher)) log_write(0, LOG_MAIN,
+             "Received TLS cert status response, itself unverifiable: %s",
+             ERR_reason_error_string(ERR_peek_error()));
       BIO_printf(bp, "OCSP response verify failure\n");
       ERR_print_errors(bp);
+      OCSP_RESPONSE_print(bp, rsp, 0);
       goto failed;
       }
 
@@ -1435,7 +1497,7 @@ static int
 tls_init(SSL_CTX **ctxp, host_item *host, uschar *dhparam, uschar *certificate,
   uschar *privatekey,
 #ifndef DISABLE_OCSP
-  uschar *ocsp_file,
+  uschar *ocsp_file,   /*XXX stack, in server*/
 #endif
   address_item *addr, tls_ext_ctx_cb ** cbp, uschar ** errstr)
 {
@@ -1447,9 +1509,10 @@ tls_ext_ctx_cb * cbinfo;
 cbinfo = store_malloc(sizeof(tls_ext_ctx_cb));
 cbinfo->certificate = certificate;
 cbinfo->privatekey = privatekey;
+cbinfo->is_server = host==NULL;
 #ifndef DISABLE_OCSP
 cbinfo->verify_stack = NULL;
-if ((cbinfo->is_server = host==NULL))
+if (!host)
   {
   cbinfo->u_ocsp.server.file = ocsp_file;
   cbinfo->u_ocsp.server.file_expanded = NULL;
@@ -1762,7 +1825,7 @@ if (expcerts && *expcerts)
           )
          {
          log_write(0, LOG_MAIN|LOG_PANIC,
-           "failed to load cert hain from %s", file);
+           "failed to load cert chain from %s", file);
          return DEFER;
          }
 #endif
@@ -1905,7 +1968,7 @@ the error. */
 
 rc = tls_init(&server_ctx, NULL, tls_dhparam, tls_certificate, tls_privatekey,
 #ifndef DISABLE_OCSP
-    tls_ocsp_file,
+    tls_ocsp_file,     /*XXX stack*/
 #endif
     NULL, &server_static_cbinfo, errstr);
 if (rc != OK) return rc;