Implemented gsasl driver for authentication.
[exim.git] / src / src / tls-gnu.c
index a73d8b89313509d8d48007ad2ea0068444480db7..2d1a327dea80307939e2fc23b0c9d5714fa2ae5c 100644 (file)
@@ -1,10 +1,8 @@
-/* $Cambridge: exim/src/src/tls-gnu.c,v 1.21 2009/06/10 07:34:04 tom Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2007 */
+/* Copyright (c) University of Cambridge 1995 - 2009 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* This module provides TLS (aka SSL) support for Exim using the GnuTLS
@@ -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)?
@@ -792,6 +820,18 @@ if (verify_requirement != VERIFY_NONE)
 
 gnutls_db_set_cache_expiration(session, ssl_session_timeout);
 
+/* Reduce security in favour of increased compatibility, if the admin
+decides to make that trade-off. */
+if (gnutls_compat_mode)
+  {
+#if LIBGNUTLS_VERSION_NUMBER >= 0x020104
+  DEBUG(D_tls) debug_printf("lowering GnuTLS security, compatibility mode\n");
+  gnutls_session_enable_compatibility_mode(session);
+#else
+  DEBUG(D_tls) debug_printf("Unable to set gnutls_compat_mode - GnuTLS version too old\n");
+#endif
+  }
+
 DEBUG(D_tls) debug_printf("initialized GnuTLS session\n");
 return session;
 }
@@ -814,7 +854,10 @@ construct_cipher_name(gnutls_session session)
 {
 static uschar cipherbuf[256];
 uschar *ver;
-int bits, c, kx, mac;
+int bits, c, kx, mac, rc;
+#ifdef GNUTLS_CB_TLS_UNIQUE
+gnutls_datum_t channel;
+#endif
 
 ver = string_copy(
   US gnutls_protocol_get_name(gnutls_protocol_get_version(session)));
@@ -831,6 +874,21 @@ string_format(cipherbuf, sizeof(cipherbuf), "%s:%s:%u", ver,
 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
 }
 
 
@@ -1298,4 +1356,26 @@ gnutls_global_deinit();
 tls_active = -1;
 }
 
+
+
+
+/*************************************************
+*         Report the library versions.           *
+*************************************************/
+
+/* See a description in tls-openssl.c for an explanation of why this exists.
+
+Arguments:   a FILE* to print the results to
+Returns:     nothing
+*/
+
+void
+tls_version_report(FILE *f)
+{
+fprintf(f, "Library version: GnuTLS: Compile: %s\n"
+           "                         Runtime: %s\n",
+           LIBGNUTLS_VERSION,
+           gnutls_check_version(NULL));
+}
+
 /* End of tls-gnu.c */