Discussion on Hacker News.

A week or so I discovered that Android P has DNS over TLS support! It piqued my curiousity - could it finally be that DNS encryption goes mainstream?

In this post we’ll survey DNS over TLS, implement a client and share some thoughts!

Party like it's 1983

Sure, with DNS over TLS your DNS queries are encrypted - that’s a major step forward! Nevertheless, the majority of the web relies on SNI (Server Name Indication), which sends the domain name in plaintext! (not for long?).

I created a DNS over TLS Node.js package, install with $ npm i dns-over-tls :

And a DNS over TLS command line tool, install with $ npm i -g dnstls :

Why DNS over TLS?

DNS is insecure. It allows anyone with an active internet tap to view user queries and forge answers.

Over the years there’ve been many proposals on securing DNS. Below is a high level description of each, just so we’re on the same page.

DNSCurve - proposed by Daniel J. Bernstein (@hashbreaker), provides confidentiality (data transport is encrypted), integrity (data can’t be modified in an undetected manner) and authenticity (the origin is verified to be who it claims to be) between recursive DNS servers (e.g. Google’s 8.8.8.8 ) and authoritative name servers (e.g. ns.ibm.com ).

- proposed by Daniel J. Bernstein (@hashbreaker), provides confidentiality (data transport is encrypted), integrity (data can’t be modified in an undetected manner) and authenticity (the origin is verified to be who it claims to be) between recursive DNS servers (e.g. Google’s ) and authoritative name servers (e.g. ). DNSCrypt - based on DNSCurve, provides confidentiality and integrity between stub resolvers (e.g. Android phone) and recursive DNS servers (e.g. Google’s 8.8.8.8 )

- based on DNSCurve, provides confidentiality and integrity between stub resolvers (e.g. Android phone) and recursive DNS servers (e.g. Google’s ) DNSSEC - provides authenticity and integrity, between resolvers (stub, recursive) and authoritative name servers. There’s a heated debate around DNSSEC, mainly because to achieve authentication, a new PKI (public-key infrastructure) was deployed, one which allows for partial government control of DNS (since some TLDs and ccTLDS are controlled by governments).

Bottom line, technicalities aside, the adoption of all lies somewhere between low to non-existent - leaving DNS a potent attack vector, ~35 years since it was created!

Moreover, evidence that DNS is actively tampered with is shown by a recent Usenix 2018 paper - Who Is Answering My Queries: Understanding and Characterizing Interception of the DNS Resolution Path.

Nevertheless, do not despair! Recently, adoption of both DNS over TLS (DoT) and DNS over HTTPS (DoH) has been gaining momentum, as evident by Android 9 support for DoT and Firefox Nightly support for DoH.

How does DNS over TLS Work?

Similarly to DNSCrypt, DNS over TLS encrypts the link between a stub resolver and a recursive resolver (1 and 8 below).

That is, it ensures no passive sniffing or active tampering of DNS can happen on that link (unless, of course, the underlying cryptography is somehow broken).

Communication between recursive resolvers and other nameservers is likely to remain not private, but with the rising adoption of DNSSEC we can at least expect authenticity! (That is, passive sniffing on DNS requests from recursive resolvers would be possible, but active tampering won’t).

What if a rogue CA issues a valid certificate for the recursive resolver?

It’s possible for resourceful adversaries to obtain valid certificates for domains. For this reason, DNS over TLS’s RFC (RFC7858) introduces SPKI (Subject Public Key Info) Pinning:

Upon successful TLS connection and handshake, the client computes the SPKI Fingerprints for the public keys found in the validated server’s certificate chain (or in the raw public key, if the server provides that instead). If a computed fingerprint exactly matches one of the configured pins, the client continues with the connection as normal. Otherwise, the client MUST treat the SPKI validation failure as a non-recoverable error.

In simple words, when you get the details of your DNS over TLS resolver, such as IP address and hostname, you should also get an SPKI pin that your client can use to verify that this is the original server public key. For instance, check out the list of DNS over TLS servers at dnsprivacy.org - an SPKI pin is provided for each server.

