Skip to content

Commit

Permalink
Ensure intermediate CA certs are created with unique serial numbers
Browse files Browse the repository at this point in the history
This change ensures that an intermediate CA cert is generated with a
unique serial number so that serving cert bundles can be loaded
without error.

Previously, intermediate CA certs were created with the same serial
number as their template. Since a serving cert bundle includes the
issuing CA cert and an intermediate CA cert (created by signing the
issuing CA cert with the previous CA's private key), this lack of
serial number differentiation resulted in
SEC_ERROR_REUSED_ISSUER_AND_SERIAL when the bundle was read by curl
due to the issuing and intermediate CAs sharing the same issuer and
serial number.
  • Loading branch information
marun committed Mar 6, 2020
1 parent c677511 commit 33e65e8
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 0 deletions.
14 changes: 14 additions & 0 deletions pkg/operator/rotate.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"crypto/rsa"
"crypto/x509"
"fmt"
"math/big"
"time"

corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -192,6 +193,19 @@ func createIntermediateCACert(targetCACert, signingCACert *x509.Certificate, sig
// Enable key identity chaining
template.AuthorityKeyId = signingCACert.SubjectKeyId

// Set a new serial number so that the intermediate CA cert is
// differentiated from the target CA cert. This ensures that a serving
// cert bundle that includes the issuing CA cert and an intermediate CA
// cert generated by this function - with the issuing CA cert as the
// target and the previous CA as the signer - will not result in
// SEC_ERROR_REUSED_ISSUER_AND_SERIAL when read by applications like curl.
serialGenerator := crypto.RandomSerialGenerator{}
serial, err := serialGenerator.Next(template)
if err != nil {
return nil, fmt.Errorf("failed to find next serial number: %v", err)
}
template.SerialNumber = big.NewInt(serial)

// Update the expiry if necessary
if expiry != nil {
template.NotAfter = *expiry
Expand Down
32 changes: 32 additions & 0 deletions pkg/operator/rotate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -247,3 +247,35 @@ func TestRotateSigningCA(t *testing.T) {
dnsName := oldServingCert.Certs[0].Subject.CommonName
util.CheckRotation(t, dnsName, oldCertPEM, oldKeyPEM, oldBundlePEM, newCertPEM, newKeyPEM, newBundlePEM)
}

// TestCreateIntermediateCACert checks that the intermediate CA cert
// created by signing a target CA cert supports identity key chaining
// and uses a serial number distinct from that of the target CA cert.
func TestCreateIntermediateCACert(t *testing.T) {
// Create the signing CA
signingCAConfig, err := crypto.MakeSelfSignedCAConfig("foo", SigningCertificateLifetimeInDays)
if err != nil {
t.Fatalf("error generating a new ca: %v", err)
}
signingCACert := signingCAConfig.Certs[0]

// Create the CA targeted for signing
targetCAConfig, err := crypto.MakeSelfSignedCAConfig("foo", SigningCertificateLifetimeInDays)
if err != nil {
t.Fatalf("error generating a new ca: %v", err)
}
targetCACert := targetCAConfig.Certs[0]

intermediateCACert, err := createIntermediateCACert(targetCACert, signingCACert, signingCAConfig.Key.(*rsa.PrivateKey), nil)
if err != nil {
t.Fatalf("Failed to create intermediate CA cert: %v", err)
}

if bytes.Compare(intermediateCACert.AuthorityKeyId, signingCACert.SubjectKeyId) != 0 {
t.Fatalf("Expected intermediate CA cert AuthorityKeyId to match signing CA cert SubjectKeyId")
}

if intermediateCACert.SerialNumber.Cmp(targetCACert.SerialNumber) == 0 {
t.Fatalf("Expected intermediate CA cert serial number to differ from serial number of target CA cert")
}
}

0 comments on commit 33e65e8

Please sign in to comment.