Blob Blame History Raw
--- contrib/mod_tls.c
+++ contrib/mod_tls.c
@@ -387,6 +387,13 @@ static int tls_required_on_ctrl = 0;
 static int tls_required_on_data = 0;
 static unsigned char *tls_authenticated = NULL;
 
+/* Define the minimum DH group length we allow (unless the AllowWeakDH
+ * TLSOption is used).  Ideally this would be 2048, per https://weakdh.org,
+ * but for compatibility with older Java versions, which only support up to
+ * 1024, we'll use 1024.  For now.
+ */
+#define TLS_DH_MIN_LEN				1024
+
 /* mod_tls session flags */
 #define	TLS_SESS_ON_CTRL			0x0001
 #define TLS_SESS_ON_DATA			0x0002
@@ -411,6 +418,7 @@ static unsigned char *tls_authenticated
 #define TLS_OPT_NO_SESSION_REUSE_REQUIRED		0x0100
 #define TLS_OPT_USE_IMPLICIT_SSL			0x0200
 #define TLS_OPT_ALLOW_CLIENT_RENEGOTIATIONS		0x0400
+#define TLS_OPT_ALLOW_WEAK_DH				0x1000
 
 /* mod_tls cleanup flags */
 #define TLS_CLEANUP_FL_SESS_INIT	0x0001
@@ -512,6 +520,8 @@ static ctrls_acttab_t tls_acttab[];
 
 static int tls_need_init_handshake = TRUE;
 
