Files
echolot/server/internal/control/httpecho_test.go
T
mrambossekandClaude Opus 5 38fb73c34e
server-release / image (push) Successful in 15s
server-test / test (push) Successful in 27s
server-release / release (push) Successful in 28s
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>
2026-07-31 20:34:03 +02:00

64 lines
1.9 KiB
Go

// 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)
}
}