{"version": "https://jsonfeed.org/version/1", "title": "/dev/posts/ - Tag index - tls", "home_page_url": "https://www.gabriel.urdhr.fr", "feed_url": "/tags/tls/feed.json", "items": [{"id": "http://www.gabriel.urdhr.fr/2015/12/09/dns-aggregator-tls/", "title": "DNS aggregation over TLS", "url": "https://www.gabriel.urdhr.fr/2015/12/09/dns-aggregator-tls/", "date_published": "2015-12-09T00:00:00+01:00", "date_modified": "2015-12-09T00:00:00+01:00", "tags": ["computer", "dns", "network", "internet", "tls"], "content_html": "

In a previous\npost, I tried\ndifferent solutions for tunnelling DNS over\nTLS. One of those solutions was\nusing a dedicated DNS-over-UDP fake\nservice replying to all\nqueries with the truncate flag set: this was causing the stub\nresolvers to retry the query using a TCP-based virtual-circuit. This\nsolution is interesting because it is dead simple (it fits in a few\nline of codes) but it is clearly a hack. Here, I'm using a dedicated\nDNS forwarder aggregating all\nthe incoming DNS-over-UDP requests over a single persistent TCP\nvirtual-circuit.

\n

Update (2017-05-17):\nThis was written before DNS/TLS was a thing\n(and before it was natively implemented in resolvers).\nSee DNS Privacy\nfor up-to-date instructions.

\n

Summary

\n

The differents solutions presented in the previous post on the\nresolver side:

\n\n

The last solution is using this protocol stack:

\n
\n     DNSSEC valid.   TLS Init.\n         cache       verify TLS\n\n[DNS]<->[DNS   ]<------------------------>[DNS]\n[   ]   [      ]<---->[  |TLS]<---------->[TLS]\n[TCP]<->[TCP   ]<---->[TCP   ]<---------->[TCP]\n[IP ]<->[IP    ]<---->[IP    ]<---------->[IP ]\nStub R.  Forwarder    TLS Init. Internet   Recursive\n         (unbound)    (stunnel)\n
\n\n

However,\neach DNS request is using a new TCP\nand TLS connection\nbetween the\nstub resolver and unbound. Between unbound and stunnel, each each\nincoming TCP connection stream is encapsulated in a new TLS\nconnection. This is very inefficient and the resulting DNS service is\nnot very robust.

\n

Performance considerations for DNS over TLS are summarized in the TLS\nfor DNS: Initiation and Performance\nConsiderations\ndraft (emphasis mine):

\n
\n

Latency: Compared to UDP, DNS-over-TCP requires an additional\nround-trip-time [\u2026]. The TLS handshake adds another two RTTs of\nlatency.

\n

State: The use of connection-oriented TCP requires keeping\nadditional state in both kernels and applications. [\u2026]

\n

Processing: [\u2026] slightly higher CPU usage.

\n

Number of connections: clients SHOULD minimize creation of new\nTCP connections. Use of a local DNS request aggregator (a\nparticular type of forwarder) allows a single active DNS-over-TLS\nconnection from any given client computer to its server.

\n
\n

DNS aggregation over a persistent TCP connection

\n

In order to fix this problem, I wrote a prototype DNS\nforwarder which aggregates all\nthe local UDP-based DNS messages over a persistent TCP stream:

\n\n

This service can then be coupled with a TLS initiator (stunnel)\nwhich encapsulates the persistent DNS stream over TCP.

\n

In the future, the tool might have an option to talk TLS natively. My\nexcuse for not adding builtin support for TLS was that it gives you\nthe freedom of choosing which TLS implementation you would like to use\n(OpenSSL with stunnel or socat, GnuTLS with gnutls-serv and a\ntool such as socat with faucet, NSS but I do not know a suitable\ntool using this library, etc).

\n
\n        VC encap. DNSSEC valid.    TLS\n    Mux.        cache        TLS verify\n\n[DNS]<->[DNS    ]<->[DNS   ]<---------------------->[DNS]\n                                     [TLS]<-------->[TLS]\n[UDP]<->[UDP|TCP]<->[TCP   ]<---->[TCP   ]<-------->[TCP]\n[IP ]<->[IP     ]<->[IP    ]<---->[IP    ]<-------->[IP ]\nClient  Aggregator  Forwarder     TLS Init. Internet  Recursive\n         (dnsfwd)   (unbound)    (stunnel)\n
\n\n

The resulting DNS service is much more robust.

\n
\n

Warning

\n

This software is a prototype. Use at your own risk.

\n
\n

References

\n"}, {"id": "http://www.gabriel.urdhr.fr/2015/02/14/recursive-dns-over-tls-over-tcp-443/", "title": "Recursive DNS over TLS over TCP 443", "url": "https://www.gabriel.urdhr.fr/2015/02/14/recursive-dns-over-tls-over-tcp-443/", "date_published": "2015-02-14T00:00:00+01:00", "date_modified": "2017-05-15T00:00:00+02:00", "tags": ["computer", "network", "dns", "internet", "tls"], "content_html": "

You might want to use an open recursive DNS servers if your ISP's DNS\nserver is lying. However, if your network/ISP is intercepting all DNS\nrequests, a standard open recursive DNS server won't help. You might\nhave more luck by using an alternative port or by forcing the usage of\nTCP (use-vc option in recent versions of glibc) but it might not\nwork. Alternatively, you could want to talk to a (trusted) remote\nrecursive DNS server over secure channel such as TLS: by using DNS\nover TLS over TCP port 443 (the HTTP/TLS port), you should be able to\navoid most filtering between you and the recursive server.

\n

Update (2016-05-18):\nRFC7858, Specification for DNS over TLS\ndescribes the use TLS over DNS (using TCP port 853).

\n

Update (2017-04-08):\nAll those solutions use\none TCP (and TLS) connection per DNS request\nwhich is quite inefficient.

\n

Update (2017-05-17):\nThis was written before DNS/TLS was a thing\n(and before it was natively implemented in resolvers).\nSee DNS Privacy\nfor up-to-date instructions.

\n
\n

Warning

\n

You might not want to use DNS/TLS to bypass state censorship.\nYou probably want some sort of stealthy VPN.

\n

If someone is able to censor your DNS requests, it could detect that\nyou are communicating to forbidden hosts.\nMoreover, it is quite easy to check that the remote TLS server is not a web\nserver (or not only a webserver) but a DNS server (by making DNS\nrequests) unless you add client authentication.

\n
\n

Table of Content

\n
\n\n
\n

Summary

\n

On the server-side:

\n\n

On the client-side:

\n\n

Generic solution:

\n
\n          cache      verify TLS\n\n[DNS ]<->[DNS     ]<->------------------------------->[DNS]\n[    ]   [        ]<->[   |TLS]----------->[TLS|  ]   [   ]\n[UDP*]<->[UDP*|TCP]<->[TCP    ]<---------->[TCP   ]<->[TCP]\n[IP  ]<->[IP      ]<->[IP     ]<---------->[IP    ]<->[IP ]\nStub R.   Forwarder   TLS Init. Internet   TLS Term.  Recursive\n         (unbound)    (stunnel)            (stunnel)\n\n*: or TCP if the reply is too long\n
\n\n

Unbound can be use directly for TLS on the server side:

\n
\n          cache      verify TLS\n\n[DNS ]<->[DNS     ]<->-------------------->[DNS]\n[    ]   [        ]<->[   |TLS]----------->[TLS]\n[UDP*]<->[UDP*|TCP]<->[TCP    ]<---------->[TCP]\n[IP  ]<->[IP      ]<->[IP     ]<---------->[IP ]\nStub R.   Forwarder   TLS Init.  Internet  Recursive\n          (unbound)   (stunnel)            (unbound)\n
\n\n

However, it is currently not safe to use unbound to DNS/TLS on the\nclient-side because unbound does not verify the remote\ncertificate1\n(MITM attack). This solution is not safe:

\n
\n          cache       MITM!\n\n[DNS ]<->[DNS     ]<--------->[DNS]\n[    ]   [    |TLS]<--------->[TLS]\n[UDP*]<->[UDP*|TCP]<--------->[TCP]\n[IP  ]<->[IP      ]<--------->[IP ]\nStub R.  Forwarder  Internet Recursive\n         (unbound)           (unbound)\n
\n\n

Software used

\n

stunnel

\n

stunnel can be used to add/remove TLS\nlayers:

\n\n

Protocol stack:

\n
\n       verify TLS\n\n[DNS]<-------------------------------->[DNS]\n[   ]   [  |TLS]<---------->[TLS|  ]   [   ]\n[TCP]<->[TCP   ]<---------->[TCP   ]<->[TCP]\n[IP ]<->[IP    ]<---------->[IP    ]<->[IP ]\nStub R. TLS Init. Internet  TLS Term.  Recursive\n        (stunnel)           (stunnel)\n
\n\n

The issue is that usually the resolver will first try to make the\nquery over UDP. If their is not UDP reply, the resolver will not\nswitch to TCP. We need a way to force the resolver to use TCP.

\n

libc

\n

The GNU libc resolver has an (undocumented) option, use-vc (see\nresolv/res_init.c) to force the usage of TCP for DNS resolutions.\nThis option is available since glibc v2.14 (available since Debian\nJessie, since Ubuntu 12.04).

\n

In /etc/resolv.conf:

\n
options use-vc\nnameserver 2001:913::8\n
\n\n\n

With the RES_OPTIONS environment variable:

\n
RES_OPTIONS=\"use-vc\"\nexport RES_OPTIONS\n
\n\n\n

Example:

\n
$ #Using UDP (SOCK_DGRAM):\n$ strace getent hosts www.ldn-fai.net 2>&1 | grep -e PF_INET\nsocket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 3\nsocket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 4\nsocket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 3\n\n$ #Using TDP (SOCK_STREAM):\n$ RES_OPTIONS=use-vc strace getent hosts www.ldn-fai.net 2>&1 | \\\n  grep -e PF_INET\nsocket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3\n
\n\n\n

Other libc implementations:

\n\n

Similar libraries:

\n\n

Truncate all answers

\n

The option to force the usage of TCP for DNS resolution is not\navailable everywhere (many stub resolvers do not handle this option and\nsome sofware do not use the system resolver). A hack to force the\nstub resolver to use TCP would be to have a simple local DNS/UDP service\nwhich always replies with the truncated bit set (TC=1): this should\nforce most implementations to switch to TCP (and talk to the local\nstunnel process):

\n
\n[Resolver] [Fake service] [local stunnel] // [Remote recursive]\n    |          |             |                 |\n    |--------->|             |                 |  Query over UDP\n    |<---------|             |                 |  Response over UDP (TC=1)\n    |----------------------->|---------------->|  Query over TCP\n    |<-----------------------|<----------------|  Response over TCP\n
\n\n

TruncateDNSd is a\nproof-of-concept implementation of this idea: I'm not sure there is a\nclean way to do this so it might remain a proof-of-concept.

\n

Unbound

\n

The correct solution is to have a local DNS recursive server which is\nable to delegate to a remote recursive DNS over TCP:\nUnbound can talk (either as a server or as a\nclient) over TCP (tcp-upstream) or over TLS/TCP (ssl-upstream,\nssl-service-key, ssl-service-pem, ssl-port).

\n

However, it seems it cannot validate the certificate (v1.5.1):

\n\n

Those two limitations can be mitigated by using a dedicated TLS\nencapsulation daemon such as stunnel or socat.

\n

Server-side configuration

\n

Using stunnel

\n

stunnel configuration:

\n
; /etc/stunnel/dns.conf\nsetuid=stunnel4\nsetgid=stunnel4\npid=/var/run/stunnel4/dns.pid\noutput=/var/log/stunnel4/dns.log\nsocket = l:TCP_NODELAY=1\nsocket = r:TCP_NODELAY=1\n\n[dns]\ncert=/etc/stunnel/dns.pem\naccept=443\nconnect=53\n
\n\n\n

Keypair and certificate generation:

\n
openssl req -days 360 -nodes -new -x509 -keyout key.pem -out cert.pem \\\n  -subj \"/CN=$MY_IP\" -sha256 -newkey rsa:2048\n(cat key.pem ; echo ; cat cert.pem ; echo ) > dns.pem\nsudo chmod root:root dns.pem\nsudo chmod 440 dns.pem\nsudo mv dns.pem /etc/stunnel/\n
\n\n\n

Protocol stack:

\n
\n[DNS]<----------------------->[DNS]\n[TLS]<------------>[TLS|  ]   [   ]\n[TCP]<------------>[TCP   ]<->[TCP]\n[IP ]<------------>[IP    ]<->[IP ]\nResolver Internet  TLS Term.  Recursive\n                   (stunnel)\n
\n\n

Using unbound

\n

Unbound can be configured to use TLS directly with ssl-port,\nssl-service-key, ssl-service-pem.

\n

Client-side configuration

\n

Using socat

\n
sudo socat \\\n  TCP4-LISTEN:53,bind=127.0.0.1,fork,nodelay,su=nobody \\\n  OPENSSL:80.67.188.188:443,verify=1,cafile=dns.pem,nodelay\n
\n\n\n

With /etc/resolv.conf:

\n
options use-vc\nnameserver 127.0.0.1\n
\n\n\n

Protocol stack:

\n
\n      verify TLS\n\n[DNS]<--------------------[DNS]\n[   ]   [  |TLS]<-------->[TLS]\n[TCP]<->[TCP   ]<-------->[TCP]\n[IP ]<->[IP    ]<-------->[IP ]\nStub R.  socat   Internet  Recursive\n
\n\n

Programs and libraries trying to parse resolv.conf directly without\nusing the res_ (for example lwresd) functions will usually ignore\nthe use-vc and fail to work if no DNS server replies on UDP.

\n

Using stunnel

\n

This is the client side stunnel configuration:

\n
setuid=stunnel4\nsetgid=stunnel4\npid=/var/run/stunnel4/dns.pid\noutput=/var/log/stunnel4/dns.log\nclient=yes\nsocket = l:TCP_NODELAY=1\nsocket = r:TCP_NODELAY=1\n\n[dns]\nCAfile=/etc/stunnel/dns.pem\naccept=127.0.0.1:53\nconnect=80.67.188.188:443\nverify=4\n
\n\n\n

with the same resolv.conf.

\n

Protocol stack:

\n
\n       verify TLS\n\n[DNS]<--------------------->[DNS]\n[   ]   [  |TLS]<---------->[TLS]\n[TCP]<->[TCP   ]<---------->[TCP]\n[IP ]<->[IP    ]<---------->[IP ]\nStub R. TLS Init. Internet  Recursive\n        (stunnel)\n
\n\n

Using unbound

\n
\n

Warning

\n

This configuration is vulnerable to\nMITM attacks1.\nUse the unbound + stunnel configuration instead.

\n
\n

A better solution would be to install a local unbound. The local\nunbound instance will cache the results and avoid a higher latency due\nto TCP and TLS initialisation:

\n
server:\n  # verbosity: 7\n  ssl-upstream: yes\nforward-zone:\n  name: \".\"\n  forward-addr: 80.67.188.188@443\n
\n\n\n
# /etc/resolv.conf\nnameserver 127.0.0.1\n
\n\n\n

Protocol stack:

\n
\n     DNSSEC valid.\n         cache\n\n[DNS]<->[DNS   ]<---------->[DNS]\n[   ]   [  |TLS]<---------->[TLS]\n[TCP]<->[TCP   ]<---------->[TCP]\n[IP ]<->[IP    ]<---------->[IP ]\nStub R. Forwarder Internet  Recursive\n        (unbound)\n
\n\n

As a bonus, you can enable local DNSSEC validation.

\n

Using unbound and stunnel

\n

Unbound currently does not verify the validity of the remote X.509\ncertificate. In order to avoid MITM attacks, you might want to add a\nlocal stunnel between unbound and the remote DNS server.

\n

The unbound configuration uses plain TCP:

\n
server:\n  # verbosity:7\n  tcp-upstream: yes\n  do-not-query-localhost: no\nforward-zone:\n  name: \".\"\n  forward-addr: 127.0.0.1@1234\n
\n\n\n

Issues:

\n\n

On Debian Jessie, this in handled by\n /etc/resolvconf/update.d/unbound and can be disabled by setting\n RESOLVCONF_FORWARDERS=false in /etc/default/unbound.

\n

A local stunnel instance handles the TLS encapsulation (with remote\ncertificate verification):

\n
setuid=stunnel4\nsetgid=stunnel4\npid=/var/run/stunnel4/dns.pid\noutput=/var/log/stunnel4/dns.log\nsocket = l:TCP_NODELAY=1\nsocket = r:TCP_NODELAY=1\n\n[dns]\nclient=yes\nCAfile=/etc/stunnel/dns.pem\naccept=127.0.0.1:1234\nconnect=80.67.188.188:443\nverify=4\n
\n\n\n

Protocol stack:

\n
\n     DNSSEC valid.\n         cache       verify TLS\n\n[DNS]<->[DNS   ]<------------------------>[DNS]\n[   ]   [      ]<---->[  |TLS]<---------->[TLS]\n[TCP]<->[TCP   ]<---->[TCP   ]<---------->[TCP]\n[IP ]<->[IP    ]<---->[IP    ]<---------->[IP ]\nClient   Forwarder    TLS Init. Internet   Recursive\n         (unbound)    (stunnel)\n
\n\n

Verifying that the setup is correct

\n
# We whould see the local traffic to your unbound instance:\nsudo tcpdump -i lo \"port 53\"\n\n# We should see the traffix from unbound to local stunnel instance:\nsudo tcpdump -i lo \"port 1234\"\n\n# We should not see outgoing DNS traffic:\nsudo tcpdump -i eth0 \"port 53\"\n\n# Make DNS requests and see if everything works as expected:\ndig attempt45.example.com\n\n# Flush the cache:\nsudo unbound-control flush_zone .\n\n# Make DNS requests directly on the tunnel (bypass unbound):\ndif +tcp @127.0.0.1 -p 1234 attempt46.example.com\n\n# Display the list of forward servers:\nsudo unbound-control list_forwards\n
\n\n\n

What about DNSSEC?

\n

If your local resolver verify the authenticity of the DNS reply with\nDNSSEC, it will be able to detect a spoofed DNS reply and reject\nit. But it will still not be able to get the correct reply. So you\nshould use DNSSEC but you might still want to use DNS/TLS.

\n

TLS configuration

\n

See the Mozilla SSL Configuration\nGenerator:

\n

stunnel

\n
[dns]\n# Append this to the service [dns] section:\noptions = NO_SSLv2\noptions = NO_SSLv3\noptions = NO_TLSv1\noptions = CIPHER_SERVER_PREFERENCE\nciphers = 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\n
\n\n\n

References

\n

TLS for DNS:

\n\n

An IETF working group working on privacy issues of DNS exchanges with drafts:

\n\n

This setup directly connects the UDP socket with the TCP socket with\n socat as a consequence, the TLS stream does not transport the DNS\n requests in the TCP\n wire-format. I\n guess there should be framing problems when converting from TCP to\n UDP.

\n\n

Open recursive DNS server:

\n\n

DNS censorship:

\n\n

DNS monitoring:

\n\n
\n
\n
    \n
  1. \n

    In the unbound code, the TLS outgoing connections are setup in\nvoid* connect_sslctx_create(char* key, char* pem, char* verifypem).\nThis function only calls SSL_CTX_set_verify()\nif the verifypem parameter is not NULL.\nHowever, connect_sslctx_create() is always\ncalled with verifypem set to NULL.

    \n

    You can verify this by configuring a local DNS/TLS service:

    \n

    \nopenssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 256 -nodes\nsocat -v OPENSSL-LISTEN:1234,fork,certificate=./cert.pem,key=./key.pem TCP:80.67.188.188:53\n
    \n

    and configure unbound to use it as a TLS upstream:

    \n

    \nserver:\n  # verbosity:7\n  ssl-upstream: yes\n  do-not-query-localhost: no\nforward-zone:\n  name: \".\"\n  forward-addr: 127.0.0.1@1234\n
    \u00a0\u21a9\u21a9\n
  2. \n
\n
"}]}