@@ -31,6 +31,8 @@ import (
3131 app "github.com/konflux-ci/application-api/api/v1alpha1"
3232 "github.com/santhosh-tekuri/jsonschema/v5"
3333 "github.com/sigstore/cosign/v3/pkg/cosign"
34+ cosignOCI "github.com/sigstore/cosign/v3/pkg/oci"
35+ ociremote "github.com/sigstore/cosign/v3/pkg/oci/remote"
3436 log "github.com/sirupsen/logrus"
3537 "github.com/spf13/afero"
3638
@@ -120,6 +122,12 @@ func (a *ApplicationSnapshotImage) SetImageURL(url string) error {
120122 return nil
121123}
122124
125+ func (a * ApplicationSnapshotImage ) hasBundles (ctx context.Context ) bool {
126+ regOpts := []ociremote.Option {ociremote .WithRemoteOptions (oci .CreateRemoteOptions (ctx )... )}
127+ bundles , _ , err := cosign .GetBundles (ctx , a .reference , regOpts )
128+ return err == nil && len (bundles ) > 0
129+ }
130+
123131func (a * ApplicationSnapshotImage ) FetchImageConfig (ctx context.Context ) error {
124132 var err error
125133 a .configJSON , err = config .FetchImageConfig (ctx , a .reference )
@@ -143,38 +151,58 @@ func (a *ApplicationSnapshotImage) FetchImageFiles(ctx context.Context) error {
143151 return err
144152}
145153
146- // ValidateImageSignature executes the cosign.VerifyImageSignature method on the ApplicationSnapshotImage image ref.
154+ // ValidateImageSignature verifies the image signature. For images with Sigstore
155+ // bundles (OCI referrers) the new bundle path is used; otherwise the legacy
156+ // tag-based path is used.
147157func (a * ApplicationSnapshotImage ) ValidateImageSignature (ctx context.Context ) error {
148- // Set the ClaimVerifier on a shallow *copy* of CheckOpts to avoid unexpected side-effects
149158 opts := a .checkOpts
150- opts .ClaimVerifier = cosign .SimpleClaimVerifier
151- signatures , _ , err := oci .NewClient (ctx ).VerifyImageSignatures (a .reference , & opts )
159+ client := oci .NewClient (ctx )
160+
161+ var sigs []cosignOCI.Signature
162+ var err error
163+
164+ if a .hasBundles (ctx ) {
165+ opts .NewBundleFormat = true
166+ opts .ClaimVerifier = cosign .IntotoSubjectClaimVerifier
167+ sigs , _ , err = client .VerifyImageAttestations (a .reference , & opts )
168+ } else {
169+ opts .ClaimVerifier = cosign .SimpleClaimVerifier
170+ sigs , _ , err = client .VerifyImageSignatures (a .reference , & opts )
171+ }
152172 if err != nil {
153173 return err
154174 }
155175
156- for _ , s := range signatures {
176+ for _ , s := range sigs {
157177 es , err := signature .NewEntitySignature (s )
158178 if err != nil {
159179 return err
160180 }
161181 a .signatures = append (a .signatures , es )
162182 }
163-
164183 return nil
165184}
166185
167- // ValidateAttestationSignature executes the cosign.VerifyImageAttestations method
186+ // ValidateAttestationSignature verifies and collects in-toto attestations
187+ // attached to the image.
168188func (a * ApplicationSnapshotImage ) ValidateAttestationSignature (ctx context.Context ) error {
169- // Set the ClaimVerifier on a shallow *copy* of CheckOpts to avoid unexpected side-effects
170189 opts := a .checkOpts
171190 opts .ClaimVerifier = cosign .IntotoSubjectClaimVerifier
172191
192+ useBundles := a .hasBundles (ctx )
193+ if useBundles {
194+ opts .NewBundleFormat = true
195+ }
196+
173197 layers , _ , err := oci .NewClient (ctx ).VerifyImageAttestations (a .reference , & opts )
174198 if err != nil {
175199 return err
176200 }
177201
202+ if useBundles {
203+ return a .parseAttestationsFromBundles (layers )
204+ }
205+
178206 // Extract the signatures from the attestations here in order to also validate that
179207 // the signatures do exist in the expected format.
180208 for _ , sig := range layers {
@@ -220,6 +248,40 @@ func (a *ApplicationSnapshotImage) ValidateAttestationSignature(ctx context.Cont
220248 return nil
221249}
222250
251+ // parseAttestationsFromBundles extracts attestations from Sigstore bundles.
252+ // Bundle-wrapped layers report an incorrect media type, so we unmarshal the
253+ // DSSE envelope from the raw payload directly.
254+ func (a * ApplicationSnapshotImage ) parseAttestationsFromBundles (layers []cosignOCI.Signature ) error {
255+ for _ , sig := range layers {
256+ payload , err := sig .Payload ()
257+ if err != nil {
258+ log .Debugf ("Skipping bundle entry: cannot read payload: %v" , err )
259+ continue
260+ }
261+ var dsseEnvelope struct {
262+ PayloadType string `json:"payloadType"`
263+ Payload string `json:"payload"`
264+ }
265+ if err := json .Unmarshal (payload , & dsseEnvelope ); err != nil {
266+ log .Debugf ("Skipping bundle entry: not a valid DSSE envelope: %v" , err )
267+ continue
268+ }
269+ if dsseEnvelope .PayloadType != "application/vnd.in-toto+json" {
270+ log .Debugf ("Skipping bundle entry with payloadType: %s" , dsseEnvelope .PayloadType )
271+ continue
272+ }
273+
274+ att , err := attestation .ProvenanceFromBundlePayload (sig , payload )
275+ if err != nil {
276+ return fmt .Errorf ("unable to parse bundle attestation: %w" , err )
277+ }
278+ t := att .PredicateType ()
279+ log .Debugf ("Found bundle attestation with predicateType: %s" , t )
280+ a .attestations = append (a .attestations , att )
281+ }
282+ return nil
283+ }
284+
223285// ValidateAttestationSyntax validates the attestations against known JSON
224286// schemas, errors out if there are no attestations to check to prevent
225287// successful syntax check of no inputs, must invoke
0 commit comments