carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package release
2
3import (
4 "crypto/ed25519"
5 "encoding/json"
6 "os"
7 "strings"
8 "testing"
9 "time"
10
11 "github.com/carlosframework/platform/internal/rootstate"
12)
13
14func retirementFixture(t *testing.T) (*RootHistory, *healthyRetryStore, HistoryRecovery, HistoryRecovery, HistoryRecoveryRetirement, ed25519.PublicKey, ed25519.PrivateKey) {
15 t.Helper()
16 h, s, b := rootHistoryFixture(t)
17 now := time.Date(2026, 10, 13, 0, 0, 0, 0, time.UTC)
18 old := HistoryRecovery{Binding: b, Attempt: HistoryAttempt{ID: "failed", Source: "canonical", Version: "v2", SHA256: "failed-digest", Platform: AdoptPlatform(), Started: now.Add(-time.Hour), PromotedAt: now.Add(-2 * time.Hour)}, Settled: true, Failed: true}
19 good := old
20 good.Attempt.ID, good.Attempt.Version, good.Attempt.SHA256 = "verified", "v3", "good-digest"
21 good.Attempt.Started, good.Attempt.PromotedAt = now, now
22 good.Failed, good.Verified = false, true
23 for _, r := range []HistoryRecovery{old, good} {
24 if err := h.RetainRecovery(r); err != nil {
25 t.Fatal(err)
26 }
27 }
28 status, err := h.ReconstructionStatus()
29 if err != nil {
30 t.Fatal(err)
31 }
32 g := HistoryRecoveryRetirement{Schema: 1, Kind: "carlos-release-recovery-retirement", Identity: status.Identity, Operation: "retire-failed", IssuedAt: now, Binding: b, Source: "canonical", AttemptID: old.Attempt.ID, OutcomeDigest: historyRecoveryDigest(old), VerifiedID: good.Attempt.ID, VerifiedDigest: historyRecoveryDigest(good)}
33 pub, key, err := ed25519.GenerateKey(nil)
34 if err != nil {
35 t.Fatal(err)
36 }
37 return h, s, old, good, g, pub, key
38}
39
40func retireForTest(h *RootHistory, g HistoryRecoveryRetirement, pub ed25519.PublicKey, key ed25519.PrivateKey) error {
41 wire, err := SignRootEnvelope(RootSigner(key), g)
42 if err != nil {
43 return err
44 }
45 return h.RetireRecovery(pub, wire, g.Binding, g.IssuedAt, func(HistoryBinding) (string, error) { return "canonical", nil })
46}
47
48func TestPlan14HistoryExactRecoveryRetirement(t *testing.T) {
49 for _, ambiguous := range []bool{false, true} {
50 t.Run(map[bool]string{false: "failed", true: "ambiguous"}[ambiguous], func(t *testing.T) {
51 h, s, old, _, g, pub, key := retirementFixture(t)
52 if ambiguous {
53 old.Failed = false
54 raw, _ := json.Marshal(historyRecoveryRecord{Schema: 1, HistoryRecovery: old})
55 if err := s.Write(historyRecoveryKey(old), raw); err != nil {
56 t.Fatal(err)
57 }
58 g.OutcomeDigest = historyRecoveryDigest(old)
59 }
60 other := old
61 other.Attempt.ID = "unrelated"
62 other.Attempt.SHA256 = "unrelated-digest"
63 other.Failed = true
64 if err := h.RetainRecovery(other); err != nil {
65 t.Fatal(err)
66 }
67 if err := retireForTest(h, g, pub, key); err != nil {
68 t.Fatal(err)
69 }
70 h = &RootHistory{Store: s}
71 refs, err := h.Refusals(old.Binding)
72 if err != nil || len(refs) != 1 || refs[0].SHA256 != "unrelated-digest" {
73 t.Fatalf("retirement not exact: %+v %v", refs, err)
74 }
75 if err := h.RetainRecovery(old); err != nil {
76 t.Fatal(err)
77 }
78 refs, err = h.Refusals(old.Binding)
79 if err != nil || len(refs) != 1 {
80 t.Fatalf("source replay erased retirement: %+v %v", refs, err)
81 }
82 if _, err := s.Read(historyRecoveryKey(old)); err != nil {
83 t.Fatal("retirement deleted evidence", err)
84 }
85 if err := h.AddRefusal(old.Binding, Refusal{Host: old.Binding.Host, App: old.Binding.App, Version: old.Attempt.Version, SHA256: old.Attempt.SHA256, Kind: RefusalWrongProgram, RefusedAt: g.IssuedAt, PointerPromotedAt: old.Attempt.PromotedAt}); err != nil {
86 t.Fatal(err)
87 }
88 refs, err = h.Refusals(old.Binding)
89 if err != nil || len(refs) != 2 {
90 t.Fatalf("retirement cleared another category: %+v %v", refs, err)
91 }
92 })
93 }
94}
95
96func TestPlan14HistoryRecoveryRetirementAuthority(t *testing.T) {
97 for _, attack := range []string{"signature", "identity", "binding", "source", "outcome", "witness", "pending", "unverified", "failed-witness", "older-witness", "canonical-change", "expired", "reconstruction-pending"} {
98 t.Run(attack, func(t *testing.T) {
99 h, s, old, good, g, pub, key := retirementFixture(t)
100 resolve := func(HistoryBinding) (string, error) { return "canonical", nil }
101 now := g.IssuedAt
102 switch attack {
103 case "signature":
104 _, key, _ = ed25519.GenerateKey(nil)
105 case "identity":
106 g.Identity = strings.Repeat("0", 64)
107 case "binding":
108 g.Binding.Host = "other.example.com"
109 case "source":
110 g.Source = "forged"
111 case "outcome":
112 g.OutcomeDigest = strings.Repeat("0", 64)
113 case "witness":
114 g.VerifiedDigest = strings.Repeat("0", 64)
115 case "pending":
116 old.Settled = false
117 case "unverified":
118 good.Verified = false
119 case "failed-witness":
120 good.Failed = true
121 case "older-witness":
122 good.Attempt.Started = old.Attempt.Started
123 case "canonical-change":
124 calls := 0
125 resolve = func(HistoryBinding) (string, error) {
126 calls++
127 if calls > 1 {
128 return "changed", nil
129 }
130 return "canonical", nil
131 }
132 case "expired":
133 now = now.Add(HostRetryGrantTTL + time.Second)
134 case "reconstruction-pending":
135 state, err := h.reconstructionState()
136 if err != nil {
137 t.Fatal(err)
138 }
139 state.Pending = &reconstructionIntent{}
140 if err := h.writeReconstruction(state); err != nil {
141 t.Fatal(err)
142 }
143 }
144 if attack == "pending" || attack == "unverified" || attack == "failed-witness" || attack == "older-witness" {
145 for _, r := range []HistoryRecovery{old, good} {
146 raw, _ := json.Marshal(historyRecoveryRecord{Schema: 1, HistoryRecovery: r})
147 if err := s.Write(historyRecoveryKey(r), raw); err != nil {
148 t.Fatal(err)
149 }
150 }
151 g.OutcomeDigest, g.VerifiedDigest = historyRecoveryDigest(old), historyRecoveryDigest(good)
152 }
153 wire, err := SignRootEnvelope(RootSigner(key), g)
154 if err != nil {
155 t.Fatal(err)
156 }
157 if err := h.RetireRecovery(pub, wire, old.Binding, now, resolve); err == nil {
158 t.Fatal("unauthorized retirement accepted")
159 }
160 refs, err := h.Refusals(old.Binding)
161 if err == nil && len(refs) == 0 {
162 t.Fatal("refusal disappeared")
163 }
164 })
165 }
166}
167
168type retirementSyncStore struct {
169 rootstate.Store
170 fail bool
171}
172
173func (s *retirementSyncStore) Write(name string, raw []byte) error {
174 if err := s.Store.Write(name, raw); err != nil {
175 return err
176 }
177 if s.fail && strings.HasSuffix(name, ".retired") {
178 return os.ErrPermission
179 }
180 return nil
181}
182func TestPlan14HistoryRetirementVisibleSync(t *testing.T) {
183 h, s, old, good, g, pub, key := retirementFixture(t)
184 fault := &retirementSyncStore{Store: s, fail: true}
185 h.Store = fault
186 if err := retireForTest(h, g, pub, key); err == nil {
187 t.Fatal("unsynced retirement reported success")
188 }
189 if refs, err := h.Refusals(old.Binding); err == nil && len(refs) == 0 {
190 t.Fatal("unsynced retirement granted permission")
191 }
192 fault.fail = false
193 if refs, err := h.Refusals(old.Binding); err != nil || len(refs) != 0 {
194 t.Fatalf("durable retry: %+v %v", refs, err)
195 }
196 good.Failed = true
197 if err := h.RetainRecovery(good); err != nil {
198 t.Fatal(err)
199 }
200 if refs, err := h.Refusals(old.Binding); err == nil && len(refs) < 2 {
201 t.Fatalf("changed witness kept retirement: %+v", refs)
202 }
203 if err := retireForTest(h, g, pub, key); err == nil {
204 t.Fatal("old signature retired changed evidence")
205 }
206}
207
208func TestPlan14HistoryRetirementDelivery(t *testing.T) {
209 h, _, old, _, g, pub, key := retirementFixture(t)
210 store := NewDirStore(t.TempDir())
211 if err := PublishHistoryRecoveryRetirement(t.Context(), store, RootSigner(key), g); err != nil {
212 t.Fatal(err)
213 }
214 resolve := func(HistoryBinding) (string, error) { return "canonical", nil }
215 if err := h.ReceiveRecoveryRetirements(t.Context(), store, pub, old.Binding, g.IssuedAt, resolve); err != nil {
216 t.Fatal(err)
217 }
218 view, err := h.MigrationStatus(old.Binding, resolve)
219 if err != nil || len(view.Hosts) != 1 || len(view.Hosts[0].RetiredRecoveries) != 1 || view.Hosts[0].RecoveryDigests[old.Attempt.ID] != g.OutcomeDigest {
220 t.Fatalf("retirement status: %+v %v", view, err)
221 }
222 if err := h.ReceiveRecoveryRetirements(t.Context(), store, pub, old.Binding, g.IssuedAt.Add(2*HostRetryGrantTTL), resolve); err != nil {
223 t.Fatal("committed retirement expired", err)
224 }
225}
226