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.
 
 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
 
 
 .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
 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
 
 
 .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
 # 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
 # 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.
 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
 
 
 .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".
 
 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".
 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;
 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",
 
   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 */
 
     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,
   exim_gnutls_err_check(US"gnutls_dh_params_generate2");
 
   /* gnutls_dh_params_export_pkcs3() will tell us the exact size, every time,