GnuTLS: Move to more-modern stapling API
[exim.git] / src / src / tls-openssl.c
index 824212dbcbdfa87a2af1b6c61e7a2babfbb5b33d..dc8729106af9147bb2ed71acb789e55b03189a16 100644 (file)
@@ -148,7 +148,7 @@ Plus SSL_OP_NO_TLSv1_3 for 1.1.2-dev
 static exim_openssl_option exim_openssl_options[] = {
 /* KEEP SORTED ALPHABETICALLY! */
 #ifdef SSL_OP_ALL
-  { US"all", SSL_OP_ALL },
+  { US"all", (long) SSL_OP_ALL },
 #endif
 #ifdef SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION
   { US"allow_unsafe_legacy_renegotiation", SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION },
@@ -315,7 +315,7 @@ static SSL_CTX *server_sni = NULL;
 
 static char ssl_errstring[256];
 
-static int  ssl_session_timeout = 3600;
+static int  ssl_session_timeout = 7200;                /* Two hours */
 static BOOL client_verify_optional = FALSE;
 static BOOL server_verify_optional = FALSE;
 
@@ -372,7 +372,7 @@ static int tls_server_stapling_cb(SSL *s, void *arg);
 
 
 
-/* Daemon-called key create/rotate */
+/* Daemon-called, before every connection, key create/rotate */
 #ifdef EXPERIMENTAL_TLS_RESUME
 static void tk_init(void);
 static int tls_exdata_idx = -1;
@@ -526,6 +526,7 @@ if (ev)
       }
     DEBUG(D_tls) debug_printf("Event-action verify failure overridden "
       "(host in tls_try_verify_hosts)\n");
+    tlsp->verify_override = TRUE;
     }
   X509_free(tlsp->peercert);
   tlsp->peercert = old_cert;
@@ -585,6 +586,7 @@ if (!X509_NAME_oneline(X509_get_subject_name(cert), CS dn, sizeof(dn)))
   }
 dn[sizeof(dn)-1] = '\0';
 
+tlsp->verify_override = FALSE;
 if (preverify_ok == 0)
   {
   uschar * extra = verify_mode ? string_sprintf(" (during %c-verify for [%s])",
@@ -603,6 +605,7 @@ if (preverify_ok == 0)
     }
   DEBUG(D_tls) debug_printf("SSL verify failure overridden (host in "
     "tls_try_verify_hosts)\n");
+  tlsp->verify_override = TRUE;
   }
 
 else if (depth != 0)
@@ -679,8 +682,9 @@ else
          tlsp->peercert = X509_dup(cert);      /* record failing cert */
        return 0;                               /* reject */
        }
-      DEBUG(D_tls) debug_printf("SSL verify failure overridden (host in "
+      DEBUG(D_tls) debug_printf("SSL verify name failure overridden (host in "
        "tls_try_verify_hosts)\n");
+      tlsp->verify_override = TRUE;
       }
     }
 
@@ -691,7 +695,6 @@ else
 
   DEBUG(D_tls) debug_printf("SSL%s verify ok: depth=0 SN=%s\n",
     *calledp ? "" : " authenticated", dn);
-  if (!*calledp) tlsp->certificate_verified = TRUE;
   *calledp = TRUE;
   }
 
