Files
felhom-agent/internal/escrow/identity_test.go
T
admin e4dfe5ccc7 slice 10D (agent): DR capstone — identity escrow + restore-mode consumption (v0.18.0)
Identity escrow wraps {tunnel_token,pbs_token} under the SAME R via age
(scrypt+ChaCha20-Poly1305), reusing the K-escrow pty; wrong R fails closed.
escrow.Create optionally emits the identity blob; escrow-create uploads it +
the non-secret directive; identity-consume recovers it (R by hand, never
logged). K-escrow + 10C Consume untouched. Closes slice 10 with hub v0.11.0;
operator-side rotation model (hub holds no Cloudflare write-power).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 09:48:36 +02:00

84 lines
2.4 KiB
Go

package escrow
import (
"bytes"
"context"
"os/exec"
"runtime"
"testing"
)
func ageAvailable() bool {
if runtime.GOOS != "linux" {
return false
}
if _, err := exec.LookPath("age"); err == nil {
return true
}
return false
}
func ensureAge(t *testing.T) {
t.Helper()
if !ageAvailable() {
t.Skip("skipping: the `age` CLI + linux required (runs on the demo/build host)")
}
if p, err := exec.LookPath("age"); err == nil {
ageBinary = p
}
}
func TestIdentity_InputValidation(t *testing.T) {
ctx := context.Background()
if _, err := WrapIdentity(ctx, nil, "R"); err == nil {
t.Error("empty bundle must error")
}
if _, err := WrapIdentity(ctx, []byte("x"), ""); err == nil {
t.Error("empty R must error")
}
if _, err := UnwrapIdentity(ctx, nil, "R"); err == nil {
t.Error("empty blob must error")
}
}
// Round-trip: a bundle wraps under R and recovers byte-identical (the identity analog of K-escrow).
func TestIdentity_RoundTrip(t *testing.T) {
ensureAge(t)
ctx := context.Background()
const R = "throwaway-correct-horse-battery-staple-words"
bundle := IdentityBundle{TunnelToken: "eyJhIjoidGVzdCIsInQiOiJ4In0", PBSToken: "felhom@pbs!n100:deadbeefcafe"}
blob, err := WrapIdentityBundle(ctx, bundle, R)
if err != nil {
t.Fatalf("WrapIdentityBundle: %v", err)
}
// the blob is opaque ciphertext, not the bundle.
if bytes.Contains(blob, []byte(bundle.TunnelToken)) || bytes.Contains(blob, []byte(bundle.PBSToken)) {
t.Fatal("the blob leaks plaintext token bytes — not encrypted")
}
got, err := UnwrapIdentityBundle(ctx, blob, R)
if err != nil {
t.Fatalf("UnwrapIdentityBundle: %v", err)
}
if got != bundle {
t.Errorf("recovered bundle = %+v, want %+v", got, bundle)
}
}
// Wrong R fails CLOSED — no bundle emitted.
func TestIdentity_WrongRFailsClosed(t *testing.T) {
ensureAge(t)
ctx := context.Background()
blob, err := WrapIdentity(ctx, []byte(`{"tunnel_token":"a","pbs_token":"b"}`), "the-correct-code")
if err != nil {
t.Fatalf("WrapIdentity: %v", err)
}
if _, err := UnwrapIdentity(ctx, blob, "DEFINITELY-the-wrong-code"); err == nil {
t.Fatal("a wrong recovery code must fail closed (no bundle)")
}
// the blob is unchanged / retryable: the RIGHT code still works after a wrong attempt.
if _, err := UnwrapIdentity(ctx, blob, "the-correct-code"); err != nil {
t.Errorf("the blob was not retryable after a wrong-R attempt: %v", err)
}
}