Skip to content

losfair/anyech

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

AnyECH

A small HTTP reverse proxy that forwards every request to a fixed upstream over TLS with mandatory Encrypted Client Hello (ECH). The ECH outer SNI (public_name) and the HPKE public key are independently overridable.

Mandatory means fail-closed: the connection to the upstream is only made if ECH is actually negotiated. If the upstream rejects ECH, the request fails with 502 Bad Gateway rather than falling back to a plaintext SNI. This is enforced by crypto/tls (a rejected ECH handshake returns an error) and re-asserted via ConnectionState.ECHAccepted.

Build

go build -o anyech .

Requires Go 1.25+ (client-side ECH lives in the standard crypto/tls).

Usage

anyech -upstream https://host [flags]
Flag Description
-listen Address to listen on for inbound HTTP (default :8080).
-upstream Upstream URL to forward all requests to, e.g. https://example.com. Must be https. Its hostname is the inner (real) SNI and the forwarded Host. Required.
-ech-config-list Base64 ECHConfigList to use as the source config (e.g. the ech value from a DNS HTTPS/SVCB record).
-ech-from-dns Fetch the source ECHConfigList from the upstream's DNS HTTPS record over DoH. Mutually exclusive with -ech-config-list.
-doh DNS-over-HTTPS resolver endpoint for -ech-from-dns (default https://cloudflare-dns.com/dns-query; must be https).
-ech-dns-name Name to query for the HTTPS record (default: the upstream host).
-outer-sni Override the ECH outer SNI (public_name) sent in cleartext.
-hpke-public-key Override the HPKE public key (base64).

Base64 input accepts standard or URL-safe alphabets, with or without padding.

How the effective ECH config is assembled

  1. The source config list comes from exactly one of: -ech-from-dns (the ech SvcParam of the upstream's DNS HTTPS record, fetched over DoH), -ech-config-list, or — if neither is given — a single config synthesized with config_id = 0, the DHKEM(X25519, HKDF-SHA256) KEM, and the HKDF-SHA256 KDF with the AES-128-GCM and ChaCha20Poly1305 AEADs.
  2. -outer-sni and -hpke-public-key, when set, replace the corresponding field in every config (so you can, e.g., DNS-fetch the real key and config_id but front with a different outer SNI).
  3. The result must have a non-empty outer SNI and HPKE public key, so when no source list is supplied both -outer-sni and -hpke-public-key are required.

-ech-from-dns performs a DoH lookup of the upstream name at startup, which reveals that name to the configured resolver. Go's standard library does not auto-discover ECH from DNS, so this lookup is explicit and opt-in.

The from-scratch path uses config_id = 0. A server selects its decryption key by config_id, so to force ECH against a server that publishes a different config_id (most do), pass that server's real -ech-config-list and override only the field(s) you need.

Examples

Auto-fetch the ECHConfigList from the upstream's DNS HTTPS record:

anyech -upstream https://tls-ech.dev -ech-from-dns

Forward to a host using an ECHConfigList supplied directly (e.g. the ech value from a DNS HTTPS record):

anyech -upstream https://tls-ech.dev \
  -ech-config-list 'AEn+DQBF...AAA='

Use that config but override the outer SNI (e.g. to front a different public name):

anyech -upstream https://tls-ech.dev \
  -ech-config-list 'AEn+DQBF...AAA=' \
  -outer-sni public.tls-ech.dev

Synthesize a config from just the outer SNI and HPKE public key (for a config_id = 0 server):

anyech -upstream https://example.internal \
  -outer-sni public.example \
  -hpke-public-key 'AViB1Bo+...Jig='

About

reverse proxy with mandatory ECH

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages