server: tls-echo — ClientHello capture + JA4 on the TCP-echo port (§4 complete)
A connection opening with a TLS handshake (first byte 0x16) and ALPN elt-echo gets the ClientHello it sent back raw (b64) and as a JA4 fingerprint (sec.clienthello_echo), then a TLS byte-echo; plain connections are unchanged. One port, multiplexed by a timed peek: plain echo is server-speaks-first, so a silent client (peek timeout) is greeted, while a TLS client's immediate ClientHello (0x16) routes to the TLS path — 500ms tolerates ~1s RTT before misdetection. JA4 (FoxIO): full ClientHello parser (ciphers, extensions, ALPN, supported_versions, sig algs) with GREASE exclusion; a_b_c fingerprint, unit-tested for structure + GREASE invariance. Live-verified: elt-echo negotiated, JA4 t13d1712eo computed, 1530-byte ClientHello returned. Capability tls-echo. This completes spec §4. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
8a854141c5
commit
1472a86508
@@ -0,0 +1,53 @@
|
||||
// SPDX-FileCopyrightText: 2026 Echolot contributors
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package tcpecho
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Plain echo must be server-speaks-first: a client that sends nothing still
|
||||
// gets the greeting (via the peek timeout), then its bytes are echoed.
|
||||
func TestPlainEchoServerSpeaksFirst(t *testing.T) {
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer ln.Close()
|
||||
go (&Server{}).Serve(ln)
|
||||
|
||||
c, err := net.Dial("tcp", ln.Addr().String())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer c.Close()
|
||||
c.SetDeadline(time.Now().Add(3 * time.Second))
|
||||
|
||||
line, err := bufio.NewReader(c).ReadBytes('\n')
|
||||
if err != nil {
|
||||
t.Fatalf("no greeting: %v", err)
|
||||
}
|
||||
var g struct {
|
||||
TLS bool `json:"tls"`
|
||||
Src string `json:"observed_src"`
|
||||
}
|
||||
if err := json.Unmarshal(line, &g); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if g.TLS {
|
||||
t.Fatal("plain connection reported tls=true")
|
||||
}
|
||||
if g.Src == "" {
|
||||
t.Fatal("greeting missing observed_src")
|
||||
}
|
||||
c.Write([]byte("xyz"))
|
||||
buf := make([]byte, 3)
|
||||
if _, err := c.Read(buf); err != nil || string(buf) != "xyz" {
|
||||
t.Fatalf("echo failed: %q %v", buf, err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user