]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-ssl-iostream: Add ssl_protocols_to_min_protocol()
authorMartti Rannanjärvi <martti.rannanjarvi@dovecot.fi>
Sat, 11 Nov 2017 02:28:57 +0000 (04:28 +0200)
committerTimo Sirainen <timo.sirainen@dovecot.fi>
Mon, 19 Feb 2018 14:29:57 +0000 (16:29 +0200)
This detects minimum SSL protocol version from the ssl_protocols
setting.

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 [new file with mode: 0644]

index 9e6a54c2ae880289fe58c702eb3afb6bb28cf6a4..b7b6c14416e2c14bc57bd3d99685533b3d45a4ec 100644 (file)
@@ -20,6 +20,28 @@ libssl_iostream_openssl_la_SOURCES = \
        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 = \
index b3f92cdc1803e1d89b2f168f23a5d3f77ce8a613..7c9370f453fff2f814d38e47ba66b0967cbecd49 100644 (file)
@@ -18,6 +18,67 @@ enum {
        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;
index d8d3d5348e2c5e127f5843205516c7ac8cb1e929..4bf9c5cabfc9725ad058a458b65789fd407289d7 100644 (file)
@@ -80,6 +80,18 @@ 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)
 
+#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);
diff --git a/src/lib-ssl-iostream/test-ssl-iostream.c b/src/lib-ssl-iostream/test-ssl-iostream.c
new file mode 100644 (file)
index 0000000..31c0970
--- /dev/null
@@ -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 */