-/* $Cambridge: exim/src/src/tls-gnu.c,v 1.2 2004/11/25 10:26:04 ph10 Exp $ */
-
/*************************************************
* Exim - an Internet mail transport agent *
*************************************************/
-/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* 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
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 */
#define UNKNOWN_NAME "unknown"
-#define DH_BITS 768
-#define RSA_BITS 512
+#define DH_BITS 1024
+#define PARAM_SIZE 2*1024
+
-/* Values for verify_requirment and initialized */
+/* Values for verify_requirment */
enum { VERIFY_NONE, VERIFY_OPTIONAL, VERIFY_REQUIRED };
-enum { INITIALIZED_NOT, INITIALIZED_SERVER, INITIALIZED_CLIENT };
/* Local static variables for GNUTLS */
-static BOOL initialized = INITIALIZED_NOT;
static host_item *client_host;
-static gnutls_rsa_params rsa_params = NULL;
static gnutls_dh_params dh_params = NULL;
static gnutls_certificate_server_credentials x509_cred = NULL;
static int ssl_session_timeout = 200;
static int verify_requirement;
-/* Priorities for TLS algorithms to use. At present, only the cipher priority
-vector can be altered. */
+/* Priorities for TLS algorithms to use. In each case there's a default table,
+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 };
-static const int protocol_priority[16] = { GNUTLS_TLS1, GNUTLS_SSL3, 0 };
+static int proto_priority[16];
-static const int kx_priority[16] = {
+static const int default_kx_priority[16] = {
GNUTLS_KX_RSA,
GNUTLS_KX_DHE_DSS,
GNUTLS_KX_DHE_RSA,
- GNUTLS_KX_RSA_EXPORT,
0 };
+static int kx_priority[16];
+
static int default_cipher_priority[16] = {
- GNUTLS_CIPHER_ARCFOUR_128,
+ GNUTLS_CIPHER_AES_256_CBC,
GNUTLS_CIPHER_AES_128_CBC,
GNUTLS_CIPHER_3DES_CBC,
- GNUTLS_CIPHER_ARCFOUR_40,
+ GNUTLS_CIPHER_ARCFOUR_128,
0 };
static int cipher_priority[16];
-static const int mac_priority[16] = {
+static const int default_mac_priority[16] = {
GNUTLS_MAC_SHA,
GNUTLS_MAC_MD5,
0 };
+static int mac_priority[16];
+
+/* These two are currently not changeable. */
+
static const int comp_priority[16] = { GNUTLS_COMP_NULL, 0 };
static const int cert_type_priority[16] = { GNUTLS_CRT_X509, 0 };
-/* Tables of cipher names and equivalent numbers */
+/* Tables of priority names and equivalent numbers */
typedef struct pri_item {
uschar *name;
int *values;
} pri_item;
+
+#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[] = {
+#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 }
+};
+
+
+static int kx_rsa_codes[] = { GNUTLS_KX_RSA,
+ GNUTLS_KX_DHE_RSA, 0 };
+static int kx_dhe_codes[] = { GNUTLS_KX_DHE_DSS,
+ GNUTLS_KX_DHE_RSA, 0 };
+static int kx_dhe_dss_codes[] = { GNUTLS_KX_DHE_DSS, 0 };
+static int kx_dhe_rsa_codes[] = { GNUTLS_KX_DHE_RSA, 0 };
+
+static pri_item kx_index[] = {
+ { US"DHE_DSS", kx_dhe_dss_codes },
+ { US"DHE_RSA", kx_dhe_rsa_codes },
+ { US"RSA", kx_rsa_codes },
+ { US"DHE", kx_dhe_codes }
+};
+
+
static int arcfour_128_codes[] = { GNUTLS_CIPHER_ARCFOUR_128, 0 };
static int arcfour_40_codes[] = { GNUTLS_CIPHER_ARCFOUR_40, 0 };
static int arcfour_codes[] = { GNUTLS_CIPHER_ARCFOUR_128,
};
+static int mac_sha_codes[] = { GNUTLS_MAC_SHA, 0 };
+static int mac_md5_codes[] = { GNUTLS_MAC_MD5, 0 };
+
+static pri_item mac_index[] = {
+ { US"SHA", mac_sha_codes },
+ { US"SHA1", mac_sha_codes },
+ { US"MD5", mac_md5_codes }
+};
+
+
/*************************************************
* Handle TLS error *
prefix text to include in the logged error
host NULL if setting up a server;
the connected host if setting up a client
- err a GnuTLS error number, or 0 if local error
+ msg additional error string (may be NULL)
+ usually obtained from gnutls_strerror()
Returns: OK/DEFER/FAIL
*/
static int
-tls_error(uschar *prefix, host_item *host, int err)
+tls_error(uschar *prefix, host_item *host, const char *msg)
{
-uschar *errtext = US"";
-if (err != 0) errtext = string_sprintf(": %s", gnutls_strerror(err));
if (host == NULL)
{
- log_write(0, LOG_MAIN, "TLS error on connection from %s (%s)%s",
- (sender_fullhost != NULL)? sender_fullhost : US "local process",
- prefix, errtext);
+ uschar *conn_info = smtp_get_connection_info();
+ if (strncmp(conn_info, "SMTP ", 5) == 0)
+ conn_info += 5;
+ log_write(0, LOG_MAIN, "TLS error on %s (%s)%s%s",
+ conn_info, prefix, msg ? ": " : "", msg ? msg : "");
return DEFER;
}
else
{
- log_write(0, LOG_MAIN, "TLS error on connection to %s [%s] (%s)%s",
- host->name, host->address, prefix, errtext);
+ log_write(0, LOG_MAIN, "TLS error on connection to %s [%s] (%s)%s%s",
+ host->name, host->address, prefix, msg ? ": " : "", msg ? msg : "");
return FAIL;
}
}
*/
static BOOL
-verify_certificate(gnutls_session session, uschar **error)
+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;
dn_string = string_copy_malloc(buff);
}
- verify = gnutls_certificate_verify_peers(session);
+ rc = gnutls_certificate_verify_peers2(session, &verify);
}
else
{
DEBUG(D_tls) debug_printf("no peer certificate supplied\n");
verify = GNUTLS_CERT_INVALID;
- *error = US"not supplied";
+ *error = "not supplied";
}
/* 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)?
- US"revoked" : US"invalid";
+ "revoked" : "invalid";
if (verify_requirement == VERIFY_REQUIRED)
{
DEBUG(D_tls) debug_printf("TLS certificate verification failed (%s): "
-
-/*************************************************
-* Write/read datum to/from file *
-*************************************************/
-
-/* These functions are used for saving and restoring the RSA and D-H parameters
-for use by all Exim processes. Data that is read is placed in malloc'd store
-because that's what happens for newly generated data.
-
-Arguments:
- fd the file descriptor
- d points to the datum
-
-returns: FALSE on error (errno set)
-*/
-
-static BOOL
-write_datum(int fd, gnutls_datum *d)
-{
-if (write(fd, &(d->size), sizeof(d->size)) != sizeof(d->size)) return FALSE;
-if (write(fd, d->data, d->size) != d->size) return FALSE;
-return TRUE;
-}
-
-
-static BOOL
-read_datum(int fd, gnutls_datum *d)
-{
-if (read(fd, &(d->size), sizeof(d->size)) != sizeof(d->size)) return FALSE;
-d->data = malloc(d->size);
-if (d->data == NULL) return FALSE;
-if (read(fd, d->data, d->size) != d->size) return FALSE;
-return TRUE;
-}
-
-
-
/*************************************************
-* Setup up RSA and DH parameters *
+* Setup up DH parameters *
*************************************************/
-/* Generating the RSA and D-H parameters takes a long time. They only need to
+/* Generating the D-H parameters may take a long time. They only need to
be re-generated every so often, depending on security policy. What we do is to
keep these parameters in a file in the spool directory. If the file does not
exist, we generate them. This means that it is easy to cause a regeneration.
*/
static int
-init_rsa_dh(host_item *host)
+init_dh(host_item *host)
{
-int fd, ret;
-gnutls_datum m, e, d, p, q, u, prime, generator;
+int fd;
+int ret;
+gnutls_datum m;
uschar filename[200];
/* Initialize the data structures for holding the parameters */
-ret = gnutls_rsa_params_init(&rsa_params);
-if (ret < 0) return tls_error(US"init rsa_params", host, ret);
-
ret = gnutls_dh_params_init(&dh_params);
-if (ret < 0) return tls_error(US"init dh_params", host, ret);
+if (ret < 0) return tls_error(US"init dh_params", host, gnutls_strerror(ret));
/* Set up the name of the cache file */
if (!string_format(filename, sizeof(filename), "%s/gnutls-params",
spool_directory))
- return tls_error(US"overlong filename", host, 0);
+ return tls_error(US"overlong filename", host, NULL);
-/* Open the cache file for reading. If this fails because of a non-existent
-file, compute a new set of parameters, write them to a temporary file, and then
-rename that file as the cache file. Other opening errors are bad. */
+/* Open the cache file for reading and if successful, read it and set up the
+parameters. */
fd = Uopen(filename, O_RDONLY, 0);
-if (fd < 0)
+if (fd >= 0)
{
- unsigned int rsa_bits = RSA_BITS;
- unsigned int dh_bits = DH_BITS;
- uschar tempfilename[sizeof(filename) + 10];
+ struct stat statbuf;
+ if (fstat(fd, &statbuf) < 0)
+ {
+ (void)close(fd);
+ return tls_error(US"TLS cache stat failed", host, strerror(errno));
+ }
- if (errno != ENOENT)
- return tls_error(string_open_failed(errno, "%s for reading", filename),
- host, 0);
+ m.size = statbuf.st_size;
+ m.data = malloc(m.size);
+ if (m.data == NULL)
+ return tls_error(US"memory allocation failed", host, strerror(errno));
+ errno = 0;
+ if (read(fd, m.data, m.size) != m.size)
+ return tls_error(US"TLS cache read failed", host, strerror(errno));
+ (void)close(fd);
- DEBUG(D_tls) debug_printf("generating %d bit RSA key...\n", RSA_BITS);
- ret = gnutls_rsa_params_generate2(rsa_params, RSA_BITS);
- if (ret < 0) return tls_error(US"RSA key generation", host, ret);
+ ret = gnutls_dh_params_import_pkcs3(dh_params, &m, GNUTLS_X509_FMT_PEM);
+ if (ret < 0)
+ return tls_error(US"DH params import", host, gnutls_strerror(ret));
+ DEBUG(D_tls) debug_printf("read D-H parameters from file\n");
+
+ free(m.data);
+ }
+
+/* If the file does not exist, fall through to compute new data and cache it.
+If there was any other opening error, it is serious. */
+
+else if (errno == ENOENT)
+ {
+ ret = -1;
+ DEBUG(D_tls)
+ debug_printf("parameter cache file %s does not exist\n", filename);
+ }
+else
+ return tls_error(string_open_failed(errno, "%s for reading", filename),
+ host, NULL);
+
+/* If ret < 0, either the cache file does not exist, or the data it contains
+is not useful. One particular case of this is when upgrading from an older
+release of Exim in which the data was stored in a different format. We don't
+try to be clever and support both formats; we just regenerate new data in this
+case. */
+
+if (ret < 0)
+ {
+ uschar tempfilename[sizeof(filename) + 10];
DEBUG(D_tls) debug_printf("generating %d bit Diffie-Hellman key...\n",
DH_BITS);
ret = gnutls_dh_params_generate2(dh_params, DH_BITS);
- if (ret < 0) return tls_error(US"D-H key generation", host, ret);
+ if (ret < 0) return tls_error(US"D-H key generation", host, gnutls_strerror(ret));
/* Write the parameters to a file in the spool directory so that we
can use them from other Exim processes. */
fd = Uopen(tempfilename, O_WRONLY|O_CREAT, 0400);
if (fd < 0)
return tls_error(string_open_failed(errno, "%s for writing", filename),
- host, 0);
+ host, NULL);
(void)fchown(fd, exim_uid, exim_gid); /* Probably not necessary */
- ret = gnutls_rsa_params_export_raw(rsa_params, &m, &e, &d, &p, &q, &u,
- &rsa_bits);
- if (ret < 0) return tls_error(US"RSA params export", host, ret);
-
- ret = gnutls_dh_params_export_raw(dh_params, &prime, &generator, &dh_bits);
- if (ret < 0) return tls_error(US"DH params export", host, ret);
-
- if (!write_datum(fd, &m) ||
- !write_datum(fd, &e) ||
- !write_datum(fd, &d) ||
- !write_datum(fd, &p) ||
- !write_datum(fd, &q) ||
- !write_datum(fd, &u) ||
- !write_datum(fd, &prime) ||
- !write_datum(fd, &generator))
- return tls_error(US"TLS cache write failed", host, 0);
-
+ /* export the parameters in a format that can be generated using GNUTLS'
+ * certtool or other programs.
+ *
+ * The commands for certtool are:
+ * $ certtool --generate-dh-params --bits 1024 > params
+ */
+
+ m.size = PARAM_SIZE;
+ m.data = malloc(m.size);
+ if (m.data == NULL)
+ return tls_error(US"memory allocation failed", host, strerror(errno));
+
+ m.size = PARAM_SIZE;
+ ret = gnutls_dh_params_export_pkcs3(dh_params, GNUTLS_X509_FMT_PEM, m.data,
+ &m.size);
+ if (ret < 0)
+ return tls_error(US"DH params export", host, gnutls_strerror(ret));
+
+ m.size = Ustrlen(m.data);
+ errno = 0;
+ if (write(fd, m.data, m.size) != m.size || write(fd, "\n", 1) != 1)
+ return tls_error(US"TLS cache write failed", host, strerror(errno));
+
+ free(m.data);
(void)close(fd);
if (rename(CS tempfilename, CS filename) < 0)
- return tls_error(string_sprintf("failed to rename %s as %s: %s",
- tempfilename, filename, strerror(errno)), host, 0);
+ return tls_error(string_sprintf("failed to rename %s as %s",
+ tempfilename, filename), host, strerror(errno));
- DEBUG(D_tls) debug_printf("wrote RSA and D-H parameters to file\n");
+ DEBUG(D_tls) debug_printf("wrote D-H parameters to file %s\n", filename);
}
-/* File opened for reading; get the data */
-
-else
- {
- if (!read_datum(fd, &m) ||
- !read_datum(fd, &e) ||
- !read_datum(fd, &d) ||
- !read_datum(fd, &p) ||
- !read_datum(fd, &q) ||
- !read_datum(fd, &u) ||
- !read_datum(fd, &prime) ||
- !read_datum(fd, &generator))
- return tls_error(US"TLS cache read failed", host, 0);
-
- (void)close(fd);
-
- ret = gnutls_rsa_params_import_raw(rsa_params, &m, &e, &d, &p, &q, &u);
- if (ret < 0) return tls_error(US"RSA params import", host, ret);
-
- ret = gnutls_dh_params_import_raw(dh_params, &prime, &generator);
- if (ret < 0) return tls_error(US"DH params import", host, ret);
-
- DEBUG(D_tls) debug_printf("read RSA and D-H parameters from file\n");
- }
-
-DEBUG(D_tls) debug_printf("initialized RSA and D-H parameters\n");
+DEBUG(D_tls) debug_printf("initialized D-H parameters\n");
return OK;
}
int rc;
uschar *cert_expanded, *key_expanded, *cas_expanded, *crl_expanded;
-initialized = (host == NULL)? INITIALIZED_SERVER : INITIALIZED_CLIENT;
+client_host = host;
rc = gnutls_global_init();
-if (rc < 0) return tls_error(US"tls-init", host, rc);
+if (rc < 0) return tls_error(US"tls-init", host, gnutls_strerror(rc));
-/* Create RSA and D-H parameters, or read them from the cache file. This
-function does its own SMTP error messaging. */
+/* Create D-H parameters, or read them from the cache file. This function does
+its own SMTP error messaging. */
-rc = init_rsa_dh(host);
+rc = init_dh(host);
if (rc != OK) return rc;
/* Create the credentials structure */
rc = gnutls_certificate_allocate_credentials(&x509_cred);
-if (rc < 0) return tls_error(US"certificate_allocate_credentials", host, rc);
+if (rc < 0)
+ return tls_error(US"certificate_allocate_credentials",
+ host, gnutls_strerror(rc));
/* This stuff must be done for each session, because different certificates
may be required for different sessions. */
if (!expand_check(certificate, US"tls_certificate", &cert_expanded))
return DEFER;
+key_expanded = NULL;
if (privatekey != NULL)
{
if (!expand_check(privatekey, US"tls_privatekey", &key_expanded))
return DEFER;
}
-else key_expanded = cert_expanded;
+
+/* If expansion was forced to fail, key_expanded will be NULL. If the result of
+the expansion is an empty string, ignore it also, and assume that the private
+key is in the same file as the certificate. */
+
+if (key_expanded == NULL || *key_expanded == 0)
+ key_expanded = cert_expanded;
/* Set the certificate and private keys */
cert_expanded, key_expanded);
rc = gnutls_certificate_set_x509_key_file(x509_cred, CS cert_expanded,
CS key_expanded, GNUTLS_X509_FMT_PEM);
- if (rc < 0)
+ if (rc < 0)
{
uschar *msg = string_sprintf("cert/key setup: cert=%s key=%s",
- cert_expanded, key_expanded);
- return tls_error(msg, host, rc);
- }
+ cert_expanded, key_expanded);
+ return tls_error(msg, host, gnutls_strerror(rc));
+ }
}
/* A certificate is mandatory in a server, but not in a client */
else
{
if (host == NULL)
- return tls_error(US"no TLS server certificate is specified", host, 0);
+ return tls_error(US"no TLS server certificate is specified", NULL, NULL);
DEBUG(D_tls) debug_printf("no TLS client certificate is specified\n");
}
return DEFER;
}
- DEBUG(D_tls) debug_printf("verify certificates = %s size=%d\n",
- cas_expanded, (int)statbuf.st_size);
+ DEBUG(D_tls) debug_printf("verify certificates = %s size=" OFF_T_FMT "\n",
+ cas_expanded, statbuf.st_size);
/* If the cert file is empty, there's no point in loading the CRL file. */
{
rc = gnutls_certificate_set_x509_trust_file(x509_cred, CS cas_expanded,
GNUTLS_X509_FMT_PEM);
- if (rc < 0) return tls_error(US"setup_certs", host, rc);
+ if (rc < 0) return tls_error(US"setup_certs", host, gnutls_strerror(rc));
if (crl != NULL && *crl != 0)
{
DEBUG(D_tls) debug_printf("loading CRL file = %s\n", crl_expanded);
rc = gnutls_certificate_set_x509_crl_file(x509_cred, CS crl_expanded,
GNUTLS_X509_FMT_PEM);
- if (rc < 0) return tls_error(US"CRL setup", host, rc);
+ if (rc < 0) return tls_error(US"CRL setup", host, gnutls_strerror(rc));
}
}
}
/* Associate the parameters with the x509 credentials structure. */
gnutls_certificate_set_dh_params(x509_cred, dh_params);
-gnutls_certificate_set_rsa_params(x509_cred, rsa_params);
DEBUG(D_tls) debug_printf("initialized certificate stuff\n");
return OK;
/*************************************************
-* Remove ciphers from priority list *
+* Remove from a priority list *
*************************************************/
/* Cautiously written so that it will remove duplicates if present.
*/
static void
-remove_ciphers(int *list, int *remove_list)
+remove_priority(int *list, int *remove_list)
{
for (; *remove_list != 0; remove_list++)
{
/*************************************************
-* Add ciphers to priority list *
+* Add to a priority list *
*************************************************/
/* Cautiously written to check the list size
*/
static BOOL
-add_ciphers(int *list, int list_max, int *add_list)
+add_priority(int *list, int list_max, int *add_list)
{
int next = 0;
while (list[next] != 0) next++;
+/*************************************************
+* Adjust a priority list *
+*************************************************/
+
+/* This function is called to adjust the lists of cipher algorithms, MAC
+algorithms, key-exchange methods, and protocols.
+
+Arguments:
+ plist the appropriate priority list
+ psize the length of the list
+ s the configuation string
+ index the index of recognized strings
+ isize the length of the index
+
+
+ which text for an error message
+
+Returns: FALSE if the table overflows, else TRUE
+*/
+
+static BOOL
+set_priority(int *plist, int psize, uschar *s, pri_item *index, int isize,
+ uschar *which)
+{
+int sep = 0;
+BOOL first = TRUE;
+uschar *t;
+
+while ((t = string_nextinlist(&s, &sep, big_buffer, big_buffer_size)) != NULL)
+ {
+ int i;
+ BOOL exclude = t[0] == '!';
+ if (first && !exclude) plist[0] = 0;
+ first = FALSE;
+ for (i = 0; i < isize; i++)
+ {
+ uschar *ss = strstric(t, index[i].name, FALSE);
+ if (ss != NULL)
+ {
+ uschar *endss = ss + Ustrlen(index[i].name);
+ if ((ss == t || !isalnum(ss[-1])) && !isalnum(*endss))
+ {
+ if (exclude)
+ remove_priority(plist, index[i].values);
+ else
+ {
+ if (!add_priority(plist, psize, index[i].values))
+ {
+ log_write(0, LOG_MAIN|LOG_PANIC, "GnuTLS init failed: %s "
+ "priority table overflow", which);
+ return FALSE;
+ }
+ }
+ }
+ }
+ }
+ }
+
+DEBUG(D_tls)
+ {
+ int *ptr = plist;
+ debug_printf("adjusted %s priorities:", which);
+ while (*ptr != 0) debug_printf(" %d", *ptr++);
+ debug_printf("\n");
+ }
+
+return TRUE;
+}
+
+
+
+
/*************************************************
* Initialize a single GNUTLS session *
*************************************************/
Arguments:
side one of GNUTLS_SERVER, GNUTLS_CLIENT
- expciphers expanded ciphers list
+ expciphers expanded ciphers list or NULL
+ expmac expanded MAC list or NULL
+ expkx expanded key-exchange list or NULL
+ expproto expanded protocol list or NULL
Returns: a gnutls_session, or NULL if there is a problem
*/
static gnutls_session
-tls_session_init(int side, uschar *expciphers)
+tls_session_init(int side, uschar *expciphers, uschar *expmac, uschar *expkx,
+ uschar *expproto)
{
gnutls_session session;
gnutls_init(&session, side);
-/* Handle the list of permitted ciphers */
+/* Initialize the lists of permitted protocols, key-exchange methods, ciphers,
+and MACs. */
memcpy(cipher_priority, default_cipher_priority, sizeof(cipher_priority));
+memcpy(mac_priority, default_mac_priority, sizeof(mac_priority));
+memcpy(kx_priority, default_kx_priority, sizeof(kx_priority));
+memcpy(proto_priority, default_proto_priority, sizeof(proto_priority));
+
+/* The names OpenSSL uses in tls_require_ciphers are of the form DES-CBC3-SHA,
+using hyphen separators. GnuTLS uses underscore separators. So that I can use
+either form for tls_require_ciphers in my tests, and also for general
+convenience, we turn hyphens into underscores before scanning the list. */
if (expciphers != NULL)
{
- int sep = 0;
- BOOL first = TRUE;
- uschar *cipher;
-
- /* The names OpenSSL uses are of the form DES-CBC3-SHA, using hyphen
- separators. GnuTLS uses underscore separators. So that I can use either form
- in my tests, and also for general convenience, we turn hyphens into
- underscores before scanning the list. */
-
uschar *s = expciphers;
while (*s != 0) { if (*s == '-') *s = '_'; s++; }
+ }
- while ((cipher = string_nextinlist(&expciphers, &sep, big_buffer,
- big_buffer_size)) != NULL)
- {
- int i;
- BOOL exclude = cipher[0] == '!';
- if (first && !exclude) cipher_priority[0] = 0;
- first = FALSE;
-
- for (i = 0; i < sizeof(cipher_index)/sizeof(pri_item); i++)
- {
- uschar *ss = strstric(cipher, cipher_index[i].name, FALSE);
- if (ss != NULL)
- {
- uschar *endss = ss + Ustrlen(cipher_index[i].name);
- if ((ss == cipher || !isalnum(ss[-1])) && !isalnum(*endss))
- {
- if (exclude)
- remove_ciphers(cipher_priority, cipher_index[i].values);
- else
- {
- if (!add_ciphers(cipher_priority,
- sizeof(cipher_priority)/sizeof(pri_item),
- cipher_index[i].values))
- {
- log_write(0, LOG_MAIN|LOG_PANIC, "GnuTLS init failed: cipher "
- "priority table overflow");
- gnutls_deinit(session);
- return NULL;
- }
- }
- }
- }
- }
- }
-
- DEBUG(D_tls)
- {
- int *ptr = cipher_priority;
- debug_printf("adjusted cipher priorities:");
- while (*ptr != 0) debug_printf(" %d", *ptr++);
- debug_printf("\n");
- }
+if ((expciphers != NULL &&
+ !set_priority(cipher_priority, sizeof(cipher_priority)/sizeof(int),
+ expciphers, cipher_index, sizeof(cipher_index)/sizeof(pri_item),
+ US"cipher")) ||
+ (expmac != NULL &&
+ !set_priority(mac_priority, sizeof(mac_priority)/sizeof(int),
+ expmac, mac_index, sizeof(mac_index)/sizeof(pri_item),
+ US"MAC")) ||
+ (expkx != NULL &&
+ !set_priority(kx_priority, sizeof(kx_priority)/sizeof(int),
+ expkx, kx_index, sizeof(kx_index)/sizeof(pri_item),
+ US"key-exchange")) ||
+ (expproto != NULL &&
+ !set_priority(proto_priority, sizeof(proto_priority)/sizeof(int),
+ expproto, proto_index, sizeof(proto_index)/sizeof(pri_item),
+ US"protocol")))
+ {
+ gnutls_deinit(session);
+ return NULL;
}
/* Define the various priorities */
gnutls_cipher_set_priority(session, cipher_priority);
gnutls_compression_set_priority(session, comp_priority);
gnutls_kx_set_priority(session, kx_priority);
-gnutls_protocol_set_priority(session, protocol_priority);
+gnutls_protocol_set_priority(session, proto_priority);
gnutls_mac_set_priority(session, mac_priority);
gnutls_cred_set(session, GNUTLS_CRD_CERTIFICATE, x509_cred);
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;
}
{
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)));
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
}
a TLS session.
Arguments:
- require_ciphers list of allowed ciphers
+ require_ciphers list of allowed ciphers or NULL
+ require_mac list of allowed MACs or NULL
+ require_kx list of allowed key_exchange methods or NULL
+ require_proto list of allowed protocols or NULL
Returns: OK on success
DEFER for errors before the start of the negotiation
*/
int
-tls_server_start(uschar *require_ciphers)
+tls_server_start(uschar *require_ciphers, uschar *require_mac,
+ uschar *require_kx, uschar *require_proto)
{
int rc;
-uschar *error;
+const char *error;
uschar *expciphers = NULL;
+uschar *expmac = NULL;
+uschar *expkx = NULL;
+uschar *expproto = NULL;
/* Check for previous activation */
if (tls_active >= 0)
{
- log_write(0, LOG_MAIN, "STARTTLS received in already encrypted "
- "connection from %s",
- (sender_fullhost != NULL)? sender_fullhost : US"local process");
+ tls_error("STARTTLS received after TLS started", NULL, "");
smtp_printf("554 Already in TLS\r\n");
return FAIL;
}
tls_crl);
if (rc != OK) return rc;
-if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers))
+if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers) ||
+ !expand_check(require_mac, US"gnutls_require_mac", &expmac) ||
+ !expand_check(require_kx, US"gnutls_require_kx", &expkx) ||
+ !expand_check(require_proto, US"gnutls_require_proto", &expproto))
return FAIL;
/* If this is a host for which certificate verification is mandatory or
/* Prepare for new connection */
-tls_session = tls_session_init(GNUTLS_SERVER, expciphers);
+tls_session = tls_session_init(GNUTLS_SERVER, expciphers, expmac, expkx,
+ expproto);
if (tls_session == NULL)
- return tls_error(US"tls_session_init", NULL, GNUTLS_E_MEMORY_ERROR);
+ return tls_error(US"tls_session_init", NULL,
+ gnutls_strerror(GNUTLS_E_MEMORY_ERROR));
/* Set context and tell client to go ahead, except in the case of TLS startup
on connection, where outputting anything now upsets the clients and tends to
/* Now negotiate the TLS session. We put our own timer on it, since it seems
that the GnuTLS library doesn't. */
-gnutls_transport_set_ptr(tls_session, (gnutls_transport_ptr)fileno(smtp_out));
+gnutls_transport_set_ptr2(tls_session, (gnutls_transport_ptr)fileno(smtp_in),
+ (gnutls_transport_ptr)fileno(smtp_out));
sigalrm_seen = FALSE;
if (smtp_receive_timeout > 0) alarm(smtp_receive_timeout);
if (rc < 0)
{
- if (sigalrm_seen)
- Ustrcpy(ssl_errstring, "timed out");
- else
- Ustrcpy(ssl_errstring, gnutls_strerror(rc));
- log_write(0, LOG_MAIN,
- "TLS error on connection from %s (gnutls_handshake): %s",
- (sender_fullhost != NULL)? sender_fullhost : US"local process",
- ssl_errstring);
+ tls_error(US"gnutls_handshake", NULL,
+ sigalrm_seen ? "timed out" : gnutls_strerror(rc));
/* It seems that, except in the case of a timeout, we have to close the
connection right here; otherwise if the other end is running OpenSSL it hangs
if (!sigalrm_seen)
{
- fclose(smtp_out);
- fclose(smtp_in);
+ (void)fclose(smtp_out);
+ (void)fclose(smtp_in);
}
return FAIL;
if (verify_requirement != VERIFY_NONE &&
!verify_certificate(tls_session, &error))
{
- log_write(0, LOG_MAIN,
- "TLS error on connection from %s: certificate verification failed (%s)",
- (sender_fullhost != NULL)? sender_fullhost : US"local process", error);
+ tls_error(US"certificate verification failed", NULL, error);
return FAIL;
}
receive_ungetc = tls_ungetc;
receive_feof = tls_feof;
receive_ferror = tls_ferror;
+receive_smtp_buffered = tls_smtp_buffered;
tls_active = fileno(smtp_out);
Arguments:
fd the fd of the connection
host connected host (for messages)
- addr
+ addr the first address (not used)
dhparam DH parameter file
certificate certificate file
privatekey private key file
verify_certs file for certificate verify
verify_crl CRL for verify
- require_ciphers list of allowed ciphers
+ require_ciphers list of allowed ciphers or NULL
+ require_mac list of allowed MACs or NULL
+ require_kx list of allowed key_exchange methods or NULL
+ require_proto list of allowed protocols or NULL
timeout startup timeout
Returns: OK/DEFER/FAIL (because using common functions),
int
tls_client_start(int fd, host_item *host, address_item *addr, uschar *dhparam,
uschar *certificate, uschar *privatekey, uschar *verify_certs,
- uschar *verify_crl, uschar *require_ciphers, int timeout)
+ uschar *verify_crl, uschar *require_ciphers, uschar *require_mac,
+ uschar *require_kx, uschar *require_proto, int timeout)
{
const gnutls_datum *server_certs;
uschar *expciphers = NULL;
-uschar *error;
+uschar *expmac = NULL;
+uschar *expkx = NULL;
+uschar *expproto = NULL;
+const char *error;
unsigned int server_certs_size;
int rc;
DEBUG(D_tls) debug_printf("initializing GnuTLS as a client\n");
-client_host = host;
verify_requirement = (verify_certs == NULL)? VERIFY_NONE : VERIFY_REQUIRED;
rc = tls_init(host, certificate, privatekey, verify_certs, verify_crl);
if (rc != OK) return rc;
-if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers))
+if (!expand_check(require_ciphers, US"tls_require_ciphers", &expciphers) ||
+ !expand_check(require_mac, US"gnutls_require_mac", &expmac) ||
+ !expand_check(require_kx, US"gnutls_require_kx", &expkx) ||
+ !expand_check(require_proto, US"gnutls_require_proto", &expproto))
return FAIL;
-tls_session = tls_session_init(GNUTLS_CLIENT, expciphers);
+tls_session = tls_session_init(GNUTLS_CLIENT, expciphers, expmac, expkx,
+ expproto);
+
if (tls_session == NULL)
- return tls_error(US "tls_session_init", host, GNUTLS_E_MEMORY_ERROR);
+ return tls_error(US "tls_session_init", host,
+ gnutls_strerror(GNUTLS_E_MEMORY_ERROR));
gnutls_transport_set_ptr(tls_session, (gnutls_transport_ptr)fd);
alarm(0);
if (rc < 0)
- {
- if (sigalrm_seen)
- {
- log_write(0, LOG_MAIN, "TLS error on connection to %s [%s]: "
- "gnutls_handshake timed out", host->name, host->address);
- return FAIL;
- }
- else return tls_error(US "gnutls_handshake", host, rc);
- }
+ return tls_error(US "gnutls_handshake", host,
+ sigalrm_seen ? "timed out" : gnutls_strerror(rc));
server_certs = gnutls_certificate_get_peers(tls_session, &server_certs_size);
if (verify_requirement != VERIFY_NONE &&
!verify_certificate(tls_session, &error))
- {
- log_write(0, LOG_MAIN,
- "TLS error on connection to %s [%s]: certificate verification failed (%s)",
- host->name, host->address, error);
- return FAIL;
- }
+ return tls_error(US"certificate verification failed", host, error);
construct_cipher_name(tls_session); /* Sets tls_cipher */
tls_active = fd;
static void
record_io_error(int ec, uschar *when, uschar *text)
{
-uschar *additional = US"";
+const char *msg;
if (ec == GNUTLS_E_FATAL_ALERT_RECEIVED)
- additional = string_sprintf(": %s",
+ msg = string_sprintf("%s: %s", gnutls_strerror(ec),
gnutls_alert_get_name(gnutls_alert_get(tls_session)));
-
-if (initialized == INITIALIZED_SERVER)
- log_write(0, LOG_MAIN, "TLS %s error on connection from %s: %s%s", when,
- (sender_fullhost != NULL)? sender_fullhost : US "local process",
- (ec == 0)? text : US gnutls_strerror(ec), additional);
-
else
- log_write(0, LOG_MAIN, "TLS %s error on connection to %s [%s]: %s%s", when,
- client_host->name, client_host->address,
- (ec == 0)? text : US gnutls_strerror(ec), additional);
+ msg = gnutls_strerror(ec);
+
+tls_error(when, client_host, msg);
}
receive_ungetc = smtp_ungetc;
receive_feof = smtp_feof;
receive_ferror = smtp_ferror;
+ receive_smtp_buffered = smtp_buffered;
gnutls_deinit(tls_session);
tls_session = NULL;
ssl_xfer_error = 1;
return EOF;
}
-
+#ifndef DISABLE_DKIM
+ dkim_exim_verify_feed(ssl_xfer_buffer, inbytes);
+#endif
ssl_xfer_buffer_hwm = inbytes;
ssl_xfer_buffer_lwm = 0;
}
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 */