How can I set up Encrypted SNI on my own servers?

Solution 1:

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

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:

# 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 _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.
  • 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.

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 visiting wireshark.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.

Solution 2:

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