package release import ( "context" "crypto/ed25519" "encoding/base64" "encoding/json" "errors" "os" "strings" "testing" "time" ) func recordFor(t *testing.T, s Store, sg Signer, acct, app, ver, channel string) PromotionRecord { t.Helper() raw, _, err := s.Get(context.Background(), ManifestKey(acct, app, ver)) if err != nil { t.Fatal(err) } rec := PromotionRecord{Account: acct, App: app, Version: ver, ManifestDigest: ManifestDigest(raw), Channel: channel, Actor: "test"} if err := sg.WritePromotionRecord(context.Background(), s, rec); err != nil { t.Fatalf("WritePromotionRecord: %v", err) } return rec } func TestPromotionRecordRoundTrip(t *testing.T) { s := NewDirStore(t.TempDir()) ctx := context.Background() priv, pub := testKeys(t) shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one")) want := recordFor(t, s, RootSigner(priv), "bab", "km", "v1", "stable") got, err := ReadPromotionRecord(ctx, s, pub, "bab", "km", "v1") if err != nil || got.ManifestDigest != want.ManifestDigest || got.Channel != "stable" || got.At.IsZero() { t.Fatalf("ReadPromotionRecord = %+v, %v", got, err) } if m, err := AuthorizedManifest(ctx, s, pub, "bab", "km", "v1"); err != nil || m.Version != "v1" { t.Fatalf("AuthorizedManifest = %+v, %v", m, err) } } // Spec §8: a fresh, never-promoted version with a matching manifest is // refused for a pin. func TestANeverPromotedVersionHasNoRecord(t *testing.T) { s := NewDirStore(t.TempDir()) ctx := context.Background() _, pub := testKeys(t) shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one")) _, err := AuthorizedManifest(ctx, s, pub, "bab", "km", "v1") if !errors.Is(err, ErrNoPromotionRecord) || !errors.Is(err, os.ErrNotExist) { t.Fatalf("AuthorizedManifest = %v; want ErrNoPromotionRecord wrapping os.ErrNotExist", err) } } // Spec §8 "Promotion binding": a reused version name with a replayed record // and a different manifest is refused. func TestAReplayedRecordDoesNotAuthorizeAReusedName(t *testing.T) { s := NewDirStore(t.TempDir()) ctx := context.Background() priv, pub := testKeys(t) shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("promoted bytes")) recordFor(t, s, RootSigner(priv), "bab", "km", "v1", "stable") saved, _, _ := s.Get(ctx, PromotionRecordKey("bab", "km", "v1")) if err := DeleteVersion(ctx, s, "bab", "km", "v1"); err != nil { t.Fatal(err) } shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("nobody promoted these")) _, etag, gerr := s.Get(ctx, PromotionRecordKey("bab", "km", "v1")) if errors.Is(gerr, os.ErrNotExist) { etag = "" } if _, err := s.Put(ctx, PromotionRecordKey("bab", "km", "v1"), saved, etag); err != nil { t.Fatal(err) } if _, err := AuthorizedManifest(ctx, s, pub, "bab", "km", "v1"); !errors.Is(err, ErrManifestDigest) { t.Fatalf("AuthorizedManifest = %v; want ErrManifestDigest", err) } } // Review Focus 2. func TestARecordCopiedToAnotherKeyIsRefused(t *testing.T) { s := NewDirStore(t.TempDir()) ctx := context.Background() priv, pub := testKeys(t) shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one")) recordFor(t, s, RootSigner(priv), "bab", "km", "v1", "stable") raw, _, _ := s.Get(ctx, PromotionRecordKey("bab", "km", "v1")) if _, err := s.Put(ctx, PromotionRecordKey("bab", "blog", "v1"), raw, ""); err != nil { t.Fatal(err) } if _, err := ReadPromotionRecord(ctx, s, pub, "bab", "blog", "v1"); err == nil || errors.Is(err, ErrNoPromotionRecord) { t.Fatalf("a copied record = %v; want a refusal that is not 'absent'", err) } } func TestScopedRecordsFollowTheGrant(t *testing.T) { s := NewDirStore(t.TempDir()) ctx := context.Background() root, pub := testKeys(t) seed, grant, err := MintKeyGrant(root, "ci", []string{"canary"}, time.Hour) if err != nil { t.Fatal(err) } key, _ := ParseSigningKey(seed, "test") sg, err := ScopedSigner(key, grant, pub) if err != nil { t.Fatal(err) } shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one")) raw, _, _ := s.Get(ctx, ManifestKey("bab", "km", "v1")) d := ManifestDigest(raw) if err := sg.WritePromotionRecord(ctx, s, PromotionRecord{Account: "bab", App: "km", Version: "v1", ManifestDigest: d, Channel: "canary/x"}); err != nil { t.Fatalf("in scope: %v", err) } if rec, err := ReadPromotionRecord(ctx, s, pub, "bab", "km", "v1"); err != nil || rec.Channel != "canary/x" { t.Fatalf("scoped record = %+v, %v", rec, err) } if err := sg.WritePromotionRecord(ctx, s, PromotionRecord{Account: "bab", App: "km", Version: "v1", ManifestDigest: d, Channel: "stable"}); err == nil { t.Fatal("a canary-scoped key signed a stable record") } if err := sg.WritePromotionRecord(ctx, s, PromotionRecord{Account: "bab", App: "km", Version: "v1", ManifestDigest: d, Authorized: true}); err == nil { t.Fatal("a scoped key signed an authorize record") } } func TestWriteKeepsAnIdenticalRecordAndReplacesAnother(t *testing.T) { s := NewDirStore(t.TempDir()) ctx := context.Background() priv, pub := testKeys(t) shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one")) first := recordFor(t, s, RootSigner(priv), "bab", "km", "v1", "canary/x") got1, _ := ReadPromotionRecord(ctx, s, pub, "bab", "km", "v1") time.Sleep(1100 * time.Millisecond) recordFor(t, s, RootSigner(priv), "bab", "km", "v1", "stable") got2, _ := ReadPromotionRecord(ctx, s, pub, "bab", "km", "v1") if !got2.At.Equal(got1.At) || got2.Channel != "canary/x" { t.Fatalf("an identical digest replaced the record: %+v -> %+v", got1, got2) } other := first other.ManifestDigest = ManifestDigest([]byte("another manifest")) if err := RootSigner(priv).WritePromotionRecord(ctx, s, other); err != nil { t.Fatal(err) } if got3, _ := ReadPromotionRecord(ctx, s, pub, "bab", "km", "v1"); got3.ManifestDigest != other.ManifestDigest { t.Fatal("a record for different bytes did not replace the old one") } } // ---- the reader's own refusals ------------------------------------------------ // // WritePromotionRecord refuses every input below before it signs, so these // tests hand-build the wrapper and Put it directly: the only thing standing // between each record and a pin is ReadPromotionRecord's own check. // recordPayload is rec as the base64 payload string a wrapper signs. func recordPayload(t *testing.T, rec PromotionRecord) string { t.Helper() payload, err := json.MarshalIndent(rec, "", " ") if err != nil { t.Fatal(err) } return base64.StdEncoding.EncodeToString(payload) } // putRecordWrapper writes sw at rec's own record key, replacing whatever is // there. func putRecordWrapper(t *testing.T, s Store, rec PromotionRecord, sw signedWrapper) { t.Helper() ctx := context.Background() key := PromotionRecordKey(rec.Account, rec.App, rec.Version) _, etag, err := s.Get(ctx, key) if errors.Is(err, os.ErrNotExist) { etag = "" } else if err != nil { t.Fatal(err) } if _, err := s.Put(ctx, key, mustJSON(t, sw), etag); err != nil { t.Fatal(err) } } // putSignedRecord signs rec with key, attaches grant (nil for a root-style // wrapper) and writes it, with none of WritePromotionRecord's refusals. func putSignedRecord(t *testing.T, s Store, rec PromotionRecord, key ed25519.PrivateKey, grant *signedGrant) signedWrapper { t.Helper() b64 := recordPayload(t, rec) sw := signedWrapper{ Payload: b64, Sig: base64.StdEncoding.EncodeToString(ed25519.Sign(key, []byte(b64))), Grant: grant, } putRecordWrapper(t, s, rec, sw) return sw } // handBuiltRecord ships bab/km/v1 and returns a record for it that every // reader check accepts, dated an hour into goodGrant's window. func handBuiltRecord(t *testing.T) (Store, PromotionRecord) { t.Helper() s := NewDirStore(t.TempDir()) shipRelease(t, s, "bab", "km", "v1", adoptPlatform, []byte("one")) raw, _, err := s.Get(context.Background(), ManifestKey("bab", "km", "v1")) if err != nil { t.Fatal(err) } return s, PromotionRecord{ Schema: 1, Account: "bab", App: "km", Version: "v1", ManifestDigest: ManifestDigest(raw), Channel: "canary/x", Actor: "test", At: goodGrant(nil).MintedAt.Add(time.Hour), } } // assertRecordRefused wants ReadPromotionRecord to refuse with an error that // names want and is never "absent", and AuthorizedManifest to refuse too. func assertRecordRefused(t *testing.T, s Store, pub ed25519.PublicKey, rec PromotionRecord, want string) { t.Helper() ctx := context.Background() got, err := ReadPromotionRecord(ctx, s, pub, rec.Account, rec.App, rec.Version) if err == nil || errors.Is(err, ErrNoPromotionRecord) || !strings.Contains(err.Error(), want) { t.Fatalf("ReadPromotionRecord = %+v, %v; want a refusal naming %q that is not 'absent'", got, err, want) } if m, err := AuthorizedManifest(ctx, s, pub, rec.Account, rec.App, rec.Version); err == nil || errors.Is(err, ErrNoPromotionRecord) { t.Fatalf("AuthorizedManifest = %+v, %v; want a refusal that is not 'absent'", m, err) } } // assertRecordReads is the control: the hand-built wrapper is a real record, // so each refusal beside it is the reader's check and not a fixture artefact. func assertRecordReads(t *testing.T, s Store, pub ed25519.PublicKey, rec PromotionRecord) { t.Helper() ctx := context.Background() got, err := ReadPromotionRecord(ctx, s, pub, rec.Account, rec.App, rec.Version) if err != nil || got.Channel != rec.Channel || got.ManifestDigest != rec.ManifestDigest { t.Fatalf("control: ReadPromotionRecord = %+v, %v", got, err) } if m, err := AuthorizedManifest(ctx, s, pub, rec.Account, rec.App, rec.Version); err != nil || m.Version != rec.Version { t.Fatalf("control: AuthorizedManifest = %+v, %v", m, err) } } // The reader's signature check: a payload edited after signing, and a record // signed by a key that is neither the root nor the key its grant names. func TestReaderRefusesABadRecordSignature(t *testing.T) { root, rootPub := testKeys(t) other, otherPub := testKeys(t) t.Run("control", func(t *testing.T) { s, rec := handBuiltRecord(t) putSignedRecord(t, s, rec, root, nil) assertRecordReads(t, s, rootPub, rec) }) t.Run("tampered payload", func(t *testing.T) { s, rec := handBuiltRecord(t) sw := putSignedRecord(t, s, rec, root, nil) edited := rec edited.Channel = "stable" sw.Payload = recordPayload(t, edited) putRecordWrapper(t, s, rec, sw) assertRecordRefused(t, s, rootPub, rec, "signature verification failed") }) t.Run("signed by a key that is not the root", func(t *testing.T) { s, rec := handBuiltRecord(t) putSignedRecord(t, s, rec, other, nil) assertRecordRefused(t, s, rootPub, rec, "signature verification failed") }) t.Run("signed by a key its grant does not name", func(t *testing.T) { s, rec := handBuiltRecord(t) putSignedRecord(t, s, rec, root, forgeGrantSub(t, root, goodGrant(otherPub))) assertRecordRefused(t, s, rootPub, rec, "signature verification failed") }) } // The reader's verifyGrant call: a grant signed by anything but the pinned // root makes the record it carries worthless. func TestReaderRefusesAGrantNotSignedByTheRoot(t *testing.T) { root, rootPub := testKeys(t) impostor, _ := testKeys(t) scoped, scopedPub := testKeys(t) t.Run("control", func(t *testing.T) { s, rec := handBuiltRecord(t) putSignedRecord(t, s, rec, scoped, forgeGrantSub(t, root, goodGrant(scopedPub))) assertRecordReads(t, s, rootPub, rec) }) t.Run("grant signed by another key", func(t *testing.T) { s, rec := handBuiltRecord(t) putSignedRecord(t, s, rec, scoped, forgeGrantSub(t, impostor, goodGrant(scopedPub))) assertRecordRefused(t, s, rootPub, rec, "key grant signature verification failed") }) } // The reader's "authorization signed by a scoped key" refusal: a grant // covers channels, never an authorization, whatever channel rides beside it. // // Mutation-mindedness: delete that refusal in ReadPromotionRecord and the // first case reads as a valid record (canary/x is in scope and in window), // while the second fails with scope.allows' "not a rung" instead. func TestReaderRefusesAScopedAuthorization(t *testing.T) { root, rootPub := testKeys(t) scoped, scopedPub := testKeys(t) grant := forgeGrantSub(t, root, goodGrant(scopedPub)) t.Run("control", func(t *testing.T) { s, rec := handBuiltRecord(t) putSignedRecord(t, s, rec, scoped, grant) assertRecordReads(t, s, rootPub, rec) }) for _, tc := range []struct { name string channel string authorized bool want string }{ {"authorized with an in-scope channel", "canary/x", true, "an authorization signed by a scoped key"}, {"authorized with no channel", "", true, "an authorization signed by a scoped key"}, {"no channel and not authorized", "", false, "names no channel"}, } { t.Run(tc.name, func(t *testing.T) { s, rec := handBuiltRecord(t) rec.Channel, rec.Authorized = tc.channel, tc.authorized putSignedRecord(t, s, rec, scoped, grant) assertRecordRefused(t, s, rootPub, rec, tc.want) }) } } // The reader's scope.allows check: a scoped record must name a channel on a // rung its grant covers, dated inside the grant's window. // // Mutation-mindedness: delete that check in ReadPromotionRecord and every // case below reads as a valid record. func TestReaderHoldsAScopedRecordToItsGrant(t *testing.T) { root, rootPub := testKeys(t) scoped, scopedPub := testKeys(t) g := goodGrant(scopedPub) // canary and edge grant := forgeGrantSub(t, root, g) t.Run("control", func(t *testing.T) { s, rec := handBuiltRecord(t) rec.Channel, rec.At = "edge", g.ExpiresAt putSignedRecord(t, s, rec, scoped, grant) assertRecordReads(t, s, rootPub, rec) }) for _, tc := range []struct { name string channel string at time.Time want string }{ {"stable", "stable", g.MintedAt.Add(time.Hour), "never delegable"}, {"a rung the grant does not cover", "beta", g.MintedAt.Add(time.Hour), "is on rung beta"}, {"dated after the grant expired", "canary/x", g.ExpiresAt.Add(time.Second), "expired"}, {"dated before the grant was minted", "canary/x", g.MintedAt.Add(-time.Second), "predates"}, } { t.Run(tc.name, func(t *testing.T) { s, rec := handBuiltRecord(t) rec.Channel, rec.At = tc.channel, tc.at putSignedRecord(t, s, rec, scoped, grant) assertRecordRefused(t, s, rootPub, rec, tc.want) }) } }