OpenSSL: tidy DH and ECDH param setup
[exim.git] / src / src / tls-openssl.c
index 590d271f7bc3c10f4ce4ccd34395ba932fb0e928..4bc92bd05d8e342c9ddb048d4301444033378a4e 100644 (file)
@@ -3,7 +3,7 @@
 *************************************************/
 
 /* Copyright (c) University of Cambridge 1995 - 2019 */
-/* Copyright (c) The Exim Maintainers 2020 */
+/* Copyright (c) The Exim Maintainers 2020 - 2021 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Portions Copyright (c) The OpenSSL Project 1999 */
@@ -232,10 +232,14 @@ static exim_openssl_option exim_openssl_options[] = {
   { US"no_tlsv1", SSL_OP_NO_TLSv1 },
 #endif
 #ifdef SSL_OP_NO_TLSv1_1
-# if SSL_OP_NO_TLSv1_1 == 0x00000400L
+# if OPENSSL_VERSION_NUMBER < 0x30000000L
+#  if SSL_OP_NO_TLSv1_1 == 0x00000400L
   /* Error in chosen value in 1.0.1a; see first item in CHANGES for 1.0.1b */
-#  warning OpenSSL 1.0.1a uses a bad value for SSL_OP_NO_TLSv1_1, ignoring
-# else
+#   warning OpenSSL 1.0.1a uses a bad value for SSL_OP_NO_TLSv1_1, ignoring
+#   define NO_SSL_OP_NO_TLSv1_1
+#  endif
+# endif
+# ifndef NO_SSL_OP_NO_TLSv1_1
   { US"no_tlsv1_1", SSL_OP_NO_TLSv1_1 },
 # endif
 #endif
@@ -545,23 +549,27 @@ EVP_add_digest(EVP_sha256());
 *************************************************/
 
 /* If dhparam is set, expand it, and load up the parameters for DH encryption.
+Server only.
 
 Arguments:
   sctx      The current SSL CTX (inbound or outbound)
   dhparam   DH parameter file or fixed parameter identity string
-  host      connected host, if client; NULL if server
   errstr    error string pointer
 
 Returns:    TRUE if OK (nothing to set up, or setup worked)
 */
 
 static BOOL
-init_dh(SSL_CTX *sctx, uschar *dhparam, const host_item *host, uschar ** errstr)
+init_dh(SSL_CTX * sctx, uschar * dhparam, uschar ** errstr)
 {
-BIO *bio;
-DH *dh;
-uschar *dhexpanded;
-const char *pem;
+BIO * bio;
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+DH * dh;
+#else
+EVP_PKEY * pkey;
+#endif
+uschar * dhexpanded;
+const char * pem;
 int dh_bitsize;
 
 if (!expand_check(dhparam, US"tls_dhparam", &dhexpanded, errstr))
@@ -574,7 +582,7 @@ else if (dhexpanded[0] == '/')
   if (!(bio = BIO_new_file(CS dhexpanded, "r")))
     {
     tls_error(string_sprintf("could not read dhparams file %s", dhexpanded),
-          host, US strerror(errno), errstr);
+          NULL, US strerror(errno), errstr);
     return FALSE;
     }
   }
@@ -589,53 +597,80 @@ else
   if (!(pem = std_dh_prime_named(dhexpanded)))
     {
     tls_error(string_sprintf("Unknown standard DH prime \"%s\"", dhexpanded),
-        host, US strerror(errno), errstr);
+        NULL, US strerror(errno), errstr);
     return FALSE;
     }
   bio = BIO_new_mem_buf(CS pem, -1);
   }
 
-if (!(dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL)))
+if (!(
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+      dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL)
+#else
+      pkey = PEM_read_bio_Parameters_ex(bio, NULL, NULL, NULL)
+#endif
+   ) )
   {
   BIO_free(bio);
   tls_error(string_sprintf("Could not read tls_dhparams \"%s\"", dhexpanded),
-      host, NULL, errstr);
+      NULL, NULL, errstr);
   return FALSE;
   }
 
 /* note: our default limit of 2236 is not a multiple of 8; the limit comes from
- * an NSS limit, and the GnuTLS APIs handle bit-sizes fine, so we went with
- * 2236.  But older OpenSSL can only report in bytes (octets), not bits.
- * If someone wants to dance at the edge, then they can raise the limit or use
- * current libraries. */
-#ifdef EXIM_HAVE_OPENSSL_DH_BITS
+an NSS limit, and the GnuTLS APIs handle bit-sizes fine, so we went with 2236.
+But older OpenSSL can only report in bytes (octets), not bits.  If someone wants
+to dance at the edge, then they can raise the limit or use current libraries. */
+
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+# ifdef EXIM_HAVE_OPENSSL_DH_BITS
 /* Added in commit 26c79d5641d; `git describe --contains` says OpenSSL_1_1_0-pre1~1022
- * This predates OpenSSL_1_1_0 (before a, b, ...) so is in all 1.1.0 */
+This predates OpenSSL_1_1_0 (before a, b, ...) so is in all 1.1.0 */
 dh_bitsize = DH_bits(dh);