The SPKI Pin (“fingerprint”) is the SHA256 of the public key part of the recursive resolver’s X509 certificate. As an implementation reference, we can look on how Android P calculates it.

Here’s an example that utilizes OpenSSL’s CLI to calculate the SPKI pin of Cloudflare’s DNS over TLS server:

$ echo | openssl s_client -connect '1.1.1.1:853' 2 > /dev/null | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64

Implementing a DNS over TLS Client

DNS over TLS simply tunnels good-old DNS packets over TLS with a default TCP port of 853 .

Once a TLS session is established, the client sends a regular DNS query and the server responds with a message that is prefixed with a two byte length field which gives the message length, excluding the two bytes length field.

Here’s a client implementation:

One thing that I haven’t implemented yet is SPKI pinning. Why? The SPKI Pin requires the raw public key DER of the server’s certificate - which is only available for Node.js 9 and 10 . And while for older Node.js versions you can actually get the DER of the entire certificate, carving the public key DER out of it is a delicate art, which I didn’t feel like doing :) (at least for now).

Playing with DNS over TLS

DNS over TLS is fairly new and there aren’t many tools for it - for this reason I made dnstls - a DNS over TLS command line tool:

It defaults to using Cloudflare’s DNS over TLS server ( 1.1.1.1 ).

Install with:

$ npm i -g dnstls

Usage is dig -like:

$ dnstls Usage: dnstls name [ type ] [ class ] [ @server ] [ -p < port > ] [ +tls-host = < host > ]

Where:

name is the domain you’d like to resolve.

is the domain you’d like to resolve. type is the record type you’re after - e.g. A (default), AAAA , NS , TXT , MX , CNAME etc.

is the record type you’re after - e.g. (default), , , , , etc. class is either IN (default), HS or CH .

is either (default), or . port is the TCP port, defaults to 853.

is the TCP port, defaults to server the address of the server, defaults to 1.1.1.1 .

the address of the server, defaults to . +tls-host the TLS hostname that is noted in the server’s certificate, defaults to cloudflare-dns.com .

Note: the server and +tls-host goes hand in hand - for instance, say we’d like to use Quad9’s DNS over TLS server, we’d have to do:

$ dnstls @9.9.9.9 +tls-host = dns.quad9.net sagi.io

The output is a JSON of the response (maybe dig -like output in the future).

Example:

{ "id" : 42209 , "type" : "response" , "flags" : 384 , "flag_qr" : true , "opcode" : "QUERY" , "flag_aa" : false , "flag_tc" : false , "flag_rd" : true , "flag_ra" : true , "flag_z" : false , "flag_ad" : false , "flag_cd" : false , "rcode" : "NOERROR" , "questions" : [ { "name" : "sagi.io" , "type" : "A" , "class" : "IN" } ] , "answers" : [ { "name" : "sagi.io" , "type" : "A" , "ttl" : 101 , "class" : "IN" , "flush" : false , "data" : "151.101.65.195" } , { "name" : "sagi.io" , "type" : "A" , "ttl" : 101 , "class" : "IN" , "flush" : false , "data" : "151.101.1.195" } ] , "authorities" : [ ] , "additionals" : [ ] }

Thoughts

DNS over TLS is a major step forward towards a safer, more private, world. Nevertheless, here’re some thoughts:

Not a Commonly-used Default Port

I developed both library and command line client while working from Google Campus Tel Aviv - which unfortunately blocks all outgoing TCP connections that aren’t using commonly used destination ports (such as 443 , for instance). I suspect that this is going to be an issue for many networks.

Most Web DNS Traffic is Still Not Private

Sure, with DNS over TLS your DNS queries are encrypted - that’s a major step forward! Nevertheless, the majority of the web relies on SNI (Server Name Indication), which sends the domain name in plaintext! (not for long?).

Summary

I’d like to thank the folks at the DNS Privacy Project and the DPRIVE Working Group for their efforts! I think it finally starts to pay off :)

If you’re interested in DNS privacy, there’s a wealth of information in dnsprivacy.org and I invite you to watch NDSS 2018’s DNS Privacy Workshop sessions - very insightful.

If you spot an error / have any question please let me know so others may gain :)

Comments and thoughts are also welcome on this tweet: