I have Web servers that run multiple virtual hosts, and I'd like to keep eavesdroppers from telling which virtual host a client is accessing. There's already a TLS extension for solving this problem: encrypted SNI. I see that Cloudflare supports it on its servers, and that Firefox has a setting to enable it on the client. I can't find any documentation of how to enable this on servers of my own, though. How do I do this? (I'm not tied to any particular server stack and will accept any working setup/architecture other than "put it behind Cloudflare".)
Encrypted Server Name Indication (ESNI) is still an Internet Draft, you will not find it in any major server implementation as it is subject to change. In fact, the draft version implemented by Firefox supports draft-ietf-tls-esni-01 which is incompatible with newer draft versions.
I posted a status of the ecosystem as observed in April 2019 here:
- Latest version is https://tools.ietf.org/html/draft-ietf-tls-esni-03
- OpenSSL is waiting for the draft to be finished. https://github.com/openssl/openssl/issues/7482
- Firefox+NSS supports draft -01 https://bugzilla.mozilla.org/show_bug.cgi?id=1495120 https://github.com/nss-dev/nss/blob/8a8b92f05d2d/lib/ssl/tls13esni.c
- Cloudflare supports draft -01
- picotls supports it https://github.com/h2o/picotls/pull/155
- Go crypto/tls won't support it until ESNI is widely deployed: https://github.com/golang/go/issues/9671#issuecomment-439561672
For experimentation or educational purposes, you could use esnitool and tris-localserver from tls-tris. Assuming you have an appropriate Go toolchain installed on Linux or macOS, something like this should work:
# Get source code and build stuff git clone https://github.com/cloudflare/tls-tris -b pwu/esni cd tls-tris make build-esnitool (cd _dev/tris-localserver && ../go.sh build -v -i .) # Generate ESNI key material, valid for 24 hours (one day) _dev/esnitool/esnitool -validity=24h -esni-keys-file=esni.pub -esni-private-file=esni.key
It will create two files:
- esni.pub - a value such as
/wFsX6klACQA...AAAA=which you have to configure in DNS. If you would like to configure ESNI for
www.example.com, create a TXT record for
/wFsX6klACQA<snip>AAAA=value. This format conforms to the draft-ietf-tls-esni-01 specification as supported by Firefox and Cloudflare. It does not use the latest draft specification.
- esni.priv - a private key file, specific to the test implementation.
The test server can be started as follows:
_dev/tris-localserver/tris-localserver -esni-keys=esni.pub -esni-private=esni.priv
Then configure Firefox to enable use ESNI, open
about:config and set
network.security.esni.enabled to true. You also have to enable DNS-over-HTTPS as well, Firefox instructions can be found on that page. More details about each preference can also be found here: https://bagder.github.io/TRRprefs/
These instructions might work now, but will break in the future as Firefox will likely be updated to support newer draft versions. The esnitool above also hard-codes the permitted cipher suite (AES128-GCM) and key exchange algorithm (X25519). This reflects the parameters that are used by Cloudflare.
ESNI implies TLS 1.3, so the certificate and its embedded host names will be encrypted. With ESNI enabled, and using a secure DNS transport such as DNS-over-HTTPS (DoH) or DNS-over-TLS (DoT), the server name will indeed not be visible on the wire, this can be verified in Wireshark using a filter such as
frame contains "wireshark" when visiting
However, if a single IP only hosts a few domains, then any passive adversary can guess that you are visiting one of those domains. A large operator such as Cloudflare has many more domains where this is not much of an issue.
Since ESNI uses semi-static keys, compromise of the private key means that any eavesdropper can decrypt the encrypted server name. That is why ESNI keys are frequently rotated, and automation is required. Cloudflare has this well-integrated, ESNI keys in DNS and the HTTPS service are regularly updated.
To conclude, ESNI is promising, but requires support from clients (web browsers), DNS servers over a secure transport (DoH/DoT), and web servers. It is still in development and unless you are closely following its development, it is likely not a good idea for you to set it up for things other than experimentation.
The previous very detailed answer inspired to reuse tls-tris to build tiny esni reverse proxy, that actually can terminate TLS 1.3 with ESNI and forward plain traffic to backend of your choice. This allows to easy use ESNI without any modification of web server you are using. The source code and detailed instruction can be found here: esni-rev-proxy