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/base64"
6 "encoding/json"
7 "strings"
8 "testing"
9 "time"
10)
11
12// mintGrant mints a scoped key + grant with the root key, failing the test on
13// error — the happy path most cases start from.
14func mintGrant(t *testing.T, root ed25519.PrivateKey, name string, rungs []string, ttl time.Duration) (ed25519.PrivateKey, string) {
15 t.Helper()
16 seed, blob, err := MintKeyGrant(root, name, rungs, ttl)
17 if err != nil {
18 t.Fatalf("MintKeyGrant(%v): %v", rungs, err)
19 }
20 key, err := ParseSigningKey(seed, "test")
21 if err != nil {
22 t.Fatalf("ParseSigningKey: %v", err)
23 }
24 return key, blob
25}
26
27// forgeGrant signs an arbitrary grant payload with signer — the hand-crafted
28// grant an attacker (or a root-key holder trying to delegate stable) would
29// produce. This is how the verify-side guards are exercised without going
30// through MintKeyGrant, whose own refusals would otherwise hide them.
31func forgeGrant(t *testing.T, signer ed25519.PrivateKey, g Grant) string {
32 t.Helper()
33 payload, err := json.MarshalIndent(g, "", " ")
34 if err != nil {
35 t.Fatal(err)
36 }
37 b64 := base64.StdEncoding.EncodeToString(payload)
38 blob, err := json.MarshalIndent(signedGrant{
39 Payload: b64,
40 Sig: base64.StdEncoding.EncodeToString(ed25519.Sign(signer, []byte(b64))),
41 }, "", " ")
42 if err != nil {
43 t.Fatal(err)
44 }
45 return base64.StdEncoding.EncodeToString(blob)
46}
47
48// goodGrant is a grant that passes every check, as the base for one-field
49// mutations.
50func goodGrant(pub ed25519.PublicKey) Grant {
51 now := time.Date(2026, 8, 3, 12, 0, 0, 0, time.UTC)
52 return Grant{
53 Schema: 1,
54 Pub: base64.StdEncoding.EncodeToString(pub),
55 Rungs: []string{RungCanary, RungEdge},
56 Name: "edge-builder",
57 MintedAt: now,
58 ExpiresAt: now.Add(90 * 24 * time.Hour),
59 }
60}
61
62// The property the whole feature exists for, direction one: not even the
63// holder of the root key can mint a grant that reaches stable.
64//
65// Mutation-mindedness: delete the RungStable check in MintKeyGrant and this
66// fails on the first two cases — including the one where stable is smuggled
67// in beside a legitimate rung, which a check that only looked at rungs[0]
68// would miss.
69func TestMintRefusesStable(t *testing.T) {
70 root, _ := testKeys(t)
71 for _, rungs := range [][]string{
72 {RungStable},
73 {RungEdge, RungStable},
74 {RungCanary, RungStable, RungBeta},
75 } {
76 _, _, err := MintKeyGrant(root, "edge-builder", rungs, 30*24*time.Hour)
77 if err == nil {
78 t.Fatalf("MintKeyGrant(%v) succeeded; stable must never be delegable", rungs)
79 }
80 if !strings.Contains(err.Error(), "stable") {
81 t.Errorf("MintKeyGrant(%v) refusal %q does not name stable", rungs, err)
82 }
83 }
84}
85
86// Direction two, enforced separately: a grant that names stable and carries a
87// VALID root signature is still refused at verification. Mint and verify are
88// two different programs in production — the minting binary may be older than
89// the verifying one, or may not have been ours at all.
90//
91// Mutation-mindedness: delete the RungStable check in validateGrant and this
92// goes green while TestMintRefusesStable stays green, which is exactly the
93// hole the spec asks to be closed in both directions.
94func TestVerifyRefusesAHandCraftedStableGrant(t *testing.T) {
95 root, rootPub := testKeys(t)
96 _, scopedPub := testKeys(t)
97 g := goodGrant(scopedPub)
98 g.Rungs = []string{RungEdge, RungStable}
99 blob := forgeGrant(t, root, g)
100
101 sg, err := parseGrantBlob(blob, "test")
102 if err != nil {
103 t.Fatalf("parseGrantBlob: %v", err)
104 }
105 got, err := verifyGrant(rootPub, sg)
106 if err == nil {
107 t.Fatalf("verifyGrant honoured a stable grant: %+v", got)
108 }
109 if !strings.Contains(err.Error(), "stable") {
110 t.Errorf("refusal %q does not name stable", err)
111 }
112}
113
114// The second stable guard, in the scope decision itself: even if a grant
115// naming stable were somehow honoured, the channel check refuses stable.
116//
117// Mutation-mindedness: this is the belt to validateGrant's braces. Remove
118// either one alone and one of these two tests fails; that is the point of
119// having both.
120func TestGrantAllowsRefusesStableChannel(t *testing.T) {
121 _, scopedPub := testKeys(t)
122 g := goodGrant(scopedPub)
123 g.Rungs = []string{RungEdge, RungStable} // as if validateGrant had let it by
124 err := g.allows("stable", g.MintedAt)
125 if err == nil {
126 t.Fatal("grant.allows permitted stable")
127 }
128 if !strings.Contains(err.Error(), "stable") {
129 t.Errorf("refusal %q does not name stable", err)
130 }
131}
132
133// canary in a grant covers every canary/<branch> channel — the fleet runs on
134// canary/* names, so a grant that only matched the literal string "canary"
135// would be useless. beta is reachable when granted; a rung outside the grant
136// is not; a channel that is not a rung at all is not.
137//
138// Mutation-mindedness: replace Rung(channel) with a plain string compare and
139// the canary/* cases fail; drop the covers() check and the out-of-scope cases
140// pass when they must not.
141func TestGrantScopeMatchesTheLadderVocabulary(t *testing.T) {
142 _, scopedPub := testKeys(t)
143 g := goodGrant(scopedPub) // canary, edge
144 at := g.MintedAt
145 for _, ch := range []string{"canary", "canary/platform-adoption", "canary/keymail", "canary/console", "canary/rehearsal", "edge"} {
146 if err := g.allows(ch, at); err != nil {
147 t.Errorf("grant covering %v refused %q: %v", g.Rungs, ch, err)
148 }
149 }
150 for _, ch := range []string{"beta", "stable", "wip"} {
151 if err := g.allows(ch, at); err == nil {
152 t.Errorf("grant covering %v allowed %q", g.Rungs, ch)
153 }
154 }
155 // A grant that names beta reaches beta and nothing above it.
156 b := goodGrant(scopedPub)
157 b.Rungs = []string{RungBeta}
158 if err := b.allows("beta", at); err != nil {
159 t.Errorf("beta grant refused beta: %v", err)
160 }
161 if err := b.allows("edge", at); err == nil {
162 t.Error("beta-only grant allowed edge")
163 }
164}
165
166// Expiry is compared against the moment passed in — the pointer's own
167// promoted_at at verification, wall clock at promotion — never against
168// time.Now() inside the check.
169//
170// Mutation-mindedness: swap `at` for time.Now() in allows() and the
171// "promoted while valid" case fails, because its `at` is a 2026 date and the
172// grant it uses expired 90 days after that.
173func TestGrantExpiryIsComparedAgainstTheGivenMoment(t *testing.T) {
174 _, scopedPub := testKeys(t)
175 g := goodGrant(scopedPub)
176 if err := g.allows("edge", g.ExpiresAt.Add(-time.Second)); err != nil {
177 t.Errorf("grant refused a moment inside its window: %v", err)
178 }
179 if err := g.allows("edge", g.ExpiresAt); err != nil {
180 t.Errorf("grant refused its exact expiry instant: %v", err)
181 }
182 err := g.allows("edge", g.ExpiresAt.Add(time.Second))
183 if err == nil {
184 t.Fatal("grant allowed a moment past its expiry")
185 }
186 if !strings.Contains(err.Error(), "expired") {
187 t.Errorf("refusal %q does not say expired", err)
188 }
189}
190
191// A grant's lifetime is capped at a year, at mint and at verification. A
192// root-key holder hand-crafting a ten-year edge key is not an attack the
193// scheme can prevent, but it is one it declines to honour: the bound on
194// delegation lives in the same place as the bound on what may be delegated.
195//
196// Mutation-mindedness: raise or delete maxGrantTTL in either place and the
197// corresponding half of this test goes green when it must not.
198func TestGrantLifetimeIsCappedAtOneYear(t *testing.T) {
199 root, rootPub := testKeys(t)
200 if _, _, err := MintKeyGrant(root, "edge-builder", []string{RungEdge}, maxGrantTTL+time.Hour); err == nil {
201 t.Error("MintKeyGrant accepted a lifetime over the cap")
202 }
203 if _, _, err := MintKeyGrant(root, "edge-builder", []string{RungEdge}, maxGrantTTL); err != nil {
204 t.Errorf("MintKeyGrant refused exactly the cap: %v", err)
205 }
206 if _, _, err := MintKeyGrant(root, "edge-builder", []string{RungEdge}, 0); err == nil {
207 t.Error("MintKeyGrant accepted a zero lifetime")
208 }
209 if _, _, err := MintKeyGrant(root, "edge-builder", []string{RungEdge}, -time.Hour); err == nil {
210 t.Error("MintKeyGrant accepted a negative lifetime")
211 }
212
213 _, scopedPub := testKeys(t)
214 g := goodGrant(scopedPub)
215 g.ExpiresAt = g.MintedAt.Add(maxGrantTTL + time.Hour)
216 sg, err := parseGrantBlob(forgeGrant(t, root, g), "test")
217 if err != nil {
218 t.Fatal(err)
219 }
220 if _, err := verifyGrant(rootPub, sg); err == nil {
221 t.Error("verifyGrant honoured a grant running longer than the cap")
222 }
223}
224
225// A grant naming a rung that is not in the frozen vocabulary fails closed —
226// a typo must not silently widen or narrow a scope.
227//
228// Mutation-mindedness: drop the RungIndex check and "prod" is honoured as a
229// rung nothing matches, which is a scope nobody can reason about.
230func TestGrantRejectsUnknownRungs(t *testing.T) {
231 root, rootPub := testKeys(t)
232 if _, _, err := MintKeyGrant(root, "edge-builder", []string{"prod"}, time.Hour); err == nil {
233 t.Error("MintKeyGrant accepted an unknown rung")
234 }
235 if _, _, err := MintKeyGrant(root, "edge-builder", nil, time.Hour); err == nil {
236 t.Error("MintKeyGrant accepted an empty rung list")
237 }
238 _, scopedPub := testKeys(t)
239 g := goodGrant(scopedPub)
240 g.Rungs = []string{"prod"}
241 sg, err := parseGrantBlob(forgeGrant(t, root, g), "test")
242 if err != nil {
243 t.Fatal(err)
244 }
245 if _, err := verifyGrant(rootPub, sg); err == nil {
246 t.Error("verifyGrant honoured an unknown rung")
247 }
248}
249
250// A grant signed by anything other than the root key the verifier is pinned
251// to fails, and says so as a GRANT failure — distinct from a pointer
252// signature failure, so an operator can tell which link of the chain broke.
253//
254// A tampered payload and a wrong signer are cryptographically the same event
255// ("this signature does not check out") and deliberately share one message;
256// what must never happen is either of them being honoured.
257//
258// Mutation-mindedness: remove the ed25519.Verify in verifyGrant and both
259// cases go green, and anyone able to write a bucket object could mint trust.
260func TestGrantSignatureMustBeByTheRootKey(t *testing.T) {
261 root, rootPub := testKeys(t)
262 notRoot, _ := testKeys(t)
263 _, scopedPub := testKeys(t)
264
265 // Signed by a key that is not the root.
266 sg, err := parseGrantBlob(forgeGrant(t, notRoot, goodGrant(scopedPub)), "test")
267 if err != nil {
268 t.Fatal(err)
269 }
270 if _, err := verifyGrant(rootPub, sg); err == nil {
271 t.Fatal("verifyGrant honoured a grant signed by a non-root key")
272 } else if !strings.Contains(err.Error(), "key grant signature") {
273 t.Errorf("refusal %q does not name the grant signature", err)
274 }
275
276 // Signed by the root, then the payload swapped for a wider scope.
277 good := goodGrant(scopedPub)
278 wider := good
279 wider.Rungs = []string{RungCanary, RungEdge, RungBeta}
280 tampered, err := parseGrantBlob(forgeGrant(t, root, good), "test")
281 if err != nil {
282 t.Fatal(err)
283 }
284 widerPayload, err := json.MarshalIndent(wider, "", " ")
285 if err != nil {
286 t.Fatal(err)
287 }
288 tampered.Payload = base64.StdEncoding.EncodeToString(widerPayload)
289 if _, err := verifyGrant(rootPub, tampered); err == nil {
290 t.Fatal("verifyGrant honoured a tampered grant payload")
291 } else if !strings.Contains(err.Error(), "key grant signature") {
292 t.Errorf("refusal %q does not name the grant signature", err)
293 }
294}
295
296// A minted grant round-trips: the blob parses, verifies against the root
297// public key, and describes the key that was minted with it.
298func TestMintedGrantRoundTrips(t *testing.T) {
299 root, rootPub := testKeys(t)
300 key, blob := mintGrant(t, root, "edge-builder", []string{RungCanary, RungEdge}, 30*24*time.Hour)
301
302 sg, err := parseGrantBlob(blob, "test")
303 if err != nil {
304 t.Fatalf("parseGrantBlob: %v", err)
305 }
306 g, err := verifyGrant(rootPub, sg)
307 if err != nil {
308 t.Fatalf("verifyGrant: %v", err)
309 }
310 if g.Schema != 1 || g.Name != "edge-builder" {
311 t.Fatalf("grant = %+v", g)
312 }
313 want := base64.StdEncoding.EncodeToString(key.Public().(ed25519.PublicKey))
314 if g.Pub != want {
315 t.Fatalf("grant pub = %q, want the minted key's public half %q", g.Pub, want)
316 }
317 if got := g.ExpiresAt.Sub(g.MintedAt); got != 30*24*time.Hour {
318 t.Fatalf("grant lifetime = %v, want 720h", got)
319 }
320 if err := g.allows("edge", g.MintedAt); err != nil {
321 t.Fatalf("minted edge grant refuses edge: %v", err)
322 }
323}
324
325// The scoped seed is TAGGED. A scoped key and a root key are both 32 random
326// bytes, so nothing downstream could otherwise tell them apart — and the one
327// mistake that must never pass silently is a scoped seed dropped into the
328// root key's slot with no grant beside it. ParseSigningKey accepts the tag
329// and an untagged seed alike, so every existing root key keeps parsing.
330//
331// Mutation-mindedness: drop the prefix from MintKeyGrant's seed and the
332// promote-side "grant is missing" refusal loses the only signal it has.
333func TestScopedSeedIsTaggedAndStillParses(t *testing.T) {
334 root, _ := testKeys(t)
335 seed, _, err := MintKeyGrant(root, "edge-builder", []string{RungEdge}, time.Hour)
336 if err != nil {
337 t.Fatal(err)
338 }
339 if !strings.HasPrefix(seed, ScopedKeyPrefix) {
340 t.Fatalf("minted seed %q is not tagged with %q", seed, ScopedKeyPrefix)
341 }
342 if !IsScopedSeed(seed) {
343 t.Fatalf("IsScopedSeed(%q) = false", seed)
344 }
345 key, err := ParseSigningKey(seed, "test")
346 if err != nil {
347 t.Fatalf("ParseSigningKey on a tagged seed: %v", err)
348 }
349 // The same 32 bytes without the tag parse to the same key, and are not
350 // mistaken for a scoped seed.
351 plain := strings.TrimPrefix(seed, ScopedKeyPrefix)
352 if IsScopedSeed(plain) {
353 t.Error("an untagged seed was reported scoped")
354 }
355 same, err := ParseSigningKey(plain, "test")
356 if err != nil {
357 t.Fatalf("ParseSigningKey on an untagged seed: %v", err)
358 }
359 if !key.Equal(same) {
360 t.Error("tagged and untagged forms of one seed parsed to different keys")
361 }
362}
363
364// A grant blob that is not base64, not JSON, or not a grant at all fails with
365// a message naming where it came from — the operator pasted the wrong secret.
366func TestGrantBlobParseErrorsNameTheSource(t *testing.T) {
367 for _, blob := range []string{"", "not base64!!", base64.StdEncoding.EncodeToString([]byte("{not json"))} {
368 _, err := parseGrantBlob(blob, "CARLOS_RELEASE_GRANT")
369 if err == nil {
370 t.Fatalf("parseGrantBlob(%q) succeeded", blob)
371 }
372 if !strings.Contains(err.Error(), "CARLOS_RELEASE_GRANT") {
373 t.Errorf("error %q does not name the source", err)
374 }
375 }
376}
377
378// A pointer dated before the grant existed is forged history: no real
379// promotion by a key can predate the key, so the floor refuses it outright
380// (2026-08-03 review, IMPORTANT 1 — before the floor, a scoped key could
381// sign a pointer dated years before its own mint and it verified).
382//
383// Mutation-mindedness: delete the at.Before(g.MintedAt) refusal in allows()
384// and the first half of this test goes green when it must not; the second
385// half pins that the exact mint instant — the earliest honest promotion —
386// stays allowed, so the floor cannot drift into refusing real history.
387func TestGrantRefusesAPointerPredatingTheGrant(t *testing.T) {
388 _, scopedPub := testKeys(t)
389 g := goodGrant(scopedPub)
390 err := g.allows("edge", g.MintedAt.Add(-time.Second))
391 if err == nil {
392 t.Fatal("grant honoured a pointer dated before the grant was minted")
393 }
394 if !strings.Contains(err.Error(), "predates") {
395 t.Errorf("refusal %q does not say the pointer predates the key", err)
396 }
397 if err := g.allows("edge", g.MintedAt); err != nil {
398 t.Errorf("grant refused its own mint instant: %v", err)
399 }
400}
401
402// Duplicate rungs are collapsed at mint (2026-08-03 review, MINOR 6):
403// harmless to verify, noise in every listing of the grant forever.
404func TestMintDeduplicatesRungs(t *testing.T) {
405 root, _ := testKeys(t)
406 rootPub := root.Public().(ed25519.PublicKey)
407 _, blob := mintGrant(t, root, "dupes", []string{"edge", "edge", "canary", "edge"}, time.Hour)
408 sg, err := parseGrantBlob(blob, "test")
409 if err != nil {
410 t.Fatal(err)
411 }
412 g, err := verifyGrant(rootPub, sg)
413 if err != nil {
414 t.Fatal(err)
415 }
416 if len(g.Rungs) != 2 {
417 t.Fatalf("rungs = %v, want the two distinct ones", g.Rungs)
418 }
419}
420