]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
resolved: basic OpenSSL support for DNS-over-TLS
authorIwan Timmer <irtimmer@gmail.com>
Thu, 26 Jul 2018 21:47:50 +0000 (22:47 +0100)
committerIwan Timmer <irtimmer@gmail.com>
Fri, 27 Jul 2018 20:23:17 +0000 (21:23 +0100)
This provides basic OpenSSL support without optimizations like TCP Fast Open and TLS Session Tickets.
Notice only a single SSL library can be enabled at a time and therefore journald functions provided by GnuTLS will be disabled when using OpenSSL.
Fixes #9531

README
meson.build
meson_options.txt
src/resolve/meson.build
src/resolve/resolved-dns-stream.c
src/resolve/resolved-dnstls-gnutls.c
src/resolve/resolved-dnstls-gnutls.h
src/resolve/resolved-dnstls-openssl.c [new file with mode: 0644]
src/resolve/resolved-dnstls-openssl.h [new file with mode: 0644]
src/resolve/resolved-dnstls.h

diff --git a/README b/README
index c0b264abf4499de1171ca9d095363aa78d2518e7..a3cfcada778fb14ecce63e42bf1b507290635fce 100644 (file)
--- a/README
+++ b/README
@@ -154,7 +154,8 @@ REQUIREMENTS:
         libmicrohttpd (optional)
         libpython (optional)
         libidn2 or libidn (optional)
-        gnutls >= 3.1.4 (optional, >= 3.5.3 is necessary to support DNS-over-TLS)
+        gnutls >= 3.1.4 (optional, >= 3.5.3 is required to support DNS-over-TLS with gnutls)
+        openssl >= 1.1.0 (optional, required to support DNS-over-TLS with openssl)
         elfutils >= 158 (optional)
         polkit (optional)
         pkg-config
index a123940f10488742b53c1957f1254b40af21c11a..5fcc57520a512907d9628e41acf58e943008b940 100644 (file)
@@ -1013,6 +1013,18 @@ else
 endif
 conf.set10('HAVE_GNUTLS', have)
 