-#else
+# else
 dh_bitsize = 8 * DH_size(dh);
+# endif
+#else  /* 3.0.0 + */
+dh_bitsize = EVP_PKEY_get_bits(pkey);
 #endif
 
-/* Even if it is larger, we silently return success rather than cause things
- * to fail out, so that a too-large DH will not knock out all TLS; it's a
- * debatable choice. */
-if (dh_bitsize > tls_dh_max_bits)
+/* Even if it is larger, we silently return success rather than cause things to
+fail out, so that a too-large DH will not knock out all TLS; it's a debatable
+choice.  Likewise for a failing attempt to set one. */
+
+if (dh_bitsize <= tls_dh_max_bits)
   {
-  DEBUG(D_tls)
-    debug_printf("dhparams file %d bits, is > tls_dh_max_bits limit of %d\n",
-        dh_bitsize, tls_dh_max_bits);
+  if (
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
+      SSL_CTX_set_tmp_dh(sctx, dh)
+#else
+      SSL_CTX_set0_tmp_dh_pkey(sctx, pkey)
+#endif
+      == 0)
+    {
+    ERR_error_string_n(ERR_get_error(), ssl_errstring, sizeof(ssl_errstring));
+    log_write(0, LOG_MAIN|LOG_PANIC, "TLS error (D-H param setting '%s'): %s",
+       dhexpanded ? dhexpanded : US"default", ssl_errstring);
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L
+    /* EVP_PKEY_free(pkey);  crashes */
+#endif
+    }
+  else
+    DEBUG(D_tls)
+      debug_printf("Diffie-Hellman initialized from %s with %d-bit prime\n",
+       dhexpanded ? dhexpanded : US"default", dh_bitsize);
   }
 else
-  {
-  SSL_CTX_set_tmp_dh(sctx, dh);
   DEBUG(D_tls)
-    debug_printf("Diffie-Hellman initialized from %s with %d-bit prime\n",
-      dhexpanded ? dhexpanded : US"default", dh_bitsize);
-  }
+    debug_printf("dhparams '%s' %d bits, is > tls_dh_max_bits limit of %d\n",
+       dhexpanded ? dhexpanded : US"default", dh_bitsize, tls_dh_max_bits);
 
+#if OPENSSL_VERSION_NUMBER < 0x30000000L
 DH_free(dh);
-BIO_free(bio);
+#endif
+/* The EVP_PKEY ownership stays with the ctx; do not free it */
 
+BIO_free(bio);
 return TRUE;
 }
 
@@ -646,7 +681,7 @@ return TRUE;
 *               Initialize for ECDH              *
 *************************************************/
 
-/* Load parameters for ECDH encryption.
+/* Load parameters for ECDH encryption.  Server only.
 
 For now, we stick to NIST P-256 because: it's simple and easy to configure;
 it avoids any patent issues that might bite redistributors; despite events in
@@ -664,27 +699,22 @@ Patches welcome.
 
 Arguments:
   sctx      The current SSL CTX (inbound or outbound)
-  host      connected host, if client; NULL if server
   errstr    error string pointer
 
 Returns:    TRUE if OK (nothing to set up, or setup worked)
 */
 
 static BOOL
-init_ecdh(SSL_CTX * sctx, host_item * host, uschar ** errstr)
+init_ecdh(SSL_CTX * sctx, uschar ** errstr)
 {
 #ifdef OPENSSL_NO_ECDH
 return TRUE;
 #else
 
-EC_KEY * ecdh;
 uschar * exp_curve;
 int nid;
 BOOL rv;
 
-if (host)      /* No ECDH setup for clients, only for servers */
-  return TRUE;
-
 # ifndef EXIM_HAVE_ECDH
 DEBUG(D_tls)
   debug_printf("No OpenSSL API to define ECDH parameters, skipping\n");
@@ -731,25 +761,38 @@ if (  (nid = OBJ_sn2nid       (CCS exp_curve)) == NID_undef
    )
   {
   tls_error(string_sprintf("Unknown curve name tls_eccurve '%s'", exp_curve),
-    host, NULL, errstr);
+    NULL, NULL, errstr);
   return FALSE;
   }
 
