From 2e22a54f4e085496088b77085f38b66532da59fb Mon Sep 17 00:00:00 2001 From: Guilhem Lettron Date: Sat, 30 Nov 2019 03:51:40 +0100 Subject: [PATCH] Implement SNI when using DNS-over-TLS Some DNS providers need SNI to identify client. This can be used by adding #name to a DNS. Example: [Resolve] DNS=192.168.1.1#example.com --- man/resolved.conf.xml | 3 +++ src/resolve/meson.build | 8 ++++++ src/resolve/resolved-conf.c | 6 +++-- src/resolve/resolved-dns-server.c | 12 ++++++++- src/resolve/resolved-dns-server.h | 5 +++- src/resolve/resolved-dnstls-gnutls.c | 6 +++++ src/resolve/resolved-dnstls-openssl.c | 11 ++++++++ src/resolve/resolved-link-bus.c | 2 +- src/resolve/resolved-link.c | 2 +- src/resolve/resolved-util.c | 36 +++++++++++++++++++++++++++ src/resolve/resolved-util.h | 6 +++++ src/resolve/test-resolved-util.c | 32 ++++++++++++++++++++++++ 12 files changed, 123 insertions(+), 6 deletions(-) create mode 100644 src/resolve/resolved-util.c create mode 100644 src/resolve/resolved-util.h create mode 100644 src/resolve/test-resolved-util.c diff --git a/man/resolved.conf.xml b/man/resolved.conf.xml index 818000145b..0f70ced5b5 100644 --- a/man/resolved.conf.xml +++ b/man/resolved.conf.xml @@ -214,6 +214,9 @@ resolver is not capable of authenticating the server, so it is vulnerable to "man-in-the-middle" attacks. + Server Name Indication (SNI) can be used when opening a TLS connection. + Entries in DNS= should be in format address#server_name. + In addition to this global DNSOverTLS setting systemd-networkd.service8 also maintains per-link DNSOverTLS settings. For system DNS diff --git a/src/resolve/meson.build b/src/resolve/meson.build index 92b67b6333..c4d8d4e5d9 100644 --- a/src/resolve/meson.build +++ b/src/resolve/meson.build @@ -64,6 +64,8 @@ systemd_resolved_sources = files(''' resolved-etc-hosts.h resolved-etc-hosts.c resolved-dnstls.h + resolved-util.c + resolved-util.h '''.split()) resolvectl_sources = files(''' @@ -228,4 +230,10 @@ tests += [ [], [], 'ENABLE_RESOLVE', 'manual'], + + [['src/resolve/test-resolved-util.c', + 'src/resolve/resolved-util.c', + 'src/resolve/resolved-util.h'], + [], + []], ] diff --git a/src/resolve/resolved-conf.c b/src/resolve/resolved-conf.c index a46c45385b..ca5b8e7918 100644 --- a/src/resolve/resolved-conf.c +++ b/src/resolve/resolved-conf.c @@ -8,6 +8,7 @@ #include "parse-util.h" #include "resolved-conf.h" #include "resolved-dnssd.h" +#include "resolved-util.h" #include "specifier.h" #include "string-table.h" #include "string-util.h" @@ -27,11 +28,12 @@ static int manager_add_dns_server_by_string(Manager *m, DnsServerType type, cons union in_addr_union address; int family, r, ifindex = 0; DnsServer *s; + _cleanup_free_ char *server_name = NULL; assert(m); assert(word); - r = in_addr_ifindex_from_string_auto(word, &family, &address, &ifindex); + r = in_addr_ifindex_name_from_string_auto(word, &family, &address, &ifindex, &server_name); if (r < 0) return r; @@ -52,7 +54,7 @@ static int manager_add_dns_server_by_string(Manager *m, DnsServerType type, cons return 0; } - return dns_server_new(m, NULL, type, NULL, family, &address, ifindex); + return dns_server_new(m, NULL, type, NULL, family, &address, ifindex, server_name); } int manager_parse_dns_server_string_and_warn(Manager *m, DnsServerType type, const char *string) { diff --git a/src/resolve/resolved-dns-server.c b/src/resolve/resolved-dns-server.c index 9f2c97314f..4b0599ab9c 100644 --- a/src/resolve/resolved-dns-server.c +++ b/src/resolve/resolved-dns-server.c @@ -25,8 +25,10 @@ int dns_server_new( Link *l, int family, const union in_addr_union *in_addr, - int ifindex) { + int ifindex, + const char *server_name) { + _cleanup_free_ char *name = NULL; DnsServer *s; assert(m); @@ -44,6 +46,12 @@ int dns_server_new( return -E2BIG; } + if (server_name) { + name = strdup(server_name); + if (!name) + return -ENOMEM; + } + s = new(DnsServer, 1); if (!s) return -ENOMEM; @@ -55,6 +63,7 @@ int dns_server_new( .family = family, .address = *in_addr, .ifindex = ifindex, + .server_name = TAKE_PTR(name), }; dns_server_reset_features(s); @@ -107,6 +116,7 @@ static DnsServer* dns_server_free(DnsServer *s) { #endif free(s->server_string); + free(s->server_name); return mfree(s); } diff --git a/src/resolve/resolved-dns-server.h b/src/resolve/resolved-dns-server.h index 54339355aa..889c80a205 100644 --- a/src/resolve/resolved-dns-server.h +++ b/src/resolve/resolved-dns-server.h @@ -53,6 +53,8 @@ struct DnsServer { char *server_string; + char *server_name; + /* The long-lived stream towards this server. */ DnsStream *stream; @@ -94,7 +96,8 @@ int dns_server_new( Link *link, int family, const union in_addr_union *address, - int ifindex); + int ifindex, + const char *server_string); DnsServer* dns_server_ref(DnsServer *s); DnsServer* dns_server_unref(DnsServer *s); diff --git a/src/resolve/resolved-dnstls-gnutls.c b/src/resolve/resolved-dnstls-gnutls.c index ed0a31e8bf..aad3bb4481 100644 --- a/src/resolve/resolved-dnstls-gnutls.c +++ b/src/resolve/resolved-dnstls-gnutls.c @@ -67,6 +67,12 @@ int dnstls_stream_connect_tls(DnsStream *stream, DnsServer *server) { gnutls_session_set_verify_cert2(gs, &stream->dnstls_data.validation, 1, 0); } + if (server->server_name) { + r = gnutls_server_name_set(gs, GNUTLS_NAME_DNS, server->server_name, strlen(server->server_name)); + if (r < 0) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set server name: %s", gnutls_strerror(r)); + } + gnutls_handshake_set_timeout(gs, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); gnutls_transport_set_ptr2(gs, (gnutls_transport_ptr_t) (long) stream->fd, stream); diff --git a/src/resolve/resolved-dnstls-openssl.c b/src/resolve/resolved-dnstls-openssl.c index 85e202ff74..ce0a437371 100644 --- a/src/resolve/resolved-dnstls-openssl.c +++ b/src/resolve/resolved-dnstls-openssl.c @@ -87,6 +87,17 @@ int dnstls_stream_connect_tls(DnsStream *stream, DnsServer *server) { return -ECONNREFUSED; } + if (server->server_name) { + r = SSL_set_tlsext_host_name(s, server->server_name); + if (r <= 0) { + char errbuf[256]; + + error = ERR_get_error(); + ERR_error_string_n(error, errbuf, sizeof(errbuf)); + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to set server name: %s", errbuf); + } + } + ERR_clear_error(); stream->dnstls_data.handshake = SSL_do_handshake(s); if (stream->dnstls_data.handshake <= 0) { diff --git a/src/resolve/resolved-link-bus.c b/src/resolve/resolved-link-bus.c index 8a2768b1e2..dae8435b45 100644 --- a/src/resolve/resolved-link-bus.c +++ b/src/resolve/resolved-link-bus.c @@ -284,7 +284,7 @@ int bus_link_method_set_dns_servers(sd_bus_message *message, void *userdata, sd_ if (s) dns_server_move_back_and_unmark(s); else { - r = dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, dns[i].family, &dns[i].address, 0); + r = dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, dns[i].family, &dns[i].address, 0, NULL); if (r < 0) goto clear; } diff --git a/src/resolve/resolved-link.c b/src/resolve/resolved-link.c index 96ebb4d23d..f19fc2f3aa 100644 --- a/src/resolve/resolved-link.c +++ b/src/resolve/resolved-link.c @@ -269,7 +269,7 @@ static int link_update_dns_server_one(Link *l, const char *name) { return 0; } - return dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, family, &a, 0); + return dns_server_new(l->manager, NULL, DNS_SERVER_LINK, l, family, &a, 0, NULL); } static int link_update_dns_servers(Link *l) { diff --git a/src/resolve/resolved-util.c b/src/resolve/resolved-util.c new file mode 100644 index 0000000000..2f18f8c19d --- /dev/null +++ b/src/resolve/resolved-util.c @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include "alloc-util.h" +#include "in-addr-util.h" +#include "macro.h" +#include "resolved-util.h" + +int in_addr_ifindex_name_from_string_auto(const char *s, int *family, union in_addr_union *ret, int *ifindex, char **server_name) { + _cleanup_free_ char *buf = NULL, *name = NULL; + const char *m; + int r; + + assert(s); + + m = strchr(s, '#'); + if (m) { + name = strdup(m+1); + if (!name) + return -ENOMEM; + + buf = strndup(s, m - s); + if (!buf) + return -ENOMEM; + + s = buf; + } + + r = in_addr_ifindex_from_string_auto(s, family, ret, ifindex); + if (r < 0) + return r; + + if (server_name) + *server_name = TAKE_PTR(name); + + return r; +} diff --git a/src/resolve/resolved-util.h b/src/resolve/resolved-util.h new file mode 100644 index 0000000000..10ebbc0874 --- /dev/null +++ b/src/resolve/resolved-util.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include "in-addr-util.h" + +int in_addr_ifindex_name_from_string_auto(const char *s, int *family, union in_addr_union *ret, int *ifindex, char **server_name); diff --git a/src/resolve/test-resolved-util.c b/src/resolve/test-resolved-util.c new file mode 100644 index 0000000000..35bd73c4f6 --- /dev/null +++ b/src/resolve/test-resolved-util.c @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ + +#include "log.h" +#include "resolved-util.h" +#include "string-util.h" +#include "tests.h" + + +static void test_in_addr_ifindex_name_from_string_auto_one(const char *a, const char *expected) { + int family, ifindex; + union in_addr_union ua; + _cleanup_free_ char *server_name = NULL; + + assert_se(in_addr_ifindex_name_from_string_auto(a, &family, &ua, &ifindex, &server_name) >= 0); + assert_se(streq_ptr(server_name, expected)); +} + +static void test_in_addr_ifindex_name_from_string_auto(void) { + log_info("/* %s */", __func__); + + test_in_addr_ifindex_name_from_string_auto_one("192.168.0.1", NULL); + test_in_addr_ifindex_name_from_string_auto_one("192.168.0.1#test.com", "test.com"); + test_in_addr_ifindex_name_from_string_auto_one("fe80::18%19", NULL); + test_in_addr_ifindex_name_from_string_auto_one("fe80::18%19#another.test.com", "another.test.com"); +} + +int main(int argc, char **argv) { + test_setup_logging(LOG_DEBUG); + + test_in_addr_ifindex_name_from_string_auto(); + return 0; +}