+want_openssl = get_option('openssl')
+if want_openssl != 'false' and not fuzzer_build
+        libopenssl = dependency('openssl',
+                                version : '>= 1.1.0',
+                                required : want_openssl == 'true')
+        have = libopenssl.found()
+else
+        have = false
+        libopenssl = []
+endif
+conf.set10('HAVE_OPENSSL', have)
+
 want_elfutils = get_option('elfutils')
 if want_elfutils != 'false' and not fuzzer_build
         libdw = dependency('libdw',
@@ -1136,15 +1148,30 @@ substs.set('DEFAULT_DNSSEC_MODE', default_dnssec)
 
 dns_over_tls = get_option('dns-over-tls')
 if dns_over_tls != 'false'
-        have = (conf.get('HAVE_GNUTLS') == 1 and
-                libgnutls.version().version_compare('>=3.5.3'))
-        if dns_over_tls == 'true' and not have
-                error('DNS-over-TLS support was requested, but dependencies are not available')
+        if dns_over_tls == 'openssl'
+                have_gnutls = false
+        else
+                have_gnutls = (conf.get('HAVE_GNUTLS') == 1 and libgnutls.version().version_compare('>= 3.5.3'))
+                if dns_over_tls == 'gnutls' and not have_gnutls
+                        error('DNS-over-TLS support was requested with gnutls, but dependencies are not available')
+                endif
         endif
+        if dns_over_tls == 'gnutls' or have_gnutls
+                have_openssl = false
+        else
+                have_openssl = conf.get('HAVE_OPENSSL') == 1
+                if dns_over_tls != 'auto' and not have_openssl
+                        str = dns_over_tls == 'openssl' ? ' with openssl' : ''
+                        error('DNS-over-TLS support was requested$0$, but dependencies are not available'.format(str))
+                endif
+        endif
+        have = have_gnutls or have_openssl
 else
-        have = false
+        have = have_gnutls = have_openssl = false
 endif
 conf.set10('ENABLE_DNS_OVER_TLS', have)
+conf.set10('DNS_OVER_TLS_USE_GNUTLS', have_gnutls)
+conf.set10('DNS_OVER_TLS_USE_OPENSSL', have_openssl)
 
 default_dns_over_tls = get_option('default-dns-over-tls')
 if fuzzer_build
@@ -2950,6 +2977,7 @@ foreach tuple : [
         ['qrencode'],
         ['microhttpd'],
         ['gnutls'],
+        ['openssl'],
         ['libcurl'],
         ['idn'],
         ['libidn2'],
@@ -2976,7 +3004,8 @@ foreach tuple : [
         ['localed'],
         ['networkd'],
         ['resolve'],
-        ['DNS-over-TLS'],
+        ['DNS-over-TLS(gnutls)',  conf.get('DNS_OVER_TLS_USE_GNUTLS') == 1],
+        ['DNS-over-TLS(openssl)', conf.get('DNS_OVER_TLS_USE_OPENSSL') == 1],
         ['coredump'],
         ['polkit'],
         ['legacy pkla',      install_polkit_pkla],
index 4fdd0caf4a64720dfbcd97c15992cdfdd3326a9f..a927473b9b235da049ff06fc119cd904c2f398d6 100644 (file)
@@ -195,7 +195,7 @@ option('default-dns-over-tls', type : 'combo',
        description : 'default DNS-over-TLS mode',
        choices : ['opportunistic', 'no'],
        value : 'no')
-option('dns-over-tls', type : 'combo', choices : ['auto', 'true', 'false'],
+option('dns-over-tls', type : 'combo', choices : ['auto', 'gnutls', 'openssl', 'true', 'false'],
        description : 'DNS-over-TLS support')
 option('dns-servers', type : 'string',
        description : 'space-separated list of default DNS servers',
@@ -255,6 +255,8 @@ option('gcrypt', type : 'combo', choices : ['auto', 'true', 'false'],
        description : 'gcrypt support')
 option('gnutls', type : 'combo', choices : ['auto', 'true', 'false'],
        description : 'gnutls support')
+option('openssl', type : 'combo', choices : ['auto', 'true', 'false'],
+       description : 'openssl support')
 option('elfutils', type : 'combo', choices : ['auto', 'true', 'false'],
        description : 'elfutils support')
 option('zlib', type : 'combo', choices : ['auto', 'true', 'false'],
index 5955875f59a1719728b35c1b1c69a7666afa3bdc..6d03ebf7f48a71a2fdb2f400d5c6c87a68e120cf 100644 (file)
@@ -142,8 +142,15 @@ systemd_resolved_sources += [resolved_gperf_c, resolved_dnssd_gperf_c]
 
 systemd_resolved_dependencies = [threads, libgpg_error, libm, libidn]
 if conf.get('ENABLE_DNS_OVER_TLS') == 1
-        systemd_resolved_sources += [files(['resolved-dnstls-gnutls.c', 'resolved-dnstls-gnutls.h'])]
-        systemd_resolved_dependencies += [libgnutls]
+        if conf.get('DNS_OVER_TLS_USE_GNUTLS') == 1
+                systemd_resolved_sources += [files(['resolved-dnstls-gnutls.c', 'resolved-dnstls-gnutls.h'])]
+                systemd_resolved_dependencies += [libgnutls]
+        elif conf.get('DNS_OVER_TLS_USE_OPENSSL') == 1
+                systemd_resolved_sources += [files(['resolved-dnstls-openssl.c', 'resolved-dnstls-openssl.h'])]
+                systemd_resolved_dependencies += [libopenssl]
+        else
+                error('unknown dependency for supporting DNS-over-TLS')
+        endif
 endif
 
 if conf.get('ENABLE_RESOLVE') == 1
index 36205a895c884ebd0845afd5526788bcfa85d4d8..faf5e26ba4659c8077151cff2aa0c4df824eeb3d 100644 (file)
@@ -234,6 +234,8 @@ ssize_t dns_stream_writev(DnsStream *s, const struct iovec *iov, size_t iovcnt,
                                 r = -EAGAIN;
                         } else if (errno == EINPROGRESS)
                                 r = -EAGAIN;
+                        else
+                                r = -errno;
                 } else
                         s->tfo_salen = 0; /* connection is made */
         } else {
index 36ee570b461f26c469a3fd0ce9be07e5eee0517a..5e6a899db81bfd1a3b70e800ed288499405608ae 100644 (file)
@@ -1,6 +1,6 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 
-#if !ENABLE_DNS_OVER_TLS || !HAVE_GNUTLS
+#if !ENABLE_DNS_OVER_TLS || !DNS_OVER_TLS_USE_GNUTLS
 #error This source file requires DNS-over-TLS to be enabled and GnuTLS to be available.
 #endif
 
index 8309f8d121558a5c7ee034ae7e6ae145d41e13d8..364eea1a038b0b11bd5a2c02c38a2e65cae654ff 100644 (file)
@@ -1,7 +1,7 @@
 /* SPDX-License-Identifier: LGPL-2.1+ */
 #pragma once
 
-#if !ENABLE_DNS_OVER_TLS || !HAVE_GNUTLS
+#if !ENABLE_DNS_OVER_TLS || !DNS_OVER_TLS_USE_GNUTLS
 #error This source file requires DNS-over-TLS to be enabled and GnuTLS to be available.
 #endif
 
diff --git a/src/resolve/resolved-dnstls-openssl.c b/src/resolve/resolved-dnstls-openssl.c
new file mode 100644 (file)
index 0000000..d0a1bba
--- /dev/null
@@ -0,0 +1,216 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#if !ENABLE_DNS_OVER_TLS || !DNS_OVER_TLS_USE_OPENSSL
+#error This source file requires DNS-over-TLS to be enabled and OpenSSL to be available.
+#endif
+
+#include "resolved-dnstls.h"
+#include "resolved-dns-stream.h"
+
+#include <openssl/bio.h>
+#include <openssl/err.h>
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(SSL*, SSL_free);
+DEFINE_TRIVIAL_CLEANUP_FUNC(BIO*, BIO_free);
+
+int dnstls_stream_connect_tls(DnsStream *stream, DnsServer *server) {
+        _cleanup_(SSL_freep) SSL *s = NULL;
+        _cleanup_(BIO_freep) BIO *b = NULL;
+
+        assert(stream);
+        assert(server);
+
+        b = BIO_new_socket(stream->fd, 0);
+        if (!b)
+                return -ENOMEM;
+
+        s = SSL_new(server->dnstls_data.ctx);
+        if (!s)
+                return -ENOMEM;
+
+        SSL_set_connect_state(s);
+        SSL_set_bio(s, b, b);
+        b = NULL;
+
+        /* DNS-over-TLS using OpenSSL doesn't support TCP Fast Open yet */
+        connect(stream->fd, &stream->tfo_address.sa, stream->tfo_salen);
+        stream->tfo_salen = 0;
+
+        stream->encrypted = true;
+        stream->dnstls_events = EPOLLOUT;
+        stream->dnstls_data.ssl = TAKE_PTR(s);
+
+        return 0;
+}
+
+void dnstls_stream_free(DnsStream *stream) {
+        assert(stream);
+        assert(stream->encrypted);
+
+        if (stream->dnstls_data.ssl)
+                SSL_free(stream->dnstls_data.ssl);
+}
+
+int dnstls_stream_on_io(DnsStream *stream) {
+        int r;
+        int error;
+
+        assert(stream);
+        assert(stream->encrypted);
+        assert(stream->dnstls_data.ssl);
+
+        if (stream->dnstls_data.shutdown) {
+                r = SSL_shutdown(stream->dnstls_data.ssl);
+                if (r == 0)
+                        return -EAGAIN;
+                else if (r < 0) {
+                        error = SSL_get_error(stream->dnstls_data.ssl, r);
+                        if (IN_SET(error, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE)) {
+                                stream->dnstls_events = error == SSL_ERROR_WANT_READ ? EPOLLIN : EPOLLOUT;
+                                return -EAGAIN;
+                        } else {
+                                char errbuf[256];
+
+                                ERR_error_string_n(error, errbuf, sizeof(errbuf));
+                                log_debug("Failed to invoke SSL_shutdown: %s", errbuf);
+                        }
+                }
+
+                stream->dnstls_events = 0;
+                stream->dnstls_data.shutdown = false;
+                dns_stream_unref(stream);
+                return DNSTLS_STREAM_CLOSED;
+        } else if (stream->dnstls_data.handshake <= 0) {
+                stream->dnstls_data.handshake = SSL_do_handshake(stream->dnstls_data.ssl);
+                if (stream->dnstls_data.handshake <= 0) {
+                        error = SSL_get_error(stream->dnstls_data.ssl, stream->dnstls_data.handshake);
+                        if (IN_SET(error, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE)) {
+                                stream->dnstls_events = error == SSL_ERROR_WANT_READ ? EPOLLIN : EPOLLOUT;
+                                return -EAGAIN;
+                        } else {
+                                char errbuf[256];
+
+                                ERR_error_string_n(error, errbuf, sizeof(errbuf));
+                                log_debug("Failed to invoke SSL_do_handshake: %s", errbuf);
+                                return -ECONNREFUSED;
+                        }
+                }
+
+                stream->dnstls_events = 0;
+        }
+
+        return 0;
+}
+
+int dnstls_stream_shutdown(DnsStream *stream, int error) {
+        int r;
+        int ssl_error;
+        SSL_SESSION *s;
+
+        assert(stream);
+        assert(stream->encrypted);
+        assert(stream->dnstls_data.ssl);
+
+        if (error == ETIMEDOUT) {
+                r = SSL_shutdown(stream->dnstls_data.ssl);
+                if (r == 0) {
+                        if (!stream->dnstls_data.shutdown) {
+                                stream->dnstls_data.shutdown = true;
+                                dns_stream_ref(stream);
+                        }
+                        return -EAGAIN;
+                } else if (r < 0) {
+                        ssl_error = SSL_get_error(stream->dnstls_data.ssl, r);
+                        if (IN_SET(ssl_error, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE)) {
+                                stream->dnstls_events = ssl_error == SSL_ERROR_WANT_READ ? EPOLLIN : EPOLLOUT;
+                                if (!stream->dnstls_data.shutdown) {
+                                        stream->dnstls_data.shutdown = true;
+                                        dns_stream_ref(stream);
+                                }
+                                return -EAGAIN;
+                        } else {
+                                char errbuf[256];
+
+                                ERR_error_string_n(ssl_error, errbuf, sizeof(errbuf));
+                                log_debug("Failed to invoke SSL_shutdown: %s", errbuf);
+                        }
+                }
+        }
+
+        return 0;
+}
+
+ssize_t dnstls_stream_write(DnsStream *stream, const char *buf, size_t count) {
+        int r;
+        int error;
+        ssize_t ss;
+
+        assert(stream);
+        assert(stream->encrypted);
+        assert(stream->dnstls_data.ssl);
+        assert(buf);
+
+        ss = r = SSL_write(stream->dnstls_data.ssl, buf, count);
+        if (r <= 0) {
+                error = SSL_get_error(stream->dnstls_data.ssl, ss);
+                if (IN_SET(error, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE)) {
+                        stream->dnstls_events = error == SSL_ERROR_WANT_READ ? EPOLLIN : EPOLLOUT;
+                        ss = -EAGAIN;
+                } else {
+                        char errbuf[256];
+
+                        ERR_error_string_n(error, errbuf, sizeof(errbuf));
+                        log_debug("Failed to invoke SSL_read: %s", errbuf);
+                        ss = -EPIPE;
+                }
+        }
+
+        stream->dnstls_events = 0;
+        return ss;
+}
+
+ssize_t dnstls_stream_read(DnsStream *stream, void *buf, size_t count) {
+        int r;
+        int error;
+        ssize_t ss;
+
+        assert(stream);
+        assert(stream->encrypted);
+        assert(stream->dnstls_data.ssl);
+        assert(buf);
+
+        ss = r = SSL_read(stream->dnstls_data.ssl, buf, count);
+        if (r <= 0) {
+                error = SSL_get_error(stream->dnstls_data.ssl, ss);
+                if (IN_SET(error, SSL_ERROR_WANT_READ, SSL_ERROR_WANT_WRITE)) {
+                        stream->dnstls_events = error == SSL_ERROR_WANT_READ ? EPOLLIN : EPOLLOUT;
+                        ss = -EAGAIN;
+                } else {
+                        char errbuf[256];
+
+                        ERR_error_string_n(error, errbuf, sizeof(errbuf));
+                        log_debug("Failed to invoke SSL_read: %s", errbuf);
+                        ss = -EPIPE;
+                }
+        }
+
+        stream->dnstls_events = 0;
+        return ss;
+}
+
+void dnstls_server_init(DnsServer *server) {
+        assert(server);
+
+        server->dnstls_data.ctx = SSL_CTX_new(TLS_client_method());
+        if (server->dnstls_data.ctx) {
+                SSL_CTX_set_min_proto_version(server->dnstls_data.ctx, TLS1_2_VERSION);
+                SSL_CTX_set_options(server->dnstls_data.ctx, SSL_OP_NO_COMPRESSION);
+        }
+}
+
+void dnstls_server_free(DnsServer *server) {
+        assert(server);
+
+        if (server->dnstls_data.ctx)
+                SSL_CTX_free(server->dnstls_data.ctx);
+}
diff --git a/src/resolve/resolved-dnstls-openssl.h b/src/resolve/resolved-dnstls-openssl.h
new file mode 100644 (file)
index 0000000..c92d2b2
--- /dev/null
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#if !ENABLE_DNS_OVER_TLS || !DNS_OVER_TLS_USE_OPENSSL
+#error This source file requires DNS-over-TLS to be enabled and OpenSSL to be available.
+#endif
+
+#include <stdbool.h>
+
+#include <openssl/ssl.h>
+
+struct DnsTlsServerData {
+        SSL_CTX *ctx;
+};
+
+struct DnsTlsStreamData {
+        int handshake;
+        bool shutdown;
+        SSL *ssl;
+};
index 32befc6414c8a850129e2674748a0cf8f888d1a8..52af3e9801c66529ffa063eb46c5be8818a8e71f 100644 (file)
@@ -8,8 +8,12 @@
 typedef struct DnsTlsServerData DnsTlsServerData;
 typedef struct DnsTlsStreamData DnsTlsStreamData;
 
-#if HAVE_GNUTLS
+#if DNS_OVER_TLS_USE_GNUTLS
 #include "resolved-dnstls-gnutls.h"
+#elif DNS_OVER_TLS_USE_OPENSSL
+#include "resolved-dnstls-openssl.h"
+#else
+#error Unknown dependency for supporting DNS-over-TLS
 #endif
 
 #include "resolved-dns-stream.h"