@@ -748,7 +751,7 @@ DEBUG(D_tls) debug_printf("verify_callback_client_dane: %s depth %d %s\n",
 
 if (preverify_ok == 1)
   {
-  tls_out.dane_verified = tls_out.certificate_verified = TRUE;
+  tls_out.dane_verified = TRUE;
 #ifndef DISABLE_OCSP
   if (client_static_cbinfo->u_ocsp.client.verify_store)
     {  /* client, wanting stapling  */
@@ -840,25 +843,24 @@ typedef struct {                  /* Session ticket encryption key */
   uschar       name[16];
 
   const EVP_CIPHER *   aes_cipher;
-  uschar               aes_key[16];    /* size needed depends on cipher. aes_128 implies 128/8 = 16? */
+  uschar               aes_key[32];    /* size needed depends on cipher. aes_128 implies 128/8 = 16? */
   const EVP_MD *       hmac_hash;
   uschar               hmac_key[16];
   time_t               renew;
   time_t               expire;
 } exim_stek;
 
-/*XXX for now just always create/find the one key.
-Worry about rotation and overlap later. */
-
-static exim_stek exim_tk;
-static exim_stek exim_tk_old;
+static exim_stek exim_tk;      /* current key */
+static exim_stek exim_tk_old;  /* previous key */
 
 static void
 tk_init(void)
 {
+time_t t = time(NULL);
+
 if (exim_tk.name[0])
   {
-  if (exim_tk.renew >= time(NULL)) return;
+  if (exim_tk.renew >= t) return;
   exim_tk_old = exim_tk;
   }
 
@@ -870,10 +872,10 @@ if (RAND_bytes(exim_tk.hmac_key, sizeof(exim_tk.hmac_key)) <= 0) return;
 if (RAND_bytes(exim_tk.name+1, sizeof(exim_tk.name)-1) <= 0) return;
 
 exim_tk.name[0] = 'E';
-exim_tk.aes_cipher = EVP_aes_128_cbc();
+exim_tk.aes_cipher = EVP_aes_256_cbc();
 exim_tk.hmac_hash = EVP_sha256();
-exim_tk.expire = time(NULL) + ssl_session_timeout;
-exim_tk.renew = exim_tk.expire - ssl_session_timeout/2;
+exim_tk.expire = t + ssl_session_timeout;
+exim_tk.renew = t + ssl_session_timeout/2;
 }
 
 static exim_stek *
@@ -910,7 +912,7 @@ if (enc)
   if (!(key = tk_current()))   /* current key doesn't exist or isn't valid */
      return 0;                 /* key couldn't be created */
   memcpy(key_name, key->name, 16);
-  DEBUG(D_tls) debug_printf("STEK expire %ld\n", key->expire - time(NULL));
+  DEBUG(D_tls) debug_printf("STEK expire " TIME_T_FMT "\n", key->expire - time(NULL));
 
   /*XXX will want these dependent on the ssl session strength */
   HMAC_Init_ex(hctx, key->hmac_key, sizeof(key->hmac_key),
@@ -932,7 +934,7 @@ else
     DEBUG(D_tls)
       {
       debug_printf("ticket not usable (%s)\n", key ? "expired" : "not found");
-      if (key) debug_printf("STEK expire %ld\n", key->expire - now);
+      if (key) debug_printf("STEK expire " TIME_T_FMT "\n", key->expire - now);
       }
     return 0;
     }
@@ -941,7 +943,13 @@ else
                key->hmac_hash, NULL);
   EVP_DecryptInit_ex(ctx, key->aes_cipher, NULL, key->aes_key, iv);
 
-  DEBUG(D_tls) debug_printf("ticket usable, STEK expire %ld\n", key->expire - now);
+  DEBUG(D_tls) debug_printf("ticket usable, STEK expire " TIME_T_FMT "\n", key->expire - now);
+
+  /* The ticket lifetime and renewal are the same as the STEK lifetime and
+  renewal, which is overenthusiastic.  A factor of, say, 3x longer STEK would
+  be better.  To do that we'd have to encode ticket lifetime in the name as
+  we don't yet see the restored session.  Could check posthandshake for TLS1.3
+  and trigger a new ticket then, but cannot do that for TLS1.2 */
   return key->renew < now ? 2 : 1;
   }
 }
