@@ -19,6 +19,7 @@ package application_snapshot_image
1919import (
2020 "bytes"
2121 "context"
22+ "encoding/base64"
2223 "encoding/json"
2324 "errors"
2425 "fmt"
@@ -181,6 +182,17 @@ func (a *ApplicationSnapshotImage) ValidateImageSignature(ctx context.Context) e
181182
182183 for _ , s := range sigs {
183184 if useBundles {
185+ // This will appears in the output under "signatures" so filter out
186+ // the sigs that are provenance attestations leaving only the sigs
187+ // that are image signatures. Note: This does seems confusing and
188+ // I'm not 100% sure we're doing the right thing here. Maybe revisit
189+ // once we have a better idea about sigstore bundles and how they're
190+ // handled by cosign itself.
191+ if ! isImageSignatureAttestation (s ) {
192+ log .Debugf ("Skipping non-image signature attestation" )
193+ continue
194+ }
195+
184196 // For bundle image signatures produced by cosign v3, the old
185197 // method of accessing the signatures doesn't work. Instead we have
186198 // to extract them from the bundle. And the bundle actually has
@@ -191,7 +203,8 @@ func (a *ApplicationSnapshotImage) ValidateImageSignature(ctx context.Context) e
191203 }
192204 a .signatures = append (a .signatures , signatures ... )
193205 } else {
194- // For older non-bundle image signatures produced by cosign v2
206+ // For older non-bundle image signatures produced by cosign v2.
207+ // Note that filtering isn't needed, since we have only image sigs here.
195208 es , err := signature .NewEntitySignature (s )
196209 if err != nil {
197210 return err
@@ -218,6 +231,11 @@ func (a *ApplicationSnapshotImage) ValidateAttestationSignature(ctx context.Cont
218231 return err
219232 }
220233
234+ // Todo:
235+ // - For the non-bundle code path we actually check the syntax.
236+ // We should do that for bundles as well probably.
237+ // - Doing an early return like thi shere seems untidy, refactor
238+ // maybe?
221239 if useBundles {
222240 return a .parseAttestationsFromBundles (layers )
223241 }
@@ -270,8 +288,19 @@ func (a *ApplicationSnapshotImage) ValidateAttestationSignature(ctx context.Cont
270288// parseAttestationsFromBundles extracts attestations from Sigstore bundles.
271289// Bundle-wrapped layers report an incorrect media type, so we unmarshal the
272290// DSSE envelope from the raw payload directly.
291+ //
292+ // Note: For v3 bundles, this function filters out image signature attestations
293+ // (https://sigstore.dev/cosign/sign/v1) since those are handled in ValidateImageSignature.
294+ // Only provenance and other attestations are added to the attestations array.
273295func (a * ApplicationSnapshotImage ) parseAttestationsFromBundles (layers []cosignOCI.Signature ) error {
274296 for _ , sig := range layers {
297+ // For v3 bundles, filter out image signature attestations - those are handled
298+ // in ValidateImageSignature. Only add provenance attestations here.
299+ if isImageSignatureAttestation (sig ) {
300+ log .Debugf ("Skipping image signature attestation - handled in ValidateImageSignature" )
301+ continue
302+ }
303+
275304 payload , err := sig .Payload ()
276305 if err != nil {
277306 log .Debugf ("Skipping bundle entry: cannot read payload: %v" , err )
@@ -294,8 +323,8 @@ func (a *ApplicationSnapshotImage) parseAttestationsFromBundles(layers []cosignO
294323 if err != nil {
295324 return fmt .Errorf ("unable to parse bundle attestation: %w" , err )
296325 }
297- t := att .PredicateType ()
298- log . Debugf ( "Found bundle attestation with predicateType: %s" , t )
326+ log . Debugf ( "Found bundle attestation with predicateType: %s" , att .PredicateType () )
327+
299328 a .attestations = append (a .attestations , att )
300329 }
301330 return nil
@@ -548,6 +577,46 @@ func (a *ApplicationSnapshotImage) WriteInputFile(ctx context.Context) (string,
548577 return inputJSONPath , inputJSON , nil
549578}
550579
580+ const cosignSignPredicateType = "https://sigstore.dev/cosign/sign/v1"
581+
582+ // isImageSignatureAttestation checks if a bundle entry represents an image
583+ // signature (vs. a provenance or other attestation). For DSSE envelopes it
584+ // inspects the predicateType of the inner in-toto statement. For non-DSSE
585+ // payloads (e.g. simple signing) it returns true since those are always
586+ // image signatures.
587+ func isImageSignatureAttestation (sig cosignOCI.Signature ) bool {
588+ payload , err := sig .Payload ()
589+ if err != nil {
590+ log .Debugf ("Cannot read signature payload: %v" , err )
591+ return false
592+ }
593+
594+ var dsseEnvelope struct {
595+ PayloadType string `json:"payloadType"`
596+ Payload string `json:"payload"`
597+ }
598+ if err := json .Unmarshal (payload , & dsseEnvelope ); err != nil || dsseEnvelope .PayloadType == "" {
599+ // Not a DSSE envelope — treat as a simple signing image signature
600+ return true
601+ }
602+
603+ innerPayload , err := base64 .StdEncoding .DecodeString (dsseEnvelope .Payload )
604+ if err != nil {
605+ log .Debugf ("Cannot decode DSSE payload: %v" , err )
606+ return false
607+ }
608+
609+ var statement struct {
610+ PredicateType string `json:"predicateType"`
611+ }
612+ if err := json .Unmarshal (innerPayload , & statement ); err != nil {
613+ log .Debugf ("Cannot parse inner statement: %v" , err )
614+ return false
615+ }
616+
617+ return statement .PredicateType == cosignSignPredicateType
618+ }
619+
551620// extractSignaturesFromBundle extracts signature information from a bundle
552621// image signature attestation, using the same pattern as createEntitySignatures.
553622//
@@ -582,7 +651,7 @@ func extractSignaturesFromBundle(sig cosignOCI.Signature) ([]signature.EntitySig
582651 // the cosign.Signature. Prioritize information from oci.Signature, but fallback
583652 // to cosign.Signature when needed (same pattern as createEntitySignatures)
584653 //
585- // Todo: Actually the above comment might be stale and/or wrong since I belive
654+ // Todo: Actually the above comment might be stale and/or wrong since I believe
586655 // it was copied from similar code in internal/attestation/attestation.go. Let's
587656 // review this later. As far as I can tell this code always produces an empty
588657 // string for the KeyId.
0 commit comments