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 "reflect"
8 "sort"
9 "strings"
10 "testing"
11 "time"
12)
13
14// The grant-signed wire shape, pinned beside the root-signed one. Captured
15// with deterministic seeds (root = bytes 1..32, scoped = bytes 101..132) and
16// frozen timestamps: a grant minted 2026-08-03T09:00Z for canary+edge,
17// 90 days, over an edge pointer promoted half an hour later.
18//
19// The root public key is goldenRootPub — the SAME anchor as the root-signed
20// fixture, which is the point of the whole scheme: one CARLOS_RELEASE_PUBKEY
21// verifies both shapes.
22const goldenGrantSignedWrapper = `{
23 "payload": "ewogICJzY2hlbWEiOiAxLAogICJhcHAiOiAia2FzcyIsCiAgImNoYW5uZWwiOiAiZWRnZSIsCiAgInZlcnNpb24iOiAiOWYzYzFhZCIsCiAgInByb21vdGVkX2F0IjogIjIwMjYtMDgtMDNUMDk6MzA6MDBaIiwKICAiYXVkaXQiOiBbCiAgICB7CiAgICAgICJ2ZXJzaW9uIjogIjlmM2MxYWQiLAogICAgICAidG8iOiAiZWRnZSIsCiAgICAgICJhY3RvciI6ICJidWlsZGVyQGNpIiwKICAgICAgImF0IjogIjIwMjYtMDgtMDNUMDk6MzA6MDBaIgogICAgfQogIF0KfQ==",
24 "sig": "oXZdFZDT5ul4RlKG3/VgSmhJjKBshdontRLT5ytm6IsB8MbAnoDicG4T2cVIN0pML2DsJUNhYEIcjg9eZvW2Dw==",
25 "grant": {
26 "payload": "ewogICJzY2hlbWEiOiAxLAogICJwdWIiOiAiMmlucFd3TGdEL29WWkZkMSt4MHJvaUtobERPVjdxQnJsT0xBVjdlK2FkQT0iLAogICJydW5ncyI6IFsKICAgICJjYW5hcnkiLAogICAgImVkZ2UiCiAgXSwKICAibmFtZSI6ICJlZGdlLWJ1aWxkZXIiLAogICJtaW50ZWRfYXQiOiAiMjAyNi0wOC0wM1QwOTowMDowMFoiLAogICJleHBpcmVzX2F0IjogIjIwMjYtMTEtMDFUMDk6MDA6MDBaIgp9",
27 "sig": "MlawPu9BYwUcYr2VhAfPqgj2SRWzfDSIeSGxbW+3noo+xB36gSiPo3Aqa4ZNZ5TZvOx+Vjv+mlg/+Rsb1wYZAw=="
28 }
29}`
30
31// rootPubKey is the fixture's root anchor, parsed.
32func rootPubKey(t *testing.T) ed25519.PublicKey {
33 t.Helper()
34 pub, err := ParsePublicKey(goldenRootPub, "golden")
35 if err != nil {
36 t.Fatal(err)
37 }
38 return pub
39}
40
41// scopedFixture returns a fresh root key, its public half, a scoped key
42// covering rungs, and a Signer built from them — the setup nearly every
43// scoped test needs.
44func scopedFixture(t *testing.T, rungs []string, ttl time.Duration) (root ed25519.PrivateKey, rootPub ed25519.PublicKey, sg Signer) {
45 t.Helper()
46 root, rootPub = testKeys(t)
47 key, blob := mintGrant(t, root, "edge-builder", rungs, ttl)
48 sg, err := ScopedSigner(key, blob, rootPub)
49 if err != nil {
50 t.Fatalf("ScopedSigner: %v", err)
51 }
52 return root, rootPub, sg
53}
54
55// signAs wraps a pointer with a given signer, without a store. The pointer's
56// promoted_at is signPointer's own stamp, so these are pointers as they would
57// really be written.
58func signAs(t *testing.T, sg Signer, app, channel, version string) []byte {
59 t.Helper()
60 w, err := signPointer(sg, &ChannelPointer{Account: "bab", App: app, Channel: channel, Version: version})
61 if err != nil {
62 t.Fatalf("signPointer(%s): %v", channel, err)
63 }
64 return w
65}
66
67// The grant-signed shape, pinned: three top-level keys, the grant a two-key
68// sub-object, and the whole thing verifying against the SAME root public key
69// the fleet already has configured.
70//
71// Mutation-mindedness: rename or restructure any wrapper/grant field and this
72// fails on the key list before the signature check even runs — which is what
73// stops a "harmless" JSON tag edit from orphaning pointers already in
74// production buckets.
75func TestGoldenGrantSignedWrapperShapeAndVerification(t *testing.T) {
76 var top map[string]json.RawMessage
77 if err := json.Unmarshal([]byte(goldenGrantSignedWrapper), &top); err != nil {
78 t.Fatal(err)
79 }
80 got := make([]string, 0, len(top))
81 for k := range top {
82 got = append(got, k)
83 }
84 sort.Strings(got)
85 if want := []string{"grant", "payload", "sig"}; !reflect.DeepEqual(got, want) {
86 t.Fatalf("grant-signed wrapper keys = %v, want %v", got, want)
87 }
88 var sub map[string]json.RawMessage
89 if err := json.Unmarshal(top["grant"], &sub); err != nil {
90 t.Fatal(err)
91 }
92 got = got[:0]
93 for k := range sub {
94 got = append(got, k)
95 }
96 sort.Strings(got)
97 if want := []string{"payload", "sig"}; !reflect.DeepEqual(got, want) {
98 t.Fatalf("grant sub-object keys = %v, want %v", got, want)
99 }
100
101 p, err := verifyPointer(rootPubKey(t), []byte(goldenGrantSignedWrapper), "bab", "kass", "edge")
102 if err != nil {
103 t.Fatalf("golden grant-signed pointer does not verify: %v", err)
104 }
105 if p.Version != "9f3c1ad" || p.Channel != "edge" {
106 t.Fatalf("pointer = %+v", p)
107 }
108
109 // The grant payload's own field names, pinned: they are wire, and a
110 // verifier that silently read zero values out of a renamed field would
111 // treat an expiring grant as one that never expires.
112 var sgw signedWrapper
113 if err := json.Unmarshal([]byte(goldenGrantSignedWrapper), &sgw); err != nil {
114 t.Fatal(err)
115 }
116 raw, err := base64.StdEncoding.DecodeString(sgw.Grant.Payload)
117 if err != nil {
118 t.Fatal(err)
119 }
120 var gk map[string]json.RawMessage
121 if err := json.Unmarshal(raw, &gk); err != nil {
122 t.Fatal(err)
123 }
124 got = got[:0]
125 for k := range gk {
126 got = append(got, k)
127 }
128 sort.Strings(got)
129 if want := []string{"expires_at", "minted_at", "name", "pub", "rungs", "schema"}; !reflect.DeepEqual(got, want) {
130 t.Fatalf("grant payload keys = %v, want %v", got, want)
131 }
132}
133
134// In scope verifies; out of scope does not — at the verify end, where it has
135// to hold even if the promoting binary was compromised, patched, or simply
136// older than this one.
137//
138// Mutation-mindedness: delete the scope.allows call in verifyPointer and the
139// beta case passes, which is a builder key placing builds on beta.
140func TestVerifyEnforcesGrantScope(t *testing.T) {
141 _, rootPub, sg := scopedFixture(t, []string{RungCanary, RungEdge}, 90*24*time.Hour)
142
143 for _, ch := range []string{"canary/keymail", "edge"} {
144 w := signAs(t, sg, "kass", ch, "9f3c1ad")
145 if _, err := verifyPointer(rootPub, w, "bab", "kass", ch); err != nil {
146 t.Errorf("in-scope channel %q did not verify: %v", ch, err)
147 }
148 }
149
150 // Out of scope: sign it anyway, bypassing signPointer's own refusal, and
151 // prove the VERIFIER refuses it. The promote-side guard and this one are
152 // independent — an attacker holding the scoped key does not run our
153 // promote path.
154 for _, ch := range []string{"beta", "stable"} {
155 w := forgePointerAs(t, sg, "kass", ch, "9f3c1ad", time.Now().UTC().Truncate(time.Second))
156 _, err := verifyPointer(rootPub, w, "bab", "kass", ch)
157 if err == nil {
158 t.Fatalf("out-of-scope channel %q verified", ch)
159 }
160 if !strings.Contains(err.Error(), "key grant") {
161 t.Errorf("refusal for %q = %q, want it to name the key grant", ch, err)
162 }
163 }
164}
165
166// forgePointerAs signs a pointer with the signer's key and grant while
167// skipping signPointer's scope check, and stamps an arbitrary promoted_at.
168// This is the hostile promoter: it holds the scoped key and will not ask
169// permission before signing.
170func forgePointerAs(t *testing.T, sg Signer, app, channel, version string, at time.Time) []byte {
171 t.Helper()
172 p := &ChannelPointer{Schema: 1, App: app, Channel: channel, Version: version, PromotedAt: at}
173 payload, err := json.MarshalIndent(p, "", " ")
174 if err != nil {
175 t.Fatal(err)
176 }
177 b64 := base64.StdEncoding.EncodeToString(payload)
178 w, err := json.MarshalIndent(signedWrapper{
179 Payload: b64,
180 Sig: base64.StdEncoding.EncodeToString(ed25519.Sign(sg.key, []byte(b64))),
181 Grant: sg.grant,
182 }, "", " ")
183 if err != nil {
184 t.Fatal(err)
185 }
186 return w
187}
188
189// Expiry is a promotion-time gate and nothing else. A pointer promoted while
190// the grant was valid verifies forever — adoption re-verifies every pass, and
191// a pointer that stopped verifying at midnight would take a running route's
192// channel resolution dark for a reason that has nothing to do with the build.
193// A pointer promoted AFTER expiry never verifies at all.
194//
195// Mutation-mindedness: replace p.PromotedAt with time.Now() in verifyPointer's
196// scope.allows call and the first case fails. The grant used here expired in
197// 2025, long before this test can ever run, and its pointer must still
198// resolve — that is only true if expiry is read from the pointer.
199func TestExpiryGatesPromotionNotAdoption(t *testing.T) {
200 root, rootPub := testKeys(t)
201 key, _ := mintGrant(t, root, "edge-builder", []string{RungEdge}, 30*24*time.Hour)
202 pub, _ := key.Public().(ed25519.PublicKey)
203
204 // A grant that lived for the first quarter of 2025 and is long dead now.
205 g := Grant{
206 Schema: 1,
207 Pub: base64.StdEncoding.EncodeToString(pub),
208 Rungs: []string{RungEdge},
209 Name: "edge-builder",
210 MintedAt: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC),
211 ExpiresAt: time.Date(2025, 4, 1, 0, 0, 0, 0, time.UTC),
212 }
213 sg := Signer{key: key, grant: forgeGrantSub(t, root, g), scope: &g, root: rootPub}
214
215 // Promoted while the grant was valid, read today: still resolves. This is
216 // the property adoption depends on — it re-verifies every pass, and a
217 // pointer that went dark on the grant's expiry date would take a running
218 // route's channel resolution with it.
219 w := forgePointerAs(t, sg, "kass", "edge", "9f3c1ad", time.Date(2025, 2, 1, 0, 0, 0, 0, time.UTC))
220 if _, err := verifyPointer(rootPub, w, "bab", "kass", "edge"); err != nil {
221 t.Fatalf("a pointer promoted while the grant was valid stopped verifying: %v", err)
222 }
223
224 // Promoted one second after expiry: dead, and it says why.
225 late := forgePointerAs(t, sg, "kass", "edge", "9f3c1ad", g.ExpiresAt.Add(time.Second))
226 _, err := verifyPointer(rootPub, late, "bab", "kass", "edge")
227 if err == nil {
228 t.Fatal("a pointer promoted after the grant expired verified")
229 }
230 if !strings.Contains(err.Error(), "expired") {
231 t.Errorf("refusal %q does not say expired", err)
232 }
233
234 // And the promote side refuses to sign with it at all, now.
235 if _, err := signPointer(sg, &ChannelPointer{Account: "bab", App: "kass", Channel: "edge", Version: "9f3c1ad"}); err == nil {
236 t.Fatal("an expired grant signed a new pointer")
237 }
238}
239
240// The three ways a grant-bearing wrapper can be malformed, each failing
241// closed, and each naming the link of the chain that broke: a grant the root
242// did not sign, a grant whose payload was swapped after signing, and a grant
243// that is perfectly valid but authorises a DIFFERENT key than the one that
244// signed the pointer.
245//
246// The first two share a message on purpose — Ed25519 cannot distinguish a
247// tampered payload from a wrong signer, and pretending otherwise would be a
248// lie in an error string. The third is a genuinely different failure and says
249// so: the grant is fine, the pointer is not.
250//
251// Mutation-mindedness: drop the grant signature check and the first two pass;
252// verify the pointer against `pub` instead of the granted key and the third
253// passes, which would let any grant launder any signature.
254func TestGrantChainFailuresAreDistinct(t *testing.T) {
255 root, rootPub := testKeys(t)
256 notRoot, _ := testKeys(t)
257 key, blob := mintGrant(t, root, "edge-builder", []string{RungEdge}, 30*24*time.Hour)
258 sg, err := ScopedSigner(key, blob, rootPub)
259 if err != nil {
260 t.Fatal(err)
261 }
262 good := signAs(t, sg, "kass", "edge", "9f3c1ad")
263
264 // (1) grant signed by a key that is not the root.
265 forged := withGrant(t, good, forgeGrantSub(t, notRoot, *sg.scope))
266 if _, err := verifyPointer(rootPub, forged, "bab", "kass", "edge"); err == nil {
267 t.Error("a grant signed by a non-root key was honoured")
268 } else if !strings.Contains(err.Error(), "key grant signature") {
269 t.Errorf("non-root grant refusal = %q", err)
270 }
271
272 // (2) grant payload swapped for a wider scope after the root signed it.
273 wider := *sg.scope
274 wider.Rungs = []string{RungCanary, RungEdge, RungBeta}
275 sub := *forgeGrantSub(t, root, *sg.scope)
276 widerPayload, err := json.MarshalIndent(wider, "", " ")
277 if err != nil {
278 t.Fatal(err)
279 }
280 sub.Payload = base64.StdEncoding.EncodeToString(widerPayload)
281 tampered := withGrant(t, good, &sub)
282 if _, err := verifyPointer(rootPub, tampered, "bab", "kass", "edge"); err == nil {
283 t.Error("a tampered grant payload was honoured")
284 } else if !strings.Contains(err.Error(), "key grant signature") {
285 t.Errorf("tampered grant refusal = %q", err)
286 }
287
288 // (3) a valid grant for someone ELSE's key, attached to a pointer this
289 // key signed. The grant verifies; the pointer signature must not.
290 otherKey, otherBlob := mintGrant(t, root, "other-builder", []string{RungEdge}, 30*24*time.Hour)
291 _ = otherKey
292 otherSub, err := parseGrantBlob(otherBlob, "test")
293 if err != nil {
294 t.Fatal(err)
295 }
296 mismatched := withGrant(t, good, otherSub)
297 if _, err := verifyPointer(rootPub, mismatched, "bab", "kass", "edge"); err == nil {
298 t.Error("a grant for a different key was honoured")
299 } else if !strings.Contains(err.Error(), "channel pointer signature") {
300 t.Errorf("mismatched-key refusal = %q, want it to name the POINTER signature", err)
301 }
302}
303
304// forgeGrantSub signs an arbitrary grant with signer and returns the wrapper
305// sub-object.
306func forgeGrantSub(t *testing.T, signer ed25519.PrivateKey, g Grant) *signedGrant {
307 t.Helper()
308 payload, err := json.MarshalIndent(g, "", " ")
309 if err != nil {
310 t.Fatal(err)
311 }
312 b64 := base64.StdEncoding.EncodeToString(payload)
313 return &signedGrant{
314 Payload: b64,
315 Sig: base64.StdEncoding.EncodeToString(ed25519.Sign(signer, []byte(b64))),
316 }
317}
318
319// withGrant swaps the grant sub-object of a wrapper, leaving payload and sig
320// untouched.
321func withGrant(t *testing.T, wrapper []byte, sub *signedGrant) []byte {
322 t.Helper()
323 var sw signedWrapper
324 if err := json.Unmarshal(wrapper, &sw); err != nil {
325 t.Fatal(err)
326 }
327 sw.Grant = sub
328 return mustJSON(t, sw)
329}
330
331func mustJSON(t *testing.T, v any) []byte {
332 t.Helper()
333 b, err := json.MarshalIndent(v, "", " ")
334 if err != nil {
335 t.Fatal(err)
336 }
337 return b
338}
339
340// A pointer signed by a scoped key but stripped of its grant is refused: the
341// signature is not the root's. This is also what an OLD binary does with a
342// new grant-bearing wrapper — it ignores the field it does not know and
343// checks the sig against the root key — so the fail-closed deploy order
344// (binaries first, scoped keys second) is a property, not a hope.
345//
346// Mutation-mindedness: default `signer` to anything but the root pubkey when
347// there is no grant and this passes, which would accept unsigned-by-root
348// pointers on the entire live fleet.
349func TestScopedPointerWithoutItsGrantIsRefused(t *testing.T) {
350 _, rootPub, sg := scopedFixture(t, []string{RungEdge}, 30*24*time.Hour)
351 w := signAs(t, sg, "kass", "edge", "9f3c1ad")
352
353 var sw signedWrapper
354 if err := json.Unmarshal(w, &sw); err != nil {
355 t.Fatal(err)
356 }
357 sw.Grant = nil
358 if _, err := verifyPointer(rootPub, mustJSON(t, sw), "bab", "kass", "edge"); err == nil {
359 t.Fatal("a scoped signature was accepted as a root signature")
360 }
361}
362
363// A scoped signer refuses to SIGN out of scope, independently of anything the
364// promote verbs check. signPointer is the one place a pointer becomes signed
365// bytes, so this is the floor under every caller present and future.
366//
367// Mutation-mindedness: remove the allows() call from signPointer and this
368// fails; it is what makes "a caller forgot to check" not a vulnerability.
369func TestSignPointerRefusesOutOfScope(t *testing.T) {
370 _, _, sg := scopedFixture(t, []string{RungCanary, RungEdge}, 30*24*time.Hour)
371 if _, err := signPointer(sg, &ChannelPointer{Account: "bab", App: "kass", Channel: "stable", Version: "9f3c1ad"}); err == nil {
372 t.Fatal("a canary/edge signer signed stable")
373 }
374 if _, err := signPointer(sg, &ChannelPointer{Account: "bab", App: "kass", Channel: "beta", Version: "9f3c1ad"}); err == nil {
375 t.Fatal("a canary/edge signer signed beta")
376 }
377 if _, err := signPointer(sg, &ChannelPointer{Account: "bab", App: "kass", Channel: "edge", Version: "9f3c1ad"}); err != nil {
378 t.Fatalf("a canary/edge signer refused edge: %v", err)
379 }
380}
381
382// ScopedSigner refuses a grant that does not authorise the key it is handed —
383// the operator pasted one builder's grant beside another builder's seed. It
384// fails when the Signer is BUILT, not at the first promote, because a signer
385// that cannot prove its own authority should not exist.
386//
387// Mutation-mindedness: drop the g.Pub comparison and this passes; the signer
388// would then produce pointers that every verifier rejects, turning a clear
389// local error into a mystery in production.
390func TestScopedSignerRefusesAGrantForAnotherKey(t *testing.T) {
391 root, rootPub := testKeys(t)
392 key, _ := mintGrant(t, root, "edge-builder", []string{RungEdge}, 30*24*time.Hour)
393 _, otherBlob := mintGrant(t, root, "other-builder", []string{RungEdge}, 30*24*time.Hour)
394
395 if _, err := ScopedSigner(key, otherBlob, rootPub); err == nil {
396 t.Fatal("ScopedSigner accepted a grant for a different key")
397 }
398 // And it refuses a grant that does not chain to THIS deployment's root.
399 elsewhere, _ := testKeys(t)
400 otherKey, otherRootBlob := mintGrant(t, elsewhere, "edge-builder", []string{RungEdge}, 30*24*time.Hour)
401 if _, err := ScopedSigner(otherKey, otherRootBlob, rootPub); err == nil {
402 t.Fatal("ScopedSigner accepted a grant signed by another deployment's root key")
403 }
404}
405