+static const char *trace_channel = "tls";
+
 static void tls_diags_cb(const SSL *ssl, int where, int ret) {
   const char *str = "(unknown)";
   int w;
@@ -1951,26 +1961,141 @@ static int tls_ctrl_renegotiate_cb(CALLB
 }
 #endif
 
-static DH *tls_dh_cb(SSL *ssl, int is_export, int keylength) {
+static DH *tls_dh_cb(SSL *ssl, int is_export, int keylen) {
   DH *dh = NULL;
+  EVP_PKEY *pkey;
+  int pkeylen = 0, use_pkeylen = FALSE;
+
+  /* OpenSSL will only ever call us (currently) with a keylen of 512 or 1024;
+   * see the SSL_EXPORT_PKEYLENGTH macro in ssl_locl.h.  Sigh.
+   *
+   * Thus we adjust the DH parameter length according to the size of the
+   * RSA/DSA private key used for the current connection.
+   *
+   * NOTE: This MAY cause interoperability issues with some clients, notably
+   * Java 7 (and earlier) clients, since Java 7 and earlier supports
+   * Diffie-Hellman only up to 1024 bits.  More sighs.  To deal with these
+   * clients, then, you need to configure a certificate/key of 1024 bits.
+   */
+  pkey = SSL_get_privatekey(ssl);
+  if (pkey != NULL) {
+    if (EVP_PKEY_type(pkey->type) == EVP_PKEY_RSA ||
+        EVP_PKEY_type(pkey->type) == EVP_PKEY_DSA) {
+      pkeylen = EVP_PKEY_bits(pkey);
+
+      if (pkeylen < TLS_DH_MIN_LEN) {
+        if (!(tls_opts & TLS_OPT_ALLOW_WEAK_DH)) {
+          pr_trace_msg(trace_channel, 11,
+            "certificate private key length %d less than %d bits, using %d "
+            "(see AllowWeakDH TLSOption)", pkeylen, TLS_DH_MIN_LEN,
+            TLS_DH_MIN_LEN);
+          pkeylen = TLS_DH_MIN_LEN;
+        }
+      }
+
+      if (pkeylen != keylen) {
+        pr_trace_msg(trace_channel, 13,
+          "adjusted DH parameter length from %d to %d bits", keylen, pkeylen);
+        use_pkeylen = TRUE;
+      }
+    }
+  }
 
   if (tls_tmp_dhs != NULL &&
       tls_tmp_dhs->nelts > 0) {
     register unsigned int i;
-    DH **dhs;
+    DH *best_dh = NULL, **dhs;
+    int best_dhlen = 0;
 
     dhs = tls_tmp_dhs->elts;
+
+    /* Search the configured list of DH parameters twice: once for any sizes
+     * matching the actual requested size (usually 1024), and once for any
+     * matching the certificate private key size (pkeylen).
+     *
+     * This behavior allows site admins to configure a TLSDHParamFile that
+     * contains 1024-bit parameters, for e.g. Java 7 (and earlier) clients.
+     */
+
+    /* Note: the keylen argument is in BITS, but DH_size() returns the number
+     * of BYTES.
+     */
     for (i = 0; i < tls_tmp_dhs->nelts; i++) {
-      /* Note: the keylength argument is in BITS, but DH_size() returns
-       * the number of BYTES.
+      int dhlen;
+
+      dhlen = DH_size(dhs[i]) * 8;
+      if (dhlen == keylen) {
+        pr_trace_msg(trace_channel, 11,
+          "found matching DH parameter for key length %d", keylen);
+        return dhs[i];
+      }
+
+      /* Try to find the next "best" DH to use, where "best" means
+       * the smallest DH that is larger than the necessary keylen.
        */
-      if (DH_size(dhs[i]) == (keylength / 8)) {
+      if (dhlen > keylen) {
+        if (best_dh != NULL) {
+          if (dhlen < best_dhlen) {
+            best_dh = dhs[i];
+            best_dhlen = dhlen;
+          }
+
+        } else {
+          best_dh = dhs[i];
+          best_dhlen = dhlen;
+        }
+      }
+    }
+
+    for (i = 0; i < tls_tmp_dhs->nelts; i++) {
+      int dhlen;
+
+      dhlen = DH_size(dhs[i]) * 8;
+      if (dhlen == pkeylen) {
+        pr_trace_msg(trace_channel, 11,
+          "found matching DH parameter for certificate private key length %d",
+          pkeylen);
         return dhs[i];
       }
+
+      if (dhlen > pkeylen) {
+        if (best_dh != NULL) {
+          if (dhlen < best_dhlen) {
+            best_dh = dhs[i];
+            best_dhlen = dhlen;
+          }
+
+        } else {
+          best_dh = dhs[i];
+          best_dhlen = dhlen;
+        }
+      }
+    }
+
+    if (best_dh != NULL) {
+      pr_trace_msg(trace_channel, 11,
+        "using best DH parameter for key length %d (length %d)", keylen,
+        best_dhlen);
+      return best_dh;
+    }
+  }
+
+  /* Still no DH parameters found?  Use the built-in ones. */
+
+  if (keylen < TLS_DH_MIN_LEN) {
+    if (!(tls_opts & TLS_OPT_ALLOW_WEAK_DH)) {
+      pr_trace_msg(trace_channel, 11,
+        "requested key length %d less than %d bits, using %d "
+        "(see AllowWeakDH TLSOption)", keylen, TLS_DH_MIN_LEN, TLS_DH_MIN_LEN);
+      keylen = TLS_DH_MIN_LEN;
     }
   }
 
-  switch (keylength) {
+  if (use_pkeylen) {
+    keylen = pkeylen;
+  }
+
+  switch (keylen) {
     case 512:
       dh = get_dh512();
       break;
@@ -1979,25 +2104,27 @@ static DH *tls_dh_cb(SSL *ssl, int is_ex
       dh = get_dh768();
       break;
 
-     case 1024:
-       dh = get_dh1024();
-       break;
-
-     case 1536:
-       dh = get_dh1536();
-       break;
-
-     case 2048:
-       dh = get_dh2048();
-       break;
-
-     default:
-       tls_log("unsupported DH key length %d requested, returning 1024 bits",
-         keylength);
-       dh = get_dh1024();
-       break;
+    case 1024:
+      dh = get_dh1024();
+      break;
+
+    case 1536:
+      dh = get_dh1536();
+      break;
+
+    case 2048:
+      dh = get_dh2048();
+      break;
+
+    default:
+      tls_log("unsupported DH key length %d requested, returning 1024 bits",
+        keylen);
+      dh = get_dh1024();
+      break;
   }
 
+  pr_trace_msg(trace_channel, 11, "using builtin DH for %d bits", keylen);
+
   /* Add this DH to the list, so that it can be freed properly later. */
   if (tls_tmp_dhs == NULL) {
     tls_tmp_dhs = make_array(session.pool, 1, sizeof(DH *));
@@ -6310,6 +6437,9 @@ MODRET set_tlsoptions(cmd_rec *cmd) {
                strcmp(cmd->argv[i], "AllowClientRenegotiations") == 0) {
       opts |= TLS_OPT_ALLOW_CLIENT_RENEGOTIATIONS;
 
+    } else if (strcmp(cmd->argv[i], "AllowWeakDH") == 0) {
+      opts |= TLS_OPT_ALLOW_WEAK_DH;
+
     } else if (strcmp(cmd->argv[i], "EnableDiags") == 0) {
       opts |= TLS_OPT_ENABLE_DIAGS;