OCSP observability: variables $tls_{in,out}_ocsp
[exim.git] / src / src / tls-openssl.c
index 93ee07967d9c26b1d702957b082ad0c696d65a64..fd257f3c6aaacb5e64aea6584778718ecabb0972 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2012 */
+/* Copyright (c) University of Cambridge 1995 - 2014 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Portions Copyright (c) The OpenSSL Project 1999 */
@@ -97,7 +97,8 @@ typedef struct tls_ext_ctx_cb {
       OCSP_RESPONSE *response;
     } server;
     struct {
-      X509_STORE    *verify_store;
+      X509_STORE    *verify_store;     /* non-null if status requested */
+      BOOL         verify_required;
     } client;
   } u_ocsp;
 #endif
@@ -115,7 +116,8 @@ tls_ext_ctx_cb *client_static_cbinfo = NULL;
 tls_ext_ctx_cb *server_static_cbinfo = NULL;
 
 static int
-setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional, BOOL client);
+setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional,
+    int (*cert_vfy_cb)(int, X509_STORE_CTX *) );
 
 /* Callbacks */
 #ifdef EXIM_HAVE_OPENSSL_TLSEXT
@@ -275,7 +277,11 @@ if (state == 0)
     txt);
   tlsp->certificate_verified = FALSE;
   *calledp = TRUE;
-  if (!*optionalp) return 0;    /* reject */
+  if (!*optionalp)
+    {
+    tlsp->peercert = X509_dup(x509ctx->current_cert);
+    return 0;                      /* reject */
+    }
   DEBUG(D_tls) debug_printf("SSL verify failure overridden (host in "
     "tls_try_verify_hosts)\n");
   return 1;                          /* accept */
@@ -302,11 +308,15 @@ else
   DEBUG(D_tls) debug_printf("SSL%s peer: %s\n",
     *calledp ? "" : " authenticated", txt);
   tlsp->peerdn = txt;
+  tlsp->peercert = X509_dup(x509ctx->current_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?
+
+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;
@@ -739,7 +749,7 @@ if (cbinfo->u_ocsp.server.file)
   }
 #endif
 
-rc = setup_certs(server_sni, tls_verify_certificates, tls_crl, NULL, FALSE, FALSE);
+rc = setup_certs(server_sni, tls_verify_certificates, tls_crl, NULL, FALSE, verify_callback_server);
 if (rc != OK) return SSL_TLSEXT_ERR_NOACK;
 
 /* do this after setup_certs, because this can require the certs for verifying
@@ -788,15 +798,18 @@ else
   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;
 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);
+response_der_len = i2d_OCSP_RESPONSE(cbinfo->u_ocsp.server.response,
+                     &response_der);
 if (response_der_len <= 0)
   return SSL_TLSEXT_ERR_NOACK;
 
 SSL_set_tlsext_status_ocsp_resp(server_ssl, response_der, response_der_len);
+tls_in.ocsp = OCSP_VFIED;
 return SSL_TLSEXT_ERR_OK;
 }
 
@@ -823,12 +836,15 @@ DEBUG(D_tls) debug_printf("Received TLS status response (OCSP stapling):");
 len = SSL_get_tlsext_status_ocsp_resp(s, &p);
 if(!p)
  {
-  if (log_extra_selector & LX_tls_cipher)
-    log_write(0, LOG_MAIN, "Received TLS status response, null content");
+  /* Expect this when we requested ocsp but got none */
+  if (  cbinfo->u_ocsp.client.verify_required
+     && log_extra_selector & LX_tls_cipher)
+    log_write(0, LOG_MAIN, "Received TLS status callback, null content");
   else
     DEBUG(D_tls) debug_printf(" null\n");
-  return 0;    /* This is the fail case for require-ocsp; none from server */
+  return cbinfo->u_ocsp.client.verify_required ? 0 : 1;
  }
