changeset 22860:ad9c924ec91f

lib-ssl-iostream: Add ssl_protocols_to_min_protocol() This detects minimum SSL protocol version from the ssl_protocols setting.
author Martti Rannanjärvi <martti.rannanjarvi@dovecot.fi>
date Sat, 11 Nov 2017 04:28:57 +0200
parents c7aa25186973
children 898ef4d4ee48
files src/lib-ssl-iostream/Makefile.am src/lib-ssl-iostream/iostream-openssl-common.c src/lib-ssl-iostream/iostream-openssl.h src/lib-ssl-iostream/test-ssl-iostream.c
diffstat 4 files changed, 158 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/src/lib-ssl-iostream/Makefile.am	Thu Nov 30 11:15:50 2017 +0200
+++ b/src/lib-ssl-iostream/Makefile.am	Sat Nov 11 04:28:57 2017 +0200
@@ -20,6 +20,28 @@
 	iostream-openssl-params.c \
 	istream-openssl.c \
 	ostream-openssl.c
+
+test_programs = test-ssl-iostream
+noinst_PROGRAMS = $(test_programs)
+
+check-local:
+	for bin in $(test_programs); do \
+	  if ! $(RUN_TEST) ./$$bin; then exit 1; fi; \
+	done
+
+LIBDOVECOT_TEST_DEPS = \
+	libssl_iostream_openssl.la \
+	libssl_iostream.la \
+	../lib-test/libtest.la \
+	../lib/liblib.la
+LIBDOVECOT_TEST = \
+	$(LIBDOVECOT_TEST_DEPS) \
+	$(MODULE_LIBS)
+
+test_ssl_iostream_LDADD = $(LIBDOVECOT_TEST)
+test_ssl_iostream_DEPENDENCIES = $(LIBDOVECOT_TEST_DEPS)
+test_ssl_iostream_CFLAGS = $(AM_CPPFLAGS)
+test_ssl_iostream_SOURCES = test-ssl-iostream.c
 endif
 
 libssl_iostream_la_SOURCES = \
--- a/src/lib-ssl-iostream/iostream-openssl-common.c	Thu Nov 30 11:15:50 2017 +0200
+++ b/src/lib-ssl-iostream/iostream-openssl-common.c	Sat Nov 11 04:28:57 2017 +0200
@@ -18,6 +18,67 @@
 	DOVECOT_SSL_PROTO_ALL		= 0x1f
 };
 
