diff --git a/cmd/cosign/cli/save.go b/cmd/cosign/cli/save.go index 6c95c2059b9..c9779b4330e 100644 --- a/cmd/cosign/cli/save.go +++ b/cmd/cosign/cli/save.go @@ -59,6 +59,27 @@ func SaveCmd(ctx context.Context, opts options.SaveOptions, imageRef string) err return fmt.Errorf("parsing image name %s: %w", imageRef, err) } + // See if we are using referrers + digest, ok := ref.(name.Digest) + if ok { + indexManifest, err := ociremote.Referrers(digest, "", regClientOpts...) + if err != nil { + return fmt.Errorf("getting referrers: %w", err) + } + + for _, manifest := range indexManifest.Manifests { + if manifest.ArtifactType == "" { + continue + } + artifactRef := ref.Context().Digest(manifest.Digest.String()) + si, err := ociremote.SignedImage(artifactRef, regClientOpts...) + if err != nil { + return fmt.Errorf("getting signed image: %w", err) + } + return layout.WriteSignedImage(opts.Directory, si) + } + } + se, err := ociremote.SignedEntity(ref, regClientOpts...) if err != nil { return fmt.Errorf("signed entity: %w", err) diff --git a/pkg/oci/empty/signed.go b/pkg/oci/empty/signed.go index 385ed0e2b5d..d958451fbb8 100644 --- a/pkg/oci/empty/signed.go +++ b/pkg/oci/empty/signed.go @@ -30,6 +30,7 @@ type signedImage struct { digest v1.Hash signature oci.Signatures attestations oci.Signatures + bundles oci.Signatures } func (se *signedImage) Signatures() (oci.Signatures, error) { @@ -40,6 +41,10 @@ func (se *signedImage) Attestations() (oci.Signatures, error) { return se.attestations, nil } +func (se *signedImage) Bundles() (oci.Signatures, error) { + return se.bundles, nil +} + func (se *signedImage) Attachment(name string) (oci.File, error) { //nolint: revive return nil, errors.New("no attachments") } @@ -66,5 +71,6 @@ func SignedImage(ref name.Reference) (oci.SignedImage, error) { digest: d, signature: Signatures(), attestations: Signatures(), + bundles: Signatures(), }, nil } diff --git a/pkg/oci/interface.go b/pkg/oci/interface.go index 9d5bb9a0fe1..bf7015c4a43 100644 --- a/pkg/oci/interface.go +++ b/pkg/oci/interface.go @@ -31,6 +31,12 @@ type SignedEntity interface { // Base64Signature because it's baked into the payload. Attestations() (Signatures, error) + // Bundles returns the set of protobuf bundle attestations currently + // associated with this entity, or the empty equivalent if none are found. + // Bundles contain all the information required for verification, and are + // stored using the OCI1.1 referrer specification. + Bundles() (Signatures, error) + // Attachment returns a named entity associated with this entity, or error if not found. Attachment(name string) (File, error) } diff --git a/pkg/oci/layout/index.go b/pkg/oci/layout/index.go index e122301a6af..2d11b4010dc 100644 --- a/pkg/oci/layout/index.go +++ b/pkg/oci/layout/index.go @@ -21,7 +21,9 @@ import ( v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/layout" "github.com/sigstore/cosign/v3/pkg/oci" + "github.com/sigstore/cosign/v3/pkg/oci/remote" "github.com/sigstore/cosign/v3/pkg/oci/signed" + "github.com/sigstore/cosign/v3/pkg/types" ) const ( @@ -59,7 +61,7 @@ var _ oci.SignedImageIndex = (*index)(nil) // Signatures implements oci.SignedImageIndex func (i *index) Signatures() (oci.Signatures, error) { - img, err := i.imageByAnnotation(sigsAnnotation) + img, err := i.imageByAnnotation(kindAnnotation, sigsAnnotation) if err != nil { return nil, err } @@ -71,7 +73,19 @@ func (i *index) Signatures() (oci.Signatures, error) { // Attestations implements oci.SignedImageIndex func (i *index) Attestations() (oci.Signatures, error) { - img, err := i.imageByAnnotation(attsAnnotation) + img, err := i.imageByAnnotation(kindAnnotation, attsAnnotation) + if err != nil { + return nil, err + } + if img == nil { + return nil, nil + } + return &sigs{img}, nil +} + +// Bundles implements oci.SignedImageIndex +func (i *index) Bundles() (oci.Signatures, error) { + img, err := i.imageByAnnotation(remote.BundlePredicateType, types.CosignSignPredicateType) if err != nil { return nil, err } @@ -92,7 +106,7 @@ func (i *index) SignedImage(h v1.Hash) (oci.SignedImage, error) { var img v1.Image var err error if h.String() == ":" { - img, err = i.imageByAnnotation(imageAnnotation) + img, err = i.imageByAnnotation(kindAnnotation, imageAnnotation) } else { img, err = i.Image(h) } @@ -107,13 +121,13 @@ func (i *index) SignedImage(h v1.Hash) (oci.SignedImage, error) { // imageByAnnotation searches through all manifests in the index.json // and returns the image that has the matching annotation -func (i *index) imageByAnnotation(annotation string) (v1.Image, error) { +func (i *index) imageByAnnotation(annotationKey string, annotationValue string) (v1.Image, error) { manifest, err := i.IndexManifest() if err != nil { return nil, err } for _, m := range manifest.Manifests { - if val, ok := m.Annotations[kindAnnotation]; ok && val == annotation { + if val, ok := m.Annotations[annotationKey]; ok && val == annotationValue { return i.Image(m.Digest) } } diff --git a/pkg/oci/mutate/mutate.go b/pkg/oci/mutate/mutate.go index f7e83087b3b..a328fd5e083 100644 --- a/pkg/oci/mutate/mutate.go +++ b/pkg/oci/mutate/mutate.go @@ -78,6 +78,11 @@ func (i *indexWrapper) Attestations() (oci.Signatures, error) { return empty.Signatures(), nil } +// Bundles implements oci.SignedImageIndex +func (i *indexWrapper) Bundles() (oci.Signatures, error) { + return empty.Signatures(), nil +} + // Attachment implements oci.SignedImage func (*indexWrapper) Attachment(name string) (oci.File, error) { //nolint: revive return nil, errors.New("unimplemented") diff --git a/pkg/oci/remote/image.go b/pkg/oci/remote/image.go index 30d30a7d53d..b7852c9ec41 100644 --- a/pkg/oci/remote/image.go +++ b/pkg/oci/remote/image.go @@ -67,6 +67,11 @@ func (i *image) Attestations() (oci.Signatures, error) { return attestations(i, i.opt) } +// Bundles implements oci.SignedImage +func (i *image) Bundles() (oci.Signatures, error) { + return nil, errors.New("not implemented") +} + // Attestations implements oci.SignedImage func (i *image) Attachment(name string) (oci.File, error) { return attachment(i, name, i.opt) diff --git a/pkg/oci/remote/index.go b/pkg/oci/remote/index.go index 0aad7480d7f..21683963bfc 100644 --- a/pkg/oci/remote/index.go +++ b/pkg/oci/remote/index.go @@ -64,6 +64,11 @@ func (i *index) Attestations() (oci.Signatures, error) { return attestations(i, i.opt) } +// Bundles implements oci.SignedImage +func (i *index) Bundles() (oci.Signatures, error) { + return nil, errors.New("not implemented") +} + // Attestations implements oci.SignedImage func (i *index) Attachment(name string) (oci.File, error) { return attachment(i, name, i.opt) diff --git a/pkg/oci/remote/unknown.go b/pkg/oci/remote/unknown.go index 8ddb9f5a045..bae07325aee 100644 --- a/pkg/oci/remote/unknown.go +++ b/pkg/oci/remote/unknown.go @@ -16,6 +16,8 @@ package remote import ( + "errors" + "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/sigstore/cosign/v3/pkg/oci" @@ -54,6 +56,11 @@ func (i *unknown) Attestations() (oci.Signatures, error) { return attestations(i, i.opt) } +// Bundles implements oci.SignedEntity +func (i *unknown) Bundles() (oci.Signatures, error) { + return nil, errors.New("not implemented") +} + // Attachment implements oci.SignedEntity func (i *unknown) Attachment(name string) (oci.File, error) { return attachment(i, name, i.opt) diff --git a/pkg/oci/remote/write.go b/pkg/oci/remote/write.go index c612759212c..4ff9bd76117 100644 --- a/pkg/oci/remote/write.go +++ b/pkg/oci/remote/write.go @@ -24,7 +24,7 @@ import ( "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" - "github.com/google/go-containerregistry/pkg/v1/remote" + goremote "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/google/go-containerregistry/pkg/v1/static" "github.com/google/go-containerregistry/pkg/v1/types" ociexperimental "github.com/sigstore/cosign/v3/internal/pkg/oci/remote" @@ -49,7 +49,7 @@ func WriteSignedImageIndexImages(ref name.Reference, sii oci.SignedImageIndex, o return fmt.Errorf("signed image index: %w", err) } if ii != nil { - if err := remote.WriteIndex(ref, ii, o.ROpt...); err != nil { + if err := goremote.WriteIndex(ref, ii, o.ROpt...); err != nil { return fmt.Errorf("writing index: %w", err) } } @@ -92,6 +92,25 @@ func WriteSignedImageIndexImages(ref name.Reference, sii oci.SignedImageIndex, o } return remoteWrite(attsTag, atts, o.ROpt...) } + + // write the bundles + bundles, err := sii.Bundles() + if err != nil { + return err + } + if bundles != nil { // will be nil if there are no associated bundles + if digest, ok := ref.(name.Digest); ok { + se, err := SignedEntity(ref, opts...) + if err != nil { + return err + } + err = WriteSignaturesExperimentalOCI(digest, se, opts...) + if err != nil { + return err + } + } + } + return nil } diff --git a/pkg/oci/signed/image.go b/pkg/oci/signed/image.go index ccdc383efab..1d2fb0baf6e 100644 --- a/pkg/oci/signed/image.go +++ b/pkg/oci/signed/image.go @@ -47,6 +47,11 @@ func (*image) Attestations() (oci.Signatures, error) { return empty.Signatures(), nil } +// Bundles implements oci.SignedImage +func (*image) Bundles() (oci.Signatures, error) { + return empty.Signatures(), nil +} + // Attestations implements oci.SignedImage func (*image) Attachment(name string) (oci.File, error) { //nolint: revive return nil, errors.New("unimplemented") diff --git a/pkg/oci/signed/index.go b/pkg/oci/signed/index.go index 61da79403c2..8c5f1149848 100644 --- a/pkg/oci/signed/index.go +++ b/pkg/oci/signed/index.go @@ -68,6 +68,11 @@ func (*index) Attestations() (oci.Signatures, error) { return empty.Signatures(), nil } +// Bundles implements oci.SignedImageIndex +func (*index) Bundles() (oci.Signatures, error) { + return empty.Signatures(), nil +} + // Attestations implements oci.SignedImage func (*index) Attachment(name string) (oci.File, error) { //nolint: revive return nil, errors.New("unimplemented") diff --git a/test/e2e_insecure_registry_test.go b/test/e2e_insecure_registry_test.go index d59273797e4..e34f17bee94 100644 --- a/test/e2e_insecure_registry_test.go +++ b/test/e2e_insecure_registry_test.go @@ -94,7 +94,7 @@ func TestInsecureRegistry(t *testing.T) { } } must(sign.SignCmd(t.Context(), ro, ko, so, []string{imgName}), t) - mustErr(verify(pubKey, imgName, true, nil, "", false), t) + mustErr(verify(pubKey, imgName, true, nil, "", false, false), t) cmd := cliverify.VerifyCommand{ KeyRef: pubKey, CheckClaims: true, diff --git a/test/e2e_kms_test.go b/test/e2e_kms_test.go index 2d212c85d52..2b686b58be9 100644 --- a/test/e2e_kms_test.go +++ b/test/e2e_kms_test.go @@ -59,7 +59,7 @@ func TestSecretsKMS(t *testing.T) { privKey := kms // Verify should fail at first - mustErr(verify(pubKey, imgName, true, nil, "", false), t) + mustErr(verify(pubKey, imgName, true, nil, "", false, false), t) rekorURL := os.Getenv(rekorURLVar) @@ -76,10 +76,10 @@ func TestSecretsKMS(t *testing.T) { TlogUpload: true, } must(sign.SignCmd(t.Context(), ro, ko, so, []string{imgName}), t) - must(verify(pubKey, imgName, true, nil, "", false), t) + must(verify(pubKey, imgName, true, nil, "", false, false), t) // Sign and verify with annotations - mustErr(verify(pubKey, imgName, true, map[string]any{"foo": "bar"}, "", false), t) + mustErr(verify(pubKey, imgName, true, map[string]any{"foo": "bar"}, "", false, false), t) soAnno := options.SignOptions{ Upload: true, TlogUpload: true, @@ -88,11 +88,11 @@ func TestSecretsKMS(t *testing.T) { }, } must(sign.SignCmd(t.Context(), ro, ko, soAnno, []string{imgName}), t) - must(verify(pubKey, imgName, true, map[string]any{"foo": "bar"}, "", false), t) + must(verify(pubKey, imgName, true, map[string]any{"foo": "bar"}, "", false, false), t) // Store signatures in a different repo t.Setenv("COSIGN_REPOSITORY", path.Join(repo, "subbedrepo")) must(sign.SignCmd(t.Context(), ro, ko, so, []string{imgName}), t) - must(verify(pubKey, imgName, true, nil, "", false), t) + must(verify(pubKey, imgName, true, nil, "", false, false), t) os.Unsetenv("COSIGN_REPOSITORY") } diff --git a/test/e2e_test.go b/test/e2e_test.go index fc6d049bb2b..2a0ef28cc26 100644 --- a/test/e2e_test.go +++ b/test/e2e_test.go @@ -108,7 +108,7 @@ func TestSignVerify(t *testing.T) { ctx := context.Background() // Verify should fail at first - mustErr(verify(pubKeyPath, imgName, true, nil, "", false), t) + mustErr(verify(pubKeyPath, imgName, true, nil, "", false, false), t) // So should download mustErr(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName, os.Stdout), t) @@ -126,7 +126,7 @@ func TestSignVerify(t *testing.T) { must(sign.SignCmd(ctx, ro, ko, so, []string{imgName}), t) // Now verify and download should work! - must(verify(pubKeyPath, imgName, true, nil, "", false), t) + must(verify(pubKeyPath, imgName, true, nil, "", false, false), t) must(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName, os.Stdout), t) // Ensure it verifies if you default to the new protobuf bundle format @@ -138,7 +138,7 @@ func TestSignVerify(t *testing.T) { must(cmd.Exec(ctx, []string{imgName}), t) // Look for a specific annotation - mustErr(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar"}, "", false), t) + mustErr(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar"}, "", false, false), t) so.AnnotationOptions = options.AnnotationOptions{ Annotations: []string{"foo=bar"}, @@ -147,10 +147,10 @@ func TestSignVerify(t *testing.T) { must(sign.SignCmd(ctx, ro, ko, so, []string{imgName}), t) // It should match this time. - must(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar"}, "", false), t) + must(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar"}, "", false, false), t) // But two doesn't work - mustErr(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar", "baz": "bat"}, "", false), t) + mustErr(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar", "baz": "bat"}, "", false, false), t) } func TestSignVerifyCertBundle(t *testing.T) { @@ -246,14 +246,14 @@ func TestSignVerifyClean(t *testing.T) { must(sign.SignCmd(ctx, ro, ko, so, []string{imgName}), t) // Now verify and download should work! - must(verify(pubKeyPath, imgName, true, nil, "", false), t) + must(verify(pubKeyPath, imgName, true, nil, "", false, false), t) must(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName, os.Stdout), t) // Now clean signature from the given image must(cli.CleanCmd(ctx, options.RegistryOptions{}, "all", imgName, true), t) // It doesn't work - mustErr(verify(pubKeyPath, imgName, true, nil, "", false), t) + mustErr(verify(pubKeyPath, imgName, true, nil, "", false, false), t) } func TestImportSignVerifyClean(t *testing.T) { @@ -288,14 +288,14 @@ func TestImportSignVerifyClean(t *testing.T) { must(sign.SignCmd(ctx, ro, ko, so, []string{imgName}), t) // Now verify and download should work! - must(verify(pubKeyPath, imgName, true, nil, "", false), t) + must(verify(pubKeyPath, imgName, true, nil, "", false, false), t) must(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName, os.Stdout), t) // Now clean signature from the given image must(cli.CleanCmd(ctx, options.RegistryOptions{}, "all", imgName, true), t) // It doesn't work - mustErr(verify(pubKeyPath, imgName, true, nil, "", false), t) + mustErr(verify(pubKeyPath, imgName, true, nil, "", false, false), t) } type targetInfo struct { @@ -1458,7 +1458,7 @@ func attestVerify(t *testing.T, newBundleFormat bool, predicateType, attestation must(verifyAttestation.Exec(ctx, []string{imgName}), t) // Look for a specific annotation - mustErr(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar"}, "", false), t) + mustErr(verify(pubKeyPath, imgName, true, map[string]interface{}{"foo": "bar"}, "", false, false), t) } func TestAttestationDownload(t *testing.T) { @@ -2311,7 +2311,7 @@ func TestRekorBundle(t *testing.T) { // Sign the image must(sign.SignCmd(t.Context(), ro, ko, so, []string{imgName}), t) // Make sure verify works - must(verify(pubKeyPath, imgName, true, nil, "", false), t) + must(verify(pubKeyPath, imgName, true, nil, "", false, false), t) // Make sure offline verification works with bundling must(verifyOffline(pubKeyPath, imgName, true, nil, ""), t) @@ -2349,7 +2349,7 @@ func TestRekorOutput(t *testing.T) { // Sign the image must(sign.SignCmd(t.Context(), ro, ko, so, []string{imgName}), t) // Make sure verify works - must(verify(pubKeyPath, imgName, true, nil, "", false), t) + must(verify(pubKeyPath, imgName, true, nil, "", false, false), t) if file, err := os.ReadFile(bundlePath); err != nil { t.Fatal(err) @@ -2396,7 +2396,7 @@ func TestFulcioBundle(t *testing.T) { // Sign the image must(sign.SignCmd(t.Context(), ro, ko, so, []string{imgName}), t) // Make sure verify works - must(verify(pubKeyPath, imgName, true, nil, "", false), t) + must(verify(pubKeyPath, imgName, true, nil, "", false, false), t) // Make sure offline verification works with bundling // use rekor prod since we have hardcoded the public key @@ -2539,7 +2539,7 @@ func TestDuplicateSign(t *testing.T) { ctx := context.Background() // Verify should fail at first - mustErr(verify(pubKeyPath, imgName, true, nil, "", true), t) + mustErr(verify(pubKeyPath, imgName, true, nil, "", true, false), t) // So should download mustErr(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName, os.Stdout), t) @@ -2555,7 +2555,7 @@ func TestDuplicateSign(t *testing.T) { // Now verify and download should work! // Ignore the tlog, because uploading to the tlog causes new signatures with new timestamp entries to be appended. - must(verify(pubKeyPath, imgName, true, nil, "", true), t) + must(verify(pubKeyPath, imgName, true, nil, "", true, false), t) must(download.SignatureCmd(ctx, options.RegistryOptions{}, imgName, os.Stdout), t) // Signing again should work just fine... @@ -2580,7 +2580,7 @@ func TestKeyURLVerify(t *testing.T) { keyRef := "https://raw.githubusercontent.com/GoogleContainerTools/distroless/main/cosign.pub" img := "gcr.io/distroless/base:latest" - must(verify(keyRef, img, true, nil, "", false), t) + must(verify(keyRef, img, true, nil, "", false, false), t) } func TestGenerateKeyPairEnvVar(t *testing.T) { @@ -2661,8 +2661,8 @@ func TestMultipleSignatures(t *testing.T) { _, priv2, pub2 := keypair(t, td2) // Verify should fail at first for both keys - mustErr(verify(pub1, imgName, true, nil, "", false), t) - mustErr(verify(pub2, imgName, true, nil, "", false), t) + mustErr(verify(pub1, imgName, true, nil, "", false, false), t) + mustErr(verify(pub2, imgName, true, nil, "", false, false), t) // Now sign the image with one key ko := options.KeyOpts{ @@ -2677,16 +2677,16 @@ func TestMultipleSignatures(t *testing.T) { } must(sign.SignCmd(t.Context(), ro, ko, so, []string{imgName}), t) // Now verify should work with that one, but not the other - must(verify(pub1, imgName, true, nil, "", false), t) - mustErr(verify(pub2, imgName, true, nil, "", false), t) + must(verify(pub1, imgName, true, nil, "", false, false), t) + mustErr(verify(pub2, imgName, true, nil, "", false, false), t) // Now sign with the other key too ko.KeyRef = priv2 must(sign.SignCmd(t.Context(), ro, ko, so, []string{imgName}), t) // Now verify should work with both - must(verify(pub1, imgName, true, nil, "", false), t) - must(verify(pub2, imgName, true, nil, "", false), t) + must(verify(pub1, imgName, true, nil, "", false, false), t) + must(verify(pub2, imgName, true, nil, "", false, false), t) } func TestSignBlob(t *testing.T) { @@ -3122,14 +3122,22 @@ func TestSaveLoad(t *testing.T) { tests := []struct { description string getSignedEntity func(t *testing.T, n string) (name.Reference, *remote.Descriptor, func()) + newBundle bool }{ { description: "save and load an image", getSignedEntity: mkimage, + newBundle: false, + }, + { + description: "save and load an image bundle", + getSignedEntity: mkimage, + newBundle: true, }, { description: "save and load an image index", getSignedEntity: mkimageindex, + newBundle: false, }, } for i, test := range tests { @@ -3154,23 +3162,24 @@ func TestSaveLoad(t *testing.T) { SkipConfirmation: true, } so := options.SignOptions{ - Upload: true, - TlogUpload: true, + Upload: true, + TlogUpload: true, + NewBundleFormat: test.newBundle, } must(sign.SignCmd(ctx, ro, ko, so, []string{imgName}), t) - must(verify(pubKeyPath, imgName, true, nil, "", false), t) + must(verify(pubKeyPath, imgName, true, nil, "", false, test.newBundle), t) // save the image to a temp dir imageDir := t.TempDir() must(cli.SaveCmd(ctx, options.SaveOptions{Directory: imageDir}, imgName), t) // verify the local image using a local key - must(verifyLocal(pubKeyPath, imageDir, true, nil, ""), t) + must(verifyLocal(pubKeyPath, imageDir, true, nil, "", test.newBundle), t) // load the image from the temp dir into a new image and verify the new image imgName2 := path.Join(repo, fmt.Sprintf("save-load-%d-2", i)) must(cli.LoadCmd(ctx, options.LoadOptions{Directory: imageDir}, imgName2), t) - must(verify(pubKeyPath, imgName2, true, nil, "", false), t) + must(verify(pubKeyPath, imgName2, true, nil, "", false, test.newBundle), t) }) } } @@ -3205,7 +3214,7 @@ func TestSaveLoadAttestation(t *testing.T) { TlogUpload: true, } must(sign.SignCmd(ctx, ro, ko, so, []string{imgName}), t) - must(verify(pubKeyPath, imgName, true, nil, "", false), t) + must(verify(pubKeyPath, imgName, true, nil, "", false, false), t) // now, append an attestation to the image slsaAttestation := `{ "buildType": "x", "builder": { "id": "2" }, "recipe": {} }` @@ -3232,7 +3241,7 @@ func TestSaveLoadAttestation(t *testing.T) { // load the image from the temp dir into a new image and verify the new image imgName2 := path.Join(repo, "save-load-2") must(cli.LoadCmd(ctx, options.LoadOptions{Directory: imageDir}, imgName2), t) - must(verify(pubKeyPath, imgName2, true, nil, "", false), t) + must(verify(pubKeyPath, imgName2, true, nil, "", false, false), t) // Use cue to verify attestation on the new image policyPath := filepath.Join(td, "policy.cue") verifyAttestation := cliverify.VerifyAttestationCommand{ @@ -3405,8 +3414,8 @@ func TestAttachSBOM(t *testing.T) { _, _, pubKeyPath2 := keypair(t, td2) // Verify should fail on a bad input - mustErr(verify(pubKeyPath1, imgName, true, nil, "sbom", false), t) - mustErr(verify(pubKeyPath2, imgName, true, nil, "sbom", false), t) + mustErr(verify(pubKeyPath1, imgName, true, nil, "sbom", false, false), t) + mustErr(verify(pubKeyPath2, imgName, true, nil, "sbom", false, false), t) // Now sign the sbom with one key ko1 := options.KeyOpts{ @@ -3422,8 +3431,8 @@ func TestAttachSBOM(t *testing.T) { must(sign.SignCmd(ctx, ro, ko1, so, []string{imgName}), t) // Now verify should work with that one, but not the other - must(verify(pubKeyPath1, imgName, true, nil, "sbom", false), t) - mustErr(verify(pubKeyPath2, imgName, true, nil, "sbom", false), t) + must(verify(pubKeyPath1, imgName, true, nil, "sbom", false, false), t) + mustErr(verify(pubKeyPath2, imgName, true, nil, "sbom", false, false), t) } func TestNoTlog(t *testing.T) { @@ -3439,7 +3448,7 @@ func TestNoTlog(t *testing.T) { _, privKeyPath, pubKeyPath := keypair(t, td) // Verify should fail at first - mustErr(verify(pubKeyPath, imgName, true, nil, "", true), t) + mustErr(verify(pubKeyPath, imgName, true, nil, "", true, false), t) // Now sign the image without the tlog ko := options.KeyOpts{ @@ -3453,7 +3462,7 @@ func TestNoTlog(t *testing.T) { must(sign.SignCmd(t.Context(), ro, ko, so, []string{imgName}), t) // Now verify should work! - must(verify(pubKeyPath, imgName, true, nil, "", true), t) + must(verify(pubKeyPath, imgName, true, nil, "", true, false), t) } func TestGetPublicKeyCustomOut(t *testing.T) { @@ -3512,7 +3521,7 @@ func TestInvalidBundle(t *testing.T) { } must(sign.SignCmd(ctx, ro, ko, so, []string{img1}), t) // verify image1 - must(verify(pubKeyPath, img1, true, nil, "", false), t) + must(verify(pubKeyPath, img1, true, nil, "", false, false), t) // extract the bundle from image1 si, err := ociremote.SignedImage(imgRef, remoteOpts) must(err, t) @@ -3539,7 +3548,7 @@ func TestInvalidBundle(t *testing.T) { TlogUpload: false, } must(sign.SignCmd(ctx, ro, ko, so, []string{img2}), t) - must(verify(pubKeyPath, img2, true, nil, "", true), t) + must(verify(pubKeyPath, img2, true, nil, "", true, false), t) si2, err := ociremote.SignedEntity(imgRef2, remoteOpts) must(err, t) @@ -3561,7 +3570,7 @@ func TestInvalidBundle(t *testing.T) { if err := remote.Delete(sigsTag); err != nil { t.Fatal(err) } - mustErr(verify(pubKeyPath, img2, true, nil, "", false), t) + mustErr(verify(pubKeyPath, img2, true, nil, "", false, false), t) newSig, err := mutate.Signature(gottenSigs2[0], mutate.WithBundle(bund)) must(err, t) @@ -3711,7 +3720,7 @@ func TestOffline(t *testing.T) { } must(sign.SignCmd(ctx, ro, ko, so, []string{img1}), t) // verify image1 online and offline - must(verify(pubKeyPath, img1, true, nil, "", false), t) + must(verify(pubKeyPath, img1, true, nil, "", false, false), t) verifyCmd := &cliverify.VerifyCommand{ KeyRef: pubKeyPath, RekorURL: "notreal", @@ -3750,7 +3759,7 @@ func TestOffline(t *testing.T) { newImage, err := mutate.AttachSignatureToEntity(si, newSig) must(err, t) - mustErr(verify(pubKeyPath, img1, true, nil, "", false), t) + mustErr(verify(pubKeyPath, img1, true, nil, "", false, false), t) if err := ociremote.WriteSignatures(sigsTag.Repository, newImage); err != nil { t.Fatal(err) } @@ -4047,7 +4056,7 @@ func TestSignVerifyWithRepoOverride(t *testing.T) { _, privKeyPath, pubKeyPath := keypair(t, td) // Verify should fail at first - mustErr(verify(pubKeyPath, imgName, true, nil, "", false), t) + mustErr(verify(pubKeyPath, imgName, true, nil, "", false, false), t) // No artifacts yet in the second registry _, err = crane.ListTags(cosignRepo) @@ -4088,7 +4097,7 @@ func TestSignVerifyWithRepoOverride(t *testing.T) { assert.Equal(t, tags[0], "latest", "expected tag name to be 'latest'") // Now verify and download should work! - must(verify(pubKeyPath, imgName, true, nil, "", false), t) + must(verify(pubKeyPath, imgName, true, nil, "", false, false), t) // Sign another image with the new protobuf bundle format so.NewBundleFormat = true @@ -4135,7 +4144,7 @@ func TestSignVerifyMultipleIdentities(t *testing.T) { _, privKeyPath, pubKeyPath := keypair(t, td) // Verify should fail at first - mustErr(verify(pubKeyPath, imgName, true, nil, "", false), t) + mustErr(verify(pubKeyPath, imgName, true, nil, "", false, false), t) // Now sign the image with multiple container identities ko := options.KeyOpts{ @@ -4152,7 +4161,7 @@ func TestSignVerifyMultipleIdentities(t *testing.T) { must(sign.SignCmd(t.Context(), ro, ko, so, []string{imgName}), t) // Now verify should work - must(verify(pubKeyPath, imgName, true, nil, "", false), t) + must(verify(pubKeyPath, imgName, true, nil, "", false, false), t) } func TestTree(t *testing.T) { diff --git a/test/helpers.go b/test/helpers.go index 46ed4291b3b..33e01c99515 100644 --- a/test/helpers.go +++ b/test/helpers.go @@ -74,16 +74,17 @@ var passFunc = func(_ bool) ([]byte, error) { return keyPass, nil } -var verify = func(keyRef, imageRef string, checkClaims bool, annotations map[string]interface{}, attachment string, skipTlogVerify bool) error { +var verify = func(keyRef, imageRef string, checkClaims bool, annotations map[string]interface{}, attachment string, skipTlogVerify, newBundle bool) error { cmd := cliverify.VerifyCommand{ - KeyRef: keyRef, - RekorURL: rekorURL, - CheckClaims: checkClaims, - Annotations: sigs.AnnotationsMap{Annotations: annotations}, - Attachment: attachment, - HashAlgorithm: crypto.SHA256, - MaxWorkers: 10, - IgnoreTlog: skipTlogVerify, + KeyRef: keyRef, + RekorURL: rekorURL, + CheckClaims: checkClaims, + Annotations: sigs.AnnotationsMap{Annotations: annotations}, + Attachment: attachment, + HashAlgorithm: crypto.SHA256, + MaxWorkers: 10, + IgnoreTlog: skipTlogVerify, + NewBundleFormat: newBundle, } args := []string{imageRef} @@ -222,16 +223,17 @@ var verifyBlobKeylessWithCARoots = func(blobRef string, } // Used to verify local images stored on disk -var verifyLocal = func(keyRef, path string, checkClaims bool, annotations map[string]interface{}, attachment string) error { +var verifyLocal = func(keyRef, path string, checkClaims bool, annotations map[string]interface{}, attachment string, newBundle bool) error { cmd := cliverify.VerifyCommand{ - KeyRef: keyRef, - RekorURL: rekorURL, - CheckClaims: checkClaims, - Annotations: sigs.AnnotationsMap{Annotations: annotations}, - Attachment: attachment, - HashAlgorithm: crypto.SHA256, - LocalImage: true, - MaxWorkers: 10, + KeyRef: keyRef, + RekorURL: rekorURL, + CheckClaims: checkClaims, + Annotations: sigs.AnnotationsMap{Annotations: annotations}, + Attachment: attachment, + HashAlgorithm: crypto.SHA256, + LocalImage: true, + MaxWorkers: 10, + NewBundleFormat: newBundle, } args := []string{path}