+tls_out.ocsp = OCSP_NOT_VFY;
 if(!(rsp = d2i_OCSP_RESPONSE(NULL, &p, len)))
  {
   if (log_extra_selector & LX_tls_cipher)
@@ -869,11 +885,12 @@ if(!(bs = OCSP_response_get1_basic(rsp)))
     /* Use the chain that verified the server cert to verify the stapled info */
     /* DEBUG(D_tls) x509_store_dump_cert_s_names(cbinfo->u_ocsp.client.verify_store); */
 
-    if ((i = OCSP_basic_verify(bs, NULL, cbinfo->u_ocsp.client.verify_store, 0)) <= 0)
+    if ((i = OCSP_basic_verify(bs, NULL,
+             cbinfo->u_ocsp.client.verify_store, 0)) <= 0)
       {
       BIO_printf(bp, "OCSP response verify failure\n");
       ERR_print_errors(bp);
-      i = 0;
+      i = cbinfo->u_ocsp.client.verify_required ? 0 : 1;
       goto out;
       }
 
@@ -885,39 +902,48 @@ if(!(bs = OCSP_response_get1_basic(rsp)))
 
       if (sk_OCSP_SINGLERESP_num(sresp) != 1)
         {
-        log_write(0, LOG_MAIN, "OCSP stapling with multiple responses not handled");
+        log_write(0, LOG_MAIN, "OCSP stapling "
+           "with multiple responses not handled");
+       i = cbinfo->u_ocsp.client.verify_required ? 0 : 1;
         goto out;
         }
       single = OCSP_resp_get0(bs, 0);
-      status = OCSP_single_get0_status(single, &reason, &rev, &thisupd, &nextupd);
+      status = OCSP_single_get0_status(single, &reason, &rev,
+                 &thisupd, &nextupd);
       }
 
-    i = 0;
     DEBUG(D_tls) time_print(bp, "This OCSP Update", thisupd);
     DEBUG(D_tls) if(nextupd) time_print(bp, "Next OCSP Update", nextupd);
-    if (!OCSP_check_validity(thisupd, nextupd, EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX_AGE))
+    if (!OCSP_check_validity(thisupd, nextupd,
+         EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX_AGE))
       {
       DEBUG(D_tls) ERR_print_errors(bp);
       log_write(0, LOG_MAIN, "Server OSCP dates invalid");
-      goto out;
+      i = cbinfo->u_ocsp.client.verify_required ? 0 : 1;
       }
-
-    DEBUG(D_tls) BIO_printf(bp, "Certificate status: %s\n", OCSP_cert_status_str(status));
-    switch(status)
+    else
       {
-      case V_OCSP_CERTSTATUS_GOOD:
-        i = 1;
-        break;
-      case V_OCSP_CERTSTATUS_REVOKED:
-        log_write(0, LOG_MAIN, "Server certificate revoked%s%s",
-            reason != -1 ? "; reason: " : "", reason != -1 ? OCSP_crl_reason_str(reason) : "");
-        DEBUG(D_tls) time_print(bp, "Revocation Time", rev);
-        i = 0;
-        break;
-      default:
-        log_write(0, LOG_MAIN, "Server certificate status unknown, in OCSP stapling");
-        i = 0;
-        break;
+      DEBUG(D_tls) BIO_printf(bp, "Certificate status: %s\n",
+                   OCSP_cert_status_str(status));
+      switch(status)
+       {
+       case V_OCSP_CERTSTATUS_GOOD:
+         i = 1;
+         tls_out.ocsp = OCSP_VFIED;
+         break;
+       case V_OCSP_CERTSTATUS_REVOKED:
+         log_write(0, LOG_MAIN, "Server certificate revoked%s%s",
+             reason != -1 ? "; reason: " : "",
+             reason != -1 ? OCSP_crl_reason_str(reason) : "");
+         DEBUG(D_tls) time_print(bp, "Revocation Time", rev);
+         i = cbinfo->u_ocsp.client.verify_required ? 0 : 1;
+         break;
+       default:
+         log_write(0, LOG_MAIN,
+             "Server certificate status unknown, in OCSP stapling");
+         i = cbinfo->u_ocsp.client.verify_required ? 0 : 1;
+         break;
+       }
       }
   out:
     BIO_free(bp);
@@ -1133,37 +1159,9 @@ construct_cipher_name(SSL *ssl, uschar *cipherbuf, int bsize, int *bits)
 yet reflect that.  It should be a safe change anyway, even 0.9.8 versions have
 the accessor functions use const in the prototype. */
 const SSL_CIPHER *c;
-uschar *ver;
-
-switch (ssl->session->ssl_version)
-  {
-  case SSL2_VERSION:
-  ver = US"SSLv2";
-  break;
-
-  case SSL3_VERSION:
-  ver = US"SSLv3";
-  break;
+const uschar *ver;
 
-  case TLS1_VERSION:
-  ver = US"TLSv1";
-  break;
-
-#ifdef TLS1_1_VERSION
-  case TLS1_1_VERSION:
-  ver = US"TLSv1.1";
-  break;
-#endif
-
-#ifdef TLS1_2_VERSION
-  case TLS1_2_VERSION:
-  ver = US"TLSv1.2";
-  break;
-#endif
-
-  default:
-  ver = US"UNKNOWN";
-  }
+ver = (const uschar *)SSL_get_version(ssl);
 
 c = (const SSL_CIPHER *) SSL_get_current_cipher(ssl);
 SSL_CIPHER_get_bits(c, bits);
@@ -1191,13 +1189,14 @@ Arguments:
   host          NULL in a server; the remote host in a client
   optional      TRUE if called from a server for a host in tls_try_verify_hosts;
                 otherwise passed as FALSE
-  client        TRUE if called for client startup, FALSE for server startup
+  cert_vfy_cb  Callback function for certificate verification
 
 Returns:        OK/DEFER/FAIL
 */
 
 static int
-setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional, BOOL client)
+setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional,
+    int (*cert_vfy_cb)(int, X509_STORE_CTX *) )
 {
 uschar *expcerts, *expcrl;
 
@@ -1296,7 +1295,7 @@ if (expcerts != NULL && *expcerts != '\0')
 
   SSL_CTX_set_verify(sctx,
     SSL_VERIFY_PEER | (optional? 0 : SSL_VERIFY_FAIL_IF_NO_PEER_CERT),
-    client ? verify_callback_client : verify_callback_server);
+    cert_vfy_cb);
   }
 
 return OK;
@@ -1375,13 +1374,15 @@ server_verify_callback_called = FALSE;
 
 if (verify_check_host(&tls_verify_hosts) == OK)
   {
-  rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL, FALSE, FALSE);
+  rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL,
+                       FALSE, verify_callback_server);
   if (rc != OK) return rc;
   server_verify_optional = FALSE;
   }
 else if (verify_check_host(&tls_try_verify_hosts) == OK)
   {
-  rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL, TRUE, FALSE);
+  rc = setup_certs(server_ctx, tls_verify_certificates, tls_crl, NULL,
+                       TRUE, verify_callback_server);
   if (rc != OK) return rc;
   server_verify_optional = TRUE;
   }
@@ -1454,6 +1455,11 @@ DEBUG(D_tls)
     debug_printf("Shared ciphers: %s\n", buf);
   }
 
