From 69dd1abe220df1c8f20687087a66efe9538d249c Mon Sep 17 00:00:00 2001 From: Maru Newby Date: Thu, 10 Oct 2019 00:23:16 -0700 Subject: [PATCH 1/2] Add pkg/crypto:MakeSelfSignedCAConfigForSubject `pkg/crypto:MakeSelfSignedCAConfig` requires that the be supplied as a string. This PR adds an equivalent method that accepts a pkix.Name to allow creation of a new self-signed cert for service ca rotation that retains all the subject fields of the previous cert. --- pkg/crypto/crypto.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/pkg/crypto/crypto.go b/pkg/crypto/crypto.go index 5a593d2b12..3bb305ad27 100644 --- a/pkg/crypto/crypto.go +++ b/pkg/crypto/crypto.go @@ -542,27 +542,36 @@ func MakeSelfSignedCA(certFile, keyFile, serialFile, name string, expireDays int } func MakeSelfSignedCAConfig(name string, expireDays int) (*TLSCertificateConfig, error) { + subject := pkix.Name{CommonName: name} + return MakeSelfSignedCAConfigForSubject(subject, expireDays) +} + +func MakeSelfSignedCAConfigForSubject(subject pkix.Name, expireDays int) (*TLSCertificateConfig, error) { var caLifetimeInDays = DefaultCACertificateLifetimeInDays if expireDays > 0 { caLifetimeInDays = expireDays } if caLifetimeInDays > DefaultCACertificateLifetimeInDays { - warnAboutCertificateLifeTime(name, DefaultCACertificateLifetimeInDays) + warnAboutCertificateLifeTime(subject.CommonName, DefaultCACertificateLifetimeInDays) } caLifetime := time.Duration(caLifetimeInDays) * 24 * time.Hour - - return MakeSelfSignedCAConfigForDuration(name, caLifetime) + return makeSelfSignedCAConfigForSubjectAndDuration(subject, caLifetime) } func MakeSelfSignedCAConfigForDuration(name string, caLifetime time.Duration) (*TLSCertificateConfig, error) { + subject := pkix.Name{CommonName: name} + return makeSelfSignedCAConfigForSubjectAndDuration(subject, caLifetime) +} + +func makeSelfSignedCAConfigForSubjectAndDuration(subject pkix.Name, caLifetime time.Duration) (*TLSCertificateConfig, error) { // Create CA cert rootcaPublicKey, rootcaPrivateKey, err := NewKeyPair() if err != nil { return nil, err } - rootcaTemplate := newSigningCertificateTemplateForDuration(pkix.Name{CommonName: name}, caLifetime, time.Now) + rootcaTemplate := newSigningCertificateTemplateForDuration(subject, caLifetime, time.Now) rootcaCert, err := signCertificate(rootcaTemplate, rootcaPublicKey, rootcaTemplate, rootcaPrivateKey) if err != nil { return nil, err From 7979cda3eb366b0765421b90d4da8fe1f74470ed Mon Sep 17 00:00:00 2001 From: Maru Newby Date: Fri, 18 Oct 2019 01:18:13 -0700 Subject: [PATCH 2/2] Add SubjectKeyId and AuthorityKeyId to server certs The Subject Common Name field was previously assumed to be unique and used in determining whether the CA used to generate a serving cert had changed. With CA rotation, the Common Name will now be retained across CAs, necessitating the use of SubjectKeyId and AuthorityKeyId to determine if the a serving cert requires regeneration. Though serving cert CA only uses self-signed certificates, SubjectKeyId and AuthorityKeyId are added to all server certificates for consistency. --- pkg/crypto/crypto.go | 55 ++++++++++++++++++++++++++++++--------- pkg/crypto/crypto_test.go | 8 +++--- 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/pkg/crypto/crypto.go b/pkg/crypto/crypto.go index 3bb305ad27..a6cffed0dc 100644 --- a/pkg/crypto/crypto.go +++ b/pkg/crypto/crypto.go @@ -6,6 +6,7 @@ import ( "crypto/ecdsa" "crypto/rand" "crypto/rsa" + "crypto/sha1" "crypto/tls" "crypto/x509" "crypto/x509/pkix" @@ -567,11 +568,14 @@ func MakeSelfSignedCAConfigForDuration(name string, caLifetime time.Duration) (* func makeSelfSignedCAConfigForSubjectAndDuration(subject pkix.Name, caLifetime time.Duration) (*TLSCertificateConfig, error) { // Create CA cert - rootcaPublicKey, rootcaPrivateKey, err := NewKeyPair() + rootcaPublicKey, rootcaPrivateKey, publicKeyHash, err := newKeyPairWithHash() if err != nil { return nil, err } - rootcaTemplate := newSigningCertificateTemplateForDuration(subject, caLifetime, time.Now) + // AuthorityKeyId and SubjectKeyId should match for a self-signed CA + authorityKeyId := publicKeyHash + subjectKeyId := publicKeyHash + rootcaTemplate := newSigningCertificateTemplateForDuration(subject, caLifetime, time.Now, authorityKeyId, subjectKeyId) rootcaCert, err := signCertificate(rootcaTemplate, rootcaPublicKey, rootcaTemplate, rootcaPrivateKey) if err != nil { return nil, err @@ -585,11 +589,13 @@ func makeSelfSignedCAConfigForSubjectAndDuration(subject pkix.Name, caLifetime t func MakeCAConfigForDuration(name string, caLifetime time.Duration, issuer *CA) (*TLSCertificateConfig, error) { // Create CA cert - signerPublicKey, signerPrivateKey, err := NewKeyPair() + signerPublicKey, signerPrivateKey, publicKeyHash, err := newKeyPairWithHash() if err != nil { return nil, err } - signerTemplate := newSigningCertificateTemplateForDuration(pkix.Name{CommonName: name}, caLifetime, time.Now) + authorityKeyId := issuer.Config.Certs[0].SubjectKeyId + subjectKeyId := publicKeyHash + signerTemplate := newSigningCertificateTemplateForDuration(pkix.Name{CommonName: name}, caLifetime, time.Now, authorityKeyId, subjectKeyId) signerCert, err := issuer.signCertificate(signerTemplate, signerPublicKey) if err != nil { return nil, err @@ -647,8 +653,10 @@ func (ca *CA) MakeAndWriteServerCert(certFile, keyFile string, hostnames sets.St type CertificateExtensionFunc func(*x509.Certificate) error func (ca *CA) MakeServerCert(hostnames sets.String, expireDays int, fns ...CertificateExtensionFunc) (*TLSCertificateConfig, error) { - serverPublicKey, serverPrivateKey, _ := NewKeyPair() - serverTemplate := newServerCertificateTemplate(pkix.Name{CommonName: hostnames.List()[0]}, hostnames.List(), expireDays, time.Now) + serverPublicKey, serverPrivateKey, publicKeyHash, _ := newKeyPairWithHash() + authorityKeyId := ca.Config.Certs[0].SubjectKeyId + subjectKeyId := publicKeyHash + serverTemplate := newServerCertificateTemplate(pkix.Name{CommonName: hostnames.List()[0]}, hostnames.List(), expireDays, time.Now, authorityKeyId, subjectKeyId) for _, fn := range fns { if err := fn(serverTemplate); err != nil { return nil, err @@ -666,8 +674,10 @@ func (ca *CA) MakeServerCert(hostnames sets.String, expireDays int, fns ...Certi } func (ca *CA) MakeServerCertForDuration(hostnames sets.String, lifetime time.Duration, fns ...CertificateExtensionFunc) (*TLSCertificateConfig, error) { - serverPublicKey, serverPrivateKey, _ := NewKeyPair() - serverTemplate := newServerCertificateTemplateForDuration(pkix.Name{CommonName: hostnames.List()[0]}, hostnames.List(), lifetime, time.Now) + serverPublicKey, serverPrivateKey, publicKeyHash, _ := newKeyPairWithHash() + authorityKeyId := ca.Config.Certs[0].SubjectKeyId + subjectKeyId := publicKeyHash + serverTemplate := newServerCertificateTemplateForDuration(pkix.Name{CommonName: hostnames.List()[0]}, hostnames.List(), lifetime, time.Now, authorityKeyId, subjectKeyId) for _, fn := range fns { if err := fn(serverTemplate); err != nil { return nil, err @@ -804,6 +814,21 @@ func (ca *CA) signCertificate(template *x509.Certificate, requestKey crypto.Publ } func NewKeyPair() (crypto.PublicKey, crypto.PrivateKey, error) { + return newRSAKeyPair() +} + +func newKeyPairWithHash() (crypto.PublicKey, crypto.PrivateKey, []byte, error) { + publicKey, privateKey, err := newRSAKeyPair() + var publicKeyHash []byte + if err == nil { + hash := sha1.New() + hash.Write(publicKey.N.Bytes()) + publicKeyHash = hash.Sum(nil) + } + return publicKey, privateKey, publicKeyHash, err +} + +func newRSAKeyPair() (*rsa.PublicKey, *rsa.PrivateKey, error) { privateKey, err := rsa.GenerateKey(rand.Reader, keyBits) if err != nil { return nil, nil, err @@ -812,7 +837,7 @@ func NewKeyPair() (crypto.PublicKey, crypto.PrivateKey, error) { } // Can be used for CA or intermediate signing certs -func newSigningCertificateTemplateForDuration(subject pkix.Name, caLifetime time.Duration, currentTime func() time.Time) *x509.Certificate { +func newSigningCertificateTemplateForDuration(subject pkix.Name, caLifetime time.Duration, currentTime func() time.Time, authorityKeyId, subjectKeyId []byte) *x509.Certificate { return &x509.Certificate{ Subject: subject, @@ -825,11 +850,14 @@ func newSigningCertificateTemplateForDuration(subject pkix.Name, caLifetime time KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, BasicConstraintsValid: true, IsCA: true, + + AuthorityKeyId: authorityKeyId, + SubjectKeyId: subjectKeyId, } } // Can be used for ListenAndServeTLS -func newServerCertificateTemplate(subject pkix.Name, hosts []string, expireDays int, currentTime func() time.Time) *x509.Certificate { +func newServerCertificateTemplate(subject pkix.Name, hosts []string, expireDays int, currentTime func() time.Time, authorityKeyId, subjectKeyId []byte) *x509.Certificate { var lifetimeInDays = DefaultCertificateLifetimeInDays if expireDays > 0 { lifetimeInDays = expireDays @@ -841,11 +869,11 @@ func newServerCertificateTemplate(subject pkix.Name, hosts []string, expireDays lifetime := time.Duration(lifetimeInDays) * 24 * time.Hour - return newServerCertificateTemplateForDuration(subject, hosts, lifetime, currentTime) + return newServerCertificateTemplateForDuration(subject, hosts, lifetime, currentTime, authorityKeyId, subjectKeyId) } // Can be used for ListenAndServeTLS -func newServerCertificateTemplateForDuration(subject pkix.Name, hosts []string, lifetime time.Duration, currentTime func() time.Time) *x509.Certificate { +func newServerCertificateTemplateForDuration(subject pkix.Name, hosts []string, lifetime time.Duration, currentTime func() time.Time, authorityKeyId, subjectKeyId []byte) *x509.Certificate { template := &x509.Certificate{ Subject: subject, @@ -858,6 +886,9 @@ func newServerCertificateTemplateForDuration(subject pkix.Name, hosts []string, KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, BasicConstraintsValid: true, + + AuthorityKeyId: authorityKeyId, + SubjectKeyId: subjectKeyId, } template.IPAddresses, template.DNSNames = IPAddressesDNSNames(hosts) diff --git a/pkg/crypto/crypto_test.go b/pkg/crypto/crypto_test.go index 8ccd02d58c..adb6a72b96 100644 --- a/pkg/crypto/crypto_test.go +++ b/pkg/crypto/crypto_test.go @@ -138,7 +138,7 @@ func newSigningCertificateTemplate(subject pkix.Name, expireDays int, currentTim caLifetime := time.Duration(caLifetimeInDays) * 24 * time.Hour - return newSigningCertificateTemplateForDuration(subject, caLifetime, currentTime) + return newSigningCertificateTemplateForDuration(subject, caLifetime, currentTime, nil, nil) } func buildCA(t *testing.T) (crypto.PrivateKey, *x509.Certificate) { @@ -176,7 +176,7 @@ func buildServer(t *testing.T, signingKey crypto.PrivateKey, signingCrt *x509.Ce t.Fatalf("Unexpected error: %#v", err) } hosts := []string{"127.0.0.1", "localhost", "www.example.com"} - serverTemplate := newServerCertificateTemplate(pkix.Name{CommonName: "Server"}, hosts, certificateLifetime, time.Now) + serverTemplate := newServerCertificateTemplate(pkix.Name{CommonName: "Server"}, hosts, certificateLifetime, time.Now, nil, nil) serverCrt, err := signCertificate(serverTemplate, serverPublicKey, signingCrt, signingKey) if err != nil { t.Fatalf("Unexpected error: %#v", err) @@ -224,7 +224,7 @@ func TestRandomSerialGenerator(t *testing.T) { generator := &RandomSerialGenerator{} hostnames := []string{"foo", "bar"} - template := newServerCertificateTemplate(pkix.Name{CommonName: hostnames[0]}, hostnames, certificateLifetime, time.Now) + template := newServerCertificateTemplate(pkix.Name{CommonName: hostnames[0]}, hostnames, certificateLifetime, time.Now, nil, nil) if _, err := generator.Next(template); err != nil { t.Fatalf("unexpected error: %v", err) } @@ -296,6 +296,8 @@ func TestValidityPeriodOfServerCertificate(t *testing.T) { []string{"www.example.com"}, test.passedExpireDays, currentFakeTime, + nil, + nil, ) expirationDate := cert.NotAfter expectedExpirationDate := currentTime.Add(time.Duration(test.realExpireDays) * 24 * time.Hour)