server: HTTP echo + TLS reference (control-plane security measurements)
- POST /v1/echo: returns the received request head + body (base64) and the observed TLS parameters (version, cipher, SNI, ALPN, resumed). The client diffs against what it sent to detect header injection/stripping, transparent proxying, or TLS interception (sec.http_echo). http-echo added to the capability set. - GET /v1/tls-reference: the served leaf-first DER chain + pin, so the app can compare an out-of-band copy against its own handshake (sec.tls_reference). Always available, no auth — public handshake info. - Optional CLEARTEXT http-echo listener (ECHOLOT_HTTP_ECHO_LISTEN, default off) exposing only /v1/echo for the plaintext-path tampering test. Live-smoke-tested (HTTPS echo reflected an injected header + observed TLS1.3; cleartext variant reports tls:none); httptest unit tests added. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
379153219e
commit
38fb73c34e
@@ -102,7 +102,7 @@ func serve(cfg *config.Config) error {
|
||||
dp := &dataplane.Server{Sessions: sessions}
|
||||
tcpSrv := &tcpecho.Server{}
|
||||
|
||||
caps := []string{"udp-probe", "delayed-echo", "connect-back"}
|
||||
caps := []string{"udp-probe", "delayed-echo", "connect-back", "http-echo"}
|
||||
if len(config.Addrs(cfg.TCPListen)) > 0 {
|
||||
caps = append(caps, "tcp-echo")
|
||||
}
|
||||
@@ -110,7 +110,7 @@ func serve(cfg *config.Config) error {
|
||||
ctl := &control.Server{
|
||||
Store: st, Sessions: sessions, Name: cfg.Name,
|
||||
UDPPort: mustPort(firstAddr(cfg.UDPListen)), TCPPort: mustPort(firstAddr(cfg.TCPListen)),
|
||||
StunPort: mustPort(firstAddr(cfg.StunListen)), PinB64: pin,
|
||||
StunPort: mustPort(firstAddr(cfg.StunListen)), PinB64: pin, CertChain: cert.Certificate,
|
||||
DelayedEcho: dp.SendDelayedEcho,
|
||||
TCPRecent: func(ip string) any { return tcpSrv.RecentFor(ip) },
|
||||
}
|
||||
@@ -189,6 +189,15 @@ func serve(cfg *config.Config) error {
|
||||
}(addr, ln)
|
||||
}
|
||||
|
||||
// Optional cleartext HTTP-echo (spec §4 plaintext-path test) — only
|
||||
// POST /v1/echo, no auth, no secrets. Off unless configured.
|
||||
var httpEchoSrvs []*http.Server
|
||||
for _, addr := range config.Addrs(cfg.HTTPEchoListen) {
|
||||
hs := &http.Server{Addr: addr, Handler: ctl.EchoHandler(), ReadHeaderTimeout: 10 * time.Second}
|
||||
httpEchoSrvs = append(httpEchoSrvs, hs)
|
||||
go func(a string, srv *http.Server) { errCh <- fmt.Errorf("http-echo %s: %w", a, srv.ListenAndServe()) }(addr, hs)
|
||||
}
|
||||
|
||||
// STUN (spec §4) — advertises stun-5780 only with ≥2 same-family addrs.
|
||||
var stunSrv *stun.Server
|
||||
if stunAddrs := config.Addrs(cfg.StunListen); len(stunAddrs) > 0 {
|
||||
@@ -263,6 +272,9 @@ func serve(cfg *config.Config) error {
|
||||
for _, l := range dnsTCP {
|
||||
_ = l.Close()
|
||||
}
|
||||
for _, hs := range httpEchoSrvs {
|
||||
_ = hs.Shutdown(shutCtx)
|
||||
}
|
||||
return nil
|
||||
case err := <-errCh:
|
||||
return err
|
||||
|
||||
Reference in New Issue
Block a user