-if (!(ecdh = EC_KEY_new_by_curve_name(nid)))
-  {
-  tls_error(US"Unable to create ec curve", host, NULL, errstr);
-  return FALSE;
-  }
+# if OPENSSL_VERSION_NUMBER < 0x30000000L
+ {
+  EC_KEY * ecdh;
+  if (!(ecdh = EC_KEY_new_by_curve_name(nid)))
+    {
+    tls_error(US"Unable to create ec curve", NULL, NULL, errstr);
+    return FALSE;
+    }
 
-/* The "tmp" in the name here refers to setting a temporary key
-not to the stability of the interface. */
+  /* The "tmp" in the name here refers to setting a temporary key
+  not to the stability of the interface. */
 
-if ((rv = SSL_CTX_set_tmp_ecdh(sctx, ecdh) == 0))
-  tls_error(string_sprintf("Error enabling '%s' curve", exp_curve), host, NULL, errstr);
+  if ((rv = SSL_CTX_set_tmp_ecdh(sctx, ecdh) == 0))
+    tls_error(string_sprintf("Error enabling '%s' curve", exp_curve), NULL, NULL, errstr);
+  else
+    DEBUG(D_tls) debug_printf("ECDH: enabled '%s' curve\n", exp_curve);
+  EC_KEY_free(ecdh);
+ }
+
+#else  /* v 3.0.0 + */
+
+if ((rv = SSL_CTX_set1_groups(sctx, &nid, 1)) == 0)
+  tls_error(string_sprintf("Error enabling '%s' group", exp_curve), NULL, NULL, errstr);
 else
-  DEBUG(D_tls) debug_printf("ECDH: enabled '%s' curve\n", exp_curve);
+  DEBUG(D_tls) debug_printf("ECDH: enabled '%s' group\n", exp_curve);
+
+#endif
 
-EC_KEY_free(ecdh);
 return !rv;
 
 # endif        /*EXIM_HAVE_ECDH*/
@@ -1658,15 +1701,19 @@ state_server.lib_state.lib_ctx = ctx;
 if (opt_unset_or_noexpand(tls_dhparam))
   {
   DEBUG(D_tls) debug_printf("TLS: preloading DH params for server\n");
-  if (init_dh(ctx, tls_dhparam, NULL, &dummy_errstr))
+  if (init_dh(ctx, tls_dhparam, &dummy_errstr))
     state_server.lib_state.dh = TRUE;
   }
+else
+  DEBUG(D_tls) debug_printf("TLS: not preloading DH params for server\n");
 if (opt_unset_or_noexpand(tls_eccurve))
   {
   DEBUG(D_tls) debug_printf("TLS: preloading ECDH curve for server\n");
-  if (init_ecdh(ctx, NULL, &dummy_errstr))
+  if (init_ecdh(ctx, &dummy_errstr))
     state_server.lib_state.ecdh = TRUE;
   }
+else
+  DEBUG(D_tls) debug_printf("TLS: not preloading ECDH curve for server\n");
 
 #if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
 /* If we can, preload the server-side cert, key and ocsp */
@@ -1778,19 +1825,6 @@ ob->tls_preload.lib_ctx = ctx;
 
 tpt_dummy_state.lib_state = ob->tls_preload;
 
-if (opt_unset_or_noexpand(tls_dhparam))
-  {
-  DEBUG(D_tls) debug_printf("TLS: preloading DH params for transport '%s'\n", t->name);
-  if (init_dh(ctx, tls_dhparam, NULL, &dummy_errstr))
-    ob->tls_preload.dh = TRUE;
-  }
-if (opt_unset_or_noexpand(tls_eccurve))
-  {
-  DEBUG(D_tls) debug_printf("TLS: preloading ECDH curve for transport '%s'\n", t->name);
-  if (init_ecdh(ctx, NULL, &dummy_errstr))
-    ob->tls_preload.ecdh = TRUE;
-  }
-
 #if defined(EXIM_HAVE_INOTIFY) || defined(EXIM_HAVE_KEVENT)
 if (  opt_set_and_noexpand(ob->tls_certificate)
    && opt_unset_or_noexpand(ob->tls_privatekey))
