@@ -155,11 +155,19 @@ func (a *ApplicationSnapshotImage) FetchImageFiles(ctx context.Context) error {
155155func (a * ApplicationSnapshotImage ) ValidateImageSignature (ctx context.Context ) error {
156156 opts := a .checkOpts
157157 client := oci .NewClient (ctx )
158+ useBundles := a .hasBundles (ctx )
158159
159160 var sigs []cosignOCI.Signature
160161 var err error
161162
162- if a .hasBundles (ctx ) {
163+ if useBundles {
164+ // For v3 bundles, both image signatures and attestations are stored as
165+ // "attestations" in the unified bundle format. So we use VerifyImageAttestations
166+ // to extract image signatures from the bundle, even though it seems unintuitive.
167+ // This is different from v2 where image signatures and attestations were separate.
168+ //
169+ // The certificate extraction requires different handling for bundles and
170+ // should be addressed in future work to achieve full v2 parity.
163171 opts .NewBundleFormat = true
164172 opts .ClaimVerifier = cosign .IntotoSubjectClaimVerifier
165173 sigs , _ , err = client .VerifyImageAttestations (a .reference , & opts )
@@ -172,11 +180,24 @@ func (a *ApplicationSnapshotImage) ValidateImageSignature(ctx context.Context) e
172180 }
173181
174182 for _ , s := range sigs {
175- es , err := signature .NewEntitySignature (s )
176- if err != nil {
177- return err
183+ if useBundles {
184+ // For bundle image signatures produced by cosign v3, the old
185+ // method of accessing the signatures doesn't work. Instead we have
186+ // to extract them from the bundle. And the bundle actually has
187+ // signatures for both the image itself and the attestation.
188+ signatures , err := extractSignaturesFromBundle (s )
189+ if err != nil {
190+ return fmt .Errorf ("cannot extract signatures from bundle: %w" , err )
191+ }
192+ a .signatures = append (a .signatures , signatures ... )
193+ } else {
194+ // For older non-bundle image signatures produced by cosign v2
195+ es , err := signature .NewEntitySignature (s )
196+ if err != nil {
197+ return err
198+ }
199+ a .signatures = append (a .signatures , es )
178200 }
179- a .signatures = append (a .signatures , es )
180201 }
181202 return nil
182203}
@@ -507,7 +528,7 @@ func (a *ApplicationSnapshotImage) WriteInputFile(ctx context.Context) (string,
507528 log .Debugf ("Created dir %s" , inputDir )
508529 inputJSONPath := path .Join (inputDir , "input.json" )
509530
510- f , err := fs .OpenFile (inputJSONPath , os .O_CREATE | os .O_WRONLY | os .O_EXCL , 0644 )
531+ f , err := fs .OpenFile (inputJSONPath , os .O_CREATE | os .O_WRONLY | os .O_EXCL , 0o644 )
511532 if err != nil {
512533 log .Debugf ("Problem creating file in %s" , inputDir )
513534 return "" , nil , err
@@ -526,3 +547,54 @@ func (a *ApplicationSnapshotImage) WriteInputFile(ctx context.Context) (string,
526547 log .Debugf ("Done preparing input file:\n %s" , inputJSONPath )
527548 return inputJSONPath , inputJSON , nil
528549}
550+
551+ // extractSignaturesFromBundle extracts signature information from a bundle
552+ // image signature attestation, using the same pattern as createEntitySignatures.
553+ //
554+ // TODO: This currently only extracts the signature value from the DSSE envelope.
555+ // Certificate information (keyid, certificate, chain, metadata) is not being
556+ // extracted because it requires different handling for v3 bundles compared to v2.
557+ // Future work should investigate how to access certificate data from bundle
558+ // attestations to achieve full parity with v2 signature output. Also, there might
559+ // be some cosign methods we can use instead of doing it ourselves here.
560+ func extractSignaturesFromBundle (sig cosignOCI.Signature ) ([]signature.EntitySignature , error ) {
561+ reader , err := sig .Uncompressed ()
562+ if err != nil {
563+ return nil , fmt .Errorf ("cannot read signature data: %w" , err )
564+ }
565+ defer reader .Close ()
566+
567+ var attestationPayload cosign.AttestationPayload
568+ if err := json .NewDecoder (reader ).Decode (& attestationPayload ); err != nil {
569+ return nil , fmt .Errorf ("cannot parse DSSE envelope: %w" , err )
570+ }
571+
572+ // Create the base EntitySignature from the oci.Signature (for certificate info)
573+ es , err := signature .NewEntitySignature (sig )
574+ if err != nil {
575+ return nil , fmt .Errorf ("cannot create base signature: %w" , err )
576+ }
577+
578+ var results []signature.EntitySignature
579+ for _ , s := range attestationPayload .Signatures {
580+ esNew := es
581+ // The Signature and KeyID can come from two locations, the oci.Signature or
582+ // the cosign.Signature. Prioritize information from oci.Signature, but fallback
583+ // to cosign.Signature when needed (same pattern as createEntitySignatures)
584+ //
585+ // Todo: Actually the above comment might be stale and/or wrong since I belive
586+ // it was copied from similar code in internal/attestation/attestation.go. Let's
587+ // review this later. As far as I can tell this code always produces an empty
588+ // string for the KeyId.
589+ //
590+ if esNew .Signature == "" {
591+ esNew .Signature = s .Sig
592+ }
593+ if esNew .KeyID == "" {
594+ esNew .KeyID = s .KeyID
595+ }
596+ results = append (results , esNew )
597+ }
598+
599+ return results , nil
600+ }
0 commit comments