+/* Record the certificate we presented */
+  {
+  X509 * crt = SSL_get_certificate(server_ssl);
+  tls_in.ourcert = crt ? X509_dup(crt) : NULL;
+  }
 
 /* Only used by the server-side tls (tls_in), including tls_getc.
    Client-side (tls_out) reads (seem to?) go via
@@ -1488,15 +1494,7 @@ Argument:
   fd               the fd of the connection
   host             connected host (for messages)
   addr             the first address
-  certificate      certificate file
-  privatekey       private key file
-  sni              TLS SNI to send to remote host
-  verify_certs     file for certificate verify
-  crl              file containing CRL
-  require_ciphers  list of allowed ciphers
-  dh_min_bits      minimum number of bits acceptable in server's DH prime
-                   (unused in OpenSSL)
-  timeout          startup timeout
+  ob               smtp transport options
 
 Returns:           OK on success
                    FAIL otherwise - note that tls_error() will not give DEFER
@@ -1505,27 +1503,26 @@ Returns:           OK on success
 
 int
 tls_client_start(int fd, host_item *host, address_item *addr,
-  uschar *certificate, uschar *privatekey, uschar *sni,
-  uschar *verify_certs, uschar *crl,
-  uschar *require_ciphers,
-#ifdef EXPERIMENTAL_OCSP
-  uschar *hosts_require_ocsp,
-#endif
-  int dh_min_bits ARG_UNUSED, int timeout)
+  void *v_ob)
 {
+smtp_transport_options_block * ob = v_ob;
 static uschar txt[256];
 uschar *expciphers;
 X509* server_cert;
 int rc;
 static uschar cipherbuf[256];
 #ifdef EXPERIMENTAL_OCSP
-BOOL require_ocsp = verify_check_this_host(&hosts_require_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;
 #endif
 
-rc = tls_init(&client_ctx, host, NULL, certificate, privatekey,
+rc = tls_init(&client_ctx, host, NULL,
+    ob->tls_certificate, ob->tls_privatekey,
 #ifdef EXPERIMENTAL_OCSP
-    require_ocsp ? US"" : NULL,
+    (void *)(long)request_ocsp,
 #endif
     addr, &client_static_cbinfo);
 if (rc != OK) return rc;
@@ -1533,7 +1530,8 @@ if (rc != OK) return rc;
 tls_out.certificate_verified = FALSE;
 client_verify_callback_called = FALSE;
 
-if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers))
+if (!expand_check(ob->tls_require_ciphers, US"tls_require_ciphers",
+    &expciphers))
   return FAIL;
 
 /* In OpenSSL, cipher components are separated by hyphens. In GnuTLS, they
@@ -1549,17 +1547,34 @@ if (expciphers != NULL)
     return tls_error(US"SSL_CTX_set_cipher_list", host, NULL);
   }
 
-rc = setup_certs(client_ctx, verify_certs, crl, host, FALSE, TRUE);
-if (rc != OK) return 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(client_ctx, ob->tls_verify_certificates,
+       ob->tls_crl, host, FALSE, verify_callback_client)) != OK)
+    return rc;
+  client_verify_optional = FALSE;
+  }
+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)
+    return rc;
+  client_verify_optional = TRUE;
+  }
 
-if ((client_ssl = SSL_new(client_ctx)) == NULL) return tls_error(US"SSL_new", host, NULL);
+if ((client_ssl = SSL_new(client_ctx)) == NULL)
+  return tls_error(US"SSL_new", host, NULL);
 SSL_set_session_id_context(client_ssl, sid_ctx, Ustrlen(sid_ctx));
 SSL_set_fd(client_ssl, fd);
 SSL_set_connect_state(client_ssl);
 
-if (sni)
+if (ob->tls_sni)
   {
-  if (!expand_check(sni, US"tls_sni", &tls_out.sni))
+  if (!expand_check(ob->tls_sni, US"tls_sni", &tls_out.sni))
     return FAIL;
   if (tls_out.sni == NULL)
     {
@@ -1583,15 +1598,19 @@ if (sni)
 #ifdef EXPERIMENTAL_OCSP
 /* Request certificate status at connection-time.  If the server
 does OCSP stapling we will get the callback (set in tls_init()) */
-if (require_ocsp)
+if (request_ocsp)
+  {
   SSL_set_tlsext_status_type(client_ssl, TLSEXT_STATUSTYPE_ocsp);
+  client_static_cbinfo->u_ocsp.client.verify_required = require_ocsp;
+  tls_out.ocsp = OCSP_NOT_RESP;
+  }
 #endif
 
 /* There doesn't seem to be a built-in timeout on connection. */
 
 DEBUG(D_tls) debug_printf("Calling SSL_connect\n");
 sigalrm_seen = FALSE;
-alarm(timeout);
+alarm(ob->command_timeout);
 rc = SSL_connect(client_ssl);
 alarm(0);
 
@@ -1601,12 +1620,13 @@ if (rc <= 0)
 DEBUG(D_tls) debug_printf("SSL_connect succeeded\n");
 
 /* Beware anonymous ciphers which lead to server_cert being NULL */
+/*XXX server_cert is never freed... use X509_free() */
 server_cert = SSL_get_peer_certificate (client_ssl);
 if (server_cert)
   {
   tls_out.peerdn = US X509_NAME_oneline(X509_get_subject_name(server_cert),
     CS txt, sizeof(txt));
-  tls_out.peerdn = txt;
+  tls_out.peerdn = txt;                /*XXX a static buffer... */
   }
 else
   tls_out.peerdn = NULL;
