[RFC master-2.2 1/1] Support setting min/max SSL protocol version

Apollon Oikonomopoulos apoikos at debian.org
Wed Sep 13 23:51:20 EEST 2017


OpenSSL 1.1 exposes a new API for setting the minimum and maximum
supported SSL protocol version, using SSL_CTX_set_min_proto_version and
SSL_CTX_set_max_proto_version respectively. The main difference with the
old SSL_CTX_set_options API is that the new API can either restrict or
relax the library defaults; the old API could only be used to
selectively disable protocols (but not enable what might have been
disabled by default).

The new API allows distributions and vendors to ship OpenSSL versions
with stricter run-time defaults (e.g. TLSv1.2-only), while still
allowing applications to enable older protocols (e.g. TLSv1) when
dealing with legacy clients.

To support the new API, we add two new config file options,
ssl_min_proto_version and ssl_max_proto_version. These settings are only
effective when built against OpenSSL 1.1. Also, dovecot will issue a
warning if the old-style ssl_options config file option is encountered
while running on OpenSSL 1.1 (although it will not ignore the option at
this point).

Signed-off-by: Apollon Oikonomopoulos <apoikos at debian.org>
---
 doc/example-config/conf.d/10-ssl.conf          |  4 ++++
 src/config/config-parser.c                     | 25 +++++++++++++++++++++
 src/lib-master/master-service-ssl-settings.c   |  4 ++++
 src/lib-master/master-service-ssl-settings.h   |  2 ++
 src/lib-master/master-service-ssl.c            |  2 ++
 src/lib-ssl-iostream/iostream-openssl-common.c | 12 +++++++++++
 src/lib-ssl-iostream/iostream-openssl.h        |  1 +
 src/lib-ssl-iostream/iostream-ssl.h            |  2 ++
 src/login-common/ssl-proxy-openssl.c           | 30 ++++++++++++++++++++++++++
 9 files changed, 82 insertions(+)

diff --git a/doc/example-config/conf.d/10-ssl.conf b/doc/example-config/conf.d/10-ssl.conf
index cf651c252..aceae233a 100644
--- a/doc/example-config/conf.d/10-ssl.conf
+++ b/doc/example-config/conf.d/10-ssl.conf
@@ -47,6 +47,10 @@ ssl_key = </etc/ssl/private/dovecot.pem
 
 # SSL protocols to use
 #ssl_protocols = !SSLv3
+# For OpenSSL 1.1.0 and later, use ssl_min_proto_version and
+# ssl_max_proto_version to specify supported protocols.
+# ssl_min_proto_version = TLSv1
+# ssl_max_proto_version = TLSv1.2
 
 # SSL ciphers to use
 #ssl_cipher_list = ALL:!LOW:!SSLv2:!EXP:!aNULL
diff --git a/src/config/config-parser.c b/src/config/config-parser.c
index b7c569339..ebcf2dd60 100644
--- a/src/config/config-parser.c
+++ b/src/config/config-parser.c
@@ -20,6 +20,7 @@
 #include <unistd.h>
 #include <fcntl.h>
 #include <time.h>
+#include <openssl/opensslv.h>
 #ifdef HAVE_GLOB_H
 #  include <glob.h>
 #endif
