diff --git a/cmd/app/createca.go b/cmd/app/createca.go index 1415f818e..933255ec5 100644 --- a/cmd/app/createca.go +++ b/cmd/app/createca.go @@ -19,6 +19,7 @@ package app import ( + "crypto" "crypto/rand" "crypto/x509" "crypto/x509/pkix" @@ -29,6 +30,7 @@ import ( "time" "github.com/ThalesIgnite/crypto11" + "github.com/sigstore/fulcio/pkg/ca" "github.com/sigstore/fulcio/pkg/log" "github.com/sigstore/sigstore/pkg/cryptoutils" "github.com/spf13/cobra" @@ -126,6 +128,11 @@ func runCreateCACmd(cmd *cobra.Command, args []string) { //nolint: revive BasicConstraintsValid: true, MaxPathLen: 1, } + rootCA.SignatureAlgorithm, err = ca.ToSignatureAlgorithm(privKey, crypto.SHA256) + if err != nil { + log.Logger.Fatal(err) + } + caBytes, err := x509.CreateCertificate(rand.Reader, rootCA, rootCA, pubKey, privKey) if err != nil { log.Logger.Fatal(err) diff --git a/pkg/ca/baseca/baseca.go b/pkg/ca/baseca/baseca.go index 6ae2ec254..bf03e4cb9 100644 --- a/pkg/ca/baseca/baseca.go +++ b/pkg/ca/baseca/baseca.go @@ -57,6 +57,11 @@ func (bca *BaseCA) CreatePrecertificate(ctx context.Context, principal identity. Value: asn1.NullBytes, }) + cert.SignatureAlgorithm, err = ca.ToSignatureAlgorithm(privateKey, crypto.SHA256) + if err != nil { + return nil, err + } + finalCertBytes, err := x509.CreateCertificate(rand.Reader, cert, certChain[0], publicKey, privateKey) if err != nil { return nil, err @@ -115,6 +120,12 @@ func (bca *BaseCA) IssueFinalCertificate(_ context.Context, precert *ca.CodeSign cert := precert.PreCert cert.ExtraExtensions = exts + + cert.SignatureAlgorithm, err = ca.ToSignatureAlgorithm(precert.PrivateKey, crypto.SHA256) + if err != nil { + return nil, err + } + finalCertBytes, err := x509.CreateCertificate(rand.Reader, cert, precert.CertChain[0], precert.PreCert.PublicKey, precert.PrivateKey) if err != nil { return nil, err @@ -131,6 +142,11 @@ func (bca *BaseCA) CreateCertificate(ctx context.Context, principal identity.Pri certChain, privateKey := bca.GetSignerWithChain() + cert.SignatureAlgorithm, err = ca.ToSignatureAlgorithm(privateKey, crypto.SHA256) + if err != nil { + return nil, err + } + finalCertBytes, err := x509.CreateCertificate(rand.Reader, cert, certChain[0], publicKey, privateKey) if err != nil { return nil, err diff --git a/pkg/ca/common.go b/pkg/ca/common.go index 9eaf5df0f..0614003bc 100644 --- a/pkg/ca/common.go +++ b/pkg/ca/common.go @@ -18,8 +18,12 @@ package ca import ( "context" "crypto" + "crypto/ecdsa" + "crypto/ed25519" + "crypto/rsa" "crypto/x509" "errors" + "fmt" "time" "github.com/sigstore/fulcio/pkg/identity" @@ -105,3 +109,47 @@ func VerifyCertChain(certs []*x509.Certificate, signer crypto.Signer) error { return cryptoutils.ValidatePubKey(signer.Public()) } + +// ToSignatureAlgorithm returns the x509.SignatureAlgorithm for the given signer and hash algorithm. +func ToSignatureAlgorithm(signer crypto.Signer, hash crypto.Hash) (x509.SignatureAlgorithm, error) { + if signer == nil { + return x509.UnknownSignatureAlgorithm, errors.New("signer is nil") + } + + pub := signer.Public() + switch pub := pub.(type) { + case *rsa.PublicKey: + switch hash { + case crypto.SHA256: + return x509.SHA256WithRSA, nil + case crypto.SHA384: + return x509.SHA384WithRSA, nil + case crypto.SHA512: + return x509.SHA512WithRSA, nil + case crypto.SHA1: + return x509.SHA1WithRSA, nil + case crypto.MD5: + return x509.MD5WithRSA, nil + default: + return x509.UnknownSignatureAlgorithm, fmt.Errorf("unsupported hash algorithm for RSA: %v", hash) + } + case *ecdsa.PublicKey: + switch hash { + case crypto.SHA256: + return x509.ECDSAWithSHA256, nil + case crypto.SHA384: + return x509.ECDSAWithSHA384, nil + case crypto.SHA512: + return x509.ECDSAWithSHA512, nil + case crypto.SHA1: + return x509.ECDSAWithSHA1, nil + default: + return x509.UnknownSignatureAlgorithm, fmt.Errorf("unsupported hash algorithm for ECDSA: %v", hash) + } + case ed25519.PublicKey: + // Ed25519 has a fixed signature so we don't need to check the hash + return x509.PureEd25519, nil + default: + return x509.UnknownSignatureAlgorithm, fmt.Errorf("unsupported public key type: %T", pub) + } +} diff --git a/pkg/ca/common_test.go b/pkg/ca/common_test.go index 4deef68d1..b6f667eb7 100644 --- a/pkg/ca/common_test.go +++ b/pkg/ca/common_test.go @@ -16,9 +16,12 @@ package ca import ( "context" + "crypto" "crypto/ecdsa" + "crypto/ed25519" "crypto/elliptic" "crypto/rand" + "crypto/rsa" "crypto/x509" "strings" "testing" @@ -137,3 +140,143 @@ func TestVerifyCertChain(t *testing.T) { t.Fatalf("expected error verifying with empty chain: %v", err) } } + +func TestToSignatureAlgorithm(t *testing.T) { + rsaKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + t.Fatalf("Failed to generate RSA key: %v", err) + } + + ecdsaKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + t.Fatalf("Failed to generate ECDSA key: %v", err) + } + + _, ed25519Key, err := ed25519.GenerateKey(rand.Reader) + if err != nil { + t.Fatalf("Failed to generate Ed25519 key: %v", err) + } + + tests := []struct { + name string + signer crypto.Signer + hash crypto.Hash + want x509.SignatureAlgorithm + wantErr bool + }{ + { + name: "RSA with SHA256", + signer: rsaKey, + hash: crypto.SHA256, + want: x509.SHA256WithRSA, + wantErr: false, + }, + { + name: "RSA with SHA384", + signer: rsaKey, + hash: crypto.SHA384, + want: x509.SHA384WithRSA, + wantErr: false, + }, + { + name: "RSA with SHA512", + signer: rsaKey, + hash: crypto.SHA512, + want: x509.SHA512WithRSA, + wantErr: false, + }, + { + name: "RSA with SHA1", + signer: rsaKey, + hash: crypto.SHA1, + want: x509.SHA1WithRSA, + wantErr: false, + }, + { + name: "RSA with MD5", + signer: rsaKey, + hash: crypto.MD5, + want: x509.MD5WithRSA, + wantErr: false, + }, + { + name: "RSA with unsupported hash", + signer: rsaKey, + hash: crypto.MD4, + want: x509.UnknownSignatureAlgorithm, + wantErr: true, + }, + + { + name: "ECDSA with SHA256", + signer: ecdsaKey, + hash: crypto.SHA256, + want: x509.ECDSAWithSHA256, + wantErr: false, + }, + { + name: "ECDSA with SHA384", + signer: ecdsaKey, + hash: crypto.SHA384, + want: x509.ECDSAWithSHA384, + wantErr: false, + }, + { + name: "ECDSA with SHA512", + signer: ecdsaKey, + hash: crypto.SHA512, + want: x509.ECDSAWithSHA512, + wantErr: false, + }, + { + name: "ECDSA with SHA1", + signer: ecdsaKey, + hash: crypto.SHA1, + want: x509.ECDSAWithSHA1, + wantErr: false, + }, + { + name: "ECDSA with unsupported hash", + signer: ecdsaKey, + hash: crypto.MD5, + want: x509.UnknownSignatureAlgorithm, + wantErr: true, + }, + + { + name: "Ed25519 with any hash", + signer: ed25519Key, + hash: crypto.SHA256, + want: x509.PureEd25519, + wantErr: false, + }, + { + name: "Ed25519 with different hash", + signer: ed25519Key, + hash: crypto.SHA512, + want: x509.PureEd25519, + wantErr: false, + }, + + { + name: "nil signer", + signer: nil, + hash: crypto.SHA256, + want: x509.UnknownSignatureAlgorithm, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ToSignatureAlgorithm(tt.signer, tt.hash) + if (err != nil) != tt.wantErr { + t.Errorf("ToSignatureAlgorithm() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("ToSignatureAlgorithm() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/ca/ephemeralca/ephemeral.go b/pkg/ca/ephemeralca/ephemeral.go index 8eca18b09..1f24dd9da 100644 --- a/pkg/ca/ephemeralca/ephemeral.go +++ b/pkg/ca/ephemeralca/ephemeral.go @@ -16,6 +16,7 @@ package ephemeralca import ( + "crypto" "crypto/rand" "crypto/x509" "crypto/x509/pkix" @@ -61,6 +62,11 @@ func NewEphemeralCA() (*EphemeralCA, error) { BasicConstraintsValid: true, MaxPathLen: 1, } + rootCA.SignatureAlgorithm, err = ca.ToSignatureAlgorithm(signer, crypto.SHA256) + if err != nil { + return nil, err + } + caBytes, err := x509.CreateCertificate(rand.Reader, rootCA, rootCA, signer.Public(), signer) if err != nil { return nil, err diff --git a/pkg/certmaker/certmaker.go b/pkg/certmaker/certmaker.go index c0f3407fb..bfa830113 100644 --- a/pkg/certmaker/certmaker.go +++ b/pkg/certmaker/certmaker.go @@ -27,6 +27,7 @@ import ( "strings" "time" + "github.com/sigstore/fulcio/pkg/ca" "github.com/sigstore/sigstore/pkg/signature" "github.com/sigstore/sigstore/pkg/signature/kms" "go.step.sm/crypto/x509util" @@ -188,6 +189,11 @@ func CreateCertificates(sv signature.SignerVerifier, config KMSConfig, return fmt.Errorf("error parsing root template: %w", err) } + rootTmpl.SignatureAlgorithm, err = ca.ToSignatureAlgorithm(rootSigner, crypto.SHA256) + if err != nil { + return fmt.Errorf("error determining signature algorithm: %w", err) + } + rootCert, err := x509util.CreateCertificate(rootTmpl, rootTmpl, rootPubKey, rootSigner) if err != nil { return fmt.Errorf("error creating root certificate: %w", err) @@ -246,6 +252,11 @@ func CreateCertificates(sv signature.SignerVerifier, config KMSConfig, return fmt.Errorf("error parsing intermediate template: %w", err) } + intermediateTmpl.SignatureAlgorithm, err = ca.ToSignatureAlgorithm(intermediateSigner, crypto.SHA256) + if err != nil { + return fmt.Errorf("error determining signature algorithm: %w", err) + } + intermediateCert, err := x509util.CreateCertificate(intermediateTmpl, rootCert, intermediatePubKey, rootSigner) if err != nil { return fmt.Errorf("error creating intermediate certificate: %w", err) @@ -307,6 +318,11 @@ func CreateCertificates(sv signature.SignerVerifier, config KMSConfig, return fmt.Errorf("error parsing leaf template: %w", err) } + leafTmpl.SignatureAlgorithm, err = ca.ToSignatureAlgorithm(signingKey, crypto.SHA256) + if err != nil { + return fmt.Errorf("error determining signature algorithm: %w", err) + } + leafCert, err := x509util.CreateCertificate(leafTmpl, signingCert, leafPubKey, signingKey) if err != nil { return fmt.Errorf("error creating leaf certificate: %w", err) diff --git a/pkg/test/cert_utils.go b/pkg/test/cert_utils.go index fa8e3556c..15ec85e68 100644 --- a/pkg/test/cert_utils.go +++ b/pkg/test/cert_utils.go @@ -17,11 +17,15 @@ package test import ( "crypto" "crypto/ecdsa" + "crypto/ed25519" "crypto/elliptic" "crypto/rand" + "crypto/rsa" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" + "errors" + "fmt" "math/big" "time" ) @@ -48,6 +52,12 @@ _, err := leafCert.Verify(opts) */ func createCertificate(template *x509.Certificate, parent *x509.Certificate, pub interface{}, priv crypto.Signer) (*x509.Certificate, error) { + signatureAlgorithm, err := toSignatureAlgorithm(priv, crypto.SHA256) + if err != nil { + return nil, err + } + + template.SignatureAlgorithm = signatureAlgorithm certBytes, err := x509.CreateCertificate(rand.Reader, template, parent, pub, priv) if err != nil { return nil, err @@ -221,3 +231,46 @@ func GenerateLeafCert(subject string, oidcIssuer string, parentTemplate *x509.Ce return cert, priv, nil } + +func toSignatureAlgorithm(signer crypto.Signer, hash crypto.Hash) (x509.SignatureAlgorithm, error) { + if signer == nil { + return x509.UnknownSignatureAlgorithm, errors.New("signer is nil") + } + + pub := signer.Public() + switch pub := pub.(type) { + case *rsa.PublicKey: + switch hash { + case crypto.SHA256: + return x509.SHA256WithRSA, nil + case crypto.SHA384: + return x509.SHA384WithRSA, nil + case crypto.SHA512: + return x509.SHA512WithRSA, nil + case crypto.SHA1: + return x509.SHA1WithRSA, nil + case crypto.MD5: + return x509.MD5WithRSA, nil + default: + return x509.UnknownSignatureAlgorithm, fmt.Errorf("unsupported hash algorithm for RSA: %v", hash) + } + case *ecdsa.PublicKey: + switch hash { + case crypto.SHA256: + return x509.ECDSAWithSHA256, nil + case crypto.SHA384: + return x509.ECDSAWithSHA384, nil + case crypto.SHA512: + return x509.ECDSAWithSHA512, nil + case crypto.SHA1: + return x509.ECDSAWithSHA1, nil + default: + return x509.UnknownSignatureAlgorithm, fmt.Errorf("unsupported hash algorithm for ECDSA: %v", hash) + } + case ed25519.PublicKey: + // Ed25519 has a fixed signature so we don't need to check the hash + return x509.PureEd25519, nil + default: + return x509.UnknownSignatureAlgorithm, fmt.Errorf("unsupported public key type: %T", pub) + } +}