TLS fixes for OpenSSL.
[exim.git] / src / src / tls-gnu.c
index 8fcdd109dd7ecec7a4d99690e079b30d598e74a1..2f952e47b3aeef121f294ae8e6d521f46fa57841 100644 (file)
@@ -1,5 +1,3 @@
-/* $Cambridge: exim/src/src/tls-gnu.c,v 1.24 2009/11/16 19:50:37 nm4 Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
@@ -14,6 +12,13 @@ is based on a patch that was contributed by Nikos Mavroyanopoulos.
 No cryptographic code is included in Exim. All this module does is to call
 functions from the GnuTLS library. */
 
+/* Note: This appears to be using an old API from compat.h; it is likely that
+someone familiary with GnuTLS programming could rework a lot of this to a
+modern API and perhaps remove the explicit knowledge of crypto algorithms from
+Exim.  Such a re-work would be most welcome and we'd sacrifice support for
+older GnuTLS releases without too many qualms -- maturity and experience
+in crypto libraries tends to improve their robustness against attack.
+Frankly, if you maintain it, you decide what's supported and what isn't. */
 
 /* Heading stuff for GnuTLS */
 
@@ -48,6 +53,13 @@ static int  verify_requirement;
 and space into which it can be copied and altered. */
 
 static const int default_proto_priority[16] = {
+  /* These are gnutls_protocol_t enum values */
+#if GNUTLS_VERSION_MAJOR > 1 || GNUTLS_VERSION_MINOR >= 7
+  GNUTLS_TLS1_2,
+#endif
+#if GNUTLS_VERSION_MAJOR > 1 || GNUTLS_VERSION_MINOR >= 2
+  GNUTLS_TLS1_1,
+#endif
   GNUTLS_TLS1,
   GNUTLS_SSL3,
   0 };
@@ -91,11 +103,27 @@ typedef struct pri_item {
 } pri_item;
 
 
-static int tls1_codes[] = { GNUTLS_TLS1, 0 };
+#if GNUTLS_VERSION_MAJOR > 1 || GNUTLS_VERSION_MINOR >= 7
+static int tls1_2_codes[] = { GNUTLS_TLS1_2, 0 };
+#endif
+#if GNUTLS_VERSION_MAJOR > 1 || GNUTLS_VERSION_MINOR >= 2
+static int tls1_1_codes[] = { GNUTLS_TLS1_1, 0 };
+#endif
+/* more recent libraries define this as an equivalent value to the
+canonical GNUTLS_TLS1_0; since they're the same, we stick to the
+older name. */
+static int tls1_0_codes[] = { GNUTLS_TLS1, 0 };
 static int ssl3_codes[] = { GNUTLS_SSL3, 0 };
 
 static pri_item proto_index[] = {
-  { US"TLS1", tls1_codes },
+#if GNUTLS_VERSION_MAJOR > 1 || GNUTLS_VERSION_MINOR >= 7
+  { US"TLS1.2", tls1_2_codes },
+#endif
+#if GNUTLS_VERSION_MAJOR > 1 || GNUTLS_VERSION_MINOR >= 2
+  { US"TLS1.1", tls1_1_codes },
+#endif
+  { US"TLS1.0", tls1_0_codes },
+  { US"TLS1", tls1_0_codes },
   { US"SSL3", ssl3_codes }
 };
 
@@ -207,10 +235,10 @@ Returns:     TRUE/FALSE
 static BOOL
 verify_certificate(gnutls_session session, const char **error)
 {
-int verify;
+int rc = -1;
 uschar *dn_string = US"";
 const gnutls_datum *cert;
-unsigned int cert_size = 0;
+unsigned int verify, cert_size = 0;
 
 *error = NULL;
 
@@ -234,7 +262,7 @@ if (cert != NULL)
       dn_string = string_copy_malloc(buff);
     }
 
-  verify = gnutls_certificate_verify_peers(session);
+  rc = gnutls_certificate_verify_peers2(session, &verify);
   }
 else
   {
@@ -246,7 +274,7 @@ else
 /* Handle the result of verification. INVALID seems to be set as well
 as REVOKED, but leave the test for both. */
 
-if ((verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED)) != 0)
+if ((rc < 0) || (verify & (GNUTLS_CERT_INVALID|GNUTLS_CERT_REVOKED)) != 0)
   {
   tls_certificate_verified = FALSE;
   if (*error == NULL) *error = ((verify & GNUTLS_CERT_REVOKED) != 0)?
@@ -826,23 +854,43 @@ construct_cipher_name(gnutls_session session)
 {
 static uschar cipherbuf[256];
 uschar *ver;
-int bits, c, kx, mac;
+int c, kx, mac;
+#ifdef GNUTLS_CB_TLS_UNIQUE
+int rc;
+gnutls_datum_t channel;
+#endif
 
 ver = string_copy(
   US gnutls_protocol_get_name(gnutls_protocol_get_version(session)));
 if (Ustrncmp(ver, "TLS ", 4) == 0) ver[3] = '-';   /* Don't want space */
 
 c = gnutls_cipher_get(session);
-bits = gnutls_cipher_get_key_size(c);
+/* returns size in "bytes" */
+tls_bits = gnutls_cipher_get_key_size(c) * 8;
 
 mac = gnutls_mac_get(session);
 kx = gnutls_kx_get(session);
 
 string_format(cipherbuf, sizeof(cipherbuf), "%s:%s:%u", ver,
-  gnutls_cipher_suite_get_name(kx, c, mac), bits);
+  gnutls_cipher_suite_get_name(kx, c, mac), tls_bits);
 tls_cipher = cipherbuf;
 
 DEBUG(D_tls) debug_printf("cipher: %s\n", cipherbuf);
+
+if (tls_channelbinding_b64)
+  free(tls_channelbinding_b64);
+tls_channelbinding_b64 = NULL;
+
+#ifdef GNUTLS_CB_TLS_UNIQUE
+channel = { NULL, 0 };
+rc = gnutls_session_channel_binding(session, GNUTLS_CB_TLS_UNIQUE, &channel);
+if (rc) {
+  DEBUG(D_tls) debug_printf("Channel binding error: %s\n", gnutls_strerror(rc));
+} else {
+  tls_channelbinding_b64 = auth_b64encode(channel.data, (int)channel.size);
+  DEBUG(D_tls) debug_printf("Have channel bindings cached for possible auth usage.\n");
+}
+#endif
 }
 
 
@@ -1326,8 +1374,10 @@ Returns:     nothing
 void
 tls_version_report(FILE *f)
 {
-fprintf(f, "GnuTLS compile-time version: %s\n", LIBGNUTLS_VERSION);
-fprintf(f, "GnuTLS runtime version: %s\n", gnutls_check_version(NULL));
+fprintf(f, "Library version: GnuTLS: Compile: %s\n"
+           "                         Runtime: %s\n",
+           LIBGNUTLS_VERSION,
+           gnutls_check_version(NULL));
 }
 
 /* End of tls-gnu.c */