Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 106 additions & 3 deletions pkg/crypto/crypto.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import (
"bytes"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
Expand Down Expand Up @@ -443,6 +445,16 @@ const (
keyBits = 2048
)

// KeyAlgorithm represents the type of key pair to generate
type KeyAlgorithm int

const (
// AlgorithmRSA generates 2048-bit RSA key pairs (default for backwards compatibility)
AlgorithmRSA KeyAlgorithm = iota
// AlgorithmECDSA generates P-256 ECDSA key pairs
AlgorithmECDSA
)

type CA struct {
Config *TLSCertificateConfig

Expand Down Expand Up @@ -796,40 +808,82 @@ func (ca *CA) MakeAndWriteServerCert(certFile, keyFile string, hostnames sets.Se
type CertificateExtensionFunc func(*x509.Certificate) error

func (ca *CA) MakeServerCert(hostnames sets.Set[string], lifetime time.Duration, fns ...CertificateExtensionFunc) (*TLSCertificateConfig, error) {
serverPublicKey, serverPrivateKey, publicKeyHash, _ := newKeyPairWithHash()
return ca.makeServerCert(hostnames, lifetime, AlgorithmRSA, fns...)
}

func (ca *CA) MakeServerCertForDuration(hostnames sets.Set[string], lifetime time.Duration, fns ...CertificateExtensionFunc) (*TLSCertificateConfig, error) {
return ca.makeServerCertForDuration(hostnames, lifetime, AlgorithmRSA, fns...)
}

// MakeServerCertWithAlgorithm creates a server certificate with the specified key algorithm
func (ca *CA) MakeServerCertWithAlgorithm(hostnames sets.Set[string], lifetime time.Duration, algorithm KeyAlgorithm, fns ...CertificateExtensionFunc) (*TLSCertificateConfig, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I might be wrong, but it looks like MakeServerCert and MakeServerCertWithAlgorithm are very similar. There are two differences. The first is the key algorithm, and the second is that MakeServerCertWithAlgorithm sets the signature algorithm.

I’m wondering if it would make sense to create a private common function for both. I think something like the following could work:

func (ca *CA) MakeServerCertWithAlgorithm(hostnames sets.Set[string], lifetime time.Duration, algorithm KeyAlgorithm, fns ...CertificateExtensionFunc) (*TLSCertificateConfig, error) {
	sigFn := func(template *x509.Certificate) error {
		template.SignatureAlgorithm = signatureAlgorithmForKey(ca.Config.Key)
		return nil
	}
	fns = append([]CertificateExtensionFunc{sigFn}, fns...)
	return ca.makeServerCertWithAlgorithm(hostnames, lifetime, algorithm, fns...)
}

and

func (ca *CA) MakeServerCert(hostnames sets.Set[string], lifetime time.Duration, fns ...CertificateExtensionFunc) (*TLSCertificateConfig, error) {
	return ca.makeServerCertWithAlgorithm(hostnames, lifetime, AlgorithmRSA, fns...)
}

sigFn := func(template *x509.Certificate) error {
template.SignatureAlgorithm = signatureAlgorithmForKey(ca.Config.Key)
return nil
}
fns = append([]CertificateExtensionFunc{sigFn}, fns...)
return ca.makeServerCert(hostnames, lifetime, algorithm, fns...)
}

// MakeServerCertForDurationWithAlgorithm creates a server certificate with specified duration and algorithm
func (ca *CA) MakeServerCertForDurationWithAlgorithm(hostnames sets.Set[string], lifetime time.Duration, algorithm KeyAlgorithm, fns ...CertificateExtensionFunc) (*TLSCertificateConfig, error) {
sigFn := func(template *x509.Certificate) error {
template.SignatureAlgorithm = signatureAlgorithmForKey(ca.Config.Key)
return nil
}
fns = append([]CertificateExtensionFunc{sigFn}, fns...)
return ca.makeServerCertForDuration(hostnames, lifetime, algorithm, fns...)
}

func (ca *CA) makeServerCert(hostnames sets.Set[string], lifetime time.Duration, algorithm KeyAlgorithm, fns ...CertificateExtensionFunc) (*TLSCertificateConfig, error) {
serverPublicKey, serverPrivateKey, publicKeyHash, err := newKeyPairWithAlgorithm(algorithm)
if err != nil {
return nil, err
}

authorityKeyId := ca.Config.Certs[0].SubjectKeyId
subjectKeyId := publicKeyHash
serverTemplate := newServerCertificateTemplate(pkix.Name{CommonName: sets.List(hostnames)[0]}, sets.List(hostnames), lifetime, time.Now, authorityKeyId, subjectKeyId)

for _, fn := range fns {
if err := fn(serverTemplate); err != nil {
return nil, err
}
}

serverCrt, err := ca.SignCertificate(serverTemplate, serverPublicKey)
if err != nil {
return nil, err
}

server := &TLSCertificateConfig{
Certs: append([]*x509.Certificate{serverCrt}, ca.Config.Certs...),
Key: serverPrivateKey,
}
return server, nil
}

func (ca *CA) MakeServerCertForDuration(hostnames sets.Set[string], lifetime time.Duration, fns ...CertificateExtensionFunc) (*TLSCertificateConfig, error) {
serverPublicKey, serverPrivateKey, publicKeyHash, _ := newKeyPairWithHash()
func (ca *CA) makeServerCertForDuration(hostnames sets.Set[string], lifetime time.Duration, algorithm KeyAlgorithm, fns ...CertificateExtensionFunc) (*TLSCertificateConfig, error) {
serverPublicKey, serverPrivateKey, publicKeyHash, err := newKeyPairWithAlgorithm(algorithm)
if err != nil {
return nil, err
}

authorityKeyId := ca.Config.Certs[0].SubjectKeyId
subjectKeyId := publicKeyHash
serverTemplate := newServerCertificateTemplateForDuration(pkix.Name{CommonName: sets.List(hostnames)[0]}, sets.List(hostnames), lifetime, time.Now, authorityKeyId, subjectKeyId)

for _, fn := range fns {
if err := fn(serverTemplate); err != nil {
return nil, err
}
}

serverCrt, err := ca.SignCertificate(serverTemplate, serverPublicKey)
if err != nil {
return nil, err
}

server := &TLSCertificateConfig{
Certs: append([]*x509.Certificate{serverCrt}, ca.Config.Certs...),
Key: serverPrivateKey,
Expand Down Expand Up @@ -1001,6 +1055,55 @@ func newRSAKeyPair() (*rsa.PublicKey, *rsa.PrivateKey, error) {
return &privateKey.PublicKey, privateKey, nil
}

// newECDSAKeyPair generates a new P-256 ECDSA key pair
func newECDSAKeyPair() (*ecdsa.PublicKey, *ecdsa.PrivateKey, error) {
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return nil, nil, err
}
return &privateKey.PublicKey, privateKey, nil
}

// newECDSAKeyPairWithHash generates a new ECDSA key pair and computes the public key hash
// Uses SHA256 for ECDSA keys (vs SHA1 for RSA) for consistency with modern standards
func newECDSAKeyPairWithHash() (crypto.PublicKey, crypto.PrivateKey, []byte, error) {
publicKey, privateKey, err := newECDSAKeyPair()
var publicKeyHash []byte
if err == nil {
hash := sha256.New()
// Marshal public key in uncompressed form for hashing
pubBytes := elliptic.Marshal(publicKey.Curve, publicKey.X, publicKey.Y)
hash.Write(pubBytes)
publicKeyHash = hash.Sum(nil)
}
return publicKey, privateKey, publicKeyHash, err
}

// newKeyPairWithAlgorithm generates a new key pair using the specified algorithm
func newKeyPairWithAlgorithm(algo KeyAlgorithm) (crypto.PublicKey, crypto.PrivateKey, []byte, error) {
switch algo {
case AlgorithmECDSA:
return newECDSAKeyPairWithHash()
case AlgorithmRSA:
return newKeyPairWithHash()
default:
return nil, nil, nil, fmt.Errorf("unsupported key algorithm: %d", algo)
}
}

// signatureAlgorithmForKey returns the appropriate x509.SignatureAlgorithm for the given private key
func signatureAlgorithmForKey(key crypto.PrivateKey) x509.SignatureAlgorithm {
switch key.(type) {
case *ecdsa.PrivateKey:
return x509.ECDSAWithSHA256
case *rsa.PrivateKey:
return x509.SHA256WithRSA
default:
// Default to RSA for backwards compatibility with unknown key types
return x509.SHA256WithRSA
}
}

// Can be used for CA or intermediate signing certs
func newSigningCertificateTemplateForDuration(subject pkix.Name, caLifetime time.Duration, currentTime func() time.Time, authorityKeyId, subjectKeyId []byte) *x509.Certificate {
return &x509.Certificate{
Expand Down
Loading