For DH, use standard primes from RFCs
[exim.git] / src / src / tls-openssl.c
index 4cdb28905331d180ba7476c14436b08ed70569a8..43b79634ed4793091931151c361501e18936c061 100644 (file)
@@ -2,7 +2,7 @@
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2009 */
+/* Copyright (c) University of Cambridge 1995 - 2012 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* This module provides the TLS (aka SSL) support for Exim using the OpenSSL
@@ -29,6 +29,10 @@ functions from the OpenSSL library. */
 #define EXIM_OCSP_MAX_AGE (-1L)
 #endif
 
+#if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT)
+#define EXIM_HAVE_OPENSSL_TLSEXT
+#endif
+
 /* Structure for collecting random data for seeding. */
 
 typedef struct randstuff {
@@ -42,7 +46,9 @@ static BOOL verify_callback_called = FALSE;
 static const uschar *sid_ctx = US"exim";
 
 static SSL_CTX *ctx = NULL;
+#ifdef EXIM_HAVE_OPENSSL_TLSEXT
 static SSL_CTX *ctx_sni = NULL;
+#endif
 static SSL *ssl = NULL;
 
 static char ssl_errstring[256];
@@ -77,7 +83,9 @@ static int
 setup_certs(SSL_CTX *sctx, uschar *certs, uschar *crl, host_item *host, BOOL optional);
 
 /* Callbacks */
+#ifdef EXIM_HAVE_OPENSSL_TLSEXT
 static int tls_servername_cb(SSL *s, int *ad ARG_UNUSED, void *arg);
+#endif
 #ifdef EXPERIMENTAL_OCSP
 static int tls_stapling_cb(SSL *s, void *arg);
 #endif
@@ -267,51 +275,85 @@ DEBUG(D_tls) debug_printf("SSL info: %s\n", SSL_state_string_long(s));
 /* If dhparam is set, expand it, and load up the parameters for DH encryption.
 
 Arguments:
-  dhparam   DH parameter file
+  dhparam   DH parameter file or fixed parameter identity string
   host      connected host, if client; NULL if server
 
 Returns:    TRUE if OK (nothing to set up, or setup worked)
 */
 
 static BOOL
-init_dh(uschar *dhparam, host_item *host)
+init_dh(SSL_CTX *sctx, uschar *dhparam, host_item *host)
 {
-BOOL yield = TRUE;
 BIO *bio;
 DH *dh;
 uschar *dhexpanded;
+const char *pem;
 
 if (!expand_check(dhparam, US"tls_dhparam", &dhexpanded))
   return FALSE;
 
-if (dhexpanded == NULL) return TRUE;
-
-if ((bio = BIO_new_file(CS dhexpanded, "r")) == NULL)
+if (dhexpanded == NULL || *dhexpanded == '\0')
   {
-  tls_error(string_sprintf("could not read dhparams file %s", dhexpanded),
-    host, (uschar *)strerror(errno));
-  yield = FALSE;
+  bio = BIO_new_mem_buf(CS std_dh_prime_default(), -1);
   }
-else
+else if (dhexpanded[0] == '/')
   {
-  if ((dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL)) == NULL)
+  bio = BIO_new_file(CS dhexpanded, "r");
+  if (bio == NULL)
     {
     tls_error(string_sprintf("could not read dhparams file %s", dhexpanded),
-      host, NULL);
-    yield = FALSE;
+          host, US strerror(errno));
+    return FALSE;
     }
-  else
+  }
+else
+  {
+  if (Ustrcmp(dhexpanded, "none") == 0)
     {
-    SSL_CTX_set_tmp_dh(ctx, dh);
-    DEBUG(D_tls)
-      debug_printf("Diffie-Hellman initialized from %s with %d-bit key\n",
-        dhexpanded, 8*DH_size(dh));
-    DH_free(dh);
+    DEBUG(D_tls) debug_printf("Requested no DH parameters.\n");
+    return TRUE;
     }
+
+  pem = std_dh_prime_named(dhexpanded);
+  if (!pem)
+    {
+    tls_error(string_sprintf("Unknown standard DH prime \"%s\"", dhexpanded),
+        host, US strerror(errno));
+    return FALSE;
+    }
+  bio = BIO_new_mem_buf(CS pem, -1);
+  }
+
+dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
+if (dh == NULL)
+  {
   BIO_free(bio);
+  tls_error(string_sprintf("Could not read tls_dhparams \"%s\"", dhexpanded),
+      host, NULL);
+  return FALSE;
+  }
+
+/* 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 ((8*DH_size(dh)) > tls_dh_max_bits)
+  {
+  DEBUG(D_tls)
+    debug_printf("dhparams file %d bits, is > tls_dh_max_bits limit of %d",
+        8*DH_size(dh), tls_dh_max_bits);
+  }
+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", 8*DH_size(dh));
   }
 
-return yield;
+DH_free(dh);
+BIO_free(bio);
+
+return TRUE;
 }
 
 
@@ -540,6 +582,7 @@ Arguments:
 Returns:          SSL_TLSEXT_ERR_{OK,ALERT_WARNING,ALERT_FATAL,NOACK}
 */
 
