Repository: sigstore/cosign
Pull request #1199 (2021-12-14 02:25:02)

create `mutate` functions for `oci.Signature`
#### Ticket Link
#844

#### Release Note
```release-note
NONE
```



Patch:
diff --git a/pkg/cosign/tuf/repository/snapshot.json b/pkg/cosign/tuf/repository/snapshot.json
index 1c30348b..069f570a 100644
--- a/pkg/cosign/tuf/repository/snapshot.json
+++ b/pkg/cosign/tuf/repository/snapshot.json
@@ -2,12 +2,12 @@
 	"signatures": [
 		{
 			"keyid": "fc61191ba8a516fe386c7d6c97d918e1d241e1589729add09b122725b8c32451",
-			"sig": "3045022073d6fbb3f7aa59403518ce524a516f404cbd37ff0a5be4f6752ff32d84fa3b07022100ff9ce494d8999d030f8667e238d313e35a0c2f45b58854efd5551bbbb37df8df"
+			"sig": "30460221008e1bb65e41e77edc8b6ef71697dfcf363ce34bcc3400f5f5fa38fbd2de13c689022100bf6c20f73999851a63e51a3e6b9befa0fd49052bbc960636c04fac8adcc5f116"
 		}
 	],
 	"signed": {
 		"_type": "snapshot",
-		"expires": "2021-12-14T18:48:46Z",
+		"expires": "2021-12-28T20:16:09Z",
 		"meta": {
 			"rekor.json": {
 				"hashes": {
@@ -43,6 +43,6 @@
 			}
 		},
 		"spec_version": "1.0",
-		"version": 3
+		"version": 4
 	}
 }
\ No newline at end of file
diff --git a/pkg/cosign/tuf/repository/timestamp.json b/pkg/cosign/tuf/repository/timestamp.json
index 460f4aa6..91f05030 100644
--- a/pkg/cosign/tuf/repository/timestamp.json
+++ b/pkg/cosign/tuf/repository/timestamp.json
@@ -2,23 +2,23 @@
 	"signatures": [
 		{
 			"keyid": "b6710623a30c010738e64c5209d367df1c0a18cf90e6ab5292fb01680f83453d",
-			"sig": "3045022100971f12878d91bfe4049738b980395d8db52b0f7d427357cdf28825a756ca13a50220094c09595c9543441f4934ca0bad8b6da0fa6a8582b406534f7e4ca0d4b02047"
+			"sig": "3046022100f338d7fd499b09c19263e8bbb9ab75e2b0ea88cf37dda5845999110d122e96d9022100b4deaf107f9df1da52dba9fb302aa8196e697d09bb97d07f0ac8d5dce5cb32df"
 		}
 	],
 	"signed": {
 		"_type": "timestamp",
-		"expires": "2021-12-14T18:48:47Z",
+		"expires": "2021-12-28T20:16:10Z",
 		"meta": {
 			"snapshot.json": {
 				"hashes": {
-					"sha256": "d484ba368256f88d5ed31c5ad89719859bb6c4ad93db271691027468fa73b8bb",
-					"sha512": "0ee8061c0096ed967cef19d0cef04933bd0cce0ff5949c4e9d8b0e0c4df955bcb9ba0b39415556e23064ff6ecc2423cc2023de77737b48102be5ed85c25025aa"
+					"sha256": "cbd07f1284315c3cddf2c478dcf97c33e45b7ec2d840d145f3a50c1754091242",
+					"sha512": "b6cfac25a0da3b7263f3bdb201453d6202849b649f4e89a4542699d437804217f7022e206e2fc49b7e42157093428a06889ff8375a35b8827e40bd81ccfa2b89"
 				},
-				"length": 1656,
-				"version": 3
+				"length": 1658,
+				"version": 4
 			}
 		},
 		"spec_version": "1.0",
-		"version": 3
+		"version": 4
 	}
 }
\ No newline at end of file
diff --git a/pkg/oci/mutate/options.go b/pkg/oci/mutate/options.go
index 458865dc..f379779e 100644
--- a/pkg/oci/mutate/options.go
+++ b/pkg/oci/mutate/options.go
@@ -15,7 +15,10 @@
 
 package mutate
 
