package offsitekeys import ( "context" "crypto/ed25519" "crypto/rand" "errors" "strings" "testing" "golang.org/x/crypto/ssh" ) // fakeFS emulates the provider's restricted shell as MEASURED 2026-10-03: `dd of=` takes stdin, `mv` // overwrites, `cat`/`ls` of a missing path exit non-zero, `test` does not exist. type fakeFS struct { files map[string]string dirs map[string]bool cmds []string } func newFS() *fakeFS { return &fakeFS{files: map[string]string{}, dirs: map[string]bool{".ssh": true}} } func (f *fakeFS) Run(_ context.Context, cmd string, stdin []byte) ([]byte, error) { f.cmds = append(f.cmds, cmd) a := strings.Fields(cmd) miss := errors.New("exit status 1") switch { case a[0] == "ls" && a[1] == "-d": if f.dirs[a[2]] { return []byte(a[2] + "\n"), nil } return nil, miss case a[0] == "ls": if _, ok := f.files[a[1]]; ok { return []byte(a[1]), nil } return nil, miss case a[0] == "cat": if v, ok := f.files[a[1]]; ok { return []byte(v), nil } return nil, miss case a[0] == "mkdir": f.dirs[a[1]] = true return nil, nil case a[0] == "chmod": return nil, nil case strings.HasPrefix(a[0], "dd") && strings.HasPrefix(a[1], "of="): f.files[strings.TrimPrefix(a[1], "of=")] = string(stdin) return nil, nil case a[0] == "mv": if v, ok := f.files[a[1]]; ok { f.files[a[2]] = v delete(f.files, a[1]) return nil, nil } if f.dirs[a[1]] { f.dirs[a[2]] = true delete(f.dirs, a[1]) return nil, nil } return nil, miss case a[0] == "rm" && a[1] == "-rf" && strings.Contains(a[2], ".orphaned-"): delete(f.dirs, a[2]) // decision 74: the ONLY delete, of a set-aside copy return nil, nil case a[0] == "rm": return nil, errors.New("the registrar must never delete anything else") } return nil, errors.New("Command not found") } func (f *fakeFS) Close() error { return nil } type fakeDialer struct{ fs *fakeFS } func (d fakeDialer) Dial(context.Context, Target, string) (Shell, error) { return d.fs, nil } var tgt = Target{Host: "u1-sub4.example", User: "u1-sub4", Port: 23, RepoPath: "/home/felhom-repo", Fingerprint: "SHA256:host"} func newKey(t *testing.T) (pub, fp string) { t.Helper() k, _, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } pk, _ := ssh.NewPublicKey(k) return strings.TrimSpace(string(ssh.MarshalAuthorizedKey(pk))) + " box", ssh.FingerprintSHA256(pk) } // The MIGRATION shape: a box whose key predates the pin (an unpinned line, exactly as ssh-copy-id left // it) registers the SAME key → it comes back pinned and the unpinned line is gone. Then the audit is clean. func TestInstall_MigratesUnpinnedKeyAndAuditGoesClean(t *testing.T) { fs := newFS() pub, fp := newKey(t) other, _ := newKey(t) fs.files[".ssh/authorized_keys"] = pub + "\n" + other + "\n" r := &Registrar{Dialer: fakeDialer{fs}} before, err := r.Audit(context.Background(), tgt, "pw", false) if err != nil || len(before.Findings) != 2 { t.Fatalf("before: %+v %v — want 2 unpinned findings (the decoy must be seen)", before, err) } res, err := r.Install(context.Background(), tgt, "pw", pub) if err != nil || res.Fingerprint != fp || res.RemovedUnpinned != 2 { t.Fatalf("install = %+v, %v", res, err) } got := fs.files[".ssh/authorized_keys"] if !strings.HasPrefix(got, PinnedPrefix(tgt.RepoPath)) || strings.Count(got, "\n") != 1 { t.Fatalf("file after install:\n%s", got) } after, _ := r.Audit(context.Background(), tgt, "pw", false) if len(after.Findings) != 0 || after.Pinned != 1 { t.Fatalf("after: %+v", after) } for _, c := range fs.cmds { if strings.HasPrefix(c, "rm") { t.Fatalf("registrar issued a delete: %q", c) } } } // Rotation: new key installed beside the old pinned one; Confirm leaves only the new one. func TestInstallThenConfirm_Rotation(t *testing.T) { fs := newFS() r := &Registrar{Dialer: fakeDialer{fs}} oldPub, oldFP := newKey(t) newPub, newFP := newKey(t) if _, err := r.Install(context.Background(), tgt, "pw", oldPub); err != nil { t.Fatal(err) } if _, err := r.Install(context.Background(), tgt, "pw", newPub); err != nil { t.Fatal(err) } lines := ParseLines(fs.files[".ssh/authorized_keys"], tgt.RepoPath) if len(lines) != 2 || !lines[0].Pinned || !lines[1].Pinned { t.Fatalf("both keys must be pinned until confirm: %+v", lines) } if _, err := r.Confirm(context.Background(), tgt, "pw", "SHA256:not-installed"); err == nil { t.Fatal("confirming an absent key must refuse") } n, err := r.Confirm(context.Background(), tgt, "pw", newFP) if err != nil || n != 1 { t.Fatalf("confirm = %d, %v", n, err) } lines = ParseLines(fs.files[".ssh/authorized_keys"], tgt.RepoPath) if len(lines) != 1 || lines[0].Fingerprint != newFP || lines[0].Fingerprint == oldFP { t.Fatalf("after confirm: %+v", lines) } } // The window (decision 68): the deleting line goes FIRST (first match wins — measured), the audit // tolerates it only while a window is open, and closing removes it. func TestWindow_PrependAuditClose(t *testing.T) { fs := newFS() r := &Registrar{Dialer: fakeDialer{fs}} pub, fp := newKey(t) if err := r.OpenWindow(context.Background(), tgt, "pw", fp); err == nil { t.Fatal("a window for a key that is not installed must refuse") } if _, err := r.Install(context.Background(), tgt, "pw", pub); err != nil { t.Fatal(err) } if err := r.OpenWindow(context.Background(), tgt, "pw", fp); err != nil { t.Fatal(err) } lines := ParseLines(fs.files[".ssh/authorized_keys"], tgt.RepoPath) if len(lines) != 2 || !lines[0].Window || !lines[1].Pinned || lines[0].Fingerprint != fp { t.Fatalf("window line must be first: %+v", lines) } if a, _ := r.Audit(context.Background(), tgt, "pw", true); len(a.Findings) != 0 { t.Fatalf("open window flagged: %+v", a) } if a, _ := r.Audit(context.Background(), tgt, "pw", false); len(a.Findings) != 1 || a.Findings[0].Kind != "window" { t.Fatalf("a window line with no open window must alarm: %+v", a) } if err := r.CloseWindow(context.Background(), tgt, "pw"); err != nil { t.Fatal(err) } if a, _ := r.Audit(context.Background(), tgt, "pw", false); len(a.Findings) != 0 || a.Pinned != 1 { t.Fatalf("after close: %+v", a) } } // Move-aside renames, never deletes, and never reuses a name. func TestMoveAside_RenamesNeverDeletes(t *testing.T) { fs := newFS() fs.dirs["/home/felhom-repo"] = true fs.dirs["/home/felhom-repo.orphaned-20261003"] = true r := &Registrar{Dialer: fakeDialer{fs}} to, err := r.MoveAside(context.Background(), tgt, "pw", "20261003") if err != nil || to != "/home/felhom-repo.orphaned-20261003-2" { t.Fatalf("move-aside = %q, %v", to, err) } if fs.dirs["/home/felhom-repo"] || !fs.dirs["/home/felhom-repo.orphaned-20261003"] { t.Fatalf("dirs after: %v", fs.dirs) } } func TestTargetWithoutFingerprint_Refused(t *testing.T) { r := &Registrar{Dialer: fakeDialer{newFS()}} pub, _ := newKey(t) nt := tgt nt.Fingerprint = "" if _, err := r.Install(context.Background(), nt, "pw", pub); err == nil { t.Fatal("no host fingerprint must refuse (no blind TOFU)") } } func TestKeyFingerprint_RefusesOptionsAndJunk(t *testing.T) { pub, _ := newKey(t) for _, bad := range []string{"", "not a key", `command="sh" ` + pub, pub + "\n" + pub} { if _, _, err := KeyFingerprint(bad); err == nil { t.Fatalf("accepted %q", bad) } } if _, _, err := KeyFingerprint(pub); err != nil { t.Fatal(err) } }