Scanners probe for fake-TLS proxies by sending structurally malformed
ClientHellos (e.g. ExtensionsLen=0 with trailing extension bytes). A
real TLS server responds with a fatal decode_error alert; previously
the proxy crashed the handler process silently, making it detectable.
Changes:
- mtp_fake_tls: add TLS_REC_ALERT, TLS_ALERT_FATAL, TLS_ALERT_DECODE_ERROR
macros; export tls_decode_error_alert/0 which builds the 7-byte alert
frame from macros
- mtp_fake_tls: add second clause to parse_client_hello/1 that throws
{protocol_error, tls_bad_client_hello, bad_client_hello} instead of
letting a bare function_clause propagate
- mtp_fake_tls: tighten parse_sni/1 catch to match the specific tagged
error rather than a catch-all error:_
- mtp_handler: add attempt_fronting clauses for tls_bad_client_hello and
tls_no_sni — both send the decode_error alert before closing
- mtp_handler: effective_secret/2 now raises tls_bad_client_hello (not
tls_invalid_digest) when per_sni_secrets=on and the ClientHello has
no SNI, so it also gets the alert treatment
- single_dc_SUITE: new malformed_tls_hello_decode_error_case/1 verifies
the alert bytes are sent and the metric is incremented
- AGENTS.md: document test organisation, process architecture diagram,
and upstream/downstream naming note
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Each fake-TLS SNI domain gets a unique 16-byte secret derived from the
SNI, base secret and a private salt, so users cannot extract the base secret
from their proxy link or forge tokens by guessing other domains.
Derivation: SHA256(salt || hex(base_secret) || sni_domain)[0:16]
Salt-first ordering ensures security even when base_secret is known
(e.g. when non-fake-TLS protocols are enabled on the same port).
New config options (default: off):
{per_sni_secrets, off | on}
{per_sni_secret_salt, <<"..ascii..">>}
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When a fake-TLS handshake fails (wrong secret, DPI probe, replay attack),
forward the raw TCP connection transparently to the SNI host instead of
closing — making the proxy indistinguishable from a normal HTTPS server.
Replay detection is moved to ClientHello level (before ServerHello) to
allow clean forwarding. Controlled by {domain_fronting, off|sni|"host:port"}.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>