-import "github.com/sigstore/cosign/pkg/oci"
+import (
+	"github.com/google/go-containerregistry/pkg/v1/types"
+	"github.com/sigstore/cosign/pkg/oci"
+)
 
 // DupeDetector scans a list of signatures looking for a duplicate.
 type DupeDetector interface {
@@ -54,3 +57,50 @@ func WithReplaceOp(ro ReplaceOp) SignOption {
 		so.ro = ro
 	}
 }
+
+type signatureOpts struct {
+	annotations map[string]string
+	bundle      *oci.Bundle
+	cert        []byte
+	chain       []byte
+	mediaType   types.MediaType
+}
+
+type SignatureOption func(*signatureOpts)
+
+// WithAnnotations specifies the annotations the Signature should have.
+func WithAnnotations(annotations map[string]string) SignatureOption {
+	return func(so *signatureOpts) {
+		so.annotations = annotations
+	}
+}
+
+// WithBundle specifies the new Bundle the Signature should have.
+func WithBundle(bundle *oci.Bundle) SignatureOption {
+	return func(so *signatureOpts) {
+		so.bundle = bundle
+	}
+}
+
+// WithCertChain specifies the new cert and chain the Signature should have.
+func WithCertChain(cert, chain []byte) SignatureOption {
+	return func(so *signatureOpts) {
+		so.cert = cert
+		so.chain = chain
+	}
+}
+
+// WithMediaType specifies the new MediaType the Signature should have.
+func WithMediaType(mediaType types.MediaType) SignatureOption {
+	return func(so *signatureOpts) {
+		so.mediaType = mediaType
+	}
+}
+
+func makeSignatureOption(opts ...SignatureOption) *signatureOpts {
+	so := &signatureOpts{}
+	for _, opt := range opts {
+		opt(so)
+	}
+	return so
+}
diff --git a/pkg/oci/mutate/signature.go b/pkg/oci/mutate/signature.go
new file mode 100644
index 00000000..3d021855
--- /dev/null
+++ b/pkg/oci/mutate/signature.go
@@ -0,0 +1,192 @@
+// Copyright 2021 The Sigstore Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package mutate
+
+import (
+	"bytes"
+	"crypto/x509"
+	"encoding/json"
+	"io"
+
+	v1 "github.com/google/go-containerregistry/pkg/v1"
+	"github.com/google/go-containerregistry/pkg/v1/types"
+	"github.com/pkg/errors"
+	"github.com/sigstore/cosign/pkg/oci"
+	"github.com/sigstore/cosign/pkg/oci/static"
+	"github.com/sigstore/sigstore/pkg/cryptoutils"
+)
+
+type sigWrapper struct {
+	wrapped oci.Signature
+
+	annotations map[string]string
+	bundle      *oci.Bundle
+	cert        *x509.Certificate
+	chain       []*x509.Certificate
+	mediaType   types.MediaType
+}
+
+var _ v1.Layer = (*sigWrapper)(nil)
+var _ oci.Signature = (*sigWrapper)(nil)
+
+func copyAnnotations(ann map[string]string) map[string]string {
+	new := make(map[string]string, len(ann))
+	for k, v := range ann {
+		new[k] = v
+	}
+	return new
+}
+
+// Annotations implements oci.Signature.
+func (sw *sigWrapper) Annotations() (map[string]string, error) {
+	if sw.annotations != nil {
+		return copyAnnotations(sw.annotations), nil
+	}
+	return sw.wrapped.Annotations()
+}
+
+// Payload implements oci.Signature.
+func (sw *sigWrapper) Payload() ([]byte, error) {
+	return sw.wrapped.Payload()
+}
+
+// Base64Signature implements oci.Signature.
+func (sw *sigWrapper) Base64Signature() (string, error) {
+	return sw.wrapped.Base64Signature()
+}
+
+// Cert implements oci.Signature.
+func (sw *sigWrapper) Cert() (*x509.Certificate, error) {
+	if sw.cert != nil {
+		return sw.cert, nil
+	}
+	return sw.wrapped.Cert()
+}
+
+// Chain implements oci.Signature.
+func (sw *sigWrapper) Chain() ([]*x509.Certificate, error) {
+	if sw.chain != nil {
+		return sw.chain, nil
+	}
+	return sw.wrapped.Chain()
+}
+
+// Bundle implements oci.Signature.
+func (sw *sigWrapper) Bundle() (*oci.Bundle, error) {
+	if sw.bundle != nil {
+		return sw.bundle, nil
+	}
+	return sw.wrapped.Bundle()
+}
+
+// MediaType implements v1.Layer
+func (sw *sigWrapper) MediaType() (types.MediaType, error) {
+	if sw.mediaType != "" {
+		return sw.mediaType, nil
+	}
+	return sw.wrapped.MediaType()
+}
+
+// Digest implements v1.Layer
+func (sw *sigWrapper) Digest() (v1.Hash, error) {
+	return sw.wrapped.Digest()
+}
+
+// DiffID implements v1.Layer
+func (sw *sigWrapper) DiffID() (v1.Hash, error) {
+	return sw.wrapped.DiffID()
+}
+
+// Compressed implements v1.Layer
+func (sw *sigWrapper) Compressed() (io.ReadCloser, error) {
+	return sw.wrapped.Compressed()
+}
+
+// Uncompressed implements v1.Layer
+func (sw *sigWrapper) Uncompressed() (io.ReadCloser, error) {
+	return sw.wrapped.Uncompressed()
+}
+
+// Size implements v1.Layer
+func (sw *sigWrapper) Size() (int64, error) {
+	return sw.wrapped.Size()
+}
+
+// Signature returns a new oci.Signature based on the provided original, plus the requested mutations.
+func Signature(original oci.Signature, opts ...SignatureOption) (oci.Signature, error) {
+	newSig := sigWrapper{wrapped: original}
+
+	so := makeSignatureOption(opts...)
+	oldAnn, err := original.Annotations()
+	if err != nil {
+		return nil, errors.Wrap(err, "could not get annotations from signature to mutate")
+	}
+
+	var newAnn map[string]string
+	if so.annotations != nil {
+		newAnn = copyAnnotations(so.annotations)
+		newAnn[static.SignatureAnnotationKey] = oldAnn[static.SignatureAnnotationKey]
+		for _, key := range []string{static.BundleAnnotationKey, static.CertificateAnnotationKey, static.ChainAnnotationKey} {
+			if val, isSet := oldAnn[key]; isSet {
+				newAnn[key] = val
+			} else {
+				delete(newAnn, key)
+			}
+		}
+	} else {
+		newAnn = copyAnnotations(oldAnn)
+	}
+
+	if so.bundle != nil {
+		newSig.bundle = so.bundle
+		b, err := json.Marshal(so.bundle)
+		if err != nil {
+			return nil, err
+		}
+		newAnn[static.BundleAnnotationKey] = string(b)
+	}
+
+	if so.cert != nil {
+		var cert *x509.Certificate
+		var chain []*x509.Certificate
+
+		certs, err := cryptoutils.LoadCertificatesFromPEM(bytes.NewReader(so.cert))
+		if err != nil {
+			return nil, err
+		}
+		newAnn[static.CertificateAnnotationKey] = string(so.cert)
+		cert = certs[0]
+
+		delete(newAnn, static.ChainAnnotationKey)
+		if so.chain != nil {
+			chain, err = cryptoutils.LoadCertificatesFromPEM(bytes.NewReader(so.chain))
+			if err != nil {
+				return nil, err
+			}
+			newAnn[static.ChainAnnotationKey] = string(so.chain)
+		}
+
+		newSig.cert = cert
+		newSig.chain = chain
+	}
+
+	if so.mediaType != "" {
+		newSig.mediaType = so.mediaType
+	}
+
+	newSig.annotations = newAnn
+
+	return &newSig, nil
+}