@@ -1304,7 +1312,8 @@ if (!OCSP_check_validity(thisupd, nextupd, EXIM_OCSP_SKEW_SECONDS, EXIM_OCSP_MAX
   }
 
 supply_response:
-  cbinfo->u_ocsp.server.response = resp;       /*XXX stack?*/
+  /*XXX stack? (these tag points are for multiple leaf-cert support for ocsp */
+  cbinfo->u_ocsp.server.response = resp;
 return;
 
 bad:
@@ -1562,7 +1571,7 @@ DEBUG(D_tls) debug_printf("Received TLS SNI \"%s\"%s\n", servername,
 
 /* Make the extension value available for expansion */
 store_pool = POOL_PERM;
-tls_in.sni = string_copy(US servername);
+tls_in.sni = string_copy_taint(US servername, TRUE);
 store_pool = old_pool;
 
 if (!reexpand_tls_files_for_sni)
@@ -1707,17 +1716,17 @@ if(!p)
   return cbinfo->u_ocsp.client.verify_required ? 0 : 1;
  }
 
-if(!(rsp = d2i_OCSP_RESPONSE(NULL, &p, len)))
- {
-  tls_out.ocsp = OCSP_FAILED;
+if (!(rsp = d2i_OCSP_RESPONSE(NULL, &p, len)))
 {
+  tls_out.ocsp = OCSP_FAILED;  /*XXX should use tlsp-> to permit concurrent outbound */
   if (LOGGING(tls_cipher))
     log_write(0, LOG_MAIN, "Received TLS cert status response, parse error");
   else
     DEBUG(D_tls) debug_printf(" parse error\n");
   return 0;
- }
 }
 
-if(!(bs = OCSP_response_get1_basic(rsp)))
+if (!(bs = OCSP_response_get1_basic(rsp)))
   {
   tls_out.ocsp = OCSP_FAILED;
   if (LOGGING(tls_cipher))
@@ -2154,8 +2163,24 @@ if (tlsp->peercert)
     { DEBUG(D_tls) debug_printf("X509_NAME_oneline() error\n"); }
   else
     {
-    peerdn[siz-1] = '\0';
-    tlsp->peerdn = peerdn;             /*XXX a static buffer... */
+    int oldpool = store_pool;
+
+    peerdn[siz-1] = '\0';              /* paranoia */
+    store_pool = POOL_PERM;
+    tlsp->peerdn = string_copy(peerdn);
+    store_pool = oldpool;
+
+    /* We used to set CV in the cert-verify callbacks (either plain or dane)
+    but they don't get called on session-resumption.  So use the official
+    interface, which uses the resumed value.  Unfortunately this claims verified
+    when it actually failed but we're in try-verify mode, due to us wanting the
+    knowlege that it failed so needing to have the callback and forcing a
+    permissive return.  If we don't force it, the TLS startup is failed.
+    The extra bit of information is set in verify_override in the cb, stashed
+    for resumption next to the TLS session, and used here. */
+
+    if (!tlsp->verify_override)
+      tlsp->certificate_verified = SSL_get_verify_result(ssl) == X509_V_OK;
     }
 }
 
@@ -2545,7 +2570,7 @@ DEBUG(D_tls)
 #ifdef EXIM_HAVE_SESSION_TICKET
   {
   SSL_SESSION * ss = SSL_get_session(server_ssl);
-  if (SSL_SESSION_has_ticket(ss))
+  if (SSL_SESSION_has_ticket(ss))      /* 1.1.0 */
     debug_printf("The session has a ticket, life %lu seconds\n",
       SSL_SESSION_get_ticket_lifetime_hint(ss));
   }
@@ -2715,6 +2740,14 @@ if (tlsp->host_resumable)
          debug_printf("decoding session: %s\n", ssl_errstring);
          }
        }
+#ifdef EXIM_HAVE_SESSION_TICKET
+      else if ( SSL_SESSION_get_ticket_lifetime_hint(ss) + dt->time_stamp
+              < time(NULL))
+       {
+       DEBUG(D_tls) debug_printf("session expired\n");
+       dbfn_delete(dbm_file, key);
+       }
+#endif
       else if (!SSL_set_session(ssl, ss))
        {
        DEBUG(D_tls)
@@ -2728,6 +2761,8 @@ if (tlsp->host_resumable)
        {
        DEBUG(D_tls) debug_printf("good session\n");
        tlsp->resumption |= RESUME_CLIENT_SUGGESTED;
+       tlsp->verify_override = dt->verify_override;
+       tlsp->ocsp = dt->ocsp;
        }
       }
     else
@@ -2750,20 +2785,22 @@ DEBUG(D_tls) debug_printf("tls_save_session_cb\n");
 
 if (!cbinfo || !(tlsp = cbinfo->tlsp)->host_resumable) return 0;
 
-# ifdef EXIM_HAVE_SESSION_TICKET
-
-if (SSL_SESSION_is_resumable(ss)) 
+# ifdef OPENSSL_HAVE_NUM_TICKETS
+if (SSL_SESSION_is_resumable(ss))      /* 1.1.1 */
+# endif
   {
   int len = i2d_SSL_SESSION(ss, NULL);
   int dlen = sizeof(dbdata_tls_session) + len;
-  dbdata_tls_session * dt = store_get(dlen);
+  dbdata_tls_session * dt = store_get(dlen, TRUE);
   uschar * s = dt->session;
   open_db dbblock, * dbm_file;
 
   DEBUG(D_tls) debug_printf("session is resumable\n");
   tlsp->resumption |= RESUME_SERVER_TICKET;    /* server gave us a ticket */
 
-  len = i2d_SSL_SESSION(ss, &s);       /* s gets bumped to end */
+  dt->verify_override = tlsp->verify_override;
+  dt->ocsp = tlsp->ocsp;
+  (void) i2d_SSL_SESSION(ss, &s);              /* s gets bumped to end */
 
   if ((dbm_file = dbfn_open(US"tls", O_RDWR, &dbblock, FALSE, FALSE)))
     {
@@ -2775,7 +2812,6 @@ if (SSL_SESSION_is_resumable(ss))
                  (unsigned)dlen);
     }
   }
-# endif
 return 1;
 }
 
@@ -2873,7 +2909,7 @@ BOOL require_ocsp = FALSE;
 
 rc = store_pool;
 store_pool = POOL_PERM;
-exim_client_ctx = store_get(sizeof(exim_openssl_client_tls_ctx));
+exim_client_ctx = store_get(sizeof(exim_openssl_client_tls_ctx), FALSE);
 exim_client_ctx->corked = NULL;
 store_pool = rc;
 
@@ -2993,12 +3029,6 @@ if (!(exim_client_ctx->ssl = SSL_new(exim_client_ctx->ctx)))
   }
 SSL_set_session_id_context(exim_client_ctx->ssl, sid_ctx, Ustrlen(sid_ctx));
 
-#ifdef EXPERIMENTAL_TLS_RESUME
-if (!tls_client_ssl_resume_prehandshake(exim_client_ctx->ssl, tlsp, host,
-      errstr))
-  return FALSE;
-#endif
-
 SSL_set_fd(exim_client_ctx->ssl, cctx->sock);
 SSL_set_connect_state(exim_client_ctx->ssl);
 
@@ -3058,6 +3088,12 @@ if (request_ocsp)
   }
 #endif
 
+#ifdef EXPERIMENTAL_TLS_RESUME
+if (!tls_client_ssl_resume_prehandshake(exim_client_ctx->ssl, tlsp, host,
+      errstr))
+  return FALSE;
+#endif
+
 #ifndef DISABLE_EVENT
 client_static_cbinfo->event_action = tb ? tb->event_action : NULL;
 #endif
@@ -3154,32 +3190,10 @@ switch(error)
   case SSL_ERROR_ZERO_RETURN:
     DEBUG(D_tls) debug_printf("Got SSL_ERROR_ZERO_RETURN\n");
 
-    receive_getc = smtp_getc;
-    receive_getbuf = smtp_getbuf;
-    receive_get_cache = smtp_get_cache;
-    receive_ungetc = smtp_ungetc;
-    receive_feof = smtp_feof;
-    receive_ferror = smtp_ferror;
-    receive_smtp_buffered = smtp_buffered;
-
     if (SSL_get_shutdown(server_ssl) == SSL_RECEIVED_SHUTDOWN)
          SSL_shutdown(server_ssl);
 
-#ifndef DISABLE_OCSP
-    sk_X509_pop_free(server_static_cbinfo->verify_stack, X509_free);
-    server_static_cbinfo->verify_stack = NULL;
-#endif
-    SSL_free(server_ssl);
-    SSL_CTX_free(server_ctx);
-    server_ctx = NULL;
-    server_ssl = NULL;
-    tls_in.active.sock = -1;
-    tls_in.active.tls_ctx = NULL;
-    tls_in.bits = 0;
-    tls_in.cipher = NULL;
-    tls_in.peerdn = NULL;
-    tls_in.sni = NULL;
-
+    tls_close(NULL, TLS_NO_SHUTDOWN);
     return FALSE;
 
   /* Handle genuine errors */
@@ -3360,14 +3374,14 @@ a store reset there, so use POOL_PERM. */
 
 if ((more || corked))
   {
-#ifdef EXPERIMENTAL_PIPE_CONNECT
+#ifdef SUPPORT_PIPE_CONNECT
   int save_pool = store_pool;
   store_pool = POOL_PERM;
 #endif
 
   corked = string_catn(corked, buff, len);
 
-#ifdef EXPERIMENTAL_PIPE_CONNECT
+#ifdef SUPPORT_PIPE_CONNECT
   store_pool = save_pool;
 #endif
 
@@ -3468,14 +3482,25 @@ if (shutdown)
     }
   }
 
-#ifndef DISABLE_OCSP
 if (!o_ctx)            /* server side */
   {
+#ifndef DISABLE_OCSP
   sk_X509_pop_free(server_static_cbinfo->verify_stack, X509_free);
   server_static_cbinfo->verify_stack = NULL;
-  }
 #endif
 
+  receive_getc =       smtp_getc;
+  receive_getbuf =     smtp_getbuf;
+  receive_get_cache =  smtp_get_cache;
+  receive_ungetc =     smtp_ungetc;
+  receive_feof =       smtp_feof;
+  receive_ferror =     smtp_ferror;
+  receive_smtp_buffered = smtp_buffered;
+  tls_in.active.tls_ctx = NULL;
+  tls_in.sni = NULL;
+  /* Leave bits, peercert, cipher, peerdn, certificate_verified set, for logging */
+  }
+
 SSL_CTX_free(*ctxp);
 SSL_free(*sslp);
 *ctxp = NULL;