Add TLS + certificate pinning for the peer link
The co-op link was authenticated (HMAC token, never sent over the wire) but not encrypted. The hub now generates a self-signed cert on first run; the peer pins its exact fingerprint (no CA involved — there isn't one for a Tailscale/LAN address), delivered via a single invite-code paste that also carries the token, replacing today's separate IP+token copy. The peer link moves to its own TLS-only port (peer_listen, 47823) so the plain overlay/panel port (47822, OBS-facing) never needs to be exposed alongside it — today, opening the overlay port to a remote partner also exposes /deaths and the panel to anyone. Mandatory pinning, no insecure fallback: a half-configured peer (some but not all of hub/token/fingerprint, or a broken invite) fails loudly at startup rather than connecting unpinned. An unconfigured peer still runs fine as a local-only overlay, same as before. New: tlscert.go (cert generation/persistence), pin.go (fingerprint pinning), invite.go (invite-code encode/decode, host auto-detection), each with tests. main.go/config.go/duo.go/ws.go carry the wiring for this — the dual listener, new config keys, and the TLS-aware WebSocket dial — and were rewritten in English in the process, per the project's new English-only code convention (see CLAUDE.md).
This commit is contained in:
1 parent
e1f7e6f529
commit
e9fe10f0f7
12 files changed
+1198
-465
No files matched your search
@@ -0,0 +1,84 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLoadOrCreateHubCert_GeneratesAndPersists(t *testing.T) {
|
||||
testExeDir = t.TempDir()
|
||||
defer func() { testExeDir = "" }()
|
||||
|
||||
cert, fp, generated, err := loadOrCreateHubCert()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if !generated {
|
||||
t.Fatal("expected a fresh cert to be reported as generated")
|
||||
}
|
||||
if len(fp) != 64 {
|
||||
t.Fatalf("fingerprint should be a 64-char hex SHA-256, got %d chars: %q", len(fp), fp)
|
||||
}
|
||||
if cert.Leaf == nil {
|
||||
t.Fatal("expected cert.Leaf to be populated")
|
||||
}
|
||||
if _, err := os.Stat(hubCertPath()); err != nil {
|
||||
t.Fatalf("cert file wasn't persisted: %v", err)
|
||||
}
|
||||
if _, err := os.Stat(hubKeyPath()); err != nil {
|
||||
t.Fatalf("key file wasn't persisted: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadOrCreateHubCert_ReusesSameFingerprint(t *testing.T) {
|
||||
testExeDir = t.TempDir()
|
||||
defer func() { testExeDir = "" }()
|
||||
|
||||
_, fp1, gen1, err := loadOrCreateHubCert()
|
||||
if err != nil || !gen1 {
|
||||
t.Fatalf("first call should generate: gen=%v err=%v", gen1, err)
|
||||
}
|
||||
_, fp2, gen2, err := loadOrCreateHubCert()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error on reload: %v", err)
|
||||
}
|
||||
if gen2 {
|
||||
t.Fatal("second call should reuse the persisted cert, not generate a new one")
|
||||
}
|
||||
if fp1 != fp2 {
|
||||
t.Fatalf("fingerprint changed across reload: %q vs %q", fp1, fp2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadOrCreateHubCert_DifferentInstallationsDiffer(t *testing.T) {
|
||||
testExeDir = t.TempDir()
|
||||
_, fpA, _, err := loadOrCreateHubCert()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
testExeDir = t.TempDir()
|
||||
_, fpB, _, err := loadOrCreateHubCert()
|
||||
testExeDir = ""
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if fpA == fpB {
|
||||
t.Fatal("two installations generated the same certificate fingerprint")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadOrCreateHubCert_PartialPairFails(t *testing.T) {
|
||||
testExeDir = t.TempDir()
|
||||
defer func() { testExeDir = "" }()
|
||||
|
||||
if err := writePEM(hubCertPath(), "CERTIFICATE", []byte("not a real cert")); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// key file intentionally left missing
|
||||
|
||||
if _, _, _, err := loadOrCreateHubCert(); err == nil {
|
||||
t.Fatal("expected an error for a partial/broken cert+key pair")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user