Deal with GnuTLS DH generation overshoot
authorPhil Pennock <pdp@exim.org>
Sun, 27 May 2012 05:17:04 +0000 (01:17 -0400)
committerPhil Pennock <pdp@exim.org>
Sun, 27 May 2012 05:17:04 +0000 (01:17 -0400)
doc/doc-docbook/spec.xfpt
doc/doc-txt/GnuTLS-FAQ.txt
src/src/tls-gnu.c

index 9eaf9e8046f7775ee8b12b11e171399fba2ee8c7..9c2bf199f7ede21fe2d9cbe8fb559afc27b84cc8 100644 (file)
@@ -15697,6 +15697,10 @@ by Thunderbird, while GnuTLS was suggesting 2432 bits as normal.
 
 If you prefer more security and are willing to break some clients, raise this
 number.
+
+Note that the value passed to GnuTLS for *generating* a new prime may be a
+little less than this figure, because GnuTLS is inexact and may produce a
+larger prime than requested.
 .wen
 
 
@@ -15708,8 +15712,8 @@ This is used only for OpenSSL. When Exim is linked with GnuTLS, this option is
 ignored. See section &<<SECTopenvsgnu>>& for further details.
 
 .new
-If the DH bit-count from loading the file is greater than tls_dh_max_bits then
-it will be ignored.
+If the DH bit-count from loading the file is greater than &%tls_dh_max_bits$&
+then it will be ignored.
 .wen
 
 
@@ -25070,6 +25074,10 @@ renaming. The relevant commands are something like this:
 # chown exim:exim new-params
 # chmod 0600 new-params
 # certtool --generate-dh-params --bits 2236 >>new-params
+# openssl dhparam -noout -text -in new-params | head
+[ check the first line, make sure it's not more than 2236;
+  if it is, then go back to the start ("rm") and repeat
+  until the size generated is at most the size requested ]
 # chmod 0400 new-params
 # mv new-params gnutls-params-2236
 .endd
@@ -25092,6 +25100,12 @@ The filename and bits used will change as the GnuTLS maintainers change the
 value for their parameter &`GNUTLS_SEC_PARAM_NORMAL`&, as clamped by
 &%tls_dh_max_bits%&.  At the time of writing (mid 2012), GnuTLS 2.12 recommends
 2432 bits, while NSS is limited to 2236 bits.
+
+In fact, the requested value will be *lower* than &%tls_dh_max_bits%&, to
+increase the chance of the generated prime actually being within acceptable
+bounds, as GnuTLS has been observed to overshoot.  Note the check step in the
+procedure above.  There is no sane procedure available to Exim to double-check
+the size of the generated prime, so it might still be too large.
 .wen
 
 
index 60f40200478a72cb1059216ae7626fd2c786b706..4339becac07b8a75d34899136135885fe3adf582 100644 (file)
@@ -232,6 +232,37 @@ security versus compatibility by raising it.
 A future release of Exim may even let the administrator tell GnuTLS to ask for
 more or less than "NORMAL".
 
+To add to the fun, the size of the prime returned by GnuTLS when we call
+gnutls_dh_params_generate2() is not limited to be the requested size.  GnuTLS
+has a tendency to overshoot.  2237 bit primes are common when 2236 is
+requested, and higher still have been observed.  Further, there is no API to
+ask how large the prime bundled up inside the parameter is; the most we can do
+is ask how large the DH prime used in an active TLS session is.  Since we're
+not able to use GnuTLS API calls (and exporting to PKCS3 and then calling
+OpenSSL routines would be undiplomatic, plus add a library dependency), we're
+left with no way to actually know the size of the freshly generated DH prime.
+
+Thus we check if the the value returned is at least 10 more than the minimum
+we'll accept as a client (EXIM_CLIENT_DH_MIN_BITS, see below, defaults to
+1024) and if it is, we subtract 10.  Then we reluctantly deploy a strategy
+called "hope".  This is not guaranteed to be successful; in the first code
+pass on this logic, we subtracted 3, asked for 2233 bits and got 2240 in the
+first test.
+
+If you see Thunderbird clients still failing, then as a user who can see into
+Exim's spool directory, run:
+
+$ openssl dhparam -noout -text -in /path/to/spool/gnutls-params-2236 | head
+
+Ideally, the first line will read "PKCS#3 DH Parameters: (2236 bit)".  If the
+count is more than 2236, then remove the file and let Exim regenerate it, or
+generate one yourself and move it into place.  Ideally use "openssl dhparam"
+to generate it, and then wait a very long time; at least this way, the size
+will be correct.  (This developer is now convinced that Exim 4.81 should
+bundle the suggested primes from a few RFCs and let the administrator choose
+those.)
+
+
 A TLS client does not get to choose the DH prime used, but can choose a
 minimum acceptable value.  For Exim, this is a compile-time constant called
 "EXIM_CLIENT_DH_MIN_BITS" of 1024, which can be overruled in "Local/Makefile".
index aa2f92514b79133173c57f4645f225cd687d3733..214007e5f744046a87c72debccbdbf5a9051859f 100644 (file)
@@ -483,6 +483,7 @@ case. */
 if (rc < 0)
   {
   uschar *temp_fn;
+  unsigned int dh_bits_gen = dh_bits;
 
   if ((PATH_MAX - Ustrlen(filename)) < 10)
     return tls_error(US"Filename too long to generate replacement",
@@ -494,8 +495,26 @@ if (rc < 0)
     return tls_error(US"Unable to open temp file", strerror(errno), NULL);
   (void)fchown(fd, exim_uid, exim_gid);   /* Probably not necessary */
 
-  DEBUG(D_tls) debug_printf("generating %d bits Diffie-Hellman key ...\n", dh_bits);
-  rc = gnutls_dh_params_generate2(dh_server_params, dh_bits);
+  /* GnuTLS overshoots!
+   * If we ask for 2236, we might get 2237 or more.
+   * But there's no way to ask GnuTLS how many bits there really are.
+   * We can ask how many bits were used in a TLS session, but that's it!
+   * The prime itself is hidden behind too much abstraction.
+   * So we ask for less, and proceed on a wing and a prayer.
+   * First attempt, subtracted 3 for 2233 and got 2240.
+   */
+  if (dh_bits > EXIM_CLIENT_DH_MIN_BITS + 10)
+    {
+    dh_bits_gen = dh_bits - 10;
+    DEBUG(D_tls)
+      debug_printf("being paranoid about DH generation, make it '%d' bits'\n",
+          dh_bits_gen);
+    }
+
+  DEBUG(D_tls)
+    debug_printf("requesting generation of %d bit Diffie-Hellman prime ...\n",
+        dh_bits_gen);
+  rc = gnutls_dh_params_generate2(dh_server_params, dh_bits_gen);
   exim_gnutls_err_check(US"gnutls_dh_params_generate2");
 
   /* gnutls_dh_params_export_pkcs3() will tell us the exact size, every time,