changeset 3888:650701d41cdf HEAD

Generate DH parameters and use them. Changed default regeneration time to 1 week.
author Timo Sirainen <tss@iki.fi>
date Sun, 15 Jan 2006 14:35:01 +0200
parents f95146a34af9
children c7462001227b
files dovecot-example.conf src/login-common/ssl-proxy-openssl.c src/master/master-settings.c src/master/ssl-init-openssl.c src/master/ssl-init.c
diffstat 5 files changed, 192 insertions(+), 11 deletions(-) [+]
line wrap: on
line diff
--- a/dovecot-example.conf	Sun Jan 15 14:15:12 2006 +0200
+++ b/dovecot-example.conf	Sun Jan 15 14:35:01 2006 +0200
@@ -47,13 +47,13 @@
 #ssl_verify_client_cert = no
 
 # SSL parameter file. Master process generates this file for login processes.
-# It contains Diffie Hellman and RSA parameters.
+# It contains Diffie Hellman parameters.
 #ssl_parameters_file = /var/run/dovecot/ssl-parameters.dat
 
 # How often to regenerate the SSL parameters file. Generation is quite CPU
 # intensive operation. The value is in hours, 0 disables regeneration
 # entirely.
-#ssl_parameters_regenerate = 24
+#ssl_parameters_regenerate = 168
 
 # SSL ciphers to use
 #ssl_cipher_list = ALL:!LOW
--- a/src/login-common/ssl-proxy-openssl.c	Sun Jan 15 14:15:12 2006 +0200
+++ b/src/login-common/ssl-proxy-openssl.c	Sun Jan 15 14:35:01 2006 +0200
@@ -1,11 +1,17 @@
 /* Copyright (C) 2002 Timo Sirainen */
 
 #include "common.h"
+#include "array.h"
 #include "ioloop.h"
 #include "network.h"
+#include "read-full.h"
 #include "hash.h"
 #include "ssl-proxy.h"
 
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/stat.h>
+
 #ifdef HAVE_OPENSSL
 
 #include <openssl/crypto.h>
@@ -16,6 +22,8 @@
 #include <openssl/rand.h>
 
 #define DOVECOT_SSL_DEFAULT_CIPHER_LIST "ALL:!LOW"
+/* Check every 30 minutes if parameters file has been updated */
+#define SSL_PARAMFILE_CHECK_INTERVAL (60*30)
 
 enum ssl_io_action {
 	SSL_ADD_INPUT,
@@ -45,9 +53,18 @@
 	unsigned int cert_broken:1;
 };
 
+struct ssl_parameters {
+	const char *fname;
+	time_t last_mtime;
+	int fd;
+
+	DH *dh_512, *dh_1024;
+};
+
 static int extdata_index;
 static SSL_CTX *ssl_ctx;
 static struct hash_table *ssl_proxies;
+static struct ssl_parameters ssl_params;
 
 static void plain_read(void *context);
 static void plain_write(void *context);
@@ -56,6 +73,111 @@
 static void ssl_proxy_destroy(struct ssl_proxy *proxy);
 static void ssl_proxy_unref(struct ssl_proxy *proxy);
 
