@@ -22,6 +22,7 @@ import (
2222 "archive/tar"
2323 "bytes"
2424 "context"
25+ "crypto/sha256"
2526 "crypto/x509"
2627 "encoding/base64"
2728 "encoding/hex"
@@ -46,17 +47,21 @@ import (
4647 "github.com/google/go-containerregistry/pkg/v1/types"
4748 "github.com/in-toto/in-toto-golang/in_toto"
4849 "github.com/sigstore/cosign/v2/pkg/cosign"
50+ "github.com/sigstore/cosign/v2/pkg/cosign/bundle"
4951 "github.com/sigstore/cosign/v2/pkg/oci"
5052 "github.com/sigstore/cosign/v2/pkg/oci/layout"
5153 cosignRemote "github.com/sigstore/cosign/v2/pkg/oci/remote"
5254 "github.com/sigstore/cosign/v2/pkg/oci/static"
5355 cosigntypes "github.com/sigstore/cosign/v2/pkg/types"
56+ rc "github.com/sigstore/rekor/pkg/client"
57+ "github.com/sigstore/sigstore/pkg/cryptoutils"
5458 "github.com/sigstore/sigstore/pkg/signature"
5559 "gopkg.in/go-jose/go-jose.v2/json"
5660
5761 "github.com/conforma/cli/acceptance/attestation"
5862 "github.com/conforma/cli/acceptance/crypto"
5963 "github.com/conforma/cli/acceptance/registry"
64+ "github.com/conforma/cli/acceptance/rekor"
6065 "github.com/conforma/cli/acceptance/testenv"
6166)
6267
@@ -135,7 +140,7 @@ func imageFrom(ctx context.Context, imageName string) (v1.Image, error) {
135140// CreateAndPushImageSignature for a named image in the Context creates a signature
136141// image, same as `cosign sign` or Tekton Chains would, of that named image and pushes it
137142// to the stub registry as a new tag for that image akin to how cosign and Tekton Chains
138- // do it
143+ // do it. This implementation includes transparency log upload to generate bundle information.
139144func CreateAndPushImageSignature (ctx context.Context , imageName string , keyName string ) (context.Context , error ) {
140145 var state * imageState
141146 ctx , err := testenv .SetupState (ctx , & state )
@@ -169,29 +174,101 @@ func CreateAndPushImageSignature(ctx context.Context, imageName string, keyName
169174 return ctx , err
170175 }
171176
172- // creates a cosign signature payload signs it and provides the raw signature
173- payload , signature , err := signature .SignImage (signer , digestImage , map [string ]interface {}{})
177+ // Create the cosign signature payload and sign it
178+ payload , rawSignature , err := signature .SignImage (signer , digestImage , map [string ]interface {}{})
174179 if err != nil {
175180 return ctx , err
176181 }
177182
178- signatureBase64 := base64 .StdEncoding .EncodeToString (signature )
183+ signatureBase64 := base64 .StdEncoding .EncodeToString (rawSignature )
179184
180- // creates the layer with the image signature
181- signatureLayer , err := static .NewSignature (payload , signatureBase64 )
185+ // Create the signature structure for the stub rekor entry
186+ signature := Signature {
187+ KeyID : "" ,
188+ Signature : signatureBase64 ,
189+ }
190+
191+ signatureJSON , err := json .Marshal (signature )
192+ if err != nil {
193+ return ctx , fmt .Errorf ("failed to marshal signature structure: %w" , err )
194+ }
195+
196+ // Get the public key from the signer for hashedrekord validation
197+ publicKey , err := signer .PublicKey ()
198+ if err != nil {
199+ return ctx , fmt .Errorf ("failed to get public key: %w" , err )
200+ }
201+
202+ publicKeyBytes , err := cryptoutils .MarshalPublicKeyToPEM (publicKey )
203+ if err != nil {
204+ return ctx , fmt .Errorf ("failed to marshal public key: %w" , err )
205+ }
206+
207+ // Create stubs for both Rekor entry signature creation and retrieval endpoints
208+ err = rekor .StubRekorEntryCreationForSignature (ctx , payload , rawSignature , signatureJSON , publicKeyBytes )
209+ if err != nil {
210+ return ctx , fmt .Errorf ("error stubbing rekor endpoints: %w" , err )
211+ }
212+
213+ // Upload to transparency log to get bundle information like Tekton Chains does
214+ rekorURL , err := rekor .StubRekor (ctx )
215+ if err != nil {
216+ return ctx , fmt .Errorf ("failed to get stub rekor URL: %w" , err )
217+ }
218+
219+ rekorClient , err := rc .GetRekorClient (rekorURL )
220+ if err != nil {
221+ return ctx , fmt .Errorf ("failed to get rekor client: %w" , err )
222+ }
223+
224+ // Get public key or cert for transparency log upload
225+ pkoc , err := getPublicKeyOrCert (signer )
226+ if err != nil {
227+ return ctx , fmt .Errorf ("failed to get public key or cert: %w" , err )
228+ }
229+
230+ // Compute payload checksum
231+ checksum := sha256 .New ()
232+ if _ , err := checksum .Write (payload ); err != nil {
233+ return ctx , fmt .Errorf ("error checksuming payload: %w" , err )
234+ }
235+
236+ tlogEntry , err := cosign .TLogUpload (ctx , rekorClient , rawSignature , checksum , pkoc )
237+ if err != nil {
238+ return ctx , fmt .Errorf ("failed to upload to transparency log: %w" , err )
239+ }
240+
241+ // Create bundle from the actual transparency log entry
242+ rekorBundle := bundle .EntryToBundle (tlogEntry )
243+ if rekorBundle == nil {
244+ return ctx , fmt .Errorf ("rekorBundle is nil after EntryToBundle" )
245+ }
246+
247+ // Create the signature layer with bundle information using static.WithBundle
248+ signatureLayer , err := static .NewSignature (payload , signatureBase64 , static .WithBundle (rekorBundle ))
182249 if err != nil {
183250 return ctx , err
184251 }
185252
253+ // Extract bundle information from signatureLayer to include in annotations
254+ annotations := map [string ]string {
255+ static .SignatureAnnotationKey : signatureBase64 ,
256+ }
257+
258+ // Add bundle annotation if bundle information exists
259+ bundleJSON , err := json .Marshal (rekorBundle )
260+ if err != nil {
261+ return ctx , fmt .Errorf ("failed to marshal bundle for annotation: %w" , err )
262+ }
263+ annotations [static .BundleAnnotationKey ] = string (bundleJSON )
264+
186265 // creates the signature image with the correct media type and config and appends
187266 // the signature layer to it
188- singnatureImage := mutate .MediaType (empty .Image , types .OCIManifestSchema1 )
189- singnatureImage = mutate .ConfigMediaType (singnatureImage , types .OCIConfigJSON )
190- singnatureImage , err = mutate .Append (singnatureImage , mutate.Addendum {
191- Layer : signatureLayer ,
192- Annotations : map [string ]string {
193- static .SignatureAnnotationKey : signatureBase64 ,
194- },
267+ signatureImage := mutate .MediaType (empty .Image , types .OCIManifestSchema1 )
268+ signatureImage = mutate .ConfigMediaType (signatureImage , types .OCIConfigJSON )
269+ signatureImage , err = mutate .Append (signatureImage , mutate.Addendum {
270+ Layer : signatureLayer ,
271+ Annotations : annotations ,
195272 })
196273 if err != nil {
197274 return ctx , err
@@ -204,16 +281,13 @@ func CreateAndPushImageSignature(ctx context.Context, imageName string, keyName
204281 }
205282
206283 // push to the registry
207- err = remote .Write (ref , singnatureImage )
284+ err = remote .Write (ref , signatureImage )
208285 if err != nil {
209286 return ctx , err
210287 }
211288
212289 state .Signatures [imageName ] = ref .String ()
213- state .ImageSignatures [imageName ] = Signature {
214- KeyID : "" ,
215- Signature : signatureBase64 ,
216- }
290+ state .ImageSignatures [imageName ] = signature
217291
218292 return ctx , nil
219293}
@@ -229,7 +303,8 @@ func CreateAndPushAttestation(ctx context.Context, imageName, keyName string) (c
229303// image, same as `cosign attest` or Tekton Chains would, and pushes it to the stub
230304// registry as a new tag for that image akin to how cosign and Tekton Chains do
231305// it; this variant applies additional JSON Patch patches to the SLSA provenance
232- // statement as required by the tests
306+ // statement as required by the tests. This implementation now includes transparency
307+ // log upload to generate bundle information like Tekton Chains does for attestations.
233308func createAndPushAttestationWithPatches (ctx context.Context , imageName , keyName string , patches * godog.Table ) (context.Context , error ) {
234309 var state * imageState
235310 ctx , err := testenv .SetupState (ctx , & state )
@@ -267,34 +342,117 @@ func createAndPushAttestationWithPatches(ctx context.Context, imageName, keyName
267342 return ctx , err
268343 }
269344
270- if sig , err := unmarshallSignatures (signedAttestation ); err != nil {
345+ // Extract signature information from the signed attestation
346+ var sig * cosign.Signatures
347+ sig , err = unmarshallSignatures (signedAttestation )
348+ if err != nil {
271349 return ctx , err
272- } else {
273- state .AttestationSignatures [imageName ] = Signature {
274- KeyID : sig .KeyID ,
275- Signature : sig .Sig ,
350+ }
351+ if sig == nil {
352+ return ctx , fmt .Errorf ("failed to extract signature from attestation: no signatures found" )
353+ }
354+
355+ state .AttestationSignatures [imageName ] = Signature {
356+ KeyID : sig .KeyID ,
357+ Signature : sig .Sig ,
358+ }
359+
360+ // Extract raw signature from the signed attestation for transparency log upload
361+ var rawSignature []byte
362+ if sig .Sig != "" {
363+ rawSignature , err = base64 .StdEncoding .DecodeString (sig .Sig )
364+ if err != nil {
365+ return ctx , fmt .Errorf ("failed to decode signature: %w" , err )
276366 }
277367 }
278368
279- attestationLayer , err := static .NewAttestation (signedAttestation )
369+ // Get the signer for transparency log operations
370+ signer , err := crypto .SignerWithKey (ctx , keyName )
280371 if err != nil {
281372 return ctx , err
282373 }
283374
375+ // Get the public key from the signer for intoto validation
376+ publicKey , err := signer .PublicKey ()
377+ if err != nil {
378+ return ctx , fmt .Errorf ("failed to get public key: %w" , err )
379+ }
380+
381+ publicKeyBytes , err := cryptoutils .MarshalPublicKeyToPEM (publicKey )
382+ if err != nil {
383+ return ctx , fmt .Errorf ("failed to marshal public key: %w" , err )
384+ }
385+
386+ // Create stubs for both Rekor entry creation and retrieval endpoints for attestations
387+ err = rekor .StubRekorEntryCreationForAttestation (ctx , signedAttestation , publicKeyBytes )
388+ if err != nil {
389+ return ctx , fmt .Errorf ("error stubbing rekor endpoints for attestation: %w" , err )
390+ }
391+
392+ // Upload to transparency log to get bundle information like Tekton Chains does
393+ rekorURL , err := rekor .StubRekor (ctx )
394+ if err != nil {
395+ return ctx , fmt .Errorf ("failed to get stub rekor URL: %w" , err )
396+ }
397+
398+ rekorClient , err := rc .GetRekorClient (rekorURL )
399+ if err != nil {
400+ return ctx , fmt .Errorf ("failed to get rekor client: %w" , err )
401+ }
402+
403+ // Get public key or cert for transparency log upload
404+ pkoc , err := getPublicKeyOrCert (signer )
405+ if err != nil {
406+ return ctx , fmt .Errorf ("failed to get public key or cert: %w" , err )
407+ }
408+
409+ // Compute payload checksum
410+ checksum := sha256 .New ()
411+ if _ , err := checksum .Write (signedAttestation ); err != nil {
412+ return ctx , fmt .Errorf ("error checksuming attestation: %w" , err )
413+ }
414+
415+ tlogEntry , err := cosign .TLogUpload (ctx , rekorClient , rawSignature , checksum , pkoc )
416+ if err != nil {
417+ return ctx , fmt .Errorf ("failed to upload attestation to transparency log: %w" , err )
418+ }
419+
420+ // Create bundle from the actual transparency log entry
421+ rekorBundle := bundle .EntryToBundle (tlogEntry )
422+ if rekorBundle == nil {
423+ return ctx , fmt .Errorf ("rekorBundle is nil after EntryToBundle" )
424+ }
425+
426+ // Create the attestation layer with bundle information using static.WithBundle
427+ attestationLayer , err := static .NewAttestation (signedAttestation , static .WithBundle (rekorBundle ))
428+ if err != nil {
429+ return ctx , err
430+ }
431+
432+ // Extract bundle information from attestationLayer to include in annotations
433+ annotations := map [string ]string {
434+ // When cosign creates an attestation, it sets this annotation to an empty
435+ // string, as seen here:
436+ // https://github.com/sigstore/cosign/blob/34afd5240ce8490a4fa427c3f46523246643047c/pkg/oci/static/signature.go#L52-L55
437+ // We choose to mimic the cosign behavior to avoid inconsistencies in the tests.
438+ static .SignatureAnnotationKey : "" ,
439+ }
440+
441+ // Add bundle annotation if bundle information exists
442+ bundleJSON , err := json .Marshal (rekorBundle )
443+ if err != nil {
444+ return ctx , fmt .Errorf ("failed to marshal bundle for annotation: %w" , err )
445+ }
446+ annotations [static .BundleAnnotationKey ] = string (bundleJSON )
447+
284448 // creates the attestation image with the correct media type and config and appends
285449 // the attestation layer to it
286450 attestationImage := mutate .MediaType (empty .Image , types .OCIManifestSchema1 )
287451 attestationImage = mutate .ConfigMediaType (attestationImage , types .OCIConfigJSON )
288452 attestationImage , err = mutate .Append (attestationImage , mutate.Addendum {
289- MediaType : cosigntypes .DssePayloadType ,
290- Layer : attestationLayer ,
291- Annotations : map [string ]string {
292- // When cosign creates an attestation, it sets this annotation to an empty
293- // string, as seen here:
294- // https://github.com/sigstore/cosign/blob/34afd5240ce8490a4fa427c3f46523246643047c/pkg/oci/static/signature.go#L52-L55
295- // We choose to mimic the cosign behavior to avoid inconsistencies in the tests.
296- static .SignatureAnnotationKey : "" ,
297- },
453+ MediaType : cosigntypes .DssePayloadType ,
454+ Layer : attestationLayer ,
455+ Annotations : annotations ,
298456 })
299457 if err != nil {
300458 return ctx , err
@@ -862,6 +1020,22 @@ func RawImageSignaturesFrom(ctx context.Context) map[string]string {
8621020 return ret
8631021}
8641022
1023+ // getPublicKeyOrCert returns the cert if we have it, otherwise return public key
1024+ // This mimics the same logic used in Tekton Chains
1025+ func getPublicKeyOrCert (signer signature.SignerVerifier ) ([]byte , error ) {
1026+ // For now, we'll always use the public key since we're using test keys
1027+ // In a real scenario with certificates, we'd check for cert first
1028+ pub , err := signer .PublicKey ()
1029+ if err != nil {
1030+ return nil , fmt .Errorf ("getting public key: %w" , err )
1031+ }
1032+ pem , err := cryptoutils .MarshalPublicKeyToPEM (pub )
1033+ if err != nil {
1034+ return nil , fmt .Errorf ("key to pem: %w" , err )
1035+ }
1036+ return pem , nil
1037+ }
1038+
8651039func applyPatches (statement * in_toto.ProvenanceStatementSLSA02 , patches * godog.Table ) (* in_toto.ProvenanceStatementSLSA02 , error ) {
8661040 if statement == nil || patches == nil || len (patches .Rows ) == 0 {
8671041 return statement , nil
0 commit comments