diff --git a/cmd/cosign/cli/attest/attest.go b/cmd/cosign/cli/attest/attest.go index b23d587fc33..cce9e0e08bf 100644 --- a/cmd/cosign/cli/attest/attest.go +++ b/cmd/cosign/cli/attest/attest.go @@ -132,7 +132,7 @@ func (c *AttestCommand) Exec(ctx context.Context, imageRef string) error { // each access. ref = digest // nolint - sv, err := sign.SignerFromKeyOpts(ctx, c.CertPath, c.CertChainPath, c.KeyOpts) + sv, err := sign.SignerFromKeyOptsWithSVOpts(ctx, c.CertPath, c.CertChainPath, c.KeyOpts) if err != nil { return fmt.Errorf("getting signer: %w", err) } diff --git a/cmd/cosign/cli/attest/attest_blob.go b/cmd/cosign/cli/attest/attest_blob.go index be4369f87d3..28ae2e72baa 100644 --- a/cmd/cosign/cli/attest/attest_blob.go +++ b/cmd/cosign/cli/attest/attest_blob.go @@ -132,7 +132,7 @@ func (c *AttestBlobCommand) Exec(ctx context.Context, artifactPath string) error } defer predicate.Close() - sv, err := sign.SignerFromKeyOpts(ctx, c.CertPath, c.CertChainPath, c.KeyOpts) + sv, err := sign.SignerFromKeyOptsWithSVOpts(ctx, c.CertPath, c.CertChainPath, c.KeyOpts) if err != nil { return fmt.Errorf("getting signer: %w", err) } diff --git a/cmd/cosign/cli/sign/sign.go b/cmd/cosign/cli/sign/sign.go index 1289e7b1bb4..c0b883deb86 100644 --- a/cmd/cosign/cli/sign/sign.go +++ b/cmd/cosign/cli/sign/sign.go @@ -18,7 +18,6 @@ package sign import ( "bytes" "context" - "crypto" "crypto/x509" "encoding/base64" "encoding/json" @@ -138,7 +137,7 @@ func SignCmd(ro *options.RootOptions, ko options.KeyOpts, signOpts options.SignO ctx, cancel := context.WithTimeout(context.Background(), ro.Timeout) defer cancel() - sv, err := SignerFromKeyOpts(ctx, signOpts.Cert, signOpts.CertChain, ko) + sv, err := SignerFromKeyOptsWithSVOpts(ctx, signOpts.Cert, signOpts.CertChain, ko) if err != nil { return fmt.Errorf("getting signer: %w", err) } @@ -391,8 +390,8 @@ func signerFromSecurityKey(ctx context.Context, keySlot string) (*SignerVerifier }, nil } -func signerFromKeyRef(ctx context.Context, certPath, certChainPath, keyRef string, passFunc cosign.PassFunc) (*SignerVerifier, error) { - k, err := sigs.SignerVerifierFromKeyRef(ctx, keyRef, passFunc) +func signerFromKeyRef(ctx context.Context, certPath, certChainPath, keyRef string, passFunc cosign.PassFunc, opts ...signature.LoadOption) (*SignerVerifier, error) { + k, err := sigs.SignerVerifierFromKeyRefWithOpts(ctx, keyRef, passFunc, opts...) if err != nil { return nil, fmt.Errorf("reading key: %w", err) } @@ -521,12 +520,12 @@ func signerFromKeyRef(ctx context.Context, certPath, certChainPath, keyRef strin return certSigner, nil } -func signerFromNewKey() (*SignerVerifier, error) { +func signerFromNewKey(opts ...signature.LoadOption) (*SignerVerifier, error) { privKey, err := cosign.GeneratePrivateKey() if err != nil { return nil, fmt.Errorf("generating cert: %w", err) } - sv, err := signature.LoadECDSASignerVerifier(privKey, crypto.SHA256) + sv, err := signature.LoadSignerVerifierWithOpts(privKey, opts...) if err != nil { return nil, err } @@ -559,7 +558,7 @@ func keylessSigner(ctx context.Context, ko options.KeyOpts, sv *SignerVerifier) }, nil } -func SignerFromKeyOpts(ctx context.Context, certPath string, certChainPath string, ko options.KeyOpts) (*SignerVerifier, error) { +func SignerFromKeyOptsWithSVOpts(ctx context.Context, certPath string, certChainPath string, ko options.KeyOpts, svOpts ...signature.LoadOption) (*SignerVerifier, error) { var sv *SignerVerifier var err error genKey := false @@ -567,11 +566,11 @@ func SignerFromKeyOpts(ctx context.Context, certPath string, certChainPath strin case ko.Sk: sv, err = signerFromSecurityKey(ctx, ko.Slot) case ko.KeyRef != "": - sv, err = signerFromKeyRef(ctx, certPath, certChainPath, ko.KeyRef, ko.PassFunc) + sv, err = signerFromKeyRef(ctx, certPath, certChainPath, ko.KeyRef, ko.PassFunc, svOpts...) default: genKey = true ui.Infof(ctx, "Generating ephemeral keys...") - sv, err = signerFromNewKey() + sv, err = signerFromNewKey(svOpts...) } if err != nil { return nil, err @@ -584,6 +583,10 @@ func SignerFromKeyOpts(ctx context.Context, certPath string, certChainPath strin return sv, nil } +func SignerFromKeyOpts(ctx context.Context, certPath string, certChainPath string, ko options.KeyOpts) (*SignerVerifier, error) { + return SignerFromKeyOptsWithSVOpts(ctx, certPath, certChainPath, ko) +} + type SignerVerifier struct { Cert []byte Chain []byte diff --git a/cmd/cosign/cli/sign/sign_blob.go b/cmd/cosign/cli/sign/sign_blob.go index 5a7b960b927..87c580e5e74 100644 --- a/cmd/cosign/cli/sign/sign_blob.go +++ b/cmd/cosign/cli/sign/sign_blob.go @@ -65,7 +65,7 @@ func SignBlobCmd(ro *options.RootOptions, ko options.KeyOpts, payloadPath string return nil, err } - sv, err := SignerFromKeyOpts(ctx, "", "", ko) + sv, err := SignerFromKeyOptsWithSVOpts(ctx, "", "", ko) if err != nil { return nil, err } diff --git a/cmd/cosign/cli/verify/verify.go b/cmd/cosign/cli/verify/verify.go index f5fc86bfddf..eaa114e6314 100644 --- a/cmd/cosign/cli/verify/verify.go +++ b/cmd/cosign/cli/verify/verify.go @@ -43,6 +43,7 @@ import ( sigs "github.com/sigstore/cosign/v2/pkg/signature" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/sigstore/sigstore/pkg/signature" + signatureoptions "github.com/sigstore/sigstore/pkg/signature/options" "github.com/sigstore/sigstore/pkg/signature/payload" ) @@ -186,7 +187,7 @@ 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) + pubKey, err = sigs.PublicKeyFromKeyRefWithOpts(ctx, keyRef, signatureoptions.WithHash(c.HashAlgorithm)) if err != nil { return fmt.Errorf("loading public key: %w", err) } @@ -230,7 +231,7 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) { if err != nil { return err } - pubKey, err = cosign.ValidateAndUnpackCertWithChain(cert, chain, co) + pubKey, err = cosign.ValidateAndUnpackCertWithOpts(cert, co, cosign.WithChain(chain)) if err != nil { return err } @@ -267,7 +268,7 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) { for _, img := range images { if c.LocalImage { - verified, bundleVerified, err := cosign.VerifyLocalImageSignatures(ctx, img, co) + verified, bundleVerified, err := cosign.VerifyLocalImageSignaturesWithOpts(ctx, img, co) if err != nil { return err } @@ -283,7 +284,7 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) { return fmt.Errorf("resolving attachment type %s for image %s: %w", c.Attachment, img, err) } - verified, bundleVerified, err := cosign.VerifyImageSignatures(ctx, ref, co) + verified, bundleVerified, err := cosign.VerifyImageSignaturesWithOpts(ctx, ref, co) if err != nil { return cosignError.WrapError(err) } diff --git a/cmd/cosign/cli/verify/verify_attestation.go b/cmd/cosign/cli/verify/verify_attestation.go index bf25cbb467a..2aad07df055 100644 --- a/cmd/cosign/cli/verify/verify_attestation.go +++ b/cmd/cosign/cli/verify/verify_attestation.go @@ -159,7 +159,7 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e // Keys are optional! switch { case keyRef != "": - co.SigVerifier, err = sigs.PublicKeyFromKeyRef(ctx, keyRef) + co.SigVerifier, err = sigs.PublicKeyFromKeyRefWithOpts(ctx, keyRef) if err != nil { return fmt.Errorf("loading public key: %w", err) } @@ -202,7 +202,7 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e if err != nil { return err } - co.SigVerifier, err = cosign.ValidateAndUnpackCertWithChain(cert, chain, co) + co.SigVerifier, err = cosign.ValidateAndUnpackCertWithOpts(cert, co, cosign.WithChain(chain)) if err != nil { return fmt.Errorf("creating certificate verifier: %w", err) } @@ -231,7 +231,7 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e var bundleVerified bool if c.LocalImage { - verified, bundleVerified, err = cosign.VerifyLocalImageAttestations(ctx, imageRef, co) + verified, bundleVerified, err = cosign.VerifyLocalImageAttestationsWithOpts(ctx, imageRef, co) if err != nil { return err } @@ -241,7 +241,7 @@ func (c *VerifyAttestationCommand) Exec(ctx context.Context, images []string) (e return err } - verified, bundleVerified, err = cosign.VerifyImageAttestations(ctx, ref, co) + verified, bundleVerified, err = cosign.VerifyImageAttestationsWithOpts(ctx, ref, co) if err != nil { return err } diff --git a/cmd/cosign/cli/verify/verify_blob.go b/cmd/cosign/cli/verify/verify_blob.go index 25932f43f87..3d6a155672e 100644 --- a/cmd/cosign/cli/verify/verify_blob.go +++ b/cmd/cosign/cli/verify/verify_blob.go @@ -46,6 +46,7 @@ import ( sgverify "github.com/sigstore/sigstore-go/pkg/verify" "github.com/sigstore/sigstore/pkg/cryptoutils" + signatureoptions "github.com/sigstore/sigstore/pkg/signature/options" ) func isb64(data []byte) bool { @@ -115,7 +116,7 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error { opts := make([]static.Option, 0) switch { case c.KeyRef != "": - co.SigVerifier, err = sigs.PublicKeyFromKeyRef(ctx, c.KeyRef) + co.SigVerifier, err = sigs.PublicKeyFromKeyRefWithOpts(ctx, c.KeyRef) if err != nil { return fmt.Errorf("loading public key: %w", err) } @@ -243,7 +244,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.LoadPublicKeyRawWithOpts(certBytes, signatureoptions.WithHash(crypto.SHA256)) if err != nil { return fmt.Errorf("loading verifier from bundle: %w", err) } @@ -330,7 +331,7 @@ func (c *VerifyBlobCmd) Exec(ctx context.Context, blobRef string) error { if err != nil { return err } - if _, err = cosign.VerifyBlobSignature(ctx, signature, co); err != nil { + if _, err = cosign.VerifyBlobSignatureWithOpts(ctx, signature, co); err != nil { return err } diff --git a/cmd/cosign/cli/verify/verify_blob_attestation.go b/cmd/cosign/cli/verify/verify_blob_attestation.go index 3d52db71370..c70636fd2bf 100644 --- a/cmd/cosign/cli/verify/verify_blob_attestation.go +++ b/cmd/cosign/cli/verify/verify_blob_attestation.go @@ -46,6 +46,7 @@ import ( sgbundle "github.com/sigstore/sigstore-go/pkg/bundle" sgverify "github.com/sigstore/sigstore-go/pkg/verify" "github.com/sigstore/sigstore/pkg/cryptoutils" + signatureoptions "github.com/sigstore/sigstore/pkg/signature/options" ) // VerifyBlobAttestationCommand verifies an attestation on a supplied blob @@ -122,7 +123,7 @@ func (c *VerifyBlobAttestationCommand) Exec(ctx context.Context, artifactPath st opts := make([]static.Option, 0) switch { case c.KeyRef != "": - co.SigVerifier, err = sigs.PublicKeyFromKeyRef(ctx, c.KeyRef) + co.SigVerifier, err = sigs.PublicKeyFromKeyRefWithOpts(ctx, c.KeyRef) if err != nil { return fmt.Errorf("loading public key: %w", err) } @@ -283,7 +284,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.LoadPublicKeyRawWithOpts(certBytes, signatureoptions.WithHash(crypto.SHA256)) if err != nil { return fmt.Errorf("loading verifier from bundle: %w", err) } @@ -363,7 +364,7 @@ func (c *VerifyBlobAttestationCommand) Exec(ctx context.Context, artifactPath st // TODO: This verifier only supports verification of a single signer/signature on // the envelope. Either have the verifier validate that only one signature exists, // or use a multi-signature verifier. - if _, err = cosign.VerifyBlobAttestation(ctx, signature, h, co); err != nil { + if _, err = cosign.VerifyBlobAttestationWithOpts(ctx, signature, h, co); err != nil { return err } diff --git a/cmd/cosign/cli/verify/verify_blob_test.go b/cmd/cosign/cli/verify/verify_blob_test.go index bda8e375c10..a5c97b2a81f 100644 --- a/cmd/cosign/cli/verify/verify_blob_test.go +++ b/cmd/cosign/cli/verify/verify_blob_test.go @@ -19,6 +19,7 @@ import ( "context" "crypto" "crypto/ecdsa" + "crypto/ed25519" "crypto/elliptic" "crypto/rand" "crypto/sha256" @@ -126,17 +127,8 @@ func TestSignaturesBundle(t *testing.T) { } } -// Does not test identity options, only blob verification with different -// options. -func TestVerifyBlob(t *testing.T) { - ctx := context.Background() - td := t.TempDir() - - leafPriv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - t.Fatal(err) - } - signer, err := signature.LoadECDSASignerVerifier(leafPriv, crypto.SHA256) +func testVerifyBlobLeafPriv(ctx context.Context, t *testing.T, leafPriv crypto.PrivateKey, hashFunc crypto.Hash, td string, svOpts ...signature.LoadOption) { + signer, err := signature.LoadSignerVerifierWithOpts(leafPriv, svOpts...) if err != nil { t.Fatal(err) } @@ -268,7 +260,7 @@ func TestVerifyBlob(t *testing.T) { signature: blobSignature, key: pubKeyBytes, rekorEntry: []*models.LogEntry{makeRekorEntry(t, *rekorSigner, blobBytes, []byte(blobSignature), - pubKeyBytes, true)}, + pubKeyBytes, true, hashFunc)}, shouldErr: false, }, { @@ -277,7 +269,7 @@ func TestVerifyBlob(t *testing.T) { signature: blobSignature, key: pubKeyBytes, bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(blobSignature), - pubKeyBytes, true), + pubKeyBytes, true, hashFunc), shouldErr: false, }, { @@ -293,8 +285,8 @@ func TestVerifyBlob(t *testing.T) { blob: blobBytes, signature: blobSignature, key: pubKeyBytes, - bundlePath: makeLocalBundle(t, *signer, blobBytes, []byte(blobSignature), - unexpiredCertPem, true), + bundlePath: makeLocalBundle(t, signer, blobBytes, []byte(blobSignature), + unexpiredCertPem, true, hashFunc), shouldErr: true, }, { @@ -303,7 +295,7 @@ func TestVerifyBlob(t *testing.T) { signature: blobSignature, key: pubKeyBytes, bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(blobSignature), - unexpiredCertPem, true), + unexpiredCertPem, true, hashFunc), shouldErr: true, }, { @@ -312,7 +304,7 @@ func TestVerifyBlob(t *testing.T) { signature: blobSignature, key: pubKeyBytes, bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(makeSignature(blobBytes)), - pubKeyBytes, true), + pubKeyBytes, true, hashFunc), shouldErr: true, }, { @@ -321,7 +313,7 @@ func TestVerifyBlob(t *testing.T) { signature: blobSignature, key: pubKeyBytes, bundlePath: makeLocalBundle(t, *rekorSigner, otherBytes, []byte(otherSignature), - pubKeyBytes, true), + pubKeyBytes, true, hashFunc), shouldErr: true, }, { @@ -371,7 +363,7 @@ func TestVerifyBlob(t *testing.T) { signature: blobSignature, key: pubKeyBytes, bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(blobSignature), - unexpiredCertPem, true), + unexpiredCertPem, true, hashFunc), shouldErr: true, }, { @@ -380,7 +372,7 @@ func TestVerifyBlob(t *testing.T) { signature: blobSignature, cert: unexpiredLeafCert, bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(makeSignature(blobBytes)), - unexpiredCertPem, true), + unexpiredCertPem, true, hashFunc), shouldErr: true, }, { @@ -389,7 +381,7 @@ func TestVerifyBlob(t *testing.T) { signature: blobSignature, cert: unexpiredLeafCert, bundlePath: makeLocalBundle(t, *rekorSigner, otherBytes, []byte(otherSignature), - unexpiredCertPem, true), + unexpiredCertPem, true, hashFunc), shouldErr: true, }, { @@ -405,7 +397,7 @@ func TestVerifyBlob(t *testing.T) { signature: blobSignature, cert: unexpiredLeafCert, rekorEntry: []*models.LogEntry{makeRekorEntry(t, *rekorSigner, blobBytes, []byte(blobSignature), - unexpiredCertPem, true)}, + unexpiredCertPem, true, hashFunc)}, shouldErr: false, }, { @@ -414,7 +406,7 @@ func TestVerifyBlob(t *testing.T) { signature: blobSignature, cert: unexpiredLeafCert, rekorEntry: []*models.LogEntry{makeRekorEntry(t, *rekorSigner, blobBytes, []byte(blobSignature), - unexpiredCertPem, true)}, + unexpiredCertPem, true, hashFunc)}, shouldErr: false, }, { @@ -438,7 +430,7 @@ func TestVerifyBlob(t *testing.T) { signature: blobSignature, cert: expiredLeafCert, rekorEntry: []*models.LogEntry{makeRekorEntry(t, *rekorSigner, blobBytes, []byte(blobSignature), - expiredLeafPem, true)}, + expiredLeafPem, true, hashFunc)}, shouldErr: false, }, { @@ -447,8 +439,8 @@ func TestVerifyBlob(t *testing.T) { signature: blobSignature, cert: expiredLeafCert, rekorEntry: []*models.LogEntry{makeRekorEntry(t, *rekorSigner, blobBytes, []byte(blobSignature), - expiredLeafPem, true), makeRekorEntry(t, *rekorSigner, blobBytes, []byte(blobSignature), - expiredLeafPem, false)}, + expiredLeafPem, true, hashFunc), makeRekorEntry(t, *rekorSigner, blobBytes, []byte(blobSignature), + expiredLeafPem, false, hashFunc)}, shouldErr: false, }, { @@ -457,7 +449,7 @@ func TestVerifyBlob(t *testing.T) { signature: blobSignature, cert: expiredLeafCert, rekorEntry: []*models.LogEntry{makeRekorEntry(t, *rekorSigner, blobBytes, []byte(blobSignature), - expiredLeafPem, false)}, + expiredLeafPem, false, hashFunc)}, shouldErr: true, }, @@ -467,7 +459,7 @@ func TestVerifyBlob(t *testing.T) { signature: blobSignature, cert: unexpiredLeafCert, bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(blobSignature), - unexpiredCertPem, true), + unexpiredCertPem, true, hashFunc), shouldErr: false, }, { @@ -476,7 +468,7 @@ func TestVerifyBlob(t *testing.T) { signature: blobSignature, cert: expiredLeafCert, bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(blobSignature), - expiredLeafPem, true), + expiredLeafPem, true, hashFunc), shouldErr: false, }, { @@ -485,7 +477,7 @@ func TestVerifyBlob(t *testing.T) { signature: blobSignature, cert: expiredLeafCert, bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(blobSignature), - expiredLeafPem, false), + expiredLeafPem, false, hashFunc), shouldErr: true, }, { @@ -493,8 +485,8 @@ func TestVerifyBlob(t *testing.T) { blob: blobBytes, signature: blobSignature, cert: expiredLeafCert, - bundlePath: makeLocalBundle(t, *signer, blobBytes, []byte(blobSignature), - expiredLeafPem, true), + bundlePath: makeLocalBundle(t, signer, blobBytes, []byte(blobSignature), + expiredLeafPem, true, hashFunc), shouldErr: true, }, { @@ -503,7 +495,7 @@ func TestVerifyBlob(t *testing.T) { signature: blobSignature, cert: expiredLeafCert, bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(blobSignature), - expiredLeafPem, true), + expiredLeafPem, true, hashFunc), shouldErr: false, }, { @@ -512,8 +504,8 @@ func TestVerifyBlob(t *testing.T) { signature: blobSignature, cert: expiredLeafCert, // This is the wrong signer for the SET! - rekorEntry: []*models.LogEntry{makeRekorEntry(t, *signer, blobBytes, []byte(blobSignature), - expiredLeafPem, true)}, + rekorEntry: []*models.LogEntry{makeRekorEntry(t, signer, blobBytes, []byte(blobSignature), + expiredLeafPem, true, hashFunc)}, shouldErr: true, }, { @@ -522,7 +514,7 @@ func TestVerifyBlob(t *testing.T) { signature: blobSignature, cert: expiredLeafCert, bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(blobSignature), - expiredLeafPem, true), + expiredLeafPem, true, hashFunc), tsPath: expiredTSPath, tsChainPath: expiredTSACertChainPath, shouldErr: false, @@ -553,7 +545,7 @@ func TestVerifyBlob(t *testing.T) { signature: blobSignature, cert: unexpiredLeafCert, bundlePath: makeLocalBundle(t, *rekorSigner, blobBytes, []byte(blobSignature), - unexpiredCertPem, true), + unexpiredCertPem, true, hashFunc), tsPath: unexpiredTSPath, tsChainPath: unexpiredTSACertChainPath, shouldErr: false, @@ -632,6 +624,57 @@ func TestVerifyBlob(t *testing.T) { } } +// Does not test identity options, only blob verification with different +// options. +func TestVerifyBlob(t *testing.T) { + ctx := context.Background() + td := t.TempDir() + + sigTts := []struct { + description string + leafPriv crypto.PrivateKey + hashFunc crypto.Hash + svOpts []signature.LoadOption + skip bool + }{ + { + description: "ECDSA P256 key", + leafPriv: func() crypto.PrivateKey { + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatal(err) + } + return priv + }(), + hashFunc: crypto.SHA256, + svOpts: []signature.LoadOption{}, + skip: false, + }, + { + description: "Ed25519 key", + leafPriv: func() crypto.PrivateKey { + _, priv, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + t.Fatal(err) + } + return priv + }(), + hashFunc: crypto.SHA512, + svOpts: []signature.LoadOption{signatureoptions.WithED25519ph()}, + skip: true, // TODO: Remove this once VerifyBlob command supports ED25519ph + }, + } + + for _, tt := range sigTts { + t.Run(tt.description, func(t *testing.T) { + if tt.skip { + t.Skip("Skipping test for " + tt.description) + } + testVerifyBlobLeafPriv(ctx, t, tt.leafPriv, tt.hashFunc, td, tt.svOpts...) + }) + } +} + func TestVerifyBlobCertMissingSubject(t *testing.T) { ctx := context.Background() @@ -661,19 +704,25 @@ func TestVerifyBlobCertMissingIssuer(t *testing.T) { } } -func makeRekorEntry(t *testing.T, rekorSigner signature.ECDSASignerVerifier, - pyld, sig, svBytes []byte, expiryValid bool) *models.LogEntry { +func makeRekorEntry(t *testing.T, rekorSigner signature.SignerVerifier, + pyld, sig, svBytes []byte, expiryValid bool, hashFunc crypto.Hash) *models.LogEntry { ctx := context.Background() // Calculate log ID, the digest of the Rekor public key - logID, err := getLogID(rekorSigner.Public()) + pub, err := rekorSigner.PublicKey() + if err != nil { + t.Fatal(err) + } + logID, err := getLogID(pub) if err != nil { t.Fatal(err) } hashedrekord := &hashedrekord_v001.V001Entry{} - h := sha256.Sum256(pyld) + h := hashFunc.New() + h.Write(pyld) + digest := h.Sum(nil) pe, err := hashedrekord.CreateFromArtifactProperties(ctx, types.ArtifactProperties{ - ArtifactHash: hex.EncodeToString(h[:]), + ArtifactHash: hex.EncodeToString(digest), SignatureBytes: sig, PublicKeyBytes: [][]byte{svBytes}, PKIFormat: "x509", @@ -732,12 +781,12 @@ func makeRekorEntry(t *testing.T, rekorSigner signature.ECDSASignerVerifier, return &models.LogEntry{hex.EncodeToString(uuid): e} } -func makeLocalBundle(t *testing.T, rekorSigner signature.ECDSASignerVerifier, - pyld []byte, sig []byte, svBytes []byte, expiryValid bool) string { +func makeLocalBundle(t *testing.T, rekorSigner signature.SignerVerifier, + pyld []byte, sig []byte, svBytes []byte, expiryValid bool, hashFunc crypto.Hash) string { td := t.TempDir() // Create bundle. - entry := makeRekorEntry(t, rekorSigner, pyld, sig, svBytes, expiryValid) + entry := makeRekorEntry(t, rekorSigner, pyld, sig, svBytes, expiryValid, hashFunc) var e models.LogEntryAnon for _, v := range *entry { e = v diff --git a/go.mod b/go.mod index 78540992179..c3df0f9073d 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( github.com/sigstore/fulcio v1.6.6 github.com/sigstore/protobuf-specs v0.4.0 github.com/sigstore/rekor v1.3.9 - github.com/sigstore/sigstore v1.8.12 + github.com/sigstore/sigstore v1.8.13-0.20250204232249-4b62818325b7 github.com/sigstore/sigstore-go v0.7.0 github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.12 github.com/sigstore/sigstore/pkg/signature/kms/azure v1.8.12 diff --git a/go.sum b/go.sum index 1489156cf11..b4b34c2a68f 100644 --- a/go.sum +++ b/go.sum @@ -624,8 +624,8 @@ github.com/sigstore/protobuf-specs v0.4.0 h1:yoZbdh0kZYKOSiVbYyA8J3f2wLh5aUk2SQB github.com/sigstore/protobuf-specs v0.4.0/go.mod h1:FKW5NYhnnFQ/Vb9RKtQk91iYd0MKJ9AxyqInEwU6+OI= github.com/sigstore/rekor v1.3.9 h1:sUjRpKVh/hhgqGMs0t+TubgYsksArZ6poLEC3MsGAzU= github.com/sigstore/rekor v1.3.9/go.mod h1:xThNUhm6eNEmkJ/SiU/FVU7pLY2f380fSDZFsdDWlcM= -github.com/sigstore/sigstore v1.8.12 h1:S8xMVZbE2z9ZBuQUEG737pxdLjnbOIcFi5v9UFfkJFc= -github.com/sigstore/sigstore v1.8.12/go.mod h1:+PYQAa8rfw0QdPpBcT+Gl3egKD9c+TUgAlF12H3Nmjo= +github.com/sigstore/sigstore v1.8.13-0.20250204232249-4b62818325b7 h1:dKgngAj90pDWGKB/yBt/AmSvNFzLOPvYGfEgEKRQWc4= +github.com/sigstore/sigstore v1.8.13-0.20250204232249-4b62818325b7/go.mod h1:2lXojNsjZjkqu1//FWxq7qUcPB8Lq1KsR5hc+GkcC/4= github.com/sigstore/sigstore-go v0.7.0 h1:bIGPc2IbnbxnzlqQcKlh1o96bxVJ4yRElpP1gHrOH48= github.com/sigstore/sigstore-go v0.7.0/go.mod h1:4RrCK+i+jhx7lyOG2Vgef0/kFLbKlDI1hrioUYvkxxA= github.com/sigstore/sigstore/pkg/signature/kms/aws v1.8.12 h1:EC3UmIaa7nV9sCgSpVevmvgvTYTkMqyrRbj5ojPp7tE= diff --git a/pkg/cosign/keys.go b/pkg/cosign/keys.go index ed5bbc16d4b..527cdc5d174 100644 --- a/pkg/cosign/keys.go +++ b/pkg/cosign/keys.go @@ -210,6 +210,10 @@ func PemToECDSAKey(pemBytes []byte) (*ecdsa.PublicKey, error) { // LoadPrivateKey loads a cosign PEM private key encrypted with the given passphrase, // and returns a SignerVerifier instance. The private key must be in the PKCS #8 format. func LoadPrivateKey(key []byte, pass []byte) (signature.SignerVerifier, error) { + return LoadPrivateKeyWithOpts(key, pass) +} + +func LoadPrivateKeyWithOpts(key []byte, pass []byte, opts ...signature.LoadOption) (signature.SignerVerifier, error) { // Decrypt first p, _ := pem.Decode(key) if p == nil { @@ -227,14 +231,5 @@ func LoadPrivateKey(key []byte, pass []byte) (signature.SignerVerifier, error) { if err != nil { return nil, fmt.Errorf("parsing private key: %w", err) } - switch pk := pk.(type) { - case *rsa.PrivateKey: - return signature.LoadRSAPKCS1v15SignerVerifier(pk, crypto.SHA256) - case *ecdsa.PrivateKey: - return signature.LoadECDSASignerVerifier(pk, crypto.SHA256) - case ed25519.PrivateKey: - return signature.LoadED25519SignerVerifier(pk) - default: - return nil, errors.New("unsupported key type") - } + return signature.LoadSignerVerifierWithOpts(pk, opts...) } diff --git a/pkg/cosign/keys_test.go b/pkg/cosign/keys_test.go index f1408da27e5..e71a790e95d 100644 --- a/pkg/cosign/keys_test.go +++ b/pkg/cosign/keys_test.go @@ -16,12 +16,16 @@ package cosign import ( + "bytes" "crypto/rand" "errors" "os" "path/filepath" "testing" + "github.com/sigstore/sigstore/pkg/cryptoutils" + "github.com/sigstore/sigstore/pkg/signature" + "github.com/sigstore/sigstore/pkg/signature/options" "github.com/stretchr/testify/require" ) @@ -408,6 +412,7 @@ func TestImportPrivateKey(t *testing.T) { fileName string pemData string expected error + opts []signature.LoadOption }{ // RSA tests { @@ -477,6 +482,14 @@ func TestImportPrivateKey(t *testing.T) { pemData: ed25519key, expected: nil, }, + { + fileName: "ed25519.key", + pemData: ed25519key, + expected: nil, + opts: []signature.LoadOption{ + options.WithED25519ph(), + }, + }, // Additional tests { fileName: "invalidkey.key", @@ -497,8 +510,21 @@ func TestImportPrivateKey(t *testing.T) { if err == nil || tc.expected == nil { require.Equal(t, tc.expected, err) // Loading the private key should also work. - _, err = LoadPrivateKey(keyBytes.PrivateBytes, []byte("hello")) + sv, err := LoadPrivateKeyWithOpts(keyBytes.PrivateBytes, []byte("hello"), tc.opts...) require.Equal(t, tc.expected, err) + + // The signer/verifier should be correctly set up. + msg := []byte("hello") + sig, err := sv.SignMessage(bytes.NewReader(msg)) + require.NoError(t, err) + require.NoError(t, sv.VerifySignature(bytes.NewReader(sig), bytes.NewReader(msg))) + + // Make sure the signer has signed the message with the correct signer and options + pubKey, err := cryptoutils.UnmarshalPEMToPublicKey(keyBytes.PublicBytes) + require.NoError(t, err) + verifier, err := signature.LoadVerifierWithOpts(pubKey, tc.opts...) + require.NoError(t, err) + require.NoError(t, verifier.VerifySignature(bytes.NewReader(sig), bytes.NewReader(msg))) } else { require.Equal(t, tc.expected.Error(), err.Error()) } diff --git a/pkg/cosign/verify.go b/pkg/cosign/verify.go index ea2ee1c5c59..104787eeefb 100644 --- a/pkg/cosign/verify.go +++ b/pkg/cosign/verify.go @@ -17,7 +17,6 @@ package cosign import ( "bytes" "context" - "crypto" "crypto/ecdsa" "crypto/sha256" "crypto/x509" @@ -255,6 +254,60 @@ func (co *CheckOpts) verificationOptions() (trustedMaterial root.TrustedMaterial return vTrustedMaterial, verifierOptions, policyOptions, nil } +type validateCertOpts struct { + chain []*x509.Certificate + svOpts []signature.LoadOption + pool *x509.CertPool +} + +type ValidateAndUnpackCertOption func(*validateCertOpts) + +func WithChain(chain []*x509.Certificate) ValidateAndUnpackCertOption { + return func(o *validateCertOpts) { + o.chain = chain + } +} + +func WithPool(pool *x509.CertPool) ValidateAndUnpackCertOption { + return func(o *validateCertOpts) { + o.pool = pool + } +} + +func WithSignerVerifierOptions(svOpts ...signature.LoadOption) ValidateAndUnpackCertOption { + return func(o *validateCertOpts) { + o.svOpts = svOpts + } +} + +func makeValidateAndUnpackCertOpts(co *CheckOpts, opts ...ValidateAndUnpackCertOption) (*validateCertOpts, error) { + o := &validateCertOpts{} + for _, opt := range opts { + opt(o) + } + + // Pool Option has precedence over chain option + if o.pool == nil && o.chain != nil { + if len(o.chain) == 0 { + return nil, errors.New("no chain provided to validate certificate") + } + rootPool := x509.NewCertPool() + rootPool.AddCert(o.chain[len(o.chain)-1]) + co.RootCerts = rootPool + + subPool := x509.NewCertPool() + for _, c := range o.chain[:len(o.chain)-1] { + subPool.AddCert(c) + } + o.pool = subPool + } + // If no pool or chain is provided, use the one from the CheckOpts + if o.pool == nil { + o.pool = co.IntermediateCerts + } + return o, nil +} + // This is a substitutable signature verification function that can be used for verifying // attestations of blobs. type signatureVerificationFn func( @@ -311,14 +364,33 @@ func verifyOCISignature(ctx context.Context, verifier signature.Verifier, sig pa // certificate chains up to a trusted root using intermediate certificate chain coming from CheckOpts. // Optionally verifies the subject and issuer of the certificate. func ValidateAndUnpackCert(cert *x509.Certificate, co *CheckOpts) (signature.Verifier, error) { - return ValidateAndUnpackCertWithIntermediates(cert, co, co.IntermediateCerts) + return ValidateAndUnpackCertWithOpts(cert, co) } // ValidateAndUnpackCertWithIntermediates creates a Verifier from a certificate. Verifies that the // certificate chains up to a trusted root using intermediate cert passed as separate argument. // Optionally verifies the subject and issuer of the certificate. func ValidateAndUnpackCertWithIntermediates(cert *x509.Certificate, co *CheckOpts, intermediateCerts *x509.CertPool) (signature.Verifier, error) { - verifier, err := signature.LoadVerifier(cert.PublicKey, crypto.SHA256) + return ValidateAndUnpackCertWithOpts(cert, co, WithPool(intermediateCerts)) +} + +// ValidateAndUnpackCertWithChain creates a Verifier from a certificate. Verifies that the certificate +// chains up to the provided root. Chain should start with the parent of the certificate and end with the root. +// Optionally verifies the subject and issuer of the certificate. +func ValidateAndUnpackCertWithChain(cert *x509.Certificate, chain []*x509.Certificate, co *CheckOpts) (signature.Verifier, error) { + return ValidateAndUnpackCertWithOpts(cert, co, WithChain(chain)) +} + +// ValidateAndUnpackCertWithOpts creates a Verifier from a certificate. Verifies that the certificate +// chains up to a trusted root. Optionally verifies the subject and issuer of the certificate. +// Accept chain and signerVerifierOptions as optional parameters. +func ValidateAndUnpackCertWithOpts(cert *x509.Certificate, co *CheckOpts, opts ...ValidateAndUnpackCertOption) (signature.Verifier, error) { + o, err := makeValidateAndUnpackCertOpts(co, opts...) + if err != nil { + return nil, err + } + + verifier, err := signature.LoadVerifierWithOpts(cert.PublicKey, o.svOpts...) if err != nil { return nil, fmt.Errorf("invalid certificate found on signature: %w", err) } @@ -338,7 +410,7 @@ func ValidateAndUnpackCertWithIntermediates(cert *x509.Certificate, co *CheckOpt } // Now verify the cert, then the signature. - chains, err := TrustedCert(cert, co.RootCerts, intermediateCerts) + chains, err := TrustedCert(cert, co.RootCerts, o.pool) if err != nil { return nil, err @@ -503,26 +575,6 @@ func validateCertExtensions(ce CertExtensions, co *CheckOpts) error { return nil } -// ValidateAndUnpackCertWithChain creates a Verifier from a certificate. Verifies that the certificate -// chains up to the provided root. Chain should start with the parent of the certificate and end with the root. -// Optionally verifies the subject and issuer of the certificate. -func ValidateAndUnpackCertWithChain(cert *x509.Certificate, chain []*x509.Certificate, co *CheckOpts) (signature.Verifier, error) { - if len(chain) == 0 { - return nil, errors.New("no chain provided to validate certificate") - } - rootPool := x509.NewCertPool() - rootPool.AddCert(chain[len(chain)-1]) - co.RootCerts = rootPool - - subPool := x509.NewCertPool() - for _, c := range chain[:len(chain)-1] { - subPool.AddCert(c) - } - co.IntermediateCerts = subPool - - return ValidateAndUnpackCert(cert, co) -} - func tlogValidateEntry(ctx context.Context, client *client.Rekor, rekorPubKeys *TrustedTransparencyLogPubKeys, sig oci.Signature, pem []byte) (*models.LogEntryAnon, error) { b64sig, err := sig.Base64Signature() @@ -572,14 +624,18 @@ func (fos *fakeOCISignatures) Get() ([]oci.Signature, error) { return fos.signatures, nil } -// VerifyImageSignatures does all the main cosign checks in a loop, returning the verified signatures. +func VerifyImageSignatures(ctx context.Context, signedImgRef name.Reference, co *CheckOpts) (checkedSignatures []oci.Signature, bundleVerified bool, err error) { + return VerifyImageSignaturesWithOpts(ctx, signedImgRef, co) +} + +// VerifyImageSignaturesWithOpts does all the main cosign checks in a loop, returning the verified signatures. // If there were no valid signatures, we return an error. // Note that if co.ExperimentlOCI11 is set, we will attempt to verify // signatures using the experimental OCI 1.1 behavior. -func VerifyImageSignatures(ctx context.Context, signedImgRef name.Reference, co *CheckOpts) (checkedSignatures []oci.Signature, bundleVerified bool, err error) { +func VerifyImageSignaturesWithOpts(ctx context.Context, signedImgRef name.Reference, co *CheckOpts, svOpts ...signature.LoadOption) (checkedSignatures []oci.Signature, bundleVerified bool, err error) { // Try first using OCI 1.1 behavior if experimental flag is set. if co.ExperimentalOCI11 { - verified, bundleVerified, err := verifyImageSignaturesExperimentalOCI(ctx, signedImgRef, co) + verified, bundleVerified, err := verifyImageSignaturesExperimentalOCI(ctx, signedImgRef, co, svOpts...) if err == nil { return verified, bundleVerified, nil } @@ -624,12 +680,16 @@ func VerifyImageSignatures(ctx context.Context, signedImgRef name.Reference, co } } - return verifySignatures(ctx, sigs, h, co) + return verifySignatures(ctx, sigs, h, co, svOpts...) } -// VerifyLocalImageSignatures verifies signatures from a saved, local image, without any network calls, returning the verified signatures. -// If there were no valid signatures, we return an error. func VerifyLocalImageSignatures(ctx context.Context, path string, co *CheckOpts) (checkedSignatures []oci.Signature, bundleVerified bool, err error) { + return VerifyLocalImageSignaturesWithOpts(ctx, path, co) +} + +// VerifyLocalImageSignaturesWithOpts verifies signatures from a saved, local image, without any network calls, returning the verified signatures. +// If there were no valid signatures, we return an error. +func VerifyLocalImageSignaturesWithOpts(ctx context.Context, path string, co *CheckOpts, svOpts ...signature.LoadOption) (checkedSignatures []oci.Signature, bundleVerified bool, err error) { // Enforce this up front. if co.RootCerts == nil && co.SigVerifier == nil { return nil, false, errors.New("one of verifier or root certs is required") @@ -673,10 +733,10 @@ func VerifyLocalImageSignatures(ctx context.Context, path string, co *CheckOpts) return nil, false, fmt.Errorf("no signatures associated with the image saved in %s", path) } - return verifySignatures(ctx, sigs, h, co) + return verifySignatures(ctx, sigs, h, co, svOpts...) } -func verifySignatures(ctx context.Context, sigs oci.Signatures, h v1.Hash, co *CheckOpts) (checkedSignatures []oci.Signature, bundleVerified bool, err error) { +func verifySignatures(ctx context.Context, sigs oci.Signatures, h v1.Hash, co *CheckOpts, svOpts ...signature.LoadOption) (checkedSignatures []oci.Signature, bundleVerified bool, err error) { sl, err := sigs.Get() if err != nil { return nil, false, err @@ -704,7 +764,7 @@ func verifySignatures(ctx context.Context, sigs oci.Signatures, h v1.Hash, co *C return } - verified, err := VerifyImageSignature(ctx, sig, h, co) + verified, err := VerifyImageSignatureWithOpts(ctx, sig, h, co, svOpts...) bundlesVerified[index] = verified if err != nil { t.Done(err) @@ -752,7 +812,7 @@ func verifySignatures(ctx context.Context, sigs oci.Signatures, h v1.Hash, co *C // we are in experimental mode). // 3. If a certificate is provided, check it's expiration using the transparency log timestamp. func verifyInternal(ctx context.Context, sig oci.Signature, h v1.Hash, - verifyFn signatureVerificationFn, co *CheckOpts) ( + verifyFn signatureVerificationFn, co *CheckOpts, svOpts ...signature.LoadOption) ( bundleVerified bool, err error) { var acceptableRFC3161Time, acceptableRekorBundleTime *time.Time // Timestamps for the signature we accept, or nil if not applicable. @@ -839,7 +899,7 @@ func verifyInternal(ctx context.Context, sig oci.Signature, h v1.Hash, if pool == nil { pool = co.IntermediateCerts } - verifier, err = ValidateAndUnpackCertWithIntermediates(cert, co, pool) + verifier, err = ValidateAndUnpackCertWithOpts(cert, co, WithPool(pool), WithSignerVerifierOptions(svOpts...)) if err != nil { return false, err } @@ -916,13 +976,23 @@ func keyBytes(sig oci.Signature, co *CheckOpts) ([]byte, error) { // VerifyBlobSignature verifies a blob signature. func VerifyBlobSignature(ctx context.Context, sig oci.Signature, co *CheckOpts) (bundleVerified bool, err error) { + return VerifyBlobSignatureWithOpts(ctx, sig, co) +} + +// VerifyBlobSignatureWithOpts verifies a blob signature. +func VerifyBlobSignatureWithOpts(ctx context.Context, sig oci.Signature, co *CheckOpts, svOpts ...signature.LoadOption) (bundleVerified bool, err error) { // The hash of the artifact is unused. - return verifyInternal(ctx, sig, v1.Hash{}, verifyOCISignature, co) + return verifyInternal(ctx, sig, v1.Hash{}, verifyOCISignature, co, svOpts...) } // VerifyImageSignature verifies a signature func VerifyImageSignature(ctx context.Context, sig oci.Signature, h v1.Hash, co *CheckOpts) (bundleVerified bool, err error) { - return verifyInternal(ctx, sig, h, verifyOCISignature, co) + return VerifyImageSignatureWithOpts(ctx, sig, h, co) +} + +// VerifyImageSignatureWithOpts verifies a signature +func VerifyImageSignatureWithOpts(ctx context.Context, sig oci.Signature, h v1.Hash, co *CheckOpts, svOpts ...signature.LoadOption) (bundleVerified bool, err error) { + return verifyInternal(ctx, sig, h, verifyOCISignature, co, svOpts...) } func loadSignatureFromFile(ctx context.Context, sigRef string, signedImgRef name.Reference, co *CheckOpts) (oci.Signatures, error) { @@ -969,9 +1039,13 @@ func loadSignatureFromFile(ctx context.Context, sigRef string, signedImgRef name }, nil } -// VerifyImageAttestations does all the main cosign checks in a loop, returning the verified attestations. -// If there were no valid attestations, we return an error. func VerifyImageAttestations(ctx context.Context, signedImgRef name.Reference, co *CheckOpts) (checkedAttestations []oci.Signature, bundleVerified bool, err error) { + return VerifyImageAttestationsWithOpts(ctx, signedImgRef, co) +} + +// VerifyImageAttestationsWithOpts does all the main cosign checks in a loop, returning the verified attestations. +// If there were no valid attestations, we return an error. +func VerifyImageAttestationsWithOpts(ctx context.Context, signedImgRef name.Reference, co *CheckOpts, svOpts ...signature.LoadOption) (checkedAttestations []oci.Signature, bundleVerified bool, err error) { // Enforce this up front. if co.RootCerts == nil && co.SigVerifier == nil { return nil, false, errors.New("one of verifier or root certs is required") @@ -997,13 +1071,17 @@ func VerifyImageAttestations(ctx context.Context, signedImgRef name.Reference, c return nil, false, err } - return VerifyImageAttestation(ctx, atts, h, co) + return VerifyImageAttestationWithOpts(ctx, atts, h, co, svOpts...) } -// VerifyLocalImageAttestations verifies attestations from a saved, local image, without any network calls, +func VerifyLocalImageAttestations(ctx context.Context, path string, co *CheckOpts) (checkedAttestations []oci.Signature, bundleVerified bool, err error) { + return VerifyLocalImageAttestationsWithOpts(ctx, path, co) +} + +// VerifyLocalImageAttestationsWithOpts verifies attestations from a saved, local image, without any network calls, // returning the verified attestations. // If there were no valid signatures, we return an error. -func VerifyLocalImageAttestations(ctx context.Context, path string, co *CheckOpts) (checkedAttestations []oci.Signature, bundleVerified bool, err error) { +func VerifyLocalImageAttestationsWithOpts(ctx context.Context, path string, co *CheckOpts, svOpts ...signature.LoadOption) (checkedAttestations []oci.Signature, bundleVerified bool, err error) { // Enforce this up front. if co.RootCerts == nil && co.SigVerifier == nil { return nil, false, errors.New("one of verifier or root certs is required") @@ -1043,15 +1121,24 @@ func VerifyLocalImageAttestations(ctx context.Context, path string, co *CheckOpt if err != nil { return nil, false, err } - return VerifyImageAttestation(ctx, atts, h, co) + return VerifyImageAttestationWithOpts(ctx, atts, h, co, svOpts...) } func VerifyBlobAttestation(ctx context.Context, att oci.Signature, h v1.Hash, co *CheckOpts) ( bool, error) { - return verifyInternal(ctx, att, h, verifyOCIAttestation, co) + return VerifyBlobAttestationWithOpts(ctx, att, h, co) +} + +func VerifyBlobAttestationWithOpts(ctx context.Context, att oci.Signature, h v1.Hash, co *CheckOpts, svOpts ...signature.LoadOption) ( + bool, error) { + return verifyInternal(ctx, att, h, verifyOCIAttestation, co, svOpts...) } func VerifyImageAttestation(ctx context.Context, atts oci.Signatures, h v1.Hash, co *CheckOpts) (checkedAttestations []oci.Signature, bundleVerified bool, err error) { + return VerifyImageAttestationWithOpts(ctx, atts, h, co) +} + +func VerifyImageAttestationWithOpts(ctx context.Context, atts oci.Signatures, h v1.Hash, co *CheckOpts, svOpts ...signature.LoadOption) (checkedAttestations []oci.Signature, bundleVerified bool, err error) { sl, err := atts.Get() if err != nil { return nil, false, err @@ -1073,7 +1160,7 @@ func VerifyImageAttestation(ctx context.Context, atts oci.Signatures, h v1.Hash, return } if err := func(att oci.Signature) error { - verified, err := verifyInternal(ctx, att, h, verifyOCIAttestation, co) + verified, err := verifyInternal(ctx, att, h, verifyOCIAttestation, co, svOpts...) bundlesVerified[index] = verified return err }(att); err != nil { @@ -1451,7 +1538,7 @@ func correctAnnotations(wanted, have map[string]interface{}) bool { // verifyImageSignaturesExperimentalOCI does all the main cosign checks in a loop, returning the verified signatures. // If there were no valid signatures, we return an error, using OCI 1.1+ behavior. -func verifyImageSignaturesExperimentalOCI(ctx context.Context, signedImgRef name.Reference, co *CheckOpts) (checkedSignatures []oci.Signature, bundleVerified bool, err error) { +func verifyImageSignaturesExperimentalOCI(ctx context.Context, signedImgRef name.Reference, co *CheckOpts, svOpts ...signature.LoadOption) (checkedSignatures []oci.Signature, bundleVerified bool, err error) { // Enforce this up front. if co.RootCerts == nil && co.SigVerifier == nil { return nil, false, errors.New("one of verifier or root certs is required") @@ -1504,5 +1591,5 @@ func verifyImageSignaturesExperimentalOCI(ctx context.Context, signedImgRef name } } - return verifySignatures(ctx, sigs, h, co) + return verifySignatures(ctx, sigs, h, co, svOpts...) } diff --git a/pkg/signature/keys.go b/pkg/signature/keys.go index dfac964725d..3f260cd593a 100644 --- a/pkg/signature/keys.go +++ b/pkg/signature/keys.go @@ -31,6 +31,7 @@ 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 @@ -41,7 +42,18 @@ func LoadPublicKey(ctx context.Context, keyRef string) (verifier signature.Verif // 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) { + return VerifierForKeyRefWithOpts(ctx, keyRef, options.WithHash(hashAlgorithm)) +} + +// VerifierForKeyRefWithOpts parses the given keyRef, loads the key and returns an appropriate +// verifier using the provided hash algorithm and options +func VerifierForKeyRefWithOpts(ctx context.Context, keyRef string, opts ...signature.LoadOption) (verifier signature.Verifier, err error) { // The key could be plaintext, in a file, at a URL, or in KMS. + hashAlgorithm := crypto.SHA256 + for _, o := range opts { + o.ApplyHash(&hashAlgorithm) + } + var perr *kms.ProviderNotFoundError kmsKey, err := kms.Get(ctx, keyRef, hashAlgorithm) switch { @@ -69,10 +81,10 @@ func VerifierForKeyRef(ctx context.Context, keyRef string, hashAlgorithm crypto. return nil, fmt.Errorf("pem to public key: %w", err) } - return signature.LoadVerifier(pubKey, hashAlgorithm) + return signature.LoadVerifierWithOpts(pubKey, opts...) } -func loadKey(keyPath string, pf cosign.PassFunc) (signature.SignerVerifier, error) { +func loadKey(keyPath string, pf cosign.PassFunc, opts ...signature.LoadOption) (signature.SignerVerifier, error) { kb, err := blob.LoadFileOrURL(keyPath) if err != nil { return nil, err @@ -84,16 +96,21 @@ func loadKey(keyPath string, pf cosign.PassFunc) (signature.SignerVerifier, erro return nil, err } } - return cosign.LoadPrivateKey(kb, pass) + return cosign.LoadPrivateKeyWithOpts(kb, pass, opts...) } // LoadPublicKeyRaw loads a verifier from a PEM-encoded public key func LoadPublicKeyRaw(raw []byte, hashAlgorithm crypto.Hash) (signature.Verifier, error) { + return LoadPublicKeyRawWithOpts(raw, options.WithHash(hashAlgorithm)) +} + +// LoadPublicKeyRawWithOpts loads a verifier from a PEM-encoded public key with options +func LoadPublicKeyRawWithOpts(raw []byte, opts ...signature.LoadOption) (signature.Verifier, error) { pub, err := cryptoutils.UnmarshalPEMToPublicKey(raw) if err != nil { return nil, err } - return signature.LoadVerifier(pub, hashAlgorithm) + return signature.LoadVerifierWithOpts(pub, opts...) } func SignerFromKeyRef(ctx context.Context, keyRef string, pf cosign.PassFunc) (signature.Signer, error) { @@ -101,6 +118,10 @@ func SignerFromKeyRef(ctx context.Context, keyRef string, pf cosign.PassFunc) (s } func SignerVerifierFromKeyRef(ctx context.Context, keyRef string, pf cosign.PassFunc) (signature.SignerVerifier, error) { + return SignerVerifierFromKeyRefWithOpts(ctx, keyRef, pf) +} + +func SignerVerifierFromKeyRefWithOpts(ctx context.Context, keyRef string, pf cosign.PassFunc, opts ...signature.LoadOption) (signature.SignerVerifier, error) { switch { case strings.HasPrefix(keyRef, pkcs11key.ReferenceScheme): pkcs11UriConfig := pkcs11key.NewPkcs11UriConfig() @@ -129,7 +150,7 @@ func SignerVerifierFromKeyRef(ctx context.Context, keyRef string, pf cosign.Pass } if len(s.Data) > 0 { - return cosign.LoadPrivateKey(s.Data["cosign.key"], s.Data["cosign.password"]) + return cosign.LoadPrivateKeyWithOpts(s.Data["cosign.key"], s.Data["cosign.password"], opts...) } case strings.HasPrefix(keyRef, gitlab.ReferenceScheme): split := strings.Split(keyRef, "://") @@ -150,7 +171,7 @@ func SignerVerifierFromKeyRef(ctx context.Context, keyRef string, pf cosign.Pass return nil, err } - return cosign.LoadPrivateKey([]byte(pk), []byte(pass)) + return cosign.LoadPrivateKeyWithOpts([]byte(pk), []byte(pass), opts...) } if strings.Contains(keyRef, "://") { @@ -165,7 +186,7 @@ func SignerVerifierFromKeyRef(ctx context.Context, keyRef string, pf cosign.Pass // ProviderNotFoundError is okay; loadKey handles other URL schemes } - return loadKey(keyRef, pf) + return loadKey(keyRef, pf, opts...) } func PublicKeyFromKeyRef(ctx context.Context, keyRef string) (signature.Verifier, error) { @@ -173,6 +194,10 @@ func PublicKeyFromKeyRef(ctx context.Context, keyRef string) (signature.Verifier } func PublicKeyFromKeyRefWithHashAlgo(ctx context.Context, keyRef string, hashAlgorithm crypto.Hash) (signature.Verifier, error) { + return PublicKeyFromKeyRefWithOpts(ctx, keyRef, options.WithHash(hashAlgorithm)) +} + +func PublicKeyFromKeyRefWithOpts(ctx context.Context, keyRef string, opts ...signature.LoadOption) (signature.Verifier, error) { if strings.HasPrefix(keyRef, kubernetes.KeyReference) { s, err := kubernetes.GetKeyPairSecret(ctx, keyRef) if err != nil { @@ -180,7 +205,7 @@ func PublicKeyFromKeyRefWithHashAlgo(ctx context.Context, keyRef string, hashAlg } if len(s.Data) > 0 { - return LoadPublicKeyRaw(s.Data["cosign.pub"], hashAlgorithm) + return LoadPublicKeyRawWithOpts(s.Data["cosign.pub"], opts...) } } @@ -219,11 +244,11 @@ func PublicKeyFromKeyRefWithHashAlgo(ctx context.Context, keyRef string, hashAlg } if len(pubKey) > 0 { - return LoadPublicKeyRaw([]byte(pubKey), hashAlgorithm) + return LoadPublicKeyRawWithOpts([]byte(pubKey), opts...) } } - return VerifierForKeyRef(ctx, keyRef, hashAlgorithm) + return VerifierForKeyRefWithOpts(ctx, keyRef, opts...) } func PublicKeyPem(key signature.PublicKeyProvider, pkOpts ...signature.PublicKeyOption) ([]byte, error) { diff --git a/pkg/signature/keys_test.go b/pkg/signature/keys_test.go index 0365aa34911..c44797857a1 100644 --- a/pkg/signature/keys_test.go +++ b/pkg/signature/keys_test.go @@ -15,6 +15,7 @@ package signature import ( + "bytes" "context" "crypto" "errors" @@ -23,10 +24,27 @@ import ( "github.com/sigstore/cosign/v2/pkg/blob" "github.com/sigstore/cosign/v2/pkg/cosign" - sigsignature "github.com/sigstore/sigstore/pkg/signature" + "github.com/sigstore/sigstore/pkg/signature" "github.com/sigstore/sigstore/pkg/signature/kms" + "github.com/sigstore/sigstore/pkg/signature/options" ) +const ed25519PrivateKey = `-----BEGIN ENCRYPTED SIGSTORE PRIVATE KEY----- +eyJrZGYiOnsibmFtZSI6InNjcnlwdCIsInBhcmFtcyI6eyJOIjo2NTUzNiwiciI6 +OCwicCI6MX0sInNhbHQiOiJxYVVSY1ppbTN3RE9ZMVlselFGaFdVWHBnMU5tZlAv +YndiM2ZpWE54ck5BPSJ9LCJjaXBoZXIiOnsibmFtZSI6Im5hY2wvc2VjcmV0Ym94 +Iiwibm9uY2UiOiJMRjNVN3crNXgzWmRvMlNGUkFicE1nY1N5Y2sxN3R1LyJ9LCJj +aXBoZXJ0ZXh0IjoiY2lUQTYrODVWRDdsTlpwRTVLdmpMdjJrTXNZdmtvOHNHV0tq +QTRYZDY2WFRaTUw3UG5xczQ2NloycDRyWGJUdXBlcStwSXlnSXhvS29UVmFHbG9N +RXc9PSJ9 +-----END ENCRYPTED SIGSTORE PRIVATE KEY----- +` + +const ed25519PublicKey = `-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEAFs2AhZmYWkoEsqUf6yotyIwVb5uATpuKK194tq8OkoQ= +-----END PUBLIC KEY----- +` + func generateKeyFile(t *testing.T, tmpDir string, pf cosign.PassFunc) (privFile, pubFile string) { t.Helper() @@ -141,7 +159,7 @@ func TestSignerVerifierFromEnvVar(t *testing.T) { } func TestVerifierForKeyRefError(t *testing.T) { - kms.AddProvider("errorkms://", func(_ context.Context, _ string, _ crypto.Hash, _ ...sigsignature.RPCOption) (kms.SignerVerifier, error) { + kms.AddProvider("errorkms://", func(_ context.Context, _ string, _ crypto.Hash, _ ...signature.RPCOption) (kms.SignerVerifier, error) { return nil, errors.New("bad") }) var uerr *blob.UnrecognizedSchemeError @@ -167,3 +185,88 @@ func pass(s string) cosign.PassFunc { return []byte(s), nil } } + +func TestPublicKeyFromKeyRefWithOpts(t *testing.T) { + t.Parallel() + ctx := context.Background() + tmpDir := t.TempDir() + + // Create a temporary public key file + tmpPubFile, err := os.CreateTemp(tmpDir, "cosign_test_*.pub") + if err != nil { + t.Fatalf("failed to create temp pub file: %v", err) + } + t.Cleanup(func() { + tmpPubFile.Close() + }) + + if _, err := tmpPubFile.Write([]byte(ed25519PublicKey)); err != nil { + t.Fatalf("failed to write pub file: %v", err) + } + + // Test data to sign and verify + testData := []byte("test data") + + testCases := []struct { + name string + signerOpts []signature.LoadOption + verifierOpts []signature.LoadOption + expectErr bool + }{ + { + name: "pure ed25519 signer/verifier", + signerOpts: []signature.LoadOption{}, + verifierOpts: []signature.LoadOption{}, + expectErr: false, + }, + { + name: "ed25519ph signer/verifier", + signerOpts: []signature.LoadOption{options.WithED25519ph()}, + verifierOpts: []signature.LoadOption{options.WithED25519ph()}, + expectErr: false, + }, + { + name: "ed25519 pure signer/ed25519ph verifier", + signerOpts: []signature.LoadOption{}, + verifierOpts: []signature.LoadOption{options.WithED25519ph()}, + expectErr: true, + }, + { + name: "ed25519ph signer/ed25519 verifier", + signerOpts: []signature.LoadOption{options.WithED25519ph()}, + verifierOpts: []signature.LoadOption{}, + expectErr: true, + }, + } + + os.Setenv("MY_ENV_VAR", string(ed25519PrivateKey)) + t.Cleanup(func() { + os.Unsetenv("MY_ENV_VAR") + }) + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + verifier, err := PublicKeyFromKeyRefWithOpts(ctx, tmpPubFile.Name(), tc.verifierOpts...) + if err != nil { + t.Fatalf("failed to load public key: %v", err) + } + + signerStandard, err := SignerVerifierFromKeyRefWithOpts(ctx, "env://MY_ENV_VAR", pass(""), tc.signerOpts...) + if err != nil { + t.Fatalf("failed to load standard signer: %v", err) + } + sig, err := signerStandard.SignMessage(bytes.NewReader(testData), options.WithContext(ctx)) + if err != nil { + t.Fatalf("failed to sign message: %v", err) + } + + err = verifier.VerifySignature(bytes.NewReader(sig), bytes.NewReader(testData), options.WithContext(ctx)) + if tc.expectErr && err == nil { + t.Fatalf("expected verification error, got none") + } else if !tc.expectErr && err != nil { + t.Fatalf("unexpected verification error: %v", err) + } + }) + } +} diff --git a/test/cert_utils.go b/test/cert_utils.go index 879f12a3ba8..75ef2454c3d 100644 --- a/test/cert_utils.go +++ b/test/cert_utils.go @@ -22,6 +22,7 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/asn1" + "errors" "math/big" "net" "net/url" @@ -118,8 +119,12 @@ func GenerateSubordinateCa(rootTemplate *x509.Certificate, rootPriv crypto.Signe } func GenerateLeafCertWithExpiration(subject string, oidcIssuer string, expiration time.Time, - priv *ecdsa.PrivateKey, + priv crypto.PrivateKey, parentTemplate *x509.Certificate, parentPriv crypto.Signer) (*x509.Certificate, error) { + signer, ok := priv.(crypto.Signer) + if !ok { + return nil, errors.New("priv is not a crypto.Signer") + } certTemplate := &x509.Certificate{ SerialNumber: big.NewInt(1), EmailAddresses: []string{subject}, @@ -137,7 +142,7 @@ func GenerateLeafCertWithExpiration(subject string, oidcIssuer string, expiratio }, } - cert, err := createCertificate(certTemplate, parentTemplate, &priv.PublicKey, parentPriv) + cert, err := createCertificate(certTemplate, parentTemplate, signer.Public(), parentPriv) if err != nil { return nil, err }