I now operate a DoH proxy
Or an oblivious DNS system
by on 2021-07-12§ Context
I recently read about Mozilla launching DoH in Canada, and the usual questions about whether anyone should trust anyone else to not spy on users.
DoH encrypts DNS queries from the client to the recursive server. Those queries used to be sent in cleartext, allowing for manipulation, interception, etc. One could of course change their DNS server, but the ISPs could still sniff and manipulate those requests.
With HTTPS, those worries go away, except for the part where one party (then the ISP, now the DoH server operator, also called the Trusted Recursive Resolver TRR) still has logs of requests + IP addresses they originated from. In the US, people probably trust Cloudflare or Google more than their ISP with this data trove but: why would anyone trust anyone? Cloudflare and other DNS operators are still corporate entities incorporated in specific countries that more or less respect basic human rights 1 2 3.
Mozilla’s justification for DoH’s privacy is laughably weak (emphasis mine):
What is the privacy policy for DNS over HTTPS?
Implementing DoH is part of our work to safeguard users from the pervasive online tracking of personal data. To do that, Mozilla requires all DNS providers that can be selected in Firefox to comply with our resolver policy through a legally-binding contract.
As if contracts were never broken or ideals never shattered. And if a TRR ever breaks the contract, how much would anyone wager that the bad TRR would go under?
Cryptographically sound setup sounds better than ink stains between megacorps.
Recently too, Apple unveiled their iCloud private relay, which works on the same principle as TOR. The idea is to add an indirection layer: client to Apple, Apple to third-party, third-party to destination website. In that chain, only the client has full knownledge of the client and destination.
Can this idea be ported to some other protocols, like DoH?
§ How does it work?
- The proxy accepts a TCP connection and collects the SNI
- The proxy establishes a TCP connection with the server indicated in the SNI
- The client exchanges data through the proxy with the server, without the proxy interfering with the contents of the transmission
- It is really just a repeat of my https setup
§ Why does it work?
- The proxy only needs the SNI; HTTPS works its magic
- The proxy can’t do any manipulation on the data, because HTTPS
- The server only sees the IP address of the proxy; the DoH operator
now has useless logs: proxy IP + domain. As usual though, this is still
not anonymous, because HTTP is still chatty, but it’s a start! (think:
User-Agent
,Accept-Language
, etc.) - The proxy has useless logs: client + destination + transfer size. And the proxy can’t possibly do anything useful with TLSv1.3 data
- The client doesn’t need to trust the proxy; and it doesn’t at all, because, again, HTTPS
§ Cool, how do I set it up?
§ Server
With haproxy:
global
daemon
...
frontend popho
bind 10.10.10.2:443
mode tcp
tcp-request inspect-delay 2s
# define ACL names
acl oneoneoneone req.ssl_sni -i cloudflare-dns.com
acl dnsgoogle req.ssl_sni -i dns.google
... # more ACLs
# Accept connections that we know how to handle
tcp-request content accept if oneoneoneone
tcp-request content accept if dnsgoogle
... # more accepts
# Reject SNIs we don't recognize
tcp-request content reject
# define where to go if there's a match
use_backend bk_oneoneoneone if oneoneoneone
use_backend bk_dnsgoogle if dnsgoogle
... # more backends
backend bk_oneoneoneone
mode tcp
server oneoneoneone1 cloudflare-dns.com:443
backend bk_dnsgoogle
mode tcp
server dnsgoogle1 dns.google:443
... # more backends
Taking the list from curl’s GitHub wiki, and their python script.
#!/bin/sh
dest="$(mktemp -d)"
acls="$dest/acls"
reject="$dest/reject"
use="$dest/use"
backends="$dest/backends"
./scrape_doh_providers.py '"{}".format(o["hostname"])' |
uniq |
while IFS='' read -r _domain; do
_domain_nodot="$(printf '%s' "$_domain" | tr -d '.')"
printf 'acl %s req.ssl_sni -i %s\n' "$_domain_nodot" "$_domain" >> "$acls"
printf 'tcp-request content accept if %s\n' "$_domain_nodot" >> "$reject"
printf 'use_backend bk_%s if %s\n' "$_domain_nodot" "$_domain_nodot" >> "$use"
printf 'backend bk_%s\n mode tcp\n server %s %s:443\n\n' "$_domain_nodot" "${_domain_nodot}1" "$_domain" >> "$backends"
done
printf 'tcp-request content reject\n' >> "$reject"
echo "see $dest"
This means that starting now, my server can be used as a proxy for all known DoH servers listed there.
§ Client
Some setups (notably Android) rely on classical DNS queries to reach
the trusted resolver (WTF, this was exactly what DoH was supposed to
protect us from?!). It would be cool if setting up an oblivious DNS was
easy: e.g. https://151.80.43.167#dns.google/dns-query
§ Firefox
In the about:config
page of Firefox, you can set:
network.trr.bootstrapAddress | 151.80.43.167 |
network.trr.mode | 3 |
network.trr.uri | https://cloudflare-dns.com/dns-query |
§ curl(1)
If you want to test it out “one-shot”, try:
curl -v --doh-url 'https://cloudflare-dns.com/dns-query' --resolve 'cloudflare-dns.com:443:151.80.43.167' https://try.popho.be
It should report:
* Trying 151.80.43.167:443...
* Connected to cloudflare-dns.com (151.80.43.167) port 443 (#2)
...
* Connected to cloudflare-dns.com (151.80.43.167) port 443 (#1)
...
* subjectAltName: host "cloudflare-dns.com" matched cert's "cloudflare-dns.com"
...
* SSL certificate verify ok.
The client can connect to my server on port https
,
request cloudflare-dns.com
and get to it with no issue.
§ The rest
I couldn’t find anything tangible for either system-wide settings nor for Chromium & Co.
§ Limitations
No idea about the legal implications of all of this. And really, maybe my operator (OVH) has logs. If someone now asks about what happened with your computer when you get arrested, they’ll see you connected to my server instead of a known TRR.
If I forward a canary or honeypot domain request, then it’s still Google or Cloudflare doing the actual resolving, and they should be contractually obligated to… whatever.
Also, I’m not opening an open recursor on Internet, because we’re using TCP.
My server is hosted by OVH in France. Services that geoblock content will break.
If haproxy blows up because it’s receiving too many connections, this blog will be unavailable in the legacy IP space. This was also an occurence of “more software = more bugs” I wrote about in my HTTPS setup.
§ See also
I may have re-discovered Princeton’s ODNS.