diff --git a/pkg/sign/certificate.go b/pkg/sign/certificate.go index 9eecf686..82138f42 100644 --- a/pkg/sign/certificate.go +++ b/pkg/sign/certificate.go @@ -28,6 +28,7 @@ import ( "strings" "time" + protocommon "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" "github.com/sigstore/sigstore-go/pkg/util" "github.com/sigstore/sigstore/pkg/oauthflow" ) @@ -131,6 +132,11 @@ func (f *Fulcio) GetCertificate(ctx context.Context, keypair Keypair, opts *Cert return nil, err } + // Fulcio doesn't support verifying Ed25519ph signatures currently. + if keypair.GetSigningAlgorithm() == protocommon.PublicKeyDetails_PKIX_ED25519_PH { + return nil, fmt.Errorf("ed25519ph unsupported by Fulcio") + } + // Sign JWT subject for proof of possession subjectSignature, _, err := keypair.SignData(ctx, []byte(subject)) if err != nil { diff --git a/pkg/sign/certificate_test.go b/pkg/sign/certificate_test.go index 128bcbd6..26b0e302 100644 --- a/pkg/sign/certificate_test.go +++ b/pkg/sign/certificate_test.go @@ -26,6 +26,7 @@ import ( "github.com/stretchr/testify/assert" + protocommon "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" "github.com/sigstore/sigstore-go/pkg/testing/ca" ) @@ -156,4 +157,14 @@ func Test_GetCertificate(t *testing.T) { cert, err = detachedFulcio.GetCertificate(ctx, keypair, certOpts) assert.NotNil(t, cert) assert.NoError(t, err) + + t.Run("ed25519ph unsupported", func(t *testing.T) { + // Test that Ed25519ph is rejected + keypair, err := NewEphemeralKeypair(&EphemeralKeypairOptions{Algorithm: protocommon.PublicKeyDetails_PKIX_ED25519_PH}) + assert.Nil(t, err) + certOpts.IDToken = "idtoken.eyJzdWIiOiJzdWJqZWN0In0K.stuff" // #nosec G101 + cert, err = fulcio.GetCertificate(ctx, keypair, certOpts) + assert.Nil(t, cert) + assert.ErrorContains(t, err, "ed25519ph unsupported by Fulcio") + }) } diff --git a/pkg/sign/keys.go b/pkg/sign/keys.go index 91af274d..d5cdff3f 100644 --- a/pkg/sign/keys.go +++ b/pkg/sign/keys.go @@ -18,50 +18,89 @@ import ( "context" "crypto" "crypto/ecdsa" - "crypto/elliptic" + "crypto/ed25519" "crypto/rand" + "crypto/rsa" "crypto/sha256" _ "crypto/sha512" // if user chooses SHA2-384 or SHA2-512 for hash "crypto/x509" "encoding/base64" - "errors" + "fmt" protocommon "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" "github.com/sigstore/sigstore/pkg/cryptoutils" + "github.com/sigstore/sigstore/pkg/signature" ) type Keypair interface { GetHashAlgorithm() protocommon.HashAlgorithm + GetSigningAlgorithm() protocommon.PublicKeyDetails GetHint() []byte GetKeyAlgorithm() string + GetPublicKey() crypto.PublicKey GetPublicKeyPem() (string, error) SignData(ctx context.Context, data []byte) ([]byte, []byte, error) } type EphemeralKeypairOptions struct { - // Optional hint of for signing key + // Optional fingerprint for public key Hint []byte - // TODO: support additional key algorithms + // Optional algorithm for generating signing key + Algorithm protocommon.PublicKeyDetails } type EphemeralKeypair struct { - options *EphemeralKeypairOptions - privateKey *ecdsa.PrivateKey - hashAlgorithm protocommon.HashAlgorithm + options *EphemeralKeypairOptions + privKey crypto.Signer + algDetails signature.AlgorithmDetails } +// NewEphemeralKeypair generates a signing key to be used for a single signature generation. +// Defaults to ECDSA P-256 SHA-256 with a SHA-256 key hint. func NewEphemeralKeypair(opts *EphemeralKeypairOptions) (*EphemeralKeypair, error) { if opts == nil { opts = &EphemeralKeypairOptions{} } - privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + // Default signing algorithm is ECDSA P-256 SHA-256 + if opts.Algorithm == protocommon.PublicKeyDetails_PUBLIC_KEY_DETAILS_UNSPECIFIED { + opts.Algorithm = protocommon.PublicKeyDetails_PKIX_ECDSA_P256_SHA_256 + } + algDetails, err := signature.GetAlgorithmDetails(opts.Algorithm) if err != nil { return nil, err } + var privKey crypto.Signer + switch kt := algDetails.GetKeyType(); kt { + case signature.ECDSA: + curve, err := algDetails.GetECDSACurve() + if err != nil { + return nil, err + } + privKey, err = ecdsa.GenerateKey(*curve, rand.Reader) + if err != nil { + return nil, err + } + case signature.RSA: + bitSize, err := algDetails.GetRSAKeySize() + if err != nil { + return nil, err + } + privKey, err = rsa.GenerateKey(rand.Reader, int(bitSize)) + if err != nil { + return nil, err + } + case signature.ED25519: + _, privKey, err = ed25519.GenerateKey(rand.Reader) + if err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("unsupported key type: %T", kt) + } if opts.Hint == nil { - pubKeyBytes, err := x509.MarshalPKIXPublicKey(privateKey.Public()) + pubKeyBytes, err := x509.MarshalPKIXPublicKey(privKey.Public()) if err != nil { return nil, err } @@ -70,28 +109,52 @@ func NewEphemeralKeypair(opts *EphemeralKeypairOptions) (*EphemeralKeypair, erro } ephemeralKeypair := EphemeralKeypair{ - options: opts, - privateKey: privateKey, - hashAlgorithm: protocommon.HashAlgorithm_SHA2_256, + options: opts, + privKey: privKey, + algDetails: algDetails, } return &ephemeralKeypair, nil } +// GetHashAlgorithm returns the hash algorithm to compute the digest to sign. func (e *EphemeralKeypair) GetHashAlgorithm() protocommon.HashAlgorithm { - return e.hashAlgorithm + return e.algDetails.GetProtoHashType() +} + +// GetSigningAlgorithm returns the signing algorithm of the key. +func (e *EphemeralKeypair) GetSigningAlgorithm() protocommon.PublicKeyDetails { + return e.algDetails.GetSignatureAlgorithm() } +// GetHint returns the fingerprint of the public key. func (e *EphemeralKeypair) GetHint() []byte { return e.options.Hint } +// GetKeyAlgorithm returns the top-level key algorithm, used as part of requests +// to Fulcio. Prefer PublicKeyDetails for a more precise algorithm. func (e *EphemeralKeypair) GetKeyAlgorithm() string { - return "ECDSA" + switch e.algDetails.GetKeyType() { + case signature.ECDSA: + return "ECDSA" + case signature.RSA: + return "RSA" + case signature.ED25519: + return "ED25519" + default: + return "" + } +} + +// GetPublicKey returns the public key. +func (e *EphemeralKeypair) GetPublicKey() crypto.PublicKey { + return e.privKey.Public() } +// GetPublicKeyPem returns the public key in PEM format. func (e *EphemeralKeypair) GetPublicKeyPem() (string, error) { - pubKeyBytes, err := cryptoutils.MarshalPublicKeyToPEM(e.privateKey.Public()) + pubKeyBytes, err := cryptoutils.MarshalPublicKeyToPEM(e.privKey.Public()) if err != nil { return "", err } @@ -99,34 +162,20 @@ func (e *EphemeralKeypair) GetPublicKeyPem() (string, error) { return string(pubKeyBytes), nil } -func getHashFunc(hashAlgorithm protocommon.HashAlgorithm) (crypto.Hash, error) { - switch hashAlgorithm { - case protocommon.HashAlgorithm_SHA2_256: - return crypto.Hash(crypto.SHA256), nil - case protocommon.HashAlgorithm_SHA2_384: - return crypto.Hash(crypto.SHA384), nil - case protocommon.HashAlgorithm_SHA2_512: - return crypto.Hash(crypto.SHA512), nil - default: - var hash crypto.Hash - return hash, errors.New("unsupported hash algorithm") - } -} - +// SignData returns the signature and the data to sign, which is a digest except when +// signing with Ed25519. func (e *EphemeralKeypair) SignData(_ context.Context, data []byte) ([]byte, []byte, error) { - hashFunc, err := getHashFunc(e.hashAlgorithm) - if err != nil { - return nil, nil, err + hf := e.algDetails.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) } - - hasher := hashFunc.New() - hasher.Write(data) - digest := hasher.Sum(nil) - - signature, err := e.privateKey.Sign(rand.Reader, digest, hashFunc) + signature, err := e.privKey.Sign(rand.Reader, dataToSign, hf) if err != nil { return nil, nil, err } - - return signature, digest, nil + return signature, dataToSign, nil } diff --git a/pkg/sign/keys_test.go b/pkg/sign/keys_test.go index 78e8b47a..b96b5795 100644 --- a/pkg/sign/keys_test.go +++ b/pkg/sign/keys_test.go @@ -15,43 +15,155 @@ package sign import ( + "bytes" "context" + "crypto" "testing" protocommon "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" + "github.com/sigstore/sigstore/pkg/signature" + "github.com/sigstore/sigstore/pkg/signature/options" "github.com/stretchr/testify/assert" ) func Test_EphemeralKeypair(t *testing.T) { - opts := &EphemeralKeypairOptions{ - Hint: []byte("asdf"), + // Test hint logic + t.Run("hint", func(t *testing.T) { + // with hint + opts := &EphemeralKeypairOptions{ + Hint: []byte("asdf"), + } + ephemeralKeypair, err := NewEphemeralKeypair(opts) + assert.NotNil(t, ephemeralKeypair) + assert.Nil(t, err) + hint := ephemeralKeypair.GetHint() + assert.Equal(t, hint, []byte("asdf")) + + // without hint (default) + defaultEphemeralKeypair, err := NewEphemeralKeypair(nil) + assert.Nil(t, err) + hint = defaultEphemeralKeypair.GetHint() + assert.NotEqual(t, hint, []byte("")) + }) + + // Test different algorithms + testCases := []struct { + name string + algorithm protocommon.PublicKeyDetails + expectedKeyAlgo string + expectedHashAlgo protocommon.HashAlgorithm + cryptoHash crypto.Hash + }{ + { + name: "default (ECDSA P-256)", + algorithm: protocommon.PublicKeyDetails_PUBLIC_KEY_DETAILS_UNSPECIFIED, + expectedKeyAlgo: "ECDSA", + expectedHashAlgo: protocommon.HashAlgorithm_SHA2_256, + cryptoHash: crypto.SHA256, + }, + { + name: "ECDSA P-384", + algorithm: protocommon.PublicKeyDetails_PKIX_ECDSA_P384_SHA_384, + expectedKeyAlgo: "ECDSA", + expectedHashAlgo: protocommon.HashAlgorithm_SHA2_384, + cryptoHash: crypto.SHA384, + }, + { + name: "ECDSA P-521", + algorithm: protocommon.PublicKeyDetails_PKIX_ECDSA_P521_SHA_512, + expectedKeyAlgo: "ECDSA", + expectedHashAlgo: protocommon.HashAlgorithm_SHA2_512, + cryptoHash: crypto.SHA512, + }, + { + name: "RSA PKCS#1 v1.5 2048", + algorithm: protocommon.PublicKeyDetails_PKIX_RSA_PKCS1V15_2048_SHA256, + expectedKeyAlgo: "RSA", + expectedHashAlgo: protocommon.HashAlgorithm_SHA2_256, + cryptoHash: crypto.SHA256, + }, + { + name: "RSA PKCS#1 v1.5 4096", + algorithm: protocommon.PublicKeyDetails_PKIX_RSA_PKCS1V15_4096_SHA256, + expectedKeyAlgo: "RSA", + expectedHashAlgo: protocommon.HashAlgorithm_SHA2_256, + cryptoHash: crypto.SHA256, + }, + { + name: "ED25519", + algorithm: protocommon.PublicKeyDetails_PKIX_ED25519, + expectedKeyAlgo: "ED25519", + expectedHashAlgo: protocommon.HashAlgorithm_HASH_ALGORITHM_UNSPECIFIED, + cryptoHash: crypto.Hash(0), + }, + { + name: "ED25519ph", + algorithm: protocommon.PublicKeyDetails_PKIX_ED25519_PH, + expectedKeyAlgo: "ED25519", + expectedHashAlgo: protocommon.HashAlgorithm_SHA2_512, + cryptoHash: crypto.SHA512, + }, } - ctx := context.Background() - ephemeralKeypair, err := NewEphemeralKeypair(opts) - assert.NotNil(t, ephemeralKeypair) - assert.Nil(t, err) + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + opts := &EphemeralKeypairOptions{ + Algorithm: tc.algorithm, + } + + ephemeralKeypair, err := NewEphemeralKeypair(opts) + assert.NotNil(t, ephemeralKeypair) + assert.Nil(t, err) + + hashAlgorithm := ephemeralKeypair.GetHashAlgorithm() + assert.Equal(t, tc.expectedHashAlgo, hashAlgorithm) - hashAlgorithm := ephemeralKeypair.GetHashAlgorithm() - assert.Equal(t, hashAlgorithm, protocommon.HashAlgorithm_SHA2_256) + signingAlgorithm := ephemeralKeypair.GetSigningAlgorithm() + expectedAlg := tc.algorithm + if tc.algorithm == protocommon.PublicKeyDetails_PUBLIC_KEY_DETAILS_UNSPECIFIED { + expectedAlg = protocommon.PublicKeyDetails_PKIX_ECDSA_P256_SHA_256 + } + assert.Equal(t, expectedAlg, signingAlgorithm) - hint := ephemeralKeypair.GetHint() - assert.Equal(t, hint, []byte("asdf")) + keyAlgorithm := ephemeralKeypair.GetKeyAlgorithm() + assert.Equal(t, tc.expectedKeyAlgo, keyAlgorithm) - keyAlgorithm := ephemeralKeypair.GetKeyAlgorithm() - assert.Equal(t, keyAlgorithm, "ECDSA") + pubKey := ephemeralKeypair.GetPublicKey() + assert.NotNil(t, pubKey) - pem, err := ephemeralKeypair.GetPublicKeyPem() - assert.NotEqual(t, pem, "") - assert.Nil(t, err) + pem, err := ephemeralKeypair.GetPublicKeyPem() + assert.NotEqual(t, pem, "") + assert.Nil(t, err) + + dataToSign := []byte("hello world") + signatureBytes, digest, err := ephemeralKeypair.SignData(ctx, dataToSign) + assert.NotNil(t, signatureBytes) + assert.NotNil(t, digest) + assert.Nil(t, err) + + // verify signature + var loadOpts []signature.LoadOption + loadOpts = append(loadOpts, options.WithHash(tc.cryptoHash)) + if tc.algorithm == protocommon.PublicKeyDetails_PKIX_ED25519_PH { + loadOpts = append(loadOpts, options.WithED25519ph()) + } + verifier, err := signature.LoadVerifierWithOpts(pubKey, loadOpts...) + assert.Nil(t, err) + err = verifier.VerifySignature(bytes.NewReader(signatureBytes), bytes.NewReader(dataToSign)) + assert.Nil(t, err) + }) + } - signature, digest, err := ephemeralKeypair.SignData(ctx, []byte("hello world")) - assert.NotEqual(t, signature, "") - assert.NotEqual(t, digest, "") - assert.Nil(t, err) + t.Run("unsupported algorithm", func(t *testing.T) { + unsupportedAlgorithms := []protocommon.PublicKeyDetails{ + protocommon.PublicKeyDetails(999), // An arbitrary invalid value + } - defaultEphemeralKeypair, err := NewEphemeralKeypair(nil) - assert.Nil(t, err) - hint = defaultEphemeralKeypair.GetHint() - assert.NotEqual(t, hint, []byte("")) + for _, alg := range unsupportedAlgorithms { + opts := &EphemeralKeypairOptions{Algorithm: alg} + _, err := NewEphemeralKeypair(opts) + assert.Error(t, err) + } + }) } diff --git a/pkg/sign/transparency.go b/pkg/sign/transparency.go index d6a963c4..ee0463b3 100644 --- a/pkg/sign/transparency.go +++ b/pkg/sign/transparency.go @@ -16,6 +16,8 @@ package sign import ( "context" + "crypto" + "crypto/x509" "encoding/hex" "encoding/json" "encoding/pem" @@ -37,6 +39,8 @@ import ( "github.com/sigstore/rekor/pkg/types/dsse" "github.com/sigstore/rekor/pkg/types/hashedrekord" rekorUtil "github.com/sigstore/rekor/pkg/util" + "github.com/sigstore/sigstore/pkg/signature" + "github.com/sigstore/sigstore/pkg/signature/options" // To initialize rekor types _ "github.com/sigstore/rekor/pkg/types/dsse/v0.0.1" @@ -123,19 +127,47 @@ func (r *Rekor) getRekorV2TLE(ctx context.Context, keyOrCertPEM []byte, b *proto bundleCertificate := verificationMaterial.GetCertificate() block, _ := pem.Decode(keyOrCertPEM) - keyDER := block.Bytes + keyOrCertDER := block.Bytes + + // Determine the signing algorithm for the public key + var pubKey crypto.PublicKey + var err error + switch block.Type { + case "PUBLIC KEY": + pubKey, err = x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return nil, err + } + case "CERTIFICATE": + c, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, err + } + pubKey = c.PublicKey + default: + return nil, fmt.Errorf("unknown key type: %s", block.Type) + } + var opts []signature.LoadOption + // When signing with ed25519, only the prehash variant is supported for hashedrekord + if messageSignature != nil { + opts = append(opts, options.WithED25519ph()) + } + algoDetails, err := signature.GetDefaultAlgorithmDetails(pubKey, opts...) + if err != nil { + return nil, fmt.Errorf("getting algorithm details: %w", err) + } - verifier := &rekortilespb.Verifier{KeyDetails: protocommon.PublicKeyDetails_PKIX_ECDSA_P256_SHA_256} + verifier := &rekortilespb.Verifier{KeyDetails: algoDetails.GetSignatureAlgorithm()} if bundleCertificate != nil { verifier.Verifier = &rekortilespb.Verifier_X509Certificate{ X509Certificate: &protocommon.X509Certificate{ - RawBytes: keyDER, + RawBytes: keyOrCertDER, }, } } else { verifier.Verifier = &rekortilespb.Verifier_PublicKey{ PublicKey: &rekortilespb.PublicKey{ - RawBytes: keyDER, + RawBytes: keyOrCertDER, }, } } diff --git a/pkg/util/util.go b/pkg/util/util.go index 4ab68528..07af9492 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package util +package util // nolint: revive import ( "runtime/debug" diff --git a/pkg/verify/signed_entity.go b/pkg/verify/signed_entity.go index 3a0a7216..5751ec85 100644 --- a/pkg/verify/signed_entity.go +++ b/pkg/verify/signed_entity.go @@ -748,6 +748,11 @@ func (v *Verifier) Verify(entity SignedEntity, pb PolicyBuilder) (*VerificationR result.Signature = &SignatureVerificationResult{ Certificate: &certSummary, } + } else { + pubKeyID := []byte(verificationContent.PublicKey().Hint()) + result.Signature = &SignatureVerificationResult{ + PublicKeyID: &pubKeyID, + } } // SignatureContent can be either an Envelope or a MessageSignature. diff --git a/test/e2e/e2e_test.go b/test/e2e/e2e_test.go index aa33dfed..3f2dea32 100644 --- a/test/e2e/e2e_test.go +++ b/test/e2e/e2e_test.go @@ -17,19 +17,21 @@ package e2e import ( - "crypto/sha256" + "crypto" "io" - "log" "net/http" "os" "testing" "time" protobundle "github.com/sigstore/protobuf-specs/gen/pb-go/bundle/v1" + protocommon "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1" "github.com/sigstore/sigstore-go/pkg/bundle" "github.com/sigstore/sigstore-go/pkg/root" "github.com/sigstore/sigstore-go/pkg/sign" "github.com/sigstore/sigstore-go/pkg/verify" + "github.com/sigstore/sigstore/pkg/signature" + "github.com/sigstore/sigstore/pkg/signature/options" "github.com/stretchr/testify/assert" ) @@ -82,12 +84,16 @@ func TestSignVerify(t *testing.T) { content sign.Content rekorVersion uint32 expectedTimestamps int + signingAlg protocommon.PublicKeyDetails // Defaults to ECDSA P-256 SHA-256 + digestAlg crypto.Hash + useKey bool }{ { name: "hashedrekord_v001", content: &sign.PlainData{ Data: artifactData, }, + digestAlg: crypto.SHA256, rekorVersion: 1, expectedTimestamps: 2, }, @@ -97,6 +103,7 @@ func TestSignVerify(t *testing.T) { Data: intotoData, PayloadType: "application/vnd.in-toto+json", }, + digestAlg: crypto.SHA256, rekorVersion: 1, expectedTimestamps: 2, }, @@ -105,44 +112,96 @@ func TestSignVerify(t *testing.T) { content: &sign.PlainData{ Data: artifactData, }, + digestAlg: crypto.SHA256, rekorVersion: 2, expectedTimestamps: 1, }, + { + name: "hashedrekor_v002_key", + content: &sign.PlainData{ + Data: artifactData, + }, + digestAlg: crypto.SHA256, + rekorVersion: 2, + expectedTimestamps: 1, + useKey: true, + }, + { + name: "hashedrekor_v002_ecdsa_p384", + content: &sign.PlainData{ + Data: artifactData, + }, + digestAlg: crypto.SHA384, + rekorVersion: 2, + expectedTimestamps: 1, + signingAlg: protocommon.PublicKeyDetails_PKIX_ECDSA_P384_SHA_384, + }, + { + name: "hashedrekor_v002_ed25519ph_key", + content: &sign.PlainData{ + Data: artifactData, + }, + digestAlg: crypto.SHA512, + rekorVersion: 2, + expectedTimestamps: 1, + signingAlg: protocommon.PublicKeyDetails_PKIX_ED25519_PH, + // when using ed25519, only self-managed keys are supported + useKey: true, + }, { name: "dsse_v002", content: &sign.DSSEData{ Data: intotoData, PayloadType: "application/vnd.in-toto+json", }, + digestAlg: crypto.SHA256, + rekorVersion: 2, + expectedTimestamps: 1, + }, + { + name: "dsse_v002_ed25519", + content: &sign.DSSEData{ + Data: intotoData, + PayloadType: "application/vnd.in-toto+json", + }, + digestAlg: crypto.SHA256, rekorVersion: 2, expectedTimestamps: 1, + signingAlg: protocommon.PublicKeyDetails_PKIX_ED25519, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - protoBundle, err := signContent(signingConfig, token, test.content, test.rekorVersion, opts) + keypair, err := sign.NewEphemeralKeypair(&sign.EphemeralKeypairOptions{Algorithm: test.signingAlg}) assert.NoError(t, err) + if test.useKey { + initTrustedRootWithKey(t, test.signingAlg, keypair.GetPublicKey(), &opts) + } - result, err := verifyBundle(protoBundle, issuerURL, defaultCertID, getDigest(artifactData), trustedRoot) + protoBundle, err := signContent(signingConfig, token, test.content, test.rekorVersion, keypair, test.useKey, opts) + assert.NoError(t, err) + + result, err := verifyBundle(protoBundle, issuerURL, defaultCertID, getDigest(artifactData, test.digestAlg), test.useKey, opts.TrustedRoot) assert.NoError(t, err) assert.NotNil(t, result) assert.NotNil(t, result.Signature) assert.Len(t, result.VerifiedTimestamps, test.expectedTimestamps) - assert.NotNil(t, result.VerifiedIdentity) - assert.Equal(t, result.VerifiedIdentity.SubjectAlternativeName.SubjectAlternativeName, defaultCertID) + if !test.useKey { + assert.NotNil(t, result.VerifiedIdentity) + assert.Equal(t, result.VerifiedIdentity.SubjectAlternativeName.SubjectAlternativeName, defaultCertID) + } }) } } -func signContent(signingConfig *root.SigningConfig, token string, content sign.Content, rekorVersion uint32, opts sign.BundleOptions) (*protobundle.Bundle, error) { +func signContent(signingConfig *root.SigningConfig, token string, content sign.Content, rekorVersion uint32, keypair sign.Keypair, useKey bool, opts sign.BundleOptions) (*protobundle.Bundle, error) { rekorServices, err := root.SelectServices(signingConfig.RekorLogURLs(), signingConfig.RekorLogURLsConfig(), []uint32{rekorVersion}, time.Now()) if err != nil { return nil, err } for _, rekorService := range rekorServices { - log.Printf("using Rekor URL %s", rekorService.URL) rekorOpts := &sign.RekorOptions{ BaseURL: rekorService.URL, Timeout: time.Duration(90 * time.Second), @@ -152,18 +211,20 @@ func signContent(signingConfig *root.SigningConfig, token string, content sign.C opts.TransparencyLogs = append(opts.TransparencyLogs, sign.NewRekor(rekorOpts)) } - fulcioService, err := root.SelectService(signingConfig.FulcioCertificateAuthorityURLs(), []uint32{1}, time.Now()) - if err != nil { - return nil, err - } - fulcioOpts := &sign.FulcioOptions{ - BaseURL: fulcioService.URL, - Timeout: time.Duration(30 * time.Second), - Retries: 1, - } - opts.CertificateProvider = sign.NewFulcio(fulcioOpts) - opts.CertificateProviderOptions = &sign.CertificateProviderOptions{ - IDToken: token, + if !useKey { + fulcioService, err := root.SelectService(signingConfig.FulcioCertificateAuthorityURLs(), []uint32{1}, time.Now()) + if err != nil { + return nil, err + } + fulcioOpts := &sign.FulcioOptions{ + BaseURL: fulcioService.URL, + Timeout: time.Duration(30 * time.Second), + Retries: 1, + } + opts.CertificateProvider = sign.NewFulcio(fulcioOpts) + opts.CertificateProviderOptions = &sign.CertificateProviderOptions{ + IDToken: token, + } } tsaServices, err := root.SelectServices(signingConfig.TimestampAuthorityURLs(), signingConfig.TimestampAuthorityURLsConfig(), []uint32{1}, time.Now()) @@ -179,28 +240,30 @@ func signContent(signingConfig *root.SigningConfig, token string, content sign.C opts.TimestampAuthorities = append(opts.TimestampAuthorities, sign.NewTimestampAuthority(tsaOpts)) } - keypair, err := sign.NewEphemeralKeypair(nil) - if err != nil { - return nil, err - } - return sign.Bundle(content, keypair, opts) } -func verifyBundle(b *protobundle.Bundle, issuer, san string, digest []byte, trustedRoot root.TrustedMaterial) (*verify.VerificationResult, error) { +func verifyBundle(b *protobundle.Bundle, issuer, san string, digest []byte, useKey bool, trustedRoot root.TrustedMaterial) (*verify.VerificationResult, error) { bundleObj := bundle.Bundle{Bundle: b} verifierConfig := []verify.VerifierOption{ - verify.WithSignedCertificateTimestamps(1), verify.WithTransparencyLog(1), verify.WithObserverTimestamps(1), } + var identityPolicies []verify.PolicyOption - certID, err := verify.NewShortCertificateIdentity(issuer, "", san, "") - if err != nil { - return nil, err + if !useKey { + verifierConfig = append(verifierConfig, verify.WithSignedCertificateTimestamps(1)) + + certID, err := verify.NewShortCertificateIdentity(issuer, "", san, "") + if err != nil { + return nil, err + } + identityPolicies = append(identityPolicies, verify.WithCertificateIdentity(certID)) + } else { + identityPolicies = append(identityPolicies, verify.WithKey()) } - identityPolicies := []verify.PolicyOption{verify.WithCertificateIdentity(certID)} + artifactPolicy := verify.WithArtifactDigest("sha256", digest) signedEntityVerifier, err := verify.NewVerifier(trustedRoot, verifierConfig...) @@ -211,8 +274,10 @@ func verifyBundle(b *protobundle.Bundle, issuer, san string, digest []byte, trus return signedEntityVerifier.Verify(&bundleObj, verify.NewPolicy(artifactPolicy, identityPolicies...)) } -func getDigest(artifact []byte) []byte { - digest := sha256.Sum256(artifact) +func getDigest(artifact []byte, hf crypto.Hash) []byte { + hasher := hf.New() + hasher.Write(artifact) + digest := hasher.Sum(nil) return digest[:] } @@ -229,3 +294,31 @@ func getOIDCToken(issuer string) (string, error) { } return string(body), nil } + +func initTrustedRootWithKey(t *testing.T, alg protocommon.PublicKeyDetails, pubKey crypto.PublicKey, opts *sign.BundleOptions) { + var defaultOpts []signature.LoadOption + if alg == protocommon.PublicKeyDetails_PKIX_ED25519_PH { + defaultOpts = []signature.LoadOption{options.WithED25519ph()} + } + verifier, err := signature.LoadDefaultVerifier(pubKey, defaultOpts...) + assert.NoError(t, err) + + key := root.NewExpiringKey(verifier, time.Time{}, time.Time{}) + keyTrustedMaterial := root.NewTrustedPublicKeyMaterial(func(_ string) (root.TimeConstrainedVerifier, error) { + return key, nil + }) + trustedMaterial := &verifyTrustedMaterial{ + TrustedMaterial: opts.TrustedRoot, + keyTrustedMaterial: keyTrustedMaterial, + } + opts.TrustedRoot = trustedMaterial +} + +type verifyTrustedMaterial struct { + root.TrustedMaterial + keyTrustedMaterial root.TrustedMaterial +} + +func (v *verifyTrustedMaterial) PublicKeyVerifier(hint string) (root.TimeConstrainedVerifier, error) { + return v.keyTrustedMaterial.PublicKeyVerifier(hint) +}