server: tls-echo — ClientHello capture + JA4 on the TCP-echo port (§4 complete)
server-release / image (push) Successful in 15s
server-test / test (push) Successful in 28s
server-release / release (push) Successful in 28s

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:
mrambossek
2026-07-31 20:59:44 +02:00
co-authored by Claude Opus 5
parent 8a854141c5
commit 1472a86508
5 changed files with 570 additions and 16 deletions
+53
View File
@@ -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)
}
}