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
@@ -0,0 +1,63 @@
|
||||
// SPDX-FileCopyrightText: 2026 Echolot contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package control
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHTTPEchoReflectsRequest(t *testing.T) {
|
||||
s := &Server{}
|
||||
req := httptest.NewRequest("POST", "/v1/echo", strings.NewReader("payload-bytes"))
|
||||
req.Header.Set("X-Injected", "canary")
|
||||
rr := httptest.NewRecorder()
|
||||
s.httpEcho(rr, req)
|
||||
|
||||
var resp struct {
|
||||
RequestHeadB64 string `json:"request_head_b64"`
|
||||
BodyB64 string `json:"body_b64"`
|
||||
BodyLen int `json:"body_len"`
|
||||
Scheme string `json:"scheme"`
|
||||
}
|
||||
if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
head, _ := base64.StdEncoding.DecodeString(resp.RequestHeadB64)
|
||||
if !strings.Contains(string(head), "X-Injected: canary") {
|
||||
t.Fatalf("echo did not reflect the injected header:\n%s", head)
|
||||
}
|
||||
body, _ := base64.StdEncoding.DecodeString(resp.BodyB64)
|
||||
if string(body) != "payload-bytes" || resp.BodyLen != 13 {
|
||||
t.Fatalf("body mismatch: %q len=%d", body, resp.BodyLen)
|
||||
}
|
||||
if resp.Scheme != "http" { // httptest requests carry no TLS
|
||||
t.Fatalf("scheme = %s, want http", resp.Scheme)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTLSReferenceReturnsChain(t *testing.T) {
|
||||
s := &Server{PinB64: "TESTPIN", CertChain: [][]byte{{0x30, 0x82, 0x01}, {0xAA, 0xBB}}}
|
||||
rr := httptest.NewRecorder()
|
||||
s.tlsReference(rr, httptest.NewRequest("GET", "/v1/tls-reference", nil))
|
||||
|
||||
var resp struct {
|
||||
PinSHA256 string `json:"pin_sha256"`
|
||||
ChainDER []string `json:"chain_der"`
|
||||
}
|
||||
if err := json.Unmarshal(rr.Body.Bytes(), &resp); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp.PinSHA256 != "TESTPIN" || len(resp.ChainDER) != 2 {
|
||||
t.Fatalf("bad tls-reference: %+v", resp)
|
||||
}
|
||||
first, _ := base64.StdEncoding.DecodeString(resp.ChainDER[0])
|
||||
if len(first) != 3 || first[0] != 0x30 {
|
||||
t.Fatalf("leaf DER not round-tripped: %x", first)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user