From bc9c6d29f3535244b564a42f7d1a93d5963f2a5b Mon Sep 17 00:00:00 2001 From: Hayden <8418760+haydentherapper@users.noreply.github.com> Date: Tue, 16 Sep 2025 16:33:51 -0700 Subject: [PATCH] Fix signing and verification with ed25519 keys with bundles and Rekor With the recent changes we made to use sigstore-go rather than Cosign for signing and verification, ed25519 managed key support broke, because we were incorrectly specifying ed25519ph for dsse Rekor entries and not specifying ed25519ph for hashedrekord entries. This PR correctly sets load options for when signing and verifying a blob (using the prehash variant) and when signing/verifying attestations (using the pure variant). This also fixes a bug where the SignerVerifier Keypair didn't handle crypto.Hash(0) for ed25519, which specifies no hash when signing. This has been tested with sign/verify, sign-blob/verify-blob, attest/verify-attestation, and attest-blob/verify-blob-attestation. Signed-off-by: Hayden <8418760+haydentherapper@users.noreply.github.com> --- cmd/cosign/cli/attest/attest.go | 6 +- cmd/cosign/cli/attest/attest_blob.go | 5 +- cmd/cosign/cli/sign/sign.go | 5 +- cmd/cosign/cli/sign/sign_blob.go | 1 + cmd/cosign/cli/verify/verify.go | 4 +- cmd/cosign/cli/verify/verify_attestation.go | 5 +- cmd/cosign/cli/verify/verify_blob.go | 12 +- .../cli/verify/verify_blob_attestation.go | 9 +- internal/key/svkeypair.go | 17 +- internal/key/svkeypair_test.go | 23 +- pkg/cosign/bundle/sign.go | 7 +- pkg/signature/keys.go | 25 ++- test/e2e_test.go | 212 ++++++++++++++++-- 13 files changed, 284 insertions(+), 47 deletions(-) diff --git a/cmd/cosign/cli/attest/attest.go b/cmd/cosign/cli/attest/attest.go index 7c38f7efb92..011646d8305 100644 --- a/cmd/cosign/cli/attest/attest.go +++ b/cmd/cosign/cli/attest/attest.go @@ -46,6 +46,7 @@ import ( "github.com/sigstore/rekor/pkg/generated/client" "github.com/sigstore/rekor/pkg/generated/models" "github.com/sigstore/sigstore-go/pkg/sign" + "github.com/sigstore/sigstore/pkg/signature" "github.com/sigstore/sigstore/pkg/signature/dsse" signatureoptions "github.com/sigstore/sigstore/pkg/signature/options" ) @@ -164,11 +165,14 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error { var err error if c.Sk || c.Slot != "" || c.KeyRef != "" || c.CertPath != "" { + // Set no load options so that Ed25519 is preferred over Ed25519ph, required for signing DSSEs + var signOpts []signature.LoadOption + c.KeyOpts.DefaultLoadOptions = &signOpts sv, _, err = cosign_sign.SignerFromKeyOpts(ctx, c.CertPath, c.CertChainPath, c.KeyOpts) if err != nil { return fmt.Errorf("getting signer: %w", err) } - keypair, err = key.NewSignerVerifierKeypair(sv, c.DefaultLoadOptions) + keypair, err = key.NewSignerVerifierKeypair(sv, &signOpts) if err != nil { return fmt.Errorf("creating signerverifier keypair: %w", err) } diff --git a/cmd/cosign/cli/attest/attest_blob.go b/cmd/cosign/cli/attest/attest_blob.go index bd369b91acc..11b417b9e66 100644 --- a/cmd/cosign/cli/attest/attest_blob.go +++ b/cmd/cosign/cli/attest/attest_blob.go @@ -165,11 +165,14 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error var err error if c.Sk || c.Slot != "" || c.KeyRef != "" || c.CertPath != "" { + // Set no load options so that Ed25519 is preferred over Ed25519ph, required for signing DSSEs + var signOpts []signature.LoadOption + c.KeyOpts.DefaultLoadOptions = &signOpts sv, _, err = cosign_sign.SignerFromKeyOpts(ctx, c.CertPath, c.CertChainPath, c.KeyOpts) if err != nil { return fmt.Errorf("getting signer: %w", err) } - keypair, err = key.NewSignerVerifierKeypair(sv, c.DefaultLoadOptions) + keypair, err = key.NewSignerVerifierKeypair(sv, &signOpts) if err != nil { return fmt.Errorf("creating signerverifier keypair: %w", err) } diff --git a/cmd/cosign/cli/sign/sign.go b/cmd/cosign/cli/sign/sign.go index 51dcedd885e..1eca1b00293 100644 --- a/cmd/cosign/cli/sign/sign.go +++ b/cmd/cosign/cli/sign/sign.go @@ -256,11 +256,14 @@ func signDigestBundle(ctx context.Context, digest name.Digest, ko options.KeyOpt var err error if ko.Sk || ko.Slot != "" || ko.KeyRef != "" || signOpts.Cert != "" { + // Set no load options so that Ed25519 is preferred over Ed25519ph, required for signing DSSEs + var signLoadOpts []signature.LoadOption + ko.DefaultLoadOptions = &signLoadOpts sv, _, err = SignerFromKeyOpts(ctx, signOpts.Cert, signOpts.CertChain, ko) if err != nil { return fmt.Errorf("getting signer: %w", err) } - keypair, err = key.NewSignerVerifierKeypair(sv, ko.DefaultLoadOptions) + keypair, err = key.NewSignerVerifierKeypair(sv, &signLoadOpts) if err != nil { return fmt.Errorf("creating signerverifier keypair: %w", err) } diff --git a/cmd/cosign/cli/sign/sign_blob.go b/cmd/cosign/cli/sign/sign_blob.go index c04d830b313..bad25d176ac 100644 --- a/cmd/cosign/cli/sign/sign_blob.go +++ b/cmd/cosign/cli/sign/sign_blob.go @@ -87,6 +87,7 @@ func SignBlobCmd(ro *options.RootOptions, ko options.KeyOpts, payloadPath string var err error if ko.Sk || ko.Slot != "" || ko.KeyRef != "" { + // Default load options prefer Ed25519ph over Ed25519, required for blobs with hashedrekord sv, _, err = SignerFromKeyOpts(ctx, "", "", ko) if err != nil { return nil, fmt.Errorf("getting signer: %w", err) diff --git a/cmd/cosign/cli/verify/verify.go b/cmd/cosign/cli/verify/verify.go index aef668a2270..24c129f1e91 100644 --- a/cmd/cosign/cli/verify/verify.go +++ b/cmd/cosign/cli/verify/verify.go @@ -240,7 +240,9 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) { var pubKey signature.Verifier switch { case keyRef != "": - pubKey, err = sigs.PublicKeyFromKeyRefWithHashAlgo(ctx, keyRef, c.HashAlgorithm) + // Set no load options so that Ed25519 is preferred over Ed25519ph, required for verifying DSSEs + var signOpts []signature.LoadOption + pubKey, err = sigs.PublicKeyFromKeyRefWithHashAlgo(ctx, keyRef, c.HashAlgorithm, &signOpts) if err != nil { return fmt.Errorf("loading public key: %w", err) } diff --git a/cmd/cosign/cli/verify/verify_attestation.go b/cmd/cosign/cli/verify/verify_attestation.go index 4536321c91c..25ed67eb3c3 100644 --- a/cmd/cosign/cli/verify/verify_attestation.go +++ b/cmd/cosign/cli/verify/verify_attestation.go @@ -40,6 +40,7 @@ import ( "github.com/sigstore/cosign/v2/pkg/policy" sigs "github.com/sigstore/cosign/v2/pkg/signature" "github.com/sigstore/sigstore-go/pkg/root" + "github.com/sigstore/sigstore/pkg/signature" ) // VerifyAttestationCommand verifies a signature on a supplied container image @@ -210,7 +211,9 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e // Keys are optional! switch { case keyRef != "": - co.SigVerifier, err = sigs.PublicKeyFromKeyRefWithHashAlgo(ctx, keyRef, c.HashAlgorithm) + // Set no load options so that Ed25519 is preferred over Ed25519ph, required for verifying DSSEs + var signOpts []signature.LoadOption + co.SigVerifier, err = sigs.PublicKeyFromKeyRefWithHashAlgo(ctx, keyRef, c.HashAlgorithm, &signOpts) if err != nil { return fmt.Errorf("loading public key: %w", err) } diff --git a/cmd/cosign/cli/verify/verify_blob.go b/cmd/cosign/cli/verify/verify_blob.go index fb0bd081868..3d67e21457a 100644 --- a/cmd/cosign/cli/verify/verify_blob.go +++ b/cmd/cosign/cli/verify/verify_blob.go @@ -47,6 +47,7 @@ import ( sgverify "github.com/sigstore/sigstore-go/pkg/verify" "github.com/sigstore/sigstore/pkg/cryptoutils" + "github.com/sigstore/sigstore/pkg/signature" ) func isb64(data []byte) bool { @@ -117,12 +118,19 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error { NewBundleFormat: c.KeyOpts.NewBundleFormat && checkNewBundle(c.BundlePath), } + if !c.IgnoreTlog { + // To maintain backwards compatibility with older cosign versions, + // we do not use ed25519ph for ed25519 keys when the signatures are not + // uploaded to the Tlog. + c.DefaultLoadOptions = &[]signature.LoadOption{} + } + // Keys are optional! var cert *x509.Certificate opts := make([]static.Option, 0) switch { case c.KeyRef != "": - co.SigVerifier, err = sigs.PublicKeyFromKeyRefWithHashAlgo(ctx, c.KeyRef, c.HashAlgorithm) + co.SigVerifier, err = sigs.PublicKeyFromKeyRefWithHashAlgo(ctx, c.KeyRef, c.HashAlgorithm, c.DefaultLoadOptions) if err != nil { return fmt.Errorf("loading public key: %w", err) } @@ -265,7 +273,7 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error { bundleCert, err := loadCertFromPEM(certBytes) if err != nil { // check if cert is actually a public key - co.SigVerifier, err = sigs.LoadPublicKeyRaw(certBytes, crypto.SHA256) + co.SigVerifier, err = sigs.LoadPublicKeyRaw(certBytes, crypto.SHA256, c.DefaultLoadOptions) if err != nil { return fmt.Errorf("loading verifier from bundle: %w", err) } diff --git a/cmd/cosign/cli/verify/verify_blob_attestation.go b/cmd/cosign/cli/verify/verify_blob_attestation.go index a84f4f3b2c4..f400ce280ba 100644 --- a/cmd/cosign/cli/verify/verify_blob_attestation.go +++ b/cmd/cosign/cli/verify/verify_blob_attestation.go @@ -47,6 +47,7 @@ import ( "github.com/sigstore/sigstore-go/pkg/root" sgverify "github.com/sigstore/sigstore-go/pkg/verify" "github.com/sigstore/sigstore/pkg/cryptoutils" + "github.com/sigstore/sigstore/pkg/signature" ) // VerifyBlobAttestationCommand verifies an attestation on a supplied blob @@ -127,12 +128,16 @@ func (c *VerifyBlobAttestationCommand) Exec(ctx context.Context, artifactPath st NewBundleFormat: c.NewBundleFormat && checkNewBundle(c.BundlePath), } + // Set no load options so that Ed25519 is preferred over Ed25519ph, required for verifying DSSEs + var signOpts []signature.LoadOption + c.DefaultLoadOptions = &signOpts + // Keys are optional! var cert *x509.Certificate opts := make([]static.Option, 0) switch { case c.KeyRef != "": - co.SigVerifier, err = sigs.PublicKeyFromKeyRefWithHashAlgo(ctx, c.KeyRef, c.HashAlgorithm) + co.SigVerifier, err = sigs.PublicKeyFromKeyRefWithHashAlgo(ctx, c.KeyRef, c.HashAlgorithm, c.DefaultLoadOptions) if err != nil { return fmt.Errorf("loading public key: %w", err) } @@ -329,7 +334,7 @@ func (c *VerifyBlobAttestationCommand) Exec(ctx context.Context, artifactPath st bundleCert, err := loadCertFromPEM(certBytes) if err != nil { // check if cert is actually a public key - co.SigVerifier, err = sigs.LoadPublicKeyRaw(certBytes, crypto.SHA256) + co.SigVerifier, err = sigs.LoadPublicKeyRaw(certBytes, crypto.SHA256, c.DefaultLoadOptions) if err != nil { return fmt.Errorf("loading verifier from bundle: %w", err) } diff --git a/internal/key/svkeypair.go b/internal/key/svkeypair.go index 5bbf822d49e..51b8c9c7ed0 100644 --- a/internal/key/svkeypair.go +++ b/internal/key/svkeypair.go @@ -125,13 +125,20 @@ func (k *SignerVerifierKeypair) GetPublicKeyPem() (string, error) { // SignData signs the given data with the SignerVerifier. func (k *SignerVerifierKeypair) SignData(ctx context.Context, data []byte) ([]byte, []byte, error) { - h := k.sigAlg.GetHashType().New() - h.Write(data) - digest := h.Sum(nil) - sOpts := []signature.SignOption{signatureoptions.WithContext(ctx), signatureoptions.WithDigest(digest)} + sOpts := []signature.SignOption{signatureoptions.WithContext(ctx)} + + hf := k.sigAlg.GetHashType() + dataToSign := data + // RSA, ECDSA, and Ed25519ph sign a digest, while pure Ed25519's interface takes data and hashes during signing + if hf != crypto.Hash(0) { + hasher := hf.New() + hasher.Write(data) + dataToSign = hasher.Sum(nil) + sOpts = append(sOpts, signatureoptions.WithDigest(dataToSign)) + } sig, err := k.sv.SignMessage(bytes.NewReader(data), sOpts...) if err != nil { return nil, nil, err } - return sig, digest, nil + return sig, dataToSign, nil } diff --git a/internal/key/svkeypair_test.go b/internal/key/svkeypair_test.go index 15422b70dfa..37dfc66592a 100644 --- a/internal/key/svkeypair_test.go +++ b/internal/key/svkeypair_test.go @@ -34,6 +34,7 @@ import ( protocommon "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature" + "github.com/sigstore/sigstore/pkg/signature/options" ) // mockSignerVerifier is a mock implementation of signature.SignerVerifier for testing. @@ -61,7 +62,7 @@ func (m *mockSignerVerifier) VerifySignature(_, _ io.Reader, _ ...signature.Veri return errors.New("not implemented") } -func TestNewKMSKeypair(t *testing.T) { +func TestNewSignerVerifierKeypair(t *testing.T) { ecdsaPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { t.Fatalf("failed to generate ecdsa key: %v", err) @@ -78,6 +79,7 @@ func TestNewKMSKeypair(t *testing.T) { testCases := []struct { name string sv signature.SignerVerifier + prehash bool expectErr bool errMsg string }{ @@ -102,6 +104,14 @@ func TestNewKMSKeypair(t *testing.T) { }, expectErr: false, }, + { + name: "ED25519ph key", + sv: &mockSignerVerifier{ + pubKey: ed25519Priv.Public(), + }, + prehash: true, + expectErr: false, + }, { name: "Unsupported key type", sv: &mockSignerVerifier{ @@ -122,7 +132,11 @@ func TestNewKMSKeypair(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - kp, err := NewSignerVerifierKeypair(tc.sv, nil) + var loadOpts []signature.LoadOption + if tc.prehash { + loadOpts = []signature.LoadOption{options.WithED25519ph()} + } + kp, err := NewSignerVerifierKeypair(tc.sv, &loadOpts) if tc.expectErr { if err == nil { t.Errorf("expected an error, but got none") @@ -137,6 +151,11 @@ func TestNewKMSKeypair(t *testing.T) { t.Error("expected a keypair, but got nil") } } + if !tc.expectErr { + if _, _, err := kp.SignData(context.Background(), []byte("data")); err != nil { + t.Errorf("unexpected error: %v", err) + } + } }) } } diff --git a/pkg/cosign/bundle/sign.go b/pkg/cosign/bundle/sign.go index 45a91aab981..c57730f0bb9 100644 --- a/pkg/cosign/bundle/sign.go +++ b/pkg/cosign/bundle/sign.go @@ -26,6 +26,7 @@ import ( "github.com/sigstore/sigstore-go/pkg/root" "github.com/sigstore/sigstore-go/pkg/sign" "github.com/sigstore/sigstore/pkg/signature" + "github.com/sigstore/sigstore/pkg/signature/options" "google.golang.org/protobuf/encoding/protojson" ) @@ -63,7 +64,11 @@ func SignData(ctx context.Context, content sign.Content, keypair sign.Keypair, i if err != nil { log.Fatal(err) } - verifier, err := signature.LoadDefaultVerifier(pubKey) + var verifierOpts []signature.LoadOption + if _, ok := content.(*sign.PlainData); ok { + verifierOpts = append(verifierOpts, options.WithED25519ph()) + } + verifier, err := signature.LoadDefaultVerifier(pubKey, verifierOpts...) if err != nil { log.Fatal(err) } diff --git a/pkg/signature/keys.go b/pkg/signature/keys.go index a396d096865..cb6cbc29713 100644 --- a/pkg/signature/keys.go +++ b/pkg/signature/keys.go @@ -31,16 +31,17 @@ import ( "github.com/sigstore/sigstore/pkg/signature" "github.com/sigstore/sigstore/pkg/signature/kms" + "github.com/sigstore/sigstore/pkg/signature/options" ) // LoadPublicKey is a wrapper for VerifierForKeyRef, hardcoding SHA256 as the hash algorithm func LoadPublicKey(ctx context.Context, keyRef string) (verifier signature.Verifier, err error) { - return VerifierForKeyRef(ctx, keyRef, crypto.SHA256) + return VerifierForKeyRef(ctx, keyRef, crypto.SHA256, nil) } // VerifierForKeyRef parses the given keyRef, loads the key and returns an appropriate // verifier using the provided hash algorithm -func VerifierForKeyRef(ctx context.Context, keyRef string, hashAlgorithm crypto.Hash) (verifier signature.Verifier, err error) { +func VerifierForKeyRef(ctx context.Context, keyRef string, hashAlgorithm crypto.Hash, defaultLoadOptions *[]signature.LoadOption) (verifier signature.Verifier, err error) { // The key could be plaintext, in a file, at a URL, or in KMS. var perr *kms.ProviderNotFoundError kmsKey, err := kms.Get(ctx, keyRef, hashAlgorithm) @@ -69,7 +70,9 @@ func VerifierForKeyRef(ctx context.Context, keyRef string, hashAlgorithm crypto. return nil, fmt.Errorf("pem to public key: %w", err) } - return signature.LoadVerifier(pubKey, hashAlgorithm) + opts := *cosign.GetDefaultLoadOptions(defaultLoadOptions) + opts = append(opts, options.WithHash(hashAlgorithm)) + return signature.LoadVerifierWithOpts(pubKey, opts...) } func loadKey(keyPath string, pf cosign.PassFunc, defaultLoadOptions *[]signature.LoadOption) (signature.SignerVerifier, error) { @@ -88,12 +91,14 @@ func loadKey(keyPath string, pf cosign.PassFunc, defaultLoadOptions *[]signature } // LoadPublicKeyRaw loads a verifier from a PEM-encoded public key -func LoadPublicKeyRaw(raw []byte, hashAlgorithm crypto.Hash) (signature.Verifier, error) { +func LoadPublicKeyRaw(raw []byte, hashAlgorithm crypto.Hash, defaultLoadOptions *[]signature.LoadOption) (signature.Verifier, error) { pub, err := cryptoutils.UnmarshalPEMToPublicKey(raw) if err != nil { return nil, err } - return signature.LoadVerifier(pub, hashAlgorithm) + opts := *cosign.GetDefaultLoadOptions(defaultLoadOptions) + opts = append(opts, options.WithHash(hashAlgorithm)) + return signature.LoadVerifierWithOpts(pub, opts...) } func SignerFromKeyRef(ctx context.Context, keyRef string, pf cosign.PassFunc) (signature.Signer, error) { @@ -169,10 +174,10 @@ func SignerVerifierFromKeyRef(ctx context.Context, keyRef string, pf cosign.Pass } func PublicKeyFromKeyRef(ctx context.Context, keyRef string) (signature.Verifier, error) { - return PublicKeyFromKeyRefWithHashAlgo(ctx, keyRef, crypto.SHA256) + return PublicKeyFromKeyRefWithHashAlgo(ctx, keyRef, crypto.SHA256, nil) } -func PublicKeyFromKeyRefWithHashAlgo(ctx context.Context, keyRef string, hashAlgorithm crypto.Hash) (signature.Verifier, error) { +func PublicKeyFromKeyRefWithHashAlgo(ctx context.Context, keyRef string, hashAlgorithm crypto.Hash, defaultLoadOptions *[]signature.LoadOption) (signature.Verifier, error) { if strings.HasPrefix(keyRef, kubernetes.KeyReference) { s, err := kubernetes.GetKeyPairSecret(ctx, keyRef) if err != nil { @@ -180,7 +185,7 @@ func PublicKeyFromKeyRefWithHashAlgo(ctx context.Context, keyRef string, hashAlg } if len(s.Data) > 0 { - return LoadPublicKeyRaw(s.Data["cosign.pub"], hashAlgorithm) + return LoadPublicKeyRaw(s.Data["cosign.pub"], hashAlgorithm, defaultLoadOptions) } } @@ -219,11 +224,11 @@ func PublicKeyFromKeyRefWithHashAlgo(ctx context.Context, keyRef string, hashAlg } if len(pubKey) > 0 { - return LoadPublicKeyRaw([]byte(pubKey), hashAlgorithm) + return LoadPublicKeyRaw([]byte(pubKey), hashAlgorithm, defaultLoadOptions) } } - return VerifierForKeyRef(ctx, keyRef, hashAlgorithm) + return VerifierForKeyRef(ctx, keyRef, hashAlgorithm, defaultLoadOptions) } func PublicKeyPem(key signature.PublicKeyProvider, pkOpts ...signature.PublicKeyOption) ([]byte, error) { diff --git a/test/e2e_test.go b/test/e2e_test.go index ce40f6ba1d7..ca8e2fce54f 100644 --- a/test/e2e_test.go +++ b/test/e2e_test.go @@ -847,7 +847,7 @@ func TestSignVerifyWithTUFMirror(t *testing.T) { } } -func prepareSigningConfig(t *testing.T, fulcioURL, rekorURL, oidcURL, tsaURL string) string { +func prepareSigningConfig(t *testing.T, fulcioURL, rekorURL, oidcURL, tsaURL string) string { //nolint: unparam startTime := "2024-01-01T00:00:00Z" fulcioSpec := fmt.Sprintf("url=%s,api-version=1,operator=fulcio-op,start-time=%s", fulcioURL, startTime) rekorSpec := fmt.Sprintf("url=%s,api-version=1,operator=rekor-op,start-time=%s", rekorURL, startTime) @@ -2804,7 +2804,7 @@ func TestSignBlobNewBundle(t *testing.T) { must(verifyBlobCmd.Exec(ctx, blobPath), t) } -func TestSignBlobNewBundleNonSHA256(t *testing.T) { +func TestSignBlobNewBundleManagedKeyNonDefaultAlgorithm(t *testing.T) { td1 := t.TempDir() blob := "someblob" @@ -2817,33 +2817,205 @@ func TestSignBlobNewBundleNonSHA256(t *testing.T) { ctx := context.Background() - // Generate ecdsa-p521 key - _, privKeyPath, pubKeyPath := keypairWithAlgorithm(t, td1, v1.PublicKeyDetails_PKIX_ECDSA_P521_SHA_512) + tts := []struct { + algo v1.PublicKeyDetails + }{ + {v1.PublicKeyDetails_PKIX_ECDSA_P384_SHA_384}, + {v1.PublicKeyDetails_PKIX_ECDSA_P521_SHA_512}, + {v1.PublicKeyDetails_PKIX_RSA_PKCS1V15_2048_SHA256}, + {v1.PublicKeyDetails_PKIX_RSA_PKCS1V15_3072_SHA256}, + {v1.PublicKeyDetails_PKIX_RSA_PKCS1V15_4096_SHA256}, + {v1.PublicKeyDetails_PKIX_ED25519}, // When Rekor isn't used, sign with the pure variant + } + for _, tt := range tts { + _, privKeyPath, pubKeyPath := keypairWithAlgorithm(t, td1, tt.algo) + tlogUpload := false - ko := options.KeyOpts{ - KeyRef: privKeyPath, - PassFunc: passFunc, - BundlePath: bundlePath, - NewBundleFormat: true, + ko := options.KeyOpts{ + KeyRef: privKeyPath, + PassFunc: passFunc, + BundlePath: bundlePath, + NewBundleFormat: true, + } + if _, err := sign.SignBlobCmd(ro, ko, blobPath, true, "", "", tlogUpload); err != nil { + t.Fatal(err) + } + algDetails, err := signature.GetAlgorithmDetails(tt.algo) + if err != nil { + t.Fatal(err) + } + + ko1 := options.KeyOpts{ + KeyRef: pubKeyPath, + BundlePath: bundlePath, + NewBundleFormat: true, + } + verifyBlobCmd := cliverify.VerifyBlobCmd{ + KeyOpts: ko1, + IgnoreTlog: true, + HashAlgorithm: algDetails.GetHashType(), + } + must(verifyBlobCmd.Exec(ctx, blobPath), t) } - if _, err := sign.SignBlobCmd(ro, ko, blobPath, true, "", "", false); err != nil { +} + +func TestSignBlobNewBundleManagedKeyRekorNonDefaultAlgorithm(t *testing.T) { + td1 := t.TempDir() + + tufLocalCache := t.TempDir() + t.Setenv("TUF_ROOT", tufLocalCache) + tufMirror := t.TempDir() + viper.Set("timestamp-signer", "memory") + viper.Set("timestamp-signer-hash", "sha256") + tsaAPIServer := server.NewRestAPIServer("localhost", 0, []string{"http"}, false, 10*time.Second, 10*time.Second) + tsaServer := httptest.NewServer(tsaAPIServer.GetHandler()) + t.Cleanup(tsaServer.Close) + tufServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.FileServer(http.Dir(tufMirror)).ServeHTTP(w, r) + })) + mirror := tufServer.URL + trustedRoot := prepareTrustedRoot(t, tsaServer.URL) + signingConfigStr := prepareSigningConfig(t, fulcioURL, rekorURL, "unused", tsaServer.URL+"/api/v1/timestamp") + _, err := newTUF(tufMirror, []targetInfo{ + { + name: "trusted_root.json", + source: trustedRoot, + }, + { + name: "signing_config.v0.2.json", + source: signingConfigStr, + }, + }) + must(err, t) + + ctx := context.Background() + + rootPath := filepath.Join(tufMirror, "1.root.json") + must(initialize.DoInitialize(ctx, rootPath, mirror), t) + + blob := "someblob" + blobPath := filepath.Join(td1, blob) + if err := os.WriteFile(blobPath, []byte(blob), 0644); err != nil { t.Fatal(err) } - ko1 := options.KeyOpts{ - KeyRef: pubKeyPath, - BundlePath: bundlePath, - NewBundleFormat: true, + bundlePath := filepath.Join(td1, "bundle.sigstore.json") + + tts := []struct { + algo v1.PublicKeyDetails + }{ + {v1.PublicKeyDetails_PKIX_ECDSA_P384_SHA_384}, + {v1.PublicKeyDetails_PKIX_ECDSA_P521_SHA_512}, + {v1.PublicKeyDetails_PKIX_RSA_PKCS1V15_2048_SHA256}, + {v1.PublicKeyDetails_PKIX_RSA_PKCS1V15_3072_SHA256}, + {v1.PublicKeyDetails_PKIX_RSA_PKCS1V15_4096_SHA256}, + {v1.PublicKeyDetails_PKIX_ED25519_PH}, // Prehash variant used with Rekor } - verifyBlobCmd := cliverify.VerifyBlobCmd{ - KeyOpts: ko1, - IgnoreTlog: true, - HashAlgorithm: crypto.SHA512, + for _, tt := range tts { + _, privKeyPath, pubKeyPath := keypairWithAlgorithm(t, td1, tt.algo) + tlogUpload := true + + ko := options.KeyOpts{ + KeyRef: privKeyPath, + PassFunc: passFunc, + BundlePath: bundlePath, + NewBundleFormat: true, + } + + trustedMaterial, err := cosign.TrustedRoot() + must(err, t) + ko.TrustedMaterial = trustedMaterial + signingConfig, err := cosign.SigningConfig() + must(err, t) + ko.SigningConfig = signingConfig + + if _, err := sign.SignBlobCmd(ro, ko, blobPath, true, "", "", tlogUpload); err != nil { + t.Fatal(err) + } + algDetails, err := signature.GetAlgorithmDetails(tt.algo) + if err != nil { + t.Fatal(err) + } + + ko1 := options.KeyOpts{ + KeyRef: pubKeyPath, + BundlePath: bundlePath, + NewBundleFormat: true, + } + verifyBlobCmd := cliverify.VerifyBlobCmd{ + KeyOpts: ko1, + IgnoreTlog: false, + HashAlgorithm: algDetails.GetHashType(), + } + must(verifyBlobCmd.Exec(ctx, blobPath), t) + } +} + +func TestAttestBlobNewBundleManagedKeyNonDefaultAlgorithm(t *testing.T) { + td := t.TempDir() + blob := "someblob" + bp := filepath.Join(td, blob) + if err := os.WriteFile(bp, []byte(blob), 0600); err != nil { + t.Fatal(err) + } + // Sign an attestation + statement := `{"_type":"https://in-toto.io/Statement/v1","subject":[{"name":"someblob","digest":{"alg":"7e9b6e7ba2842c91cf49f3e214d04a7a496f8214356f41d81a6e6dcad11f11e3"}}],"predicateType":"something","predicate":{}}` + attestDir := t.TempDir() + statementPath := filepath.Join(attestDir, "statement") + if err := os.WriteFile(statementPath, []byte(statement), 0644); err != nil { + t.Fatal(err) + } + attBundlePath := filepath.Join(attestDir, "attest.bundle.json") + + ctx := context.Background() + + tts := []struct { + algo v1.PublicKeyDetails + }{ + {v1.PublicKeyDetails_PKIX_ECDSA_P384_SHA_384}, + {v1.PublicKeyDetails_PKIX_ECDSA_P521_SHA_512}, + {v1.PublicKeyDetails_PKIX_RSA_PKCS1V15_2048_SHA256}, + {v1.PublicKeyDetails_PKIX_RSA_PKCS1V15_3072_SHA256}, + {v1.PublicKeyDetails_PKIX_RSA_PKCS1V15_4096_SHA256}, + {v1.PublicKeyDetails_PKIX_ED25519}, // Only pure variant is supported + } + for _, tt := range tts { + _, privKeyPath, pubKeyPath := keypairWithAlgorithm(t, td, tt.algo) + + ko := options.KeyOpts{ + KeyRef: privKeyPath, + PassFunc: passFunc, + BundlePath: attBundlePath, + NewBundleFormat: true, + } + + algDetails, err := signature.GetAlgorithmDetails(tt.algo) + if err != nil { + t.Fatal(err) + } + + attestBlobCmd := attest.AttestBlobCommand{ + KeyOpts: ko, + RekorEntryType: "dsse", + StatementPath: statementPath, + } + must(attestBlobCmd.Exec(ctx, bp), t) + + // Verify an attestation + ko.KeyRef = pubKeyPath + verifyBlobAttestationCmd := cliverify.VerifyBlobAttestationCommand{ + KeyOpts: ko, + UseSignedTimestamps: true, + Digest: "7e9b6e7ba2842c91cf49f3e214d04a7a496f8214356f41d81a6e6dcad11f11e3", + DigestAlg: "alg", + CheckClaims: true, + HashAlgorithm: algDetails.GetHashType(), + } + must(verifyBlobAttestationCmd.Exec(ctx, ""), t) } - must(verifyBlobCmd.Exec(ctx, blobPath), t) } -func TestSignBlobNewBundleNonDefaultAlgorithm(t *testing.T) { +func TestSignBlobNewBundleFulcioNonDefaultAlgorithm(t *testing.T) { tts := []struct { algo v1.PublicKeyDetails }{