+#ifdef EXIM_HAVE_OPENSSL_TLSEXT
 static int
 tls_servername_cb(SSL *s, int *ad ARG_UNUSED, void *arg)
 {
@@ -601,11 +644,15 @@ OCSP information. */
 rc = tls_expand_session_files(ctx_sni, cbinfo);
 if (rc != OK) return SSL_TLSEXT_ERR_NOACK;
 
+rc = init_dh(ctx_sni, cbinfo->dhparam, NULL);
+if (rc != OK) return SSL_TLSEXT_ERR_NOACK;
+
 DEBUG(D_tls) debug_printf("Switching SSL context.\n");
 SSL_set_SSL_CTX(s, ctx_sni);
 
 return SSL_TLSEXT_ERR_OK;
 }
+#endif /* EXIM_HAVE_OPENSSL_TLSEXT */
 
 
 
@@ -760,7 +807,7 @@ else
 
 /* Initialize with DH parameters if supplied */
 
-if (!init_dh(dhparam, host)) return DEFER;
+if (!init_dh(ctx, dhparam, host)) return DEFER;
 
 /* Set up certificate and key (and perhaps OCSP info) */
 
@@ -768,7 +815,7 @@ rc = tls_expand_session_files(ctx, cbinfo);
 if (rc != OK) return rc;
 
 /* If we need to handle SNI, do so */
-#if OPENSSL_VERSION_NUMBER >= 0x0090806fL && !defined(OPENSSL_NO_TLSEXT)
+#ifdef EXIM_HAVE_OPENSSL_TLSEXT
 if (host == NULL)
   {
 #ifdef EXPERIMENTAL_OCSP
@@ -1240,8 +1287,14 @@ if (sni)
     tls_sni = NULL;
   else
     {
+#ifdef EXIM_HAVE_OPENSSL_TLSEXT
     DEBUG(D_tls) debug_printf("Setting TLS SNI \"%s\"\n", tls_sni);
     SSL_set_tlsext_host_name(ssl, tls_sni);
+#else
+    DEBUG(D_tls)
+      debug_printf("OpenSSL at build-time lacked SNI support, ignoring \"%s\"\n",
+          tls_sni);
+#endif
     }
   }
 
@@ -1489,6 +1542,72 @@ tls_active = -1;
 
 
 
+/*************************************************
+*  Let tls_require_ciphers be checked at startup *
+*************************************************/
+
+/* The tls_require_ciphers option, if set, must be something which the
+library can parse.
+
+Returns:     NULL on success, or error message
+*/
+
+uschar *
+tls_validate_require_cipher(void)
+{
+SSL_CTX *ctx;
+uschar *s, *expciphers, *err;
+
+/* this duplicates from tls_init(), we need a better "init just global
+state, for no specific purpose" singleton function of our own */
+
+SSL_load_error_strings();
+OpenSSL_add_ssl_algorithms();
+#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL) && !defined(OPENSSL_NO_SHA256)
+/* SHA256 is becoming ever more popular. This makes sure it gets added to the
+list of available digests. */
+EVP_add_digest(EVP_sha256());
+#endif
+
+if (!(tls_require_ciphers && *tls_require_ciphers))
+  return NULL;
+
+if (!expand_check(tls_require_ciphers, US"tls_require_ciphers", &expciphers))
+  return US"failed to expand tls_require_ciphers";
+
+if (!(expciphers && *expciphers))
+  return NULL;
+
+/* normalisation ripped from above */
+s = expciphers;
+while (*s != 0) { if (*s == '_') *s = '-'; s++; }
+
+err = NULL;
+
+ctx = SSL_CTX_new(SSLv23_server_method());
+if (!ctx)
+  {
+  ERR_error_string(ERR_get_error(), ssl_errstring);
+  return string_sprintf("SSL_CTX_new() failed: %s", ssl_errstring);
+  }
+
+DEBUG(D_tls)
+  debug_printf("tls_require_ciphers expands to \"%s\"\n", expciphers);
+
+if (!SSL_CTX_set_cipher_list(ctx, CS expciphers))
+  {
+  ERR_error_string(ERR_get_error(), ssl_errstring);
+  err = string_sprintf("SSL_CTX_set_cipher_list(%s) failed", expciphers);
+  }
+
+SSL_CTX_free(ctx);
+
+return err;
+}
+
+
+
+
 /*************************************************
 *         Report the library versions.           *
 *************************************************/