Prelude
I have a DNS resolver setup at home which my home devices use and set it to use DNS over TLS (DoT) to resolve queries so ISPs and such can’t see the domains I’m visiting, but SNI is leaking out the sites I’m visiting and there’s nothing much I can do about it for now. 😣
What is the Purpose of SNI
Problem
Name-based virtual hosting allows multiple DNS hostnames to be hosted by a single server (usually a web server) on the same IP address. To achieve this, the server uses a hostname presented by the client as part of the protocol (for HTTP the name is presented in the host header). Source
However, when using HTTPS, the TLS connection happens before the sending of any HTTP data, so we need another way of letting the server know which host we are connecting to so it can establish the TLS connection with the correct certificate.
Solution
Server Name Indication (SNI) solves this by having the client send the hostname as part of the TLS handshake. This enables the server to establish the TLS connection with the correct certificate.
The hostname sent during TLS handshake is not encrypted, so eavesdroppers for example ISPs can see which sites you are visiting.
Here’s a diagram to illustrate. 🔒 Means that it is encrypted.
I have written a SNI Sniffer (only tested on linux) to see this in effect, the code quality is pretty bad I’ll update it when I have time.
➜ sudo sni-sniffer -s eth0
Started capturing on ens33
TCP V4(192.168.14.128):50596 -> V4(172.217.27.46):443
SNI: [
"google.com",
]
TCP V4(192.168.14.128):51262 -> V4(117.18.232.200):443
SNI: [
"az764295.vo.msecnd.net",
]
TCP V4(192.168.14.128):45508 -> V4(111.221.29.254):443
SNI: [
"vortex.data.microsoft.com",
]
TCP V4(192.168.14.128):40952 -> V4(172.217.160.10):443
SNI: [
"safebrowsing.googleapis.com",
]
TCP V4(192.168.14.128):37934 -> V4(35.166.72.120):443
SNI: [
"shavar.services.mozilla.com",
]
ESNI🔒
Encrypted SNI is an extension to TLS 1.3 and above which encrypts the SNI so eavesdroppers cannot see which sites you are visiting.
The general idea of how ESNI works is the server publishes a public key on as a DNS record, which can be fetched by the client before connecting. The client then encrypts the SNI extension using a symmetric encryption key derived using the server’s public key. The server can then decrypt it by deriving the same symmetric encryption key using it’s private key.
For now the only browser I know that supports ESNI is Firefox, but it is not enabled by default, you have to enable it yourself, by going to about:config
and setting network.security.esni.enabled
to true.
The problem now is the server you are connecting to has to support ESNI as well, and there isn’t much support for it software wise as the specifications for ESNI is still in draft. Even when the software support is there, most people are probably lazy to setup ESNI for their servers, currently the best/easiest solution is to use cloudflare as a reverse proxy as they have ESNI enabled by default.
Hopefully specifications for ESNI will be firmed up soon and more sites and browsers will start using ESNI.