@@ -419,6 +420,11 @@ config_all_parsers_check(struct config_parser_context *ctx,
 	struct master_service_settings_output output;
 	unsigned int i, count;
 	const char *ssl_set, *global_ssl_set;
+#if OPENSSL_VERSION_NUMBER >= 0x10100000
+	const char *ssl_protocols;
+#else
+	const char *ssl_min_proto_version, *ssl_max_proto_version;
+#endif
 	pool_t tmp_pool;
 	bool ssl_warned = FALSE;
 	int ret = 0;
@@ -454,6 +460,25 @@ config_all_parsers_check(struct config_parser_context *ctx,
 			ssl_warned = TRUE;
 		}
 
+#if OPENSSL_VERSION_NUMBER >= 0x10100000
+		ssl_protocols = get_str_setting(parsers[i], "ssl_protocols", "");
+		if (*ssl_protocols != '\0')
+			i_warning("ssl_protocols is deprecated and will be "
+				  "ignored in future versions when running "
+				  "with OpenSSL 1.1. Please use "
+				  "ssl_min_proto_version and "
+				  "ssl_max_proto_version instead.");
+#else
+		ssl_min_proto_version = get_str_setting(parsers[i],
+				"ssl_min_proto_version", "");
+		ssl_max_proto_version = get_str_setting(parsers[i],
+				"ssl_max_proto_version", "");
+		if ((*ssl_min_proto_version != '\0') ||
+				(*ssl_max_proto_version != '\0'))
+			i_warning("ssl_*_proto_version ignored, "
+				  "not supported by OpenSSL");
+#endif
+
 		ret = config_filter_parser_check(ctx, tmp_parsers, error_r);
 		config_filter_parsers_free(tmp_parsers);
 		p_clear(tmp_pool);
diff --git a/src/lib-master/master-service-ssl-settings.c b/src/lib-master/master-service-ssl-settings.c
index 2487c8369..484022618 100644
--- a/src/lib-master/master-service-ssl-settings.c
+++ b/src/lib-master/master-service-ssl-settings.c
@@ -24,6 +24,8 @@ static const struct setting_define master_service_ssl_setting_defines[] = {
 	DEF(SET_STR, ssl_key_password),
 	DEF(SET_STR, ssl_cipher_list),
 	DEF(SET_STR, ssl_protocols),
+	DEF(SET_STR, ssl_min_proto_version),
+	DEF(SET_STR, ssl_max_proto_version),
 	DEF(SET_STR, ssl_cert_username_field),
 	DEF(SET_STR, ssl_crypto_device),
 	DEF(SET_BOOL, ssl_verify_client_cert),
@@ -53,6 +55,8 @@ static const struct master_service_ssl_settings master_service_ssl_default_setti
 #else
 	.ssl_protocols = "!SSLv3",
 #endif
+	.ssl_min_proto_version = "",
+	.ssl_max_proto_version = "",
 	.ssl_cert_username_field = "commonName",
 	.ssl_crypto_device = "",
 	.ssl_verify_client_cert = FALSE,
diff --git a/src/lib-master/master-service-ssl-settings.h b/src/lib-master/master-service-ssl-settings.h
index a4157d3ef..0fc9aa9ca 100644
--- a/src/lib-master/master-service-ssl-settings.h
+++ b/src/lib-master/master-service-ssl-settings.h
@@ -13,6 +13,8 @@ struct master_service_ssl_settings {
 	const char *ssl_key_password;
 	const char *ssl_cipher_list;
 	const char *ssl_protocols;
+	const char *ssl_min_proto_version;
+	const char *ssl_max_proto_version;
 	const char *ssl_cert_username_field;
 	const char *ssl_crypto_device;
 	const char *ssl_options;
diff --git a/src/lib-master/master-service-ssl.c b/src/lib-master/master-service-ssl.c
index f8966bec8..54a22b6ba 100644
--- a/src/lib-master/master-service-ssl.c
+++ b/src/lib-master/master-service-ssl.c
@@ -113,6 +113,8 @@ void master_service_ssl_ctx_init(struct master_service *service)
 
 	i_zero(&ssl_set);
 	ssl_set.protocols = set->ssl_protocols;
+	ssl_set.min_proto_version = set->ssl_min_proto_version;
+	ssl_set.max_proto_version = set->ssl_max_proto_version;
 	ssl_set.cipher_list = set->ssl_cipher_list;
 	ssl_set.ca = set->ssl_ca;
 	ssl_set.cert = set->ssl_cert;
diff --git a/src/lib-ssl-iostream/iostream-openssl-common.c b/src/lib-ssl-iostream/iostream-openssl-common.c
index bc0180367..8cbb99085 100644
--- a/src/lib-ssl-iostream/iostream-openssl-common.c
+++ b/src/lib-ssl-iostream/iostream-openssl-common.c
@@ -80,6 +80,18 @@ int openssl_get_protocol_options(const char *protocols)
 	return op;
 }
 
+int openssl_get_protocol_version(const char *protocol)
+{
+	if (!strcmp(protocol, "TLSv1"))
+		return TLS1_VERSION;
+	else if (!strcmp(protocol, "TLSv1.1"))
+		return TLS1_1_VERSION;
+	else if (!strcmp(protocol, "TLSv1.2"))
+		return TLS1_2_VERSION;
+
+	return 0;
+}
+
 static const char *asn1_string_to_c(ASN1_STRING *asn_str)
 {
 	const char *cstr;
diff --git a/src/lib-ssl-iostream/iostream-openssl.h b/src/lib-ssl-iostream/iostream-openssl.h
index d46d608d1..b6e5e100d 100644
--- a/src/lib-ssl-iostream/iostream-openssl.h
+++ b/src/lib-ssl-iostream/iostream-openssl.h
@@ -76,6 +76,7 @@ int openssl_cert_match_name(SSL *ssl, const char *verify_name);
 int openssl_get_protocol_options(const char *protocols);
 #define OPENSSL_ALL_PROTOCOL_OPTIONS \
 	(SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1)
+int openssl_get_protocol_version(const char *protocol);
 
 /* Sync plain_input/plain_output streams with BIOs. Returns TRUE if at least
    one byte was read/written. */
diff --git a/src/lib-ssl-iostream/iostream-ssl.h b/src/lib-ssl-iostream/iostream-ssl.h
index 3969f74df..948878d26 100644
--- a/src/lib-ssl-iostream/iostream-ssl.h
+++ b/src/lib-ssl-iostream/iostream-ssl.h
@@ -6,6 +6,8 @@ struct ssl_iostream_context;
 
 struct ssl_iostream_settings {
 	const char *protocols;
+	const char *min_proto_version;
+	const char *max_proto_version;
 	const char *cipher_list;
 	const char *ca, *ca_file, *ca_dir; /* context-only */
 	const char *cert;
diff --git a/src/login-common/ssl-proxy-openssl.c b/src/login-common/ssl-proxy-openssl.c
index 8c7c3d531..dd1c2e67b 100644
--- a/src/login-common/ssl-proxy-openssl.c
+++ b/src/login-common/ssl-proxy-openssl.c
@@ -105,6 +105,8 @@ struct ssl_server_context {
 	const char *ca;
 	const char *cipher_list;
 	const char *protocols;
+	const char *min_proto_version;
+	const char *max_proto_version;
 	bool verify_client_cert;
 	bool prefer_server_ciphers;
 	bool compression;
@@ -186,6 +188,10 @@ static int ssl_server_context_cmp(const struct ssl_server_context *ctx1,
 		return 1;
 	if (null_strcmp(ctx1->protocols, ctx2->protocols) != 0)
 		return 1;
+	if (null_strcmp(ctx1->min_proto_version, ctx2->min_proto_version) != 0)
+		return 1;
+	if (null_strcmp(ctx1->max_proto_version, ctx2->max_proto_version) != 0)
+		return 1;
 
 	return ctx1->verify_client_cert == ctx2->verify_client_cert ? 0 : 1;
 }
@@ -631,6 +637,8 @@ ssl_server_context_get(const struct login_settings *login_set,
 	lookup_ctx.ca = set->ssl_ca;
 	lookup_ctx.cipher_list = set->ssl_cipher_list;
 	lookup_ctx.protocols = set->ssl_protocols;
+	lookup_ctx.min_proto_version = set->ssl_min_proto_version;
+	lookup_ctx.max_proto_version = set->ssl_max_proto_version;
 	lookup_ctx.verify_client_cert = set->ssl_verify_client_cert ||
 		login_set->auth_ssl_require_client_cert ||
 		login_set->auth_ssl_username_from_cert;
@@ -1272,6 +1280,9 @@ ssl_server_context_init(const struct login_settings *login_set,
 	SSL_CTX *ssl_ctx;
 	pool_t pool;
 	STACK_OF(X509_NAME) *xnames;
+#if OPENSSL_VERSION_NUMBER >= 0x10100000
+	int proto_version;
+#endif
 
 	pool = pool_alloconly_create("ssl server context", 4096);
 	ctx = p_new(pool, struct ssl_server_context, 1);
@@ -1283,6 +1294,8 @@ ssl_server_context_init(const struct login_settings *login_set,
 	ctx->ca = p_strdup(pool, ssl_set->ssl_ca);
 	ctx->cipher_list = p_strdup(pool, ssl_set->ssl_cipher_list);
 	ctx->protocols = p_strdup(pool, ssl_set->ssl_protocols);
+	ctx->min_proto_version = p_strdup(pool, ssl_set->ssl_min_proto_version);
+	ctx->max_proto_version = p_strdup(pool, ssl_set->ssl_max_proto_version);
 	ctx->verify_client_cert = ssl_set->ssl_verify_client_cert ||
 		login_set->auth_ssl_require_client_cert ||
 		login_set->auth_ssl_username_from_cert;
@@ -1301,6 +1314,23 @@ ssl_server_context_init(const struct login_settings *login_set,
 	}
 	if (ctx->prefer_server_ciphers)
 		SSL_CTX_set_options(ssl_ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
+#if OPENSSL_VERSION_NUMBER >= 0x10100000
+        if (*ctx->min_proto_version != '\0') {
+		proto_version = openssl_get_protocol_version(ctx->min_proto_version);
+		if (!proto_version)
+			i_fatal("Invalid minimum TLS version '%s'",
+					ctx->min_proto_version);
+		SSL_CTX_set_min_proto_version(ssl_ctx, proto_version);
+	}
+	if (*ctx->max_proto_version != '\0') {
+		proto_version = openssl_get_protocol_version(ctx->max_proto_version);
+		if (!proto_version)
+			i_fatal("Invalid maximum TLS version '%s'",
+					ctx->max_proto_version);
+		SSL_CTX_set_max_proto_version(ssl_ctx, proto_version);
+        }
+#endif
+	/* TODO: stop setting the protocol options for OpenSSL >= 1.1 */
 	SSL_CTX_set_options(ssl_ctx, openssl_get_protocol_options(ctx->protocols));
 
 	if (ctx->pri.cert != NULL && *ctx->pri.cert != '\0' &&
-- 
2.14.1



More information about the dovecot mailing list