Files
felhom-agent/internal/localapi/cert_test.go
T
admin 9b0d6c2c82 agent: EnsureLeaf signals + loud-WARNs a regenerated leaf (prevention B.1) v0.46.0
EnsureLeaf returns generated bool; call-site logs INFO 'leaf LOADED' vs WARN 'leaf REGENERATED —
previously issued bootstrap pins now INVALID'. Catches the 2026-06-28 silent-regen incident class.
Test: first=generated, second=loaded+same fp. No new sudo surface.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pg8ANF97SEeKYSN5Jxw3qJ
2026-06-29 21:45:25 +02:00

67 lines
2.2 KiB
Go

package localapi
import (
"crypto/sha256"
"encoding/hex"
"path/filepath"
"testing"
)
// The leaf is generated once and its fingerprint stays STABLE across "restarts" (re-loads from
// disk) — a fresh cert each boot would invalidate every already-baked bootstrap pin.
func TestEnsureLeaf_StableFingerprintAcrossReload(t *testing.T) {
dir := t.TempDir()
certPath := filepath.Join(dir, "leaf.crt")
keyPath := filepath.Join(dir, "leaf.key")
cert1, fp1, gen1, err := EnsureLeaf(certPath, keyPath, "192.168.0.162")
if err != nil {
t.Fatalf("first ensure: %v", err)
}
// B.1/B.3: first call GENERATES; second call LOADS (generated=false) with the SAME fingerprint —
// persistence keeps the pin stable. A regression that regenerated would flip gen2 true AND change
// the fp, failing both asserts (the prevention this change exists for).
if !gen1 {
t.Fatal("first EnsureLeaf must report generated=true")
}
cert2, fp2, gen2, err := EnsureLeaf(certPath, keyPath, "192.168.0.162")
if err != nil {
t.Fatalf("second ensure: %v", err)
}
if gen2 {
t.Fatal("second EnsureLeaf must report generated=false (LOADED, not regenerated)")
}
if fp1 != fp2 {
t.Fatalf("fingerprint changed across reload: %s != %s", fp1, fp2)
}
// The reported fingerprint must equal the SHA-256 of the served leaf DER (the pin the
// controller checks against).
got := sha256.Sum256(cert2.Certificate[0])
if hex.EncodeToString(got[:]) != fp2 {
t.Fatal("reported fingerprint does not match the served leaf DER")
}
if len(fp1) != 64 {
t.Fatalf("fingerprint is not a 64-hex SHA-256: %q", fp1)
}
_ = cert1
}
// The generated leaf is a usable TLS server cert whose presented leaf matches the pin.
func TestEnsureLeaf_ServesPinnableLeaf(t *testing.T) {
dir := t.TempDir()
cert, fp, _, err := EnsureLeaf(filepath.Join(dir, "c"), filepath.Join(dir, "k"), "10.0.0.1")
if err != nil {
t.Fatalf("ensure: %v", err)
}
if len(cert.Certificate) == 0 {
t.Fatal("no leaf in cert chain")
}
if cert.PrivateKey == nil {
t.Fatal("generated cert has no private key")
}
sum := sha256.Sum256(cert.Certificate[0])
if hex.EncodeToString(sum[:]) != fp {
t.Fatal("pin mismatch against served leaf")
}
}