+static void read_next(struct ssl_parameters *params, void *data, size_t size)
+{
+	int ret;
+
+	if ((ret = read_full(params->fd, data, size)) < 0)
+		i_fatal("read(%s) failed: %m", params->fname);
+	if (ret == 0)
+		i_fatal("read(%s) failed: Unexpected EOF", params->fname);
+}
+
+static bool read_dh_parameters_next(struct ssl_parameters *params)
+{
+	unsigned char *buf;
+	const unsigned char *cbuf;
+	unsigned int len;
+	int bits;
+
+	/* read bit size. 0 ends the DH parameters list. */
+	read_next(params, &bits, sizeof(bits));
+
+	if (bits == 0)
+		return FALSE;
+
+	/* read data size. */
+	read_next(params, &len, sizeof(len));
+	if (len > 1024*100) /* should be enough? */
+		i_fatal("Corrupted SSL parameters file: %s", params->fname);
+
+	buf = i_malloc(len);
+	read_next(params, buf, len);
+
+	cbuf = buf;
+	switch (bits) {
+	case 512:
+		params->dh_512 = d2i_DHparams(NULL, &cbuf, len);
+		break;
+	case 1024:
+		params->dh_1024 = d2i_DHparams(NULL, &cbuf, len);
+		break;
+	}
+
+	i_free(buf);
+	return TRUE;
+}
+
+static void ssl_free_parameters(struct ssl_parameters *params)
+{
+	if (params->dh_512 != NULL) {
+		DH_free(params->dh_512);
+                params->dh_512 = NULL;
+	}
+	if (params->dh_1024 != NULL) {
+		DH_free(params->dh_1024);
+                params->dh_1024 = NULL;
+	}
+}
+
+static void ssl_read_parameters(struct ssl_parameters *params)
+{
+	bool warned = FALSE;
+
+	/* we'll wait until parameter file exists */
+	for (;;) {
+		params->fd = open(params->fname, O_RDONLY);
+		if (params->fd != -1)
+			break;
+
+		if (errno != ENOENT) {
+			i_fatal("Can't open SSL parameter file %s: %m",
+				params->fname);
+		}
+
+		if (!warned) {
+			i_warning("Waiting for SSL parameter file %s",
+				  params->fname);
+			warned = TRUE;
+		}
+		sleep(1);
+	}
+
+	ssl_free_parameters(params);
+	while (read_dh_parameters_next(params)) ;
+
+	if (close(params->fd) < 0)
+		i_error("close() failed: %m");
+	params->fd = -1;
+}
+
+static void ssl_refresh_parameters(struct ssl_parameters *params)
+{
+	struct stat st;
+
+	if (params->last_mtime > ioloop_time - SSL_PARAMFILE_CHECK_INTERVAL)
+		return;
+
+	if (params->last_mtime == 0)
+		ssl_read_parameters(params);
+	else {
+		if (stat(params->fname, &st) < 0)
+			i_error("stat(%s) failed: %m", params->fname);
+		else if (st.st_mtime != params->last_mtime)
+			ssl_read_parameters(params);
+	}
+}
+
 static void ssl_set_io(struct ssl_proxy *proxy, enum ssl_io_action action)
 {
 	switch (action) {
@@ -327,6 +449,8 @@
 		return -1;
 	}
 
+	ssl_refresh_parameters(&ssl_params);
+
 	ssl = SSL_new(ssl_ctx);
 	if (ssl == NULL) {
 		i_error("SSL_new() failed: %s", ssl_last_error());
@@ -437,6 +561,17 @@
 	return RSA_generate_key(keylength, RSA_F4, NULL, NULL);
 }
 
+static DH *ssl_tmp_dh_callback(SSL *ssl __attr_unused__,
+			       int is_export, int keylength)
+{
+	/* Well, I'm not exactly sure why the logic in here is this.
+	   It's the same as in Postfix, so it can't be too wrong. */
+	if (is_export && keylength == 512 && ssl_params.dh_512 != NULL)
+		return ssl_params.dh_512;
+
+	return ssl_params.dh_1024;
+}
+
 static int ssl_verify_client_cert(int preverify_ok, X509_STORE_CTX *ctx)
 {
 	SSL *ssl;
@@ -455,15 +590,17 @@
 
 void ssl_proxy_init(void)
 {
-	const char *cafile, *certfile, *keyfile, *paramfile, *cipher_list;
+	const char *cafile, *certfile, *keyfile, *cipher_list;
 	unsigned char buf;
 
+	memset(&ssl_params, 0, sizeof(ssl_params));
+
 	cafile = getenv("SSL_CA_FILE");
 	certfile = getenv("SSL_CERT_FILE");
 	keyfile = getenv("SSL_KEY_FILE");
-	paramfile = getenv("SSL_PARAM_FILE");
+	ssl_params.fname = getenv("SSL_PARAM_FILE");
 
-	if (certfile == NULL || keyfile == NULL || paramfile == NULL) {
+	if (certfile == NULL || keyfile == NULL || ssl_params.fname == NULL) {
 		/* SSL support is disabled */
 		return;
 	}
@@ -506,6 +643,7 @@
 
 	if (SSL_CTX_need_tmp_RSA(ssl_ctx))
 		SSL_CTX_set_tmp_rsa_callback(ssl_ctx, ssl_gen_rsa_key);
+	SSL_CTX_set_tmp_dh_callback(ssl_ctx, ssl_tmp_dh_callback);
 
 	if (getenv("SSL_VERIFY_CLIENT_CERT") != NULL) {
 		SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_PEER |
@@ -537,6 +675,7 @@
 	hash_iterate_deinit(iter);
 	hash_destroy(ssl_proxies);
 
+	ssl_free_parameters(&ssl_params);
 	SSL_CTX_free(ssl_ctx);
 }
 
--- a/src/master/master-settings.c	Sun Jan 15 14:15:12 2006 +0200
+++ b/src/master/master-settings.c	Sun Jan 15 14:35:01 2006 +0200
@@ -257,7 +257,7 @@
 	MEMBER(ssl_cert_file) SSLDIR"/certs/dovecot.pem",
 	MEMBER(ssl_key_file) SSLDIR"/private/dovecot.pem",
 	MEMBER(ssl_parameters_file) "ssl-parameters.dat",
-	MEMBER(ssl_parameters_regenerate) 24,
+	MEMBER(ssl_parameters_regenerate) 168,
 	MEMBER(ssl_cipher_list) NULL,
 	MEMBER(ssl_verify_client_cert) FALSE,
 	MEMBER(disable_plaintext_auth) TRUE,
--- a/src/master/ssl-init-openssl.c	Sun Jan 15 14:15:12 2006 +0200
+++ b/src/master/ssl-init-openssl.c	Sun Jan 15 14:35:01 2006 +0200
@@ -1,13 +1,50 @@
 /* Copyright (C) 2002 Timo Sirainen */
 
 #include "common.h"
+#include "write-full.h"
 #include "ssl-init.h"
 
 #ifdef HAVE_OPENSSL
 
-void _ssl_generate_parameters(int fd __attr_unused__,
-			      const char *fname __attr_unused__)
+#include <openssl/ssl.h>
+
+/* 2 or 5. Haven't seen their difference explained anywhere, but 2 is the
+   default.. */
+#define DH_GENERATOR 2
+
+static int dh_param_bitsizes[] = { 512, 1024 };
+#define DH_PARAM_BITSIZE_COUNT \
+        (sizeof(dh_param_bitsizes)/sizeof(dh_param_bitsizes[0]))
+
+static void generate_dh_parameters(int bitsize, int fd, const char *fname)
 {
+        DH *dh = DH_generate_parameters(bitsize, DH_GENERATOR, NULL, NULL);
+	unsigned char *buf, *p;
+	int len;
+
+	len = i2d_DHparams(dh, NULL);
+	if (len < 0)
+		i_fatal("i2d_DHparams() failed");
+
+	buf = p = i_malloc(len);
+	len = i2d_DHparams(dh, &p);
+
+	if (write_full(fd, &bitsize, sizeof(bitsize)) < 0 ||
+	    write_full(fd, &len, sizeof(len)) < 0 ||
+	    write_full(fd, buf, len) < 0)
+		i_fatal("write_full() failed for file %s: %m", fname);
+	i_free(buf);
+}
+
+void _ssl_generate_parameters(int fd, const char *fname)
+{
+	unsigned int i;
+	int bits;
+
+	for (i = 0; i < DH_PARAM_BITSIZE_COUNT; i++)
+		generate_dh_parameters(dh_param_bitsizes[i], fd, fname);
+	bits = 0;
+	write_full(fd, &bits, sizeof(bits));
 }
 
 #endif
--- a/src/master/ssl-init.c	Sun Jan 15 14:15:12 2006 +0200
+++ b/src/master/ssl-init.c	Sun Jan 15 14:35:01 2006 +0200
@@ -18,12 +18,16 @@
 static void generate_parameters_file(const char *fname)
 {
 	const char *temp_fname;
+	mode_t old_mask;
 	int fd;
 
 	temp_fname = t_strconcat(fname, ".tmp", NULL);
 	(void)unlink(temp_fname);
 
-	fd = open(temp_fname, O_WRONLY | O_CREAT | O_EXCL, 0600);
+	old_mask = umask(0);
+	fd = open(temp_fname, O_WRONLY | O_CREAT | O_EXCL, 0644);
+	umask(old_mask);
+
 	if (fd == -1) {
 		i_fatal("Can't create temporary SSL parameters file %s: %m",
 			temp_fname);
@@ -82,10 +86,11 @@
 		st.st_mtime = 0;
 	}
 
-	/* make sure it's new enough and the permissions are correct */
+	/* make sure it's new enough, it's not 0 sized, and the permissions
+	   are correct */
 	regen_time = st.st_mtime +
 		(time_t)(set->ssl_parameters_regenerate*3600);
-	if (regen_time < ioloop_time || (st.st_mode & 077) != 0 ||
+	if (regen_time < ioloop_time || st.st_size == 0 ||
 	    st.st_uid != master_uid || st.st_gid != getegid()) {
 		start_generate_process(set);
 		return FALSE;