X-Git-Url: https://git.exim.org/exim.git/blobdiff_plain/de365dedeec38c30d7ff318eb6505d323e8a4411..184e88237dea64ce48076cdd0184612d057cbafd:/src/src/tls-gnu.c diff --git a/src/src/tls-gnu.c b/src/src/tls-gnu.c index ade383e42..891d1ea00 100644 --- a/src/src/tls-gnu.c +++ b/src/src/tls-gnu.c @@ -1,10 +1,10 @@ -/* $Cambridge: exim/src/src/tls-gnu.c,v 1.2 2004/11/25 10:26:04 ph10 Exp $ */ +/* $Cambridge: exim/src/src/tls-gnu.c,v 1.17 2007/01/08 10:50:18 ph10 Exp $ */ /************************************************* * Exim - an Internet mail transport agent * *************************************************/ -/* Copyright (c) University of Cambridge 1995 - 2004 */ +/* Copyright (c) University of Cambridge 1995 - 2007 */ /* See the file NOTICE for conditions of use and distribution. */ /* This module provides TLS (aka SSL) support for Exim using the GnuTLS @@ -22,8 +22,9 @@ functions from the GnuTLS library. */ #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 */ @@ -35,7 +36,6 @@ enum { INITIALIZED_NOT, INITIALIZED_SERVER, INITIALIZED_CLIENT }; 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; @@ -55,14 +55,13 @@ static const int kx_priority[16] = { GNUTLS_KX_RSA, GNUTLS_KX_DHE_DSS, GNUTLS_KX_DHE_RSA, - GNUTLS_KX_RSA_EXPORT, 0 }; 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]; @@ -230,48 +229,11 @@ return TRUE; /* accept */ - /************************************************* -* Write/read datum to/from file * +* Setup up DH parameters * *************************************************/ -/* 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 * -*************************************************/ - -/* 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. @@ -288,17 +250,15 @@ Returns: OK/DEFER/FAIL */ 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); @@ -308,24 +268,56 @@ if (!string_format(filename, sizeof(filename), "%s/gnutls-params", spool_directory)) return tls_error(US"overlong filename", host, 0); -/* 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, 0); + } - 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, 0); + if (read(fd, m.data, m.size) != m.size) + return tls_error(US"TLS cache read failed", host, 0); + (void)close(fd); + + ret = gnutls_dh_params_import_pkcs3(dh_params, &m, GNUTLS_X509_FMT_PEM); + if (ret < 0) return tls_error(US"DH params import", host, 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, 0); - 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); +/* 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); @@ -342,58 +334,38 @@ if (fd < 0) host, 0); (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); + /* 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, 0); + + 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, 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)) + m.size = Ustrlen(m.data); + if (write(fd, m.data, m.size) != m.size || write(fd, "\n", 1) != 1) return tls_error(US"TLS cache write failed", host, 0); + 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); - 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; } @@ -429,10 +401,10 @@ initialized = (host == NULL)? INITIALIZED_SERVER : INITIALIZED_CLIENT; rc = gnutls_global_init(); if (rc < 0) return tls_error(US"tls-init", host, 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 */ @@ -446,12 +418,19 @@ 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 */ @@ -461,12 +440,12 @@ if (cert_expanded != NULL) 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); + cert_expanded, key_expanded); return tls_error(msg, host, rc); - } + } } /* A certificate is mandatory in a server, but not in a client */ @@ -498,8 +477,8 @@ if (cas != NULL) 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. */ @@ -524,7 +503,6 @@ if (cas != NULL) /* 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; @@ -831,7 +809,8 @@ if (!tls_on_connect) /* 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); @@ -855,8 +834,8 @@ if (rc < 0) if (!sigalrm_seen) { - fclose(smtp_out); - fclose(smtp_in); + (void)fclose(smtp_out); + (void)fclose(smtp_in); } return FAIL;