Support optional server certificate name checking. Bug 1479
[exim.git] / src / src / tls-openssl.c
index db2544c037f56ffcfa1a0ebb985fbc5f57445ca9..1d6b91470aa87d94eb5fe951470a500471afbc2e 100644 (file)
@@ -23,7 +23,7 @@ functions from the OpenSSL library. */
 #include <openssl/err.h>
 #include <openssl/rand.h>
 #ifdef EXPERIMENTAL_OCSP
-#include <openssl/ocsp.h>
+# include <openssl/ocsp.h>
 #endif
 
 #ifdef EXPERIMENTAL_OCSP
@@ -32,7 +32,7 @@ functions from the OpenSSL library. */
 #endif
 
 #if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT)
-#define EXIM_HAVE_OPENSSL_TLSEXT
+# define EXIM_HAVE_OPENSSL_TLSEXT
 #endif
 
 /* Structure for collecting random data for seeding. */
@@ -107,6 +107,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
@@ -262,35 +266,35 @@ 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);
+  DEBUG(D_tls) debug_printf("SSL verify ok: depth=%d SN=%s\n",
+     X509_STORE_CTX_get_error_depth(x509ctx), txt);
 #ifdef EXPERIMENTAL_OCSP
   if (tlsp == &tls_out && client_static_cbinfo->u_ocsp.client.verify_store)
     {  /* client, wanting stapling  */
@@ -298,28 +302,55 @@ if (x509ctx->error_depth != 0)
     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
+    {
+    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))
+       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 */
 }
@@ -791,11 +822,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;
@@ -965,8 +993,8 @@ 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:
   host            connected host, if client; NULL if server
@@ -975,6 +1003,7 @@ Arguments:
   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
 
 Returns:          OK/DEFER/FAIL
 */
@@ -1128,6 +1157,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);
@@ -1555,6 +1588,7 @@ if (expciphers != 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))
   {
@@ -1562,6 +1596,19 @@ if ((!ob->tls_verify_hosts && !ob->tls_try_verify_hosts) ||
        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",
+                     &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);
+    }
+#endif
   }
 else if (verify_check_host(&ob->tls_try_verify_hosts) == OK)
   {