@@ -1614,6 +1634,12 @@ else
 construct_cipher_name(client_ssl, cipherbuf, sizeof(cipherbuf), &tls_out.bits);
 tls_out.cipher = cipherbuf;
 
+/* Record the certificate we presented */
+  {
+  X509 * crt = SSL_get_certificate(client_ssl);
+  tls_out.ourcert = crt ? X509_dup(crt) : NULL;
+  }
+
 tls_out.active = fd;
 return OK;
 }
@@ -1927,6 +1953,11 @@ one version of OpenSSL but the run-time linker picks up another version,
 it can result in serious failures, including crashing with a SIGSEGV.  So
 report the version found by the compiler and the run-time version.
 
+Note: some OS vendors backport security fixes without changing the version
+number/string, and the version date remains unchanged.  The _build_ date
+will change, so we can more usefully assist with version diagnosis by also
+reporting the build date.
+
 Arguments:   a FILE* to print the results to
 Returns:     nothing
 */
@@ -1935,9 +1966,13 @@ void
 tls_version_report(FILE *f)
 {
 fprintf(f, "Library version: OpenSSL: Compile: %s\n"
-           "                          Runtime: %s\n",
+           "                          Runtime: %s\n"
+           "                                 : %s\n",
            OPENSSL_VERSION_TEXT,
-           SSLeay_version(SSLEAY_VERSION));
+           SSLeay_version(SSLEAY_VERSION),
+           SSLeay_version(SSLEAY_BUILT_ON));
+/* third line is 38 characters for the %s and the line is 73 chars long;
+the OpenSSL output includes a "built on: " prefix already. */
 }
 
 
@@ -1963,12 +1998,26 @@ vaguely_random_number(int max)
 {
 unsigned int r;
 int i, needed_len;
+static pid_t pidlast = 0;
+pid_t pidnow;
 uschar *p;
 uschar smallbuf[sizeof(r)];
 
 if (max <= 1)
   return 0;
 
+pidnow = getpid();
+if (pidnow != pidlast)
+  {
+  /* Although OpenSSL documents that "OpenSSL makes sure that the PRNG state
+  is unique for each thread", this doesn't apparently apply across processes,
+  so our own warning from vaguely_random_number_fallback() applies here too.
+  Fix per PostgreSQL. */
+  if (pidlast != 0)
+    RAND_cleanup();
+  pidlast = pidnow;
+  }
+
 /* OpenSSL auto-seeds from /dev/random, etc, but this a double-check. */
 if (!RAND_status())
   {
@@ -2040,7 +2089,9 @@ all options unless explicitly for DTLS, let the administrator choose which
 to apply.
 
 This list is current as of:
-  ==>  1.0.1b  <==  */
+  ==>  1.0.1b  <==
+Plus SSL_OP_SAFARI_ECDHE_ECDSA_BUG from 2013-June patch/discussion on openssl-dev
+*/
 static struct exim_openssl_option exim_openssl_options[] = {
 /* KEEP SORTED ALPHABETICALLY! */
 #ifdef SSL_OP_ALL
@@ -2105,6 +2156,9 @@ static struct exim_openssl_option exim_openssl_options[] = {
 #ifdef SSL_OP_NO_TLSv1_2
   { US"no_tlsv1_2", SSL_OP_NO_TLSv1_2 },
 #endif
+#ifdef SSL_OP_SAFARI_ECDHE_ECDSA_BUG
+  { US"safari_ecdhe_ecdsa_bug", SSL_OP_SAFARI_ECDHE_ECDSA_BUG },
+#endif
 #ifdef SSL_OP_SINGLE_DH_USE
   { US"single_dh_use", SSL_OP_SINGLE_DH_USE },
 #endif
@@ -2226,4 +2280,6 @@ for (s=option_spec; *s != '\0'; /**/)
 return TRUE;
 }
 
+/* vi: aw ai sw=2
+*/
 /* End of tls-openssl.c */