Test patch:
diff --git a/pkg/oci/mutate/signature_test.go b/pkg/oci/mutate/signature_test.go
new file mode 100644
index 00000000..ca3cead4
--- /dev/null
+++ b/pkg/oci/mutate/signature_test.go
@@ -0,0 +1,381 @@
+// Copyright 2021 The Sigstore Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package mutate
+
+import (
+	"encoding/base64"
+	"io"
+	"testing"
+
+	"github.com/google/go-cmp/cmp"
+	"github.com/google/go-containerregistry/pkg/v1/types"
+	"github.com/sigstore/cosign/pkg/oci"
+	"github.com/sigstore/cosign/pkg/oci/static"
+)
+
+var (
+	testCertBytes = []byte(`
+-----BEGIN CERTIFICATE-----
+MIICjzCCAhSgAwIBAgITV2heiswW9YldtVEAu98QxDO8TTAKBggqhkjOPQQDAzAq
+MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIx
+MDkxNDE5MTI0MFoXDTIxMDkxNDE5MzIzOVowADBZMBMGByqGSM49AgEGCCqGSM49
+AwEHA0IABMF1AWZcfvubslc4ABNnvGbRjm6GWVHxrJ1RRthTHMCE4FpFmiHQBfGt
+6n80DqszGj77Whb35O33+Dal4Y2po+CjggFBMIIBPTAOBgNVHQ8BAf8EBAMCB4Aw
+EwYDVR0lBAwwCgYIKwYBBQUHAwMwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU340G
+3G1ozVNmFC5TBFV0yNuouvowHwYDVR0jBBgwFoAUyMUdAEGaJCkyUSTrDa5K7UoG
+0+wwgY0GCCsGAQUFBwEBBIGAMH4wfAYIKwYBBQUHMAKGcGh0dHA6Ly9wcml2YXRl
+Y2EtY29udGVudC02MDNmZTdlNy0wMDAwLTIyMjctYmY3NS1mNGY1ZTgwZDI5NTQu
+c3RvcmFnZS5nb29nbGVhcGlzLmNvbS9jYTM2YTFlOTYyNDJiOWZjYjE0Ni9jYS5j
+cnQwOAYDVR0RAQH/BC4wLIEqa2V5bGVzc0BkaXN0cm9sZXNzLmlhbS5nc2Vydmlj
+ZWFjY291bnQuY29tMAoGCCqGSM49BAMDA2kAMGYCMQDcH9cdkxW6ugsbPHqX9qrM
+wlMaprcwnlktS3+5xuABr5icuqwrB/Fj5doFtS7AnM0CMQD9MjSaUmHFFF7zoLMx
+uThR1Z6JuA21HwxtL3GyJ8UQZcEPOlTBV593HrSAwBhiCoY=
+-----END CERTIFICATE-----
+`)
+	testChainBytes = []byte(`
+-----BEGIN CERTIFICATE-----
+MIIB+DCCAX6gAwIBAgITNVkDZoCiofPDsy7dfm6geLbuhzAKBggqhkjOPQQDAzAq
+MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxETAPBgNVBAMTCHNpZ3N0b3JlMB4XDTIx
+MDMwNzAzMjAyOVoXDTMxMDIyMzAzMjAyOVowKjEVMBMGA1UEChMMc2lnc3RvcmUu
+ZGV2MREwDwYDVQQDEwhzaWdzdG9yZTB2MBAGByqGSM49AgEGBSuBBAAiA2IABLSy
+A7Ii5k+pNO8ZEWY0ylemWDowOkNa3kL+GZE5Z5GWehL9/A9bRNA3RbrsZ5i0Jcas
+taRL7Sp5fp/jD5dxqc/UdTVnlvS16an+2Yfswe/QuLolRUCrcOE2+2iA5+tzd6Nm
+MGQwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE
+FMjFHQBBmiQpMlEk6w2uSu1KBtPsMB8GA1UdIwQYMBaAFMjFHQBBmiQpMlEk6w2u
+Su1KBtPsMAoGCCqGSM49BAMDA2gAMGUCMH8liWJfMui6vXXBhjDgY4MwslmN/TJx
+Ve/83WrFomwmNf056y1X48F9c4m3a3ozXAIxAKjRay5/aj/jsKKGIkmQatjI8uup
+Hr/+CxFvaJWmpYqNkLDGRU+9orzh5hI2RrcuaQ==
+-----END CERTIFICATE-----
+`)
+)
+
+func mustCreateSignature(t *testing.T, payload []byte, b64sig string, opts ...static.Option) oci.Signature {
+	t.Helper()
+	sig, err := static.NewSignature(payload, b64sig, opts...)
+	if err != nil {
+		t.Fatalf("failed to create static signature: %v", err)
+	}
+	return sig
+}
+
+func mustBase64Decode(t *testing.T, s string) []byte {
+	t.Helper()
+	b, err := base64.StdEncoding.DecodeString(s)
+	if err != nil {
+		t.Fatalf("failed to base64 decode: %v", err)
+	}
+	return b
+}
+
+func assertSignaturesEqual(t *testing.T, wanted, got oci.Signature) {
+	t.Helper()
+
+	t.Run("Payloads match", func(t *testing.T) {
+		t.Helper()
+		wantedPayload, err := wanted.Payload()
+		if err != nil {
+			t.Fatalf("wanted.Payload() returned error: %v", err)
+		}
+		gotPayload, err := got.Payload()
+		if err != nil {
+			t.Fatalf("got.Payload() returned error: %v", err)
+		}
+		if diff := cmp.Diff(wantedPayload, gotPayload); diff != "" {
+			t.Errorf("Payload() mismatch (-want +got):\n%s", diff)
+		}
+	})
+
+	t.Run("Base64Signatures match", func(t *testing.T) {
+		t.Helper()
+		wantedB64Sig, err := wanted.Base64Signature()
+		if err != nil {
+			t.Fatalf("wanted.Base64Signature() returned error: %v", err)
+		}
+		gotB64Sig, err := got.Base64Signature()
+		if err != nil {
+			t.Fatalf("got.Base64Signature() returned error: %v", err)
+		}
+		if diff := cmp.Diff(wantedB64Sig, gotB64Sig); diff != "" {
+			t.Errorf("Base64Signature() mismatch (-want +got):\n%s", diff)
+		}
+	})
+
+	t.Run("Bundles match", func(t *testing.T) {
+		t.Helper()
+		wantedBundle, err := wanted.Bundle()
+		if err != nil {
+			t.Fatalf("wanted.Bundle() returned error: %v", err)
+		}
+		gotBundle, err := got.Bundle()
+		if err != nil {
+			t.Fatalf("got.Bundle() returned error: %v", err)
+		}
+		if diff := cmp.Diff(wantedBundle, gotBundle); diff != "" {
+			t.Errorf("Bundle() mismatch (-want +got):\n%s", diff)
+		}
+	})
+
+	t.Run("Certs match", func(t *testing.T) {
+		t.Helper()
+		wantedCert, err := wanted.Cert()
+		if err != nil {
+			t.Fatalf("wanted.Bundle() returned error: %v", err)
+		}
+		gotCert, err := got.Cert()
+		if err != nil {
+			t.Fatalf("got.Cert() returned error: %v", err)
+		}
+		if diff := cmp.Diff(wantedCert, gotCert); diff != "" {
+			t.Errorf("Cert() mismatch (-want +got):\n%s", diff)
+		}
+	})
+
+	t.Run("Chains match", func(t *testing.T) {
+		t.Helper()
+		wantedChain, err := wanted.Chain()
+		if err != nil {
+			t.Fatalf("wanted.Bundle() returned error: %v", err)
+		}
+		gotChain, err := got.Chain()
+		if err != nil {
+			t.Fatalf("got.Chain() returned error: %v", err)
+		}
+		if diff := cmp.Diff(wantedChain, gotChain); diff != "" {
+			t.Errorf("Chain() mismatch (-want +got):\n%s", diff)
+		}
+	})
+
+	t.Run("MediaTypes match", func(t *testing.T) {
+		t.Helper()
+		wantedMediaType, err := wanted.MediaType()
+		if err != nil {
+			t.Fatalf("wanted.MediaType() returned error: %v", err)
+		}
+		gotMediaType, err := got.MediaType()
+		if err != nil {
+			t.Fatalf("got.MediaType() returned error: %v", err)
+		}
+		if diff := cmp.Diff(wantedMediaType, gotMediaType); diff != "" {
+			t.Errorf("MediaType() mismatch (-want +got):\n%s", diff)
+		}
+	})
+
+	var gotAnnotations map[string]string
+	t.Run("Annotations match", func(t *testing.T) {
+		t.Helper()
+		wantedAnnotations, err := wanted.Annotations()
+		if err != nil {
+			t.Fatalf("wanted.Annotations() returned error: %v", err)
+		}
+		gotAnnotations, err = got.Annotations()
+		if err != nil {
+			t.Fatalf("got.Annotations() returned error: %v", err)
+		}
+		if diff := cmp.Diff(wantedAnnotations, gotAnnotations); diff != "" {
+			t.Errorf("Annotations() mismatch (-want +got):\n%s", diff)
+		}
+	})
+
+	t.Run("DiffIDs match", func(t *testing.T) {
+		t.Helper()
+		wantedDiffID, err := wanted.DiffID()
+		if err != nil {
+			t.Fatalf("wanted.DiffID() returned error: %v", err)
+		}
+		gotDiffID, err := got.DiffID()
+		if err != nil {
+			t.Fatalf("got.DiffID() returned error: %v", err)
+		}
+		if wantedDiffID != gotDiffID {
+			t.Errorf("DiffID() mismatch. Wanted: %v, got: %v", wantedDiffID, gotDiffID)
+		}
+	})
+
+	t.Run("Sizes match", func(t *testing.T) {
+		t.Helper()
+		wantedSize, err := wanted.Size()
+		if err != nil {
+			t.Fatalf("wanted.Size() returned error: %v", err)
+		}
+		gotSize, err := got.Size()
+		if err != nil {
+			t.Fatalf("got.Size() returned error: %v", err)
+		}
+		if wantedSize != gotSize {
+			t.Errorf("Size() mismatch. Wanted: %v, got: %v", wantedSize, gotSize)
+		}
+	})
+
+	t.Run("Compressed values match", func(t *testing.T) {
+		t.Helper()
+		wantedCompReader, err := wanted.Compressed()
+		if err != nil {
+			t.Fatalf("wanted.Compressed() returned error: %v", err)
+		}
+		defer wantedCompReader.Close()
+		wantedCompressed, err := io.ReadAll(wantedCompReader)
+		if err != nil {
+			t.Fatalf("io.ReadAll(wanted.Compressed()) returned error: %v", err)
+		}
+		gotCompReader, err := got.Compressed()
+		if err != nil {
+			t.Fatalf("got.Compressed() returned error: %v", err)
+		}
+		defer gotCompReader.Close()
+		gotCompressed, err := io.ReadAll(gotCompReader)
+		if err != nil {
+			t.Fatalf("io.ReadAll(got.Compressed()) returned error: %v", err)
+		}
+		if diff := cmp.Diff(wantedCompressed, gotCompressed); diff != "" {
+			t.Errorf("MediaType() mismatch (-want +got):\n%s", diff)
+		}
+	})
+
+	t.Run("Uncompressed values match", func(t *testing.T) {
+		t.Helper()
+		wantedUncompReader, err := wanted.Uncompressed()
+		if err != nil {
+			t.Fatalf("wanted.Uncompressed() returned error: %v", err)
+		}
+		defer wantedUncompReader.Close()
+		wantedUncompressed, err := io.ReadAll(wantedUncompReader)
+		if err != nil {
+			t.Fatalf("io.ReadAll(wanted.Uncompressed()) returned error: %v", err)
+		}
+		gotUncompReader, err := got.Uncompressed()
+		if err != nil {
+			t.Fatalf("got.Compressed() returned error: %v", err)
+		}
+		defer gotUncompReader.Close()
+		gotUncompressed, err := io.ReadAll(gotUncompReader)
+		if err != nil {
+			t.Fatalf("io.ReadAll(got.Uncompressed()) returned error: %v", err)
+		}
+		if diff := cmp.Diff(wantedUncompressed, gotUncompressed); diff != "" {
+			t.Errorf("MediaType() mismatch (-want +got):\n%s", diff)
+		}
+	})
+}
+
+func TestSignatureWithAnnotations(t *testing.T) {
+	payload := "this is the TestSignatureWithAnnotations content!"
+	b64sig := "b64 content1="
+	annotations := map[string]string{
+		"foo":  "bar",
+		"test": "yes",
+	}
+	originalSig := mustCreateSignature(t, []byte(payload), b64sig)
+	expectedSig := mustCreateSignature(t, []byte(payload), b64sig, static.WithAnnotations(annotations))
+
+	newSig, err := Signature(originalSig, WithAnnotations(annotations))
+	if err != nil {
+		t.Fatalf("Signature(WithAnnotations()) returned error: %v", err)
+	}
+
+	assertSignaturesEqual(t, expectedSig, newSig)
+}
+
+func TestSignatureWithBundle(t *testing.T) {
+	payload := "this is the TestSignatureWithBundle content!"
+	b64sig := "b64 content2="
+	bundle := &oci.Bundle{
+		SignedEntryTimestamp: mustBase64Decode(t, "MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE="),
+		Payload: oci.BundlePayload{
+			Body:           "REMOVED",
+			IntegratedTime: 1631646761,
+			LogIndex:       693591,
+			LogID:          "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d",
+		},
+	}
+	originalSig := mustCreateSignature(t, []byte(payload), b64sig)
+	expectedSig := mustCreateSignature(t, []byte(payload), b64sig, static.WithBundle(bundle))
+
+	newSig, err := Signature(originalSig, WithBundle(bundle))
+	if err != nil {
+		t.Fatalf("Signature(WithBundle()) returned error: %v", err)
+	}
+
+	assertSignaturesEqual(t, expectedSig, newSig)
+}
+
+func TestSignatureWithCertChain(t *testing.T) {
+	payload := "this is the TestSignatureWithCertChain content!"
+	b64sig := "b64 content3="
+
+	originalSig := mustCreateSignature(t, []byte(payload), b64sig)
+	expectedSig := mustCreateSignature(t, []byte(payload), b64sig, static.WithCertChain(testCertBytes, testChainBytes))
+
+	newSig, err := Signature(originalSig, WithCertChain(testCertBytes, testChainBytes))
+	if err != nil {
+		t.Fatalf("Signature(WithCertChain()) returned error: %v", err)
+	}
+
+	assertSignaturesEqual(t, expectedSig, newSig)
+}
+
+func TestSignatureWithMediaType(t *testing.T) {
+	payload := "this is the TestSignatureWithMediaType content!"
+	b64sig := "b64 content4="
+	mediaType := types.MediaType("test/media.type")
+
+	originalSig := mustCreateSignature(t, []byte(payload), b64sig)
+	expectedSig := mustCreateSignature(t, []byte(payload), b64sig, static.WithLayerMediaType(mediaType))
+
+	newSig, err := Signature(originalSig, WithMediaType(mediaType))
+	if err != nil {
+		t.Fatalf("Signature(WithMediaType()) returned error: %v", err)
+	}
+
+	assertSignaturesEqual(t, expectedSig, newSig)
+}
+
+func TestSignatureWithEverything(t *testing.T) {
+	payload := "this is the TestSignatureWithEverything content!"
+	b64sig := "b64 content5="
+	annotations := map[string]string{
+		"foo":  "bar",
+		"test": "yes",
+	}
+	bundle := &oci.Bundle{
+		SignedEntryTimestamp: mustBase64Decode(t, "MEUCIQClUkUqZNf+6dxBc/pxq22JIluTB7Kmip1G0FIF5E0C1wIgLqXm+IM3JYW/P/qjMZSXW+J8bt5EOqNfe3R+0A9ooFE="),
+		Payload: oci.BundlePayload{
+			Body:           "REMOVED",
+			IntegratedTime: 1631646761,
+			LogIndex:       693591,
+			LogID:          "c0d23d6ad406973f9559f3ba2d1ca01f84147d8ffc5b8445c224f98b9591801d",
+		},
+	}
+	mediaType := types.MediaType("test/media.type")
+
+	originalSig := mustCreateSignature(t, []byte(payload), b64sig)
+
+	expectedSig := mustCreateSignature(t, []byte(payload), b64sig,
+		static.WithAnnotations(annotations),
+		static.WithBundle(bundle),
+		static.WithCertChain(testCertBytes, testChainBytes),
+		static.WithLayerMediaType(mediaType))
+
+	newSig, err := Signature(originalSig,
+		WithAnnotations(annotations),
+		WithBundle(bundle),
+		WithCertChain(testCertBytes, testChainBytes),
+		WithMediaType(mediaType))
+
+	if err != nil {
+		t.Fatalf("Signature(With...) returned error: %v", err)
+	}
+
+	assertSignaturesEqual(t, expectedSig, newSig)
+}