+#ifdef HAVE_SSL_CTX_SET_MIN_PROTO_VERSION
+static const struct {
+	const char *name;
+	int version;
+} protocol_versions[] = {
+	{ SSL_TXT_SSLV3, SSL3_VERSION },
+	{ SSL_TXT_TLSV1, TLS1_VERSION },
+	{ SSL_TXT_TLSV1_1, TLS1_1_VERSION },
+	{ SSL_TXT_TLSV1_2, TLS1_2_VERSION },
+};
+int ssl_protocols_to_min_protocol(const char *ssl_protocols,
+				  int *min_protocol_r, const char **error_r)
+{
+	/* Array where -1 = disable, 0 = not found, 1 = enable */
+	int protos[N_ELEMENTS(protocol_versions)];
+	memset(protos, 0, sizeof(protos));
+	bool explicit_enable = FALSE;
+
+	const char *const *tmp = t_strsplit_spaces(ssl_protocols, ", ");
+	for (; *tmp != NULL; tmp++) {
+		const char *p = *tmp;
+		bool enable = TRUE;
+		if (p[0] == '!') {
+			enable = FALSE;
+			++p;
+		}
+		for (unsigned i = 0; i < N_ELEMENTS(protocol_versions); i++) {
+			if (strcmp(p, protocol_versions[i].name) == 0) {
+				if (enable) {
+					protos[i] = 1;
+					explicit_enable = TRUE;
+				} else {
+					protos[i] = -1;
+				}
+				goto found;
+			}
+		}
+		*error_r = t_strdup_printf("Unrecognized protocol '%s'", p);
+		return -1;
+
+		found:;
+	}
+
+	unsigned min = N_ELEMENTS(protocol_versions);
+	for (unsigned i = 0; i < N_ELEMENTS(protocol_versions); i++) {
+		if (explicit_enable) {
+			if (protos[i] > 0)
+				min = I_MIN(min, i);
+		} else if (protos[i] == 0)
+			min = I_MIN(min, i);
+	}
+	if (min == N_ELEMENTS(protocol_versions)) {
+		*error_r = "All protocols disabled";
+		return -1;
+	}
+
+	*min_protocol_r = protocol_versions[min].version;
+	return 0;
+}
+#endif /* HAVE_SSL_CTX_SET_MIN_PROTO_VERSION */
+
 int openssl_get_protocol_options(const char *protocols)
 {
 	const char *const *tmp;
--- a/src/lib-ssl-iostream/iostream-openssl.h	Thu Nov 30 11:15:50 2017 +0200
+++ b/src/lib-ssl-iostream/iostream-openssl.h	Sat Nov 11 04:28:57 2017 +0200
@@ -80,6 +80,18 @@
 #define OPENSSL_ALL_PROTOCOL_OPTIONS \
 	(SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_NO_TLSv1)
 
+#ifdef HAVE_SSL_CTX_SET_MIN_PROTO_VERSION
+/* min_protocol_r is the version int for SSL_CTX_set_min_proto_version().
+   Return 0 on success, and -1 on failure.
+
+   If ssl_protocols only disables protocols like "!SSLv3 !TLSv1", then all the
+   remaining protocols are considered enabled. If it enables some protocols
+   like "TLSv1.1 TLSv1.2", then only the explicitly enabled protocols are
+   considered enabled. */
+int ssl_protocols_to_min_protocol(const char *ssl_protocols,
+				  int *min_protocol_r, const char **error_r);
+#endif
+
 /* Sync plain_input/plain_output streams with BIOs. Returns TRUE if at least
    one byte was read/written. */
 bool openssl_iostream_bio_sync(struct ssl_iostream *ssl_io);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/lib-ssl-iostream/test-ssl-iostream.c	Sat Nov 11 04:28:57 2017 +0200
@@ -0,0 +1,63 @@
+/* Copyright (c) 2017-2018 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "test-common.h"
+#include "iostream-openssl.h"
+
+#include <stdio.h>
+
+#ifdef HAVE_SSL_CTX_SET_MIN_PROTO_VERSION
+
+struct test {
+	/* ssl_protocols input */
+	const char *s;
+	/* expected output */
+	int min;
+	int ret;
+};
+
+static const struct test tests[] = {
+	{ "!TLSv1 !TLSv1.2", SSL3_VERSION, 0 },
+	{ "!SSLv3", TLS1_VERSION, 0 },
+	{ "SSLv3", SSL3_VERSION, 0 },
+	{ "!SSLv3 !TLSv1 !TLSv1.2", TLS1_1_VERSION, 0 },
+	{ "!SSLv3 !TLSv1 !TLSv1.1 !TLSv1.2", 0, -1},
+	{ "TLSv1.1 TLSv1.2", TLS1_1_VERSION, 0 },
+	{ "TLSv1.1", TLS1_1_VERSION, 0 },
+	{ "TLSv1.1 !SSLv3", TLS1_1_VERSION, 0 },
+	{ "TLSv1.2 !TLSv1.1", TLS1_2_VERSION, 0 },
+};
+
+static
+void test_ssl_protocols_to_min_protocol(void)
+{
+	test_begin("test_ssl_protocols_to_min_protocol");
+	for (unsigned i = 0; i < N_ELEMENTS(tests); ++i) {
+		const struct test *t = &tests[i];
+		const char *error;
+		int min, ret;
+		ret = ssl_protocols_to_min_protocol(t->s, &min, &error);
+		if (ret >= 0 && t->min != min)
+			i_debug("%s (exp,actual): min(%d,%d) ret(%d,%d)",
+				t->s, t->min, min, t->ret, ret);
+		test_assert_idx(t->ret == ret, i);
+		if (ret < 0)
+			continue;
+		test_assert_idx(t->min == min, i);
+	}
+	test_end();
+}
+
+int main(void) {
+	static void (*test_functions[])(void) = {
+		test_ssl_protocols_to_min_protocol,
+		NULL,
+	};
+	return test_run(test_functions);
+}
+
+#else /* HAVE_SSL_CTX_SET_MIN_PROTO_VERSION */
+int main(void) {
+	return 0;
+}
+#endif /* HAVE_SSL_CTX_SET_MIN_PROTO_VERSION */