DNS Privacy for up-to-date instructions.
Warning! You might not want to use DNS/TLS to bypass state censorship (you probably want some sort of stealthy VPN):
if someone is able to censor your DNS requests, it could detect that you are communicating to forbidden hosts;
it is quite easy to check that the remote TLS server is not a web server (or not only a webserver) but a DNS server (by making DNS requests) unless you add client authentication.
On the server-side:
On the client-side:
unbound → stunnel (in client mode) if possible;
force DNS/TCP (
resolv.conf for glibc;
tcp for OpenBSD) → stunnel (but programs which do not rely on the libc resolver functions will probably ignore the option).
cache verify TLS [DNS ][DNS ]------------------------------->[DNS] [ ] [ ][ |TLS]----------->[TLS| ] [ ] [UDP*][UDP*|TCP][TCP ][TCP ][TCP] [IP ][IP ][IP ][IP ][IP ] Stub R. Forwarder TLS Init. Internet TLS Term. Recursive (unbound) (stunnel) (stunnel) *: or TCP if the reply is too long
Unbound can be use directly for TLS on the server side:
cache verify TLS [DNS ][DNS ]-------------------->[DNS] [ ] [ ][ |TLS]----------->[TLS] [UDP*][UDP*|TCP][TCP ][TCP] [IP ][IP ][IP ][IP ] Stub R. Forwarder TLS Init. Internet Recursive (unbound) (stunnel) (unbound)
However, it is currently not safe to use unbound to DNS/TLS on the client-side because unbound does not verify the remote certificate1 (MITM attack). This solution is not safe:
cache MITM! [DNS ][DNS ][DNS] [ ] [ |TLS][TLS] [UDP*][UDP*|TCP][TCP] [IP ][IP ][IP ] Stub R. Forwarder Internet Recursive (unbound) (unbound)
stunnel can be used to add/remove TLS layers:
it can be used on the DNS server side to wrap the DNS/TCP service into a DNS/TLS/TCP service;
it can be used on the client side to unwrap the DNS/TLS/TCP and provide a local DNS/TCP service which can be consumed by most resolvers and other DNS clients.
verify TLS [DNS][DNS] [ ] [ |TLS][TLS| ] [ ] [TCP][TCP ][TCP ][TCP] [IP ][IP ][IP ][IP ] Stub R. TLS Init. Internet TLS Term. Recursive (stunnel) (stunnel)
The issue is that usually the resolver will first try to make the query over UDP. If their is not UDP reply, the resolver will not switch to TCP. We need a way to force the resolver to use TCP.
The GNU libc resolver has an (undocumented) option,
resolv/res_init.c) to force the usage of TCP for DNS resolutions. This option is available since glibc v2.14 (available since Debian Jessie, since Ubuntu 12.04).
options use-vc nameserver 2001:913::8
RES_OPTIONS environment variable:
RES_OPTIONS="use-vc" export RES_OPTIONS
$ #Using UDP (SOCK_DGRAM): $ strace getent hosts www.ldn-fai.net 2>&1 | grep -e PF_INET socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 3 socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 4 socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 3 $ #Using TDP (SOCK_STREAM): $ RES_OPTIONS=use-vc strace getent hosts www.ldn-fai.net 2>&1 | \ grep -e PF_INET socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3
Other libc implementations:
The OpenBSD libc seems to have a
tcp option for this.
dietlibc does not handle the
options at all (and does not support
RES_USEVC and DNS/TCP).
uclibc and musl do not have the option and does not handle DNS/TCP at all.
klibc do not have real DNS resolution.
optionsfield at all.
The option to force the usage of TCP for DNS resolution is not available everywhere (many stub resolvers do not handle this option and some sofware do not use the system resolver). A hack to force the stub resolver to use TCP would be to have a simple local DNS/UDP service which always replies with the truncated bit set (
TC=1): this should force most implementations to switch to TCP (and talk to the local stunnel process):
[Resolver] [Fake service] [local stunnel] // [Remote recursive] | | | | |--------->| | | Query over UDP ||---------------->| Query over TCP |
TruncateDNSd is a proof-of-concept implementation of this idea: I'm not sure there is a clean way to do this so it might remain a proof-of-concept.
The correct solution is to have a local DNS recursive server which is able to delegate to a remote recursive DNS over TCP: Unbound can talk (either as a server or as a client) over TCP (
tcp-upstream) or over TLS/TCP (
However, it seems it cannot validate the certificate (v1.5.1):
when used as the local client, it cannot by itself protect against MITM attacks;
when used as the TLS server, it cannot handle TLS-based client authentication.
Those two limitations can be mitigated by using a dedicated TLS encapsulation daemon such as stunnel or socat.
; /etc/stunnel/dns.conf setuid=stunnel4 setgid=stunnel4 pid=/var/run/stunnel4/dns.pid output=/var/log/stunnel4/dns.log socket = l:TCP_NODELAY=1 socket = r:TCP_NODELAY=1 [dns] cert=/etc/stunnel/dns.pem accept=443 connect=53
Keypair and certificate generation:
openssl req -days 360 -nodes -new -x509 -keyout key.pem -out cert.pem \ -subj "/CN=$MY_IP" -sha256 -newkey rsa:2048 (cat key.pem ; echo ; cat cert.pem ; echo ) > dns.pem sudo chmod root:root dns.pem sudo chmod 440 dns.pem sudo mv dns.pem /etc/stunnel/
[DNS][DNS] [TLS][TLS| ] [ ] [TCP][TCP ][TCP] [IP ][IP ][IP ] Resolver Internet TLS Term. Recursive (stunnel)
Unbound can be configured to use TLS directly with
sudo socat \ TCP4-LISTEN:53,bind=127.0.0.1,fork,nodelay,su=nobody \ OPENSSL:188.8.131.52:443,verify=1,cafile=dns.pem,nodelay
options use-vc nameserver 127.0.0.1
verify TLS [DNS][TLS] [TCP][TCP ][TCP] [IP ][IP ][IP ] Stub R. socat Internet Recursive
Programs and libraries trying to parse
resolv.conf directly without using the
res_ (for example
lwresd) functions will usually ignore the
use-vc and fail to work if no DNS server replies on UDP.
This is the client side stunnel configuration:
setuid=stunnel4 setgid=stunnel4 pid=/var/run/stunnel4/dns.pid output=/var/log/stunnel4/dns.log client=yes socket = l:TCP_NODELAY=1 socket = r:TCP_NODELAY=1 [dns] CAfile=/etc/stunnel/dns.pem accept=127.0.0.1:53 connect=184.108.40.206:443 verify=4
with the same
verify TLS [DNS][DNS] [ ] [ |TLS][TLS] [TCP][TCP ][TCP] [IP ][IP ][IP ] Stub R. TLS Init. Internet Recursive (stunnel)
Warning: This configuration is vulnerable to MITM attacks1. Use the unbound + stunnel configuration instead.
A better solution would be to install a local unbound. The local unbound instance will cache the results and avoid a higher latency due to TCP and TLS initialisation:
server: # verbosity: 7 ssl-upstream: yes forward-zone: name: "." forward-addr: 220.127.116.11@443
# /etc/resolv.conf nameserver 127.0.0.1
DNSSEC valid. cache [DNS][DNS ][DNS] [ ] [ |TLS][TLS] [TCP][TCP ][TCP] [IP ][IP ][IP ] Stub R. Forwarder Internet Recursive (unbound)
As a bonus, you can enable local DNSSEC validation.
Unbound currently does not verify the validity of the remote X.509 certificate. In order to avoid MITM attacks, you might want to add a local stunnel between unbound and the remote DNS server.
The unbound configuration uses plain TCP:
server: # verbosity:7 tcp-upstream: yes do-not-query-localhost: no forward-zone: name: "." forward-addr: 127.0.0.1@1234
By default, unbound will not make request on localhost. If you bind youd stunnel to a localhost address (such as 127.0.0.1), you must use
unbound can be shipped with a script for
resolvconf which modified the forwarders which will override your tunnel configuration.
On Debian Jessie, this in handled by
/etc/resolvconf/update.d/unbound and can be disabled by setting
A local stunnel instance handles the TLS encapsulation (with remote certificate verification):
setuid=stunnel4 setgid=stunnel4 pid=/var/run/stunnel4/dns.pid output=/var/log/stunnel4/dns.log socket = l:TCP_NODELAY=1 socket = r:TCP_NODELAY=1 [dns] client=yes CAfile=/etc/stunnel/dns.pem accept=127.0.0.1:1234 connect=18.104.22.168:443 verify=4
DNSSEC valid. cache verify TLS [DNS][DNS ][DNS] [ ] [ ][ |TLS][TLS] [TCP][TCP ][TCP ][TCP] [IP ][IP ][IP ][IP ] Client Forwarder TLS Init. Internet Recursive (unbound) (stunnel)
# We whould see the local traffic to your unbound instance: sudo tcpdump -i lo "port 53" # We should see the traffix from unbound to local stunnel instance: sudo tcpdump -i lo "port 1234" # We should not see outgoing DNS traffic: sudo tcpdump -i eth0 "port 53" # Make DNS requests and see if everything works as expected: dig attempt45.example.com # Flush the cache: sudo unbound-control flush_zone . # Make DNS requests directly on the tunnel (bypass unbound): dif +tcp @127.0.0.1 -p 1234 attempt46.example.com # Display the list of forward servers: sudo unbound-control list_forwards
If your local resolver verify the authenticity of the DNS reply with DNSSEC, it will be able to detect a spoofed DNS reply and reject it. But it will still not be able to get the correct reply. So you should use DNSSEC but you might still want to use DNS/TLS.
See the Mozilla SSL Configuration Generator:
[dns] # Append this to the service [dns] section: options = NO_SSLv2 options = NO_SSLv3 options = NO_TLSv1 options = CIPHER_SERVER_PREFERENCE ciphers = ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK
TLS for DNS:
An IETF working group working on privacy issues of DNS exchanges with drafts:
Propose to used the "dns" string to identify the protocol DNS when negociating a protocol with ALPN. ALPN is an extension for TLS for negociation of the protocol encapsulated over a TLS connection.
Encapsulation of DNS requests over HTTP over TLS with:
The response is sent with
Propose reserving a new port dedicated to DNS over TLS.
A mechanism for upgrading DNS over TCP to DNS over TLS over TCP.
DNS over TCP and TLS (slides)
This setup directly connects the UDP socket with the TCP socket with
socat as a consequence, the TLS stream does not transport the DNS requests in the TCP wire-format. I guess there should be framing problems when converting from TCP to UDP.
Open recursive DNS server:
NSA's MORECOWBELL: Knell for DNS, detailed article with informations about DNSSEC, DNS/TLS, DNSCurve, DNSCrypt, Namecoin,the GNU Name System;
In the unbound code, the TLS outgoing connections are setup in
void* connect_sslctx_create(char* key, char* pem, char* verifypem). This function only calls
SSL_CTX_set_verify() if the
verifypem parameter is not
connect_sslctx_create() is always called with
verifypem set to
You can verify this by configuring a local DNS/TLS service:
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 256 -nodes socat -v OPENSSL-LISTEN:1234,fork,certificate=./cert.pem,key=./key.pem TCP:22.214.171.124:53
and configure unbound to use it as a TLS upstream:
server: # verbosity:7 ssl-upstream: yes do-not-query-localhost: no forward-zone: name: "." forward-addr: 127.0.0.1@1234↩↩