Tagged in: dns, haproxy, privacy, projects, tls, networking

§ 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?

Oh wow, this looks… so simple (digraph source)

§ How does it work?

Oblivious DNS-over-HTTPS, see the source here

§ Why does it work?

§ 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.