@@ -2100,8 +2134,8 @@ already exists.  Might even need this selfsame callback, for reneg? */
   SSL_CTX_set_tlsext_servername_arg(server_sni, state);
   }
 
-if (  !init_dh(server_sni, state->dhparam, NULL, &dummy_errstr)
-   || !init_ecdh(server_sni, NULL, &dummy_errstr)
+if (  !init_dh(server_sni, state->dhparam, &dummy_errstr)
+   || !init_ecdh(server_sni, &dummy_errstr)
    )
   goto bad;
 
@@ -2235,9 +2269,6 @@ if (!olist)
   const X509 * cert_sent = SSL_get_certificate(s);
   const ASN1_INTEGER * cert_serial = X509_get0_serialNumber(cert_sent);
   const BIGNUM * cert_bn = ASN1_INTEGER_to_BN(cert_serial, NULL);
-  const X509_NAME * cert_issuer = X509_get_issuer_name(cert_sent);
-  uschar * chash;
-  uint chash_len;
 
   for (; olist; olist = olist->next)
     {
@@ -2609,15 +2640,18 @@ will never be used because we use a new context every time. */
 /* Initialize with DH parameters if supplied */
 /* Initialize ECDH temp key parameter selection */
 
-if (state->lib_state.dh)
-  { DEBUG(D_tls) debug_printf("TLS: DH params were preloaded\n"); }
-else
-  if (!init_dh(ctx, state->dhparam, host, errstr)) return DEFER;
+if (!host)
+  {
+  if (state->lib_state.dh)
+    { DEBUG(D_tls) debug_printf("TLS: DH params were preloaded\n"); }
+  else
+    if (!init_dh(ctx, state->dhparam, errstr)) return DEFER;
 
-if (state->lib_state.ecdh)
-  { DEBUG(D_tls) debug_printf("TLS: ECDH curve was preloaded\n"); }
-else
-  if (!init_ecdh(ctx, host, errstr)) return DEFER;
+  if (state->lib_state.ecdh)
+    { DEBUG(D_tls) debug_printf("TLS: ECDH curve was preloaded\n"); }
+  else
+    if (!init_ecdh(ctx, errstr)) return DEFER;
+  }
 
 /* Set up certificate and key (and perhaps OCSP info) */
 
@@ -3113,7 +3147,7 @@ else if (verify_check_host(&tls_try_verify_hosts) == OK)
 else
   goto skip_certs;
 
 {
+ {
   uschar * expcerts;
   if (!expand_check(tls_verify_certificates, US"tls_verify_certificates",
                    &expcerts, errstr))
@@ -3128,7 +3162,7 @@ else
 
   if (expcerts && *expcerts)
     setup_cert_verify(ctx, server_verify_optional, verify_callback_server);
 }
+ }
 skip_certs: ;
 
 #ifndef DISABLE_TLS_RESUME
@@ -3354,7 +3388,6 @@ receive_hasc = tls_hasc;
 receive_ungetc = tls_ungetc;
 receive_feof = tls_feof;
 receive_ferror = tls_ferror;
-receive_smtp_buffered = tls_smtp_buffered;
 
 tls_in.active.sock = fileno(smtp_out);
 tls_in.active.tls_ctx = NULL;  /* not using explicit ctx for server-side */
@@ -3390,7 +3423,7 @@ else if (verify_check_given_host(CUSS &ob->tls_try_verify_hosts, host) == OK)
 else
   return OK;
 
 {
+ {
   uschar * expcerts;
   if (!expand_check(ob->tls_verify_certificates, US"tls_verify_certificates",
                    &expcerts, errstr))
@@ -3405,7 +3438,7 @@ else
 
   if (expcerts && *expcerts)
     setup_cert_verify(ctx, client_verify_optional, verify_callback_client);
 }
+ }
 
 if (verify_check_given_host(CUSS &ob->tls_verify_cert_hostnames, host) == OK)
   {
@@ -4171,7 +4204,7 @@ if (n > 0)
 
 
 BOOL
-tls_could_read(void)
+tls_could_getc(void)
 {
 return ssl_xfer_buffer_lwm < ssl_xfer_buffer_hwm
     || SSL_pending(state_server.lib_state.lib_ssl) > 0;
@@ -4426,7 +4459,6 @@ if (!o_ctx)               /* server side */
   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 */