From 09b91d2517851da6cc7b53728678f62c8bfadfc0 Mon Sep 17 00:00:00 2001 From: scttfrdmn <3011922+scttfrdmn@users.noreply.github.com> Date: Mon, 20 Jul 2026 13:53:17 -0700 Subject: [PATCH] fix(plugin): clear error on cosign legacy-bundle signatures (spore-plugins#8) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A cosign bundle written without --new-bundle-format uses the legacy shape ({base64Signature,cert,rekorBundle}), which sigstore-go can't parse — it failed with a cryptic 'proto: unknown field base64Signature'. Detect that shape up front and return an actionable error pointing at --new-bundle-format. Found during the real-workflow signing smoke: the virtual-Sigstore unit tests use protobuf entities, so they never exercised the legacy-bundle path. The registry release workflow is being fixed to emit the new format (spore-plugins); this makes spawn fail clearly if a legacy bundle ever reaches it. --- pkg/plugin/signature.go | 7 +++++++ pkg/plugin/signature_test.go | 15 +++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/pkg/plugin/signature.go b/pkg/plugin/signature.go index b00d335..e809b5b 100644 --- a/pkg/plugin/signature.go +++ b/pkg/plugin/signature.go @@ -46,6 +46,13 @@ var trustedRootFetcher = func() (root.TrustedMaterial, error) { // inclusion and a trusted timestamp. Any failure — bad signature, wrong identity, // missing log entry, digest mismatch against manifestData — is an error. func verifyManifestSignature(sigBundleJSON, manifestData []byte) error { + // cosign's LEGACY bundle ({base64Signature,cert,rekorBundle}) is not the + // Sigstore protobuf bundle sigstore-go parses; detect it and give a clear + // error rather than a cryptic protobuf "unknown field" message. Releases must + // sign with `cosign sign-blob --new-bundle-format`. + if bytes.Contains(sigBundleJSON, []byte(`"base64Signature"`)) { + return fmt.Errorf("signature is in cosign's legacy bundle format; the release must be signed with --new-bundle-format") + } var b bundle.Bundle if err := b.UnmarshalJSON(sigBundleJSON); err != nil { return fmt.Errorf("parse signature bundle: %w", err) diff --git a/pkg/plugin/signature_test.go b/pkg/plugin/signature_test.go index 1b9f6e6..cb5091d 100644 --- a/pkg/plugin/signature_test.go +++ b/pkg/plugin/signature_test.go @@ -1,6 +1,7 @@ package plugin import ( + "strings" "testing" "github.com/sigstore/sigstore-go/pkg/root" @@ -68,6 +69,20 @@ func TestVerifySignedEntity(t *testing.T) { }) } +// TestVerifyManifestSignature_RejectsLegacyBundle ensures a cosign legacy bundle +// (the default `cosign sign-blob --bundle` output) is rejected with a clear, +// actionable error rather than a cryptic protobuf parse failure. +func TestVerifyManifestSignature_RejectsLegacyBundle(t *testing.T) { + legacy := []byte(`{"base64Signature":"MEUCIQ...","cert":"LS0tLS1CRUdJTi...","rekorBundle":{}}`) + err := verifyManifestSignature(legacy, []byte(`{"any":"manifest"}`)) + if err == nil { + t.Fatal("expected an error for a legacy cosign bundle, got nil") + } + if !strings.Contains(err.Error(), "legacy bundle format") { + t.Errorf("error %q does not explain the legacy-format problem", err) + } +} + // TestTrustedRootFetcherOverride confirms the fetcher hook is overridable, so // verifyManifestSignature never touches the network in tests. (The JSON-bundle // unmarshal is sigstore-go's own tested code; the verification policy it feeds