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:
As you can see, OpenSSL, used by Nginx and Apache, do not support it. You could try to build Caddy with a patched Go crypto/tls library (using this PR from tls-tris), but it might not work over time.
Experimentation
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:
It will create two files:
/wFsX6klACQA...AAAA=
which you have to configure in DNS. If you would like to configure ESNI forwww.example.com
, create a TXT record for_esni.www.example.com
with that/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.The test server can be started as follows:
Then configure Firefox to enable use ESNI, open
about:config
and setnetwork.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.
Remark
